物业代码生成
This commit is contained in:
5
packages/@core/ui-kit/layout-ui/src/components/index.ts
Normal file
5
packages/@core/ui-kit/layout-ui/src/components/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export { default as LayoutContent } from './layout-content.vue';
|
||||
export { default as LayoutFooter } from './layout-footer.vue';
|
||||
export { default as LayoutHeader } from './layout-header.vue';
|
||||
export { default as LayoutSidebar } from './layout-sidebar.vue';
|
||||
export { default as LayoutTabbar } from './layout-tabbar.vue';
|
@@ -0,0 +1,62 @@
|
||||
<script setup lang="ts">
|
||||
import type { ContentCompactType } from '@vben-core/typings';
|
||||
import type { CSSProperties } from 'vue';
|
||||
|
||||
import { useLayoutContentStyle } from '@vben-core/composables';
|
||||
import { Slot } from '@vben-core/shadcn-ui';
|
||||
import { computed } from 'vue';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* 内容区域定宽
|
||||
*/
|
||||
contentCompact: ContentCompactType;
|
||||
/**
|
||||
* 定宽布局宽度
|
||||
*/
|
||||
contentCompactWidth: number;
|
||||
padding: number;
|
||||
paddingBottom: number;
|
||||
paddingLeft: number;
|
||||
paddingRight: number;
|
||||
paddingTop: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {});
|
||||
|
||||
const { contentElement, overlayStyle } = useLayoutContentStyle();
|
||||
|
||||
const style = computed((): CSSProperties => {
|
||||
const {
|
||||
contentCompact,
|
||||
padding,
|
||||
paddingBottom,
|
||||
paddingLeft,
|
||||
paddingRight,
|
||||
paddingTop,
|
||||
} = props;
|
||||
|
||||
const compactStyle: CSSProperties =
|
||||
contentCompact === 'compact'
|
||||
? { margin: '0 auto', width: `${props.contentCompactWidth}px` }
|
||||
: {};
|
||||
return {
|
||||
...compactStyle,
|
||||
flex: 1,
|
||||
padding: `${padding}px`,
|
||||
paddingBottom: `${paddingBottom}px`,
|
||||
paddingLeft: `${paddingLeft}px`,
|
||||
paddingRight: `${paddingRight}px`,
|
||||
paddingTop: `${paddingTop}px`,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main ref="contentElement" :style="style" class="bg-background-deep relative">
|
||||
<Slot :style="overlayStyle">
|
||||
<slot name="overlay"></slot>
|
||||
</Slot>
|
||||
<slot></slot>
|
||||
</main>
|
||||
</template>
|
@@ -0,0 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
import type { CSSProperties } from 'vue';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* 是否固定在底部
|
||||
*/
|
||||
fixed?: boolean;
|
||||
height: number;
|
||||
/**
|
||||
* 是否显示
|
||||
* @default true
|
||||
*/
|
||||
show?: boolean;
|
||||
width: string;
|
||||
zIndex: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
show: true,
|
||||
});
|
||||
|
||||
const style = computed((): CSSProperties => {
|
||||
const { fixed, height, show, width, zIndex } = props;
|
||||
return {
|
||||
height: `${height}px`,
|
||||
marginBottom: show ? '0' : `-${height}px`,
|
||||
position: fixed ? 'fixed' : 'static',
|
||||
width,
|
||||
zIndex,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer
|
||||
:style="style"
|
||||
class="bg-background-deep bottom-0 w-full transition-all duration-200"
|
||||
>
|
||||
<slot></slot>
|
||||
</footer>
|
||||
</template>
|
@@ -0,0 +1,77 @@
|
||||
<script setup lang="ts">
|
||||
import type { CSSProperties } from 'vue';
|
||||
|
||||
import { computed, useSlots } from 'vue';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* 横屏
|
||||
*/
|
||||
fullWidth: boolean;
|
||||
/**
|
||||
* 高度
|
||||
*/
|
||||
height: number;
|
||||
/**
|
||||
* 是否移动端
|
||||
*/
|
||||
isMobile: boolean;
|
||||
/**
|
||||
* 是否显示
|
||||
*/
|
||||
show: boolean;
|
||||
/**
|
||||
* 侧边菜单宽度
|
||||
*/
|
||||
sidebarWidth: number;
|
||||
/**
|
||||
* 主题
|
||||
*/
|
||||
theme: string | undefined;
|
||||
/**
|
||||
* 宽度
|
||||
*/
|
||||
width: string;
|
||||
/**
|
||||
* zIndex
|
||||
*/
|
||||
zIndex: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {});
|
||||
|
||||
const slots = useSlots();
|
||||
|
||||
const style = computed((): CSSProperties => {
|
||||
const { fullWidth, height, show } = props;
|
||||
const right = !show || !fullWidth ? undefined : 0;
|
||||
|
||||
return {
|
||||
height: `${height}px`,
|
||||
marginTop: show ? 0 : `-${height}px`,
|
||||
right,
|
||||
};
|
||||
});
|
||||
|
||||
const logoStyle = computed((): CSSProperties => {
|
||||
return {
|
||||
minWidth: `${props.isMobile ? 40 : props.sidebarWidth}px`,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header
|
||||
:class="theme"
|
||||
:style="style"
|
||||
class="border-border bg-header top-0 flex w-full flex-[0_0_auto] items-center border-b pl-2 transition-[margin-top] duration-200"
|
||||
>
|
||||
<div v-if="slots.logo" :style="logoStyle">
|
||||
<slot name="logo"></slot>
|
||||
</div>
|
||||
|
||||
<slot name="toggle-button"> </slot>
|
||||
|
||||
<slot></slot>
|
||||
</header>
|
||||
</template>
|
@@ -0,0 +1,322 @@
|
||||
<script setup lang="ts">
|
||||
import type { CSSProperties } from 'vue';
|
||||
|
||||
import { computed, shallowRef, useSlots, watchEffect } from 'vue';
|
||||
|
||||
import { VbenScrollbar } from '@vben-core/shadcn-ui';
|
||||
|
||||
import { useScrollLock } from '@vueuse/core';
|
||||
|
||||
import { SidebarCollapseButton, SidebarFixedButton } from './widgets';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* 折叠区域高度
|
||||
* @default 42
|
||||
*/
|
||||
collapseHeight?: number;
|
||||
/**
|
||||
* 折叠宽度
|
||||
* @default 48
|
||||
*/
|
||||
collapseWidth?: number;
|
||||
/**
|
||||
* 隐藏的dom是否可见
|
||||
* @default true
|
||||
*/
|
||||
domVisible?: boolean;
|
||||
/**
|
||||
* 扩展区域宽度
|
||||
*/
|
||||
extraWidth: number;
|
||||
/**
|
||||
* 固定扩展区域
|
||||
* @default false
|
||||
*/
|
||||
fixedExtra?: boolean;
|
||||
/**
|
||||
* 头部高度
|
||||
*/
|
||||
headerHeight: number;
|
||||
/**
|
||||
* 是否侧边混合模式
|
||||
* @default false
|
||||
*/
|
||||
isSidebarMixed?: boolean;
|
||||
/**
|
||||
* 顶部margin
|
||||
* @default 60
|
||||
*/
|
||||
marginTop?: number;
|
||||
/**
|
||||
* 混合菜单宽度
|
||||
* @default 80
|
||||
*/
|
||||
mixedWidth?: number;
|
||||
/**
|
||||
* 顶部padding
|
||||
* @default 60
|
||||
*/
|
||||
paddingTop?: number;
|
||||
/**
|
||||
* 是否显示
|
||||
* @default true
|
||||
*/
|
||||
show?: boolean;
|
||||
/**
|
||||
* 显示折叠按钮
|
||||
* @default true
|
||||
*/
|
||||
showCollapseButton?: boolean;
|
||||
/**
|
||||
* 显示固定按钮
|
||||
* @default true
|
||||
*/
|
||||
showFixedButton?: boolean;
|
||||
/**
|
||||
* 主题
|
||||
*/
|
||||
theme: string;
|
||||
|
||||
/**
|
||||
* 宽度
|
||||
*/
|
||||
width: number;
|
||||
/**
|
||||
* zIndex
|
||||
* @default 0
|
||||
*/
|
||||
zIndex?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
collapseHeight: 42,
|
||||
collapseWidth: 48,
|
||||
domVisible: true,
|
||||
fixedExtra: false,
|
||||
isSidebarMixed: false,
|
||||
marginTop: 0,
|
||||
mixedWidth: 70,
|
||||
paddingTop: 0,
|
||||
show: true,
|
||||
showCollapseButton: true,
|
||||
showFixedButton: true,
|
||||
zIndex: 0,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{ leave: [] }>();
|
||||
const collapse = defineModel<boolean>('collapse');
|
||||
const extraCollapse = defineModel<boolean>('extraCollapse');
|
||||
const expandOnHovering = defineModel<boolean>('expandOnHovering');
|
||||
const expandOnHover = defineModel<boolean>('expandOnHover');
|
||||
const extraVisible = defineModel<boolean>('extraVisible');
|
||||
|
||||
const isLocked = useScrollLock(document.body);
|
||||
const slots = useSlots();
|
||||
|
||||
const asideRef = shallowRef<HTMLDivElement | null>();
|
||||
|
||||
const hiddenSideStyle = computed((): CSSProperties => calcMenuWidthStyle(true));
|
||||
|
||||
const style = computed((): CSSProperties => {
|
||||
const { isSidebarMixed, marginTop, paddingTop, zIndex } = props;
|
||||
|
||||
return {
|
||||
'--scroll-shadow': 'var(--sidebar)',
|
||||
...calcMenuWidthStyle(false),
|
||||
height: `calc(100% - ${marginTop}px)`,
|
||||
marginTop: `${marginTop}px`,
|
||||
paddingTop: `${paddingTop}px`,
|
||||
zIndex,
|
||||
...(isSidebarMixed && extraVisible.value ? { transition: 'none' } : {}),
|
||||
};
|
||||
});
|
||||
|
||||
const extraStyle = computed((): CSSProperties => {
|
||||
const { extraWidth, show, width, zIndex } = props;
|
||||
|
||||
return {
|
||||
left: `${width}px`,
|
||||
width: extraVisible.value && show ? `${extraWidth}px` : 0,
|
||||
zIndex,
|
||||
};
|
||||
});
|
||||
|
||||
const extraTitleStyle = computed((): CSSProperties => {
|
||||
const { headerHeight } = props;
|
||||
|
||||
return {
|
||||
height: `${headerHeight - 1}px`,
|
||||
};
|
||||
});
|
||||
|
||||
const contentWidthStyle = computed((): CSSProperties => {
|
||||
const { collapseWidth, fixedExtra, isSidebarMixed, mixedWidth } = props;
|
||||
if (isSidebarMixed && fixedExtra) {
|
||||
return { width: `${collapse.value ? collapseWidth : mixedWidth}px` };
|
||||
}
|
||||
return {};
|
||||
});
|
||||
|
||||
const contentStyle = computed((): CSSProperties => {
|
||||
const { collapseHeight, headerHeight } = props;
|
||||
|
||||
return {
|
||||
height: `calc(100% - ${headerHeight + collapseHeight}px)`,
|
||||
paddingTop: '8px',
|
||||
...contentWidthStyle.value,
|
||||
};
|
||||
});
|
||||
|
||||
const headerStyle = computed((): CSSProperties => {
|
||||
const { headerHeight, isSidebarMixed } = props;
|
||||
|
||||
return {
|
||||
...(isSidebarMixed ? { display: 'flex', justifyContent: 'center' } : {}),
|
||||
height: `${headerHeight - 1}px`,
|
||||
...contentWidthStyle.value,
|
||||
};
|
||||
});
|
||||
|
||||
const extraContentStyle = computed((): CSSProperties => {
|
||||
const { collapseHeight, headerHeight } = props;
|
||||
return {
|
||||
height: `calc(100% - ${headerHeight + collapseHeight}px)`,
|
||||
};
|
||||
});
|
||||
|
||||
const collapseStyle = computed((): CSSProperties => {
|
||||
return {
|
||||
height: `${props.collapseHeight}px`,
|
||||
};
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
extraVisible.value = props.fixedExtra ? true : extraVisible.value;
|
||||
});
|
||||
|
||||
function calcMenuWidthStyle(isHiddenDom: boolean): CSSProperties {
|
||||
const { extraWidth, fixedExtra, isSidebarMixed, show, width } = props;
|
||||
|
||||
let widthValue =
|
||||
width === 0
|
||||
? '0px'
|
||||
: `${width + (isSidebarMixed && fixedExtra && extraVisible.value ? extraWidth : 0)}px`;
|
||||
|
||||
const { collapseWidth } = props;
|
||||
|
||||
if (isHiddenDom && expandOnHovering.value && !expandOnHover.value) {
|
||||
widthValue = `${collapseWidth}px`;
|
||||
}
|
||||
|
||||
return {
|
||||
...(widthValue === '0px' ? { overflow: 'hidden' } : {}),
|
||||
flex: `0 0 ${widthValue}`,
|
||||
marginLeft: show ? 0 : `-${widthValue}`,
|
||||
maxWidth: widthValue,
|
||||
minWidth: widthValue,
|
||||
width: widthValue,
|
||||
};
|
||||
}
|
||||
|
||||
function handleMouseenter(e: MouseEvent) {
|
||||
if (e?.offsetX < 10) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 未开启和未折叠状态不生效
|
||||
if (expandOnHover.value) {
|
||||
return;
|
||||
}
|
||||
if (!expandOnHovering.value) {
|
||||
collapse.value = false;
|
||||
}
|
||||
if (props.isSidebarMixed) {
|
||||
isLocked.value = true;
|
||||
}
|
||||
expandOnHovering.value = true;
|
||||
}
|
||||
|
||||
function handleMouseleave() {
|
||||
emit('leave');
|
||||
if (props.isSidebarMixed) {
|
||||
isLocked.value = false;
|
||||
}
|
||||
if (expandOnHover.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
expandOnHovering.value = false;
|
||||
collapse.value = true;
|
||||
extraVisible.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="domVisible"
|
||||
:class="theme"
|
||||
:style="hiddenSideStyle"
|
||||
class="h-full transition-all duration-150"
|
||||
></div>
|
||||
<aside
|
||||
:class="[
|
||||
theme,
|
||||
{
|
||||
'bg-sidebar-deep': isSidebarMixed,
|
||||
'bg-sidebar border-border border-r': !isSidebarMixed,
|
||||
},
|
||||
]"
|
||||
:style="style"
|
||||
class="fixed left-0 top-0 h-full transition-all duration-150"
|
||||
@mouseenter="handleMouseenter"
|
||||
@mouseleave="handleMouseleave"
|
||||
>
|
||||
<SidebarFixedButton
|
||||
v-if="!collapse && !isSidebarMixed && showFixedButton"
|
||||
v-model:expand-on-hover="expandOnHover"
|
||||
/>
|
||||
<div v-if="slots.logo" :style="headerStyle">
|
||||
<slot name="logo"></slot>
|
||||
</div>
|
||||
<VbenScrollbar :style="contentStyle" shadow shadow-border>
|
||||
<slot></slot>
|
||||
</VbenScrollbar>
|
||||
|
||||
<div :style="collapseStyle"></div>
|
||||
<SidebarCollapseButton
|
||||
v-if="showCollapseButton && !isSidebarMixed"
|
||||
v-model:collapsed="collapse"
|
||||
/>
|
||||
<div
|
||||
v-if="isSidebarMixed"
|
||||
ref="asideRef"
|
||||
:class="{
|
||||
'border-l': extraVisible,
|
||||
}"
|
||||
:style="extraStyle"
|
||||
class="border-border bg-sidebar fixed top-0 h-full overflow-hidden border-r transition-all duration-200"
|
||||
>
|
||||
<SidebarCollapseButton
|
||||
v-if="isSidebarMixed && expandOnHover"
|
||||
v-model:collapsed="extraCollapse"
|
||||
/>
|
||||
|
||||
<SidebarFixedButton
|
||||
v-if="!extraCollapse"
|
||||
v-model:expand-on-hover="expandOnHover"
|
||||
/>
|
||||
<div v-if="!extraCollapse" :style="extraTitleStyle" class="pl-2">
|
||||
<slot name="extra-title"></slot>
|
||||
</div>
|
||||
<VbenScrollbar
|
||||
:style="extraContentStyle"
|
||||
class="border-border py-2"
|
||||
shadow
|
||||
shadow-border
|
||||
>
|
||||
<slot name="extra"></slot>
|
||||
</VbenScrollbar>
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
@@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import type { CSSProperties } from 'vue';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* 高度
|
||||
*/
|
||||
height: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {});
|
||||
|
||||
const style = computed((): CSSProperties => {
|
||||
const { height } = props;
|
||||
return {
|
||||
height: `${height}px`,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
:style="style"
|
||||
class="border-border bg-background flex w-full border-b transition-all"
|
||||
>
|
||||
<slot></slot>
|
||||
</section>
|
||||
</template>
|
@@ -0,0 +1,2 @@
|
||||
export { default as SidebarCollapseButton } from './sidebar-collapse-button.vue';
|
||||
export { default as SidebarFixedButton } from './sidebar-fixed-button.vue';
|
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { ChevronsLeft, ChevronsRight } from '@vben-core/icons';
|
||||
|
||||
const collapsed = defineModel<boolean>('collapsed');
|
||||
|
||||
function handleCollapsed() {
|
||||
collapsed.value = !collapsed.value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex-center hover:text-foreground text-foreground/60 hover:bg-accent-hover bg-accent absolute bottom-2 left-3 z-10 cursor-pointer rounded-sm p-1"
|
||||
@click.stop="handleCollapsed"
|
||||
>
|
||||
<ChevronsRight v-if="collapsed" class="size-4" />
|
||||
<ChevronsLeft v-else class="size-4" />
|
||||
</div>
|
||||
</template>
|
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { Pin, PinOff } from '@vben-core/icons';
|
||||
|
||||
const expandOnHover = defineModel<boolean>('expandOnHover');
|
||||
|
||||
function toggleFixed() {
|
||||
expandOnHover.value = !expandOnHover.value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex-center hover:text-foreground text-foreground/60 hover:bg-accent-hover bg-accent absolute bottom-2 right-3 z-10 cursor-pointer rounded-sm p-[5px] transition-all duration-300"
|
||||
@click="toggleFixed"
|
||||
>
|
||||
<PinOff v-if="!expandOnHover" class="size-3.5" />
|
||||
<Pin v-else class="size-3.5" />
|
||||
</div>
|
||||
</template>
|
Reference in New Issue
Block a user