Files
xk-admin/packages/@core/ui-kit/layout-ui/src/vben-layout.vue

713 lines
18 KiB
Vue
Raw Normal View History

2024-05-19 21:20:42 +08:00
<script setup lang="ts">
import type { CSSProperties } from 'vue';
import type { VbenLayoutProps } from './vben-layout';
2024-06-08 19:49:06 +08:00
import { computed, ref, watch } from 'vue';
2024-05-19 21:20:42 +08:00
import {
SCROLL_FIXED_CLASS,
useLayoutFooterStyle,
useLayoutHeaderStyle,
} from '@vben-core/composables';
import { IconifyIcon } from '@vben-core/icons';
import { VbenIconButton } from '@vben-core/shadcn-ui';
import {
ELEMENT_ID_LAYOUT_SCROLL,
ELEMENT_ID_MAIN_CONTENT,
} from '@vben-core/shared/constants';
import { useEventListener, useScroll } from '@vueuse/core';
2024-05-19 21:20:42 +08:00
import {
LayoutContent,
LayoutFooter,
LayoutHeader,
2024-06-09 15:39:11 +08:00
LayoutSidebar,
LayoutTabbar,
2024-05-19 21:20:42 +08:00
} from './components';
import { resolveHeaderHiddenOnScroll } from './header-scroll-state';
2024-08-14 20:37:21 +08:00
import { useLayout } from './hooks/use-layout';
2024-05-19 21:20:42 +08:00
interface Props extends VbenLayoutProps {}
defineOptions({
name: 'VbenLayout',
});
const props = withDefaults(defineProps<Props>(), {
contentCompact: 'wide',
contentCompactWidth: 1200,
2024-05-19 21:20:42 +08:00
contentPadding: 0,
contentPaddingBottom: 0,
contentPaddingLeft: 0,
contentPaddingRight: 0,
contentPaddingTop: 0,
2024-06-09 13:31:43 +08:00
footerEnable: false,
2024-05-19 21:20:42 +08:00
footerFixed: true,
footerHeight: 32,
headerHeight: 50,
2024-06-01 22:17:52 +08:00
headerHidden: false,
2024-05-19 21:20:42 +08:00
headerMode: 'fixed',
headerToggleSidebarButton: true,
2024-05-19 21:20:42 +08:00
headerVisible: true,
isMobile: false,
2024-06-09 15:39:11 +08:00
layout: 'sidebar-nav',
sidebarCollapsedButton: true,
2024-06-09 15:39:11 +08:00
sidebarCollapseShowTitle: false,
sidebarExtraCollapsedWidth: 60,
sidebarFixedButton: true,
2024-06-09 15:39:11 +08:00
sidebarHidden: false,
sidebarMixedWidth: 80,
sidebarTheme: 'dark',
sidebarThemeSub: 'dark',
2024-06-09 15:39:11 +08:00
sidebarWidth: 180,
sideCollapseWidth: 60,
2024-06-09 15:39:11 +08:00
tabbarEnable: true,
tabbarHeight: 40,
2024-05-19 21:20:42 +08:00
zIndex: 200,
});
const emit = defineEmits<{
sideMouseLeave: [];
toggleSidebar: [];
2026-06-06 16:21:25 +08:00
'update:sidebarWidth': [value: number];
}>();
const sidebarDraggable = defineModel<boolean>('sidebarDraggable', {
default: true,
});
2025-03-10 18:59:56 +08:00
const sidebarCollapse = defineModel<boolean>('sidebarCollapse', {
default: false,
});
2024-06-09 15:39:11 +08:00
const sidebarExtraVisible = defineModel<boolean>('sidebarExtraVisible');
2025-03-10 18:59:56 +08:00
const sidebarExtraCollapse = defineModel<boolean>('sidebarExtraCollapse', {
default: false,
});
const sidebarExpandOnHover = defineModel<boolean>('sidebarExpandOnHover', {
default: false,
});
2024-06-09 15:39:11 +08:00
const sidebarEnable = defineModel<boolean>('sidebarEnable', { default: true });
2024-05-19 21:20:42 +08:00
const HEADER_TRIGGER_DISTANCE = 12;
// side是否处于hover状态展开菜单中
const sidebarExpandOnHovering = ref(false);
const headerIsHidden = ref(false);
const mainRef = ref<HTMLElement | null>(null);
const contentRef = ref<HTMLElement | null>(null);
let lastMouseY: null | number = null;
2024-05-19 21:20:42 +08:00
const {
arrivedState,
directions,
y: scrollY,
} = useScroll(contentRef, {
onScroll: handleLayoutScroll,
});
const { setLayoutHeaderHeight } = useLayoutHeaderStyle();
const { setLayoutFooterHeight } = useLayoutFooterStyle();
2024-08-14 20:37:21 +08:00
const {
currentLayout,
isFullContent,
isHeaderMixedNav,
2024-08-14 20:37:21 +08:00
isHeaderNav,
isMixedNav,
isSidebarMixedNav,
} = useLayout(props);
2024-05-19 21:20:42 +08:00
/**
* 顶栏是否自动隐藏
*/
const isHeaderAutoActive = computed(
() =>
props.headerMode === 'auto' && !isMixedNav.value && !isFullContent.value,
);
const isHeaderOverlayModeActive = computed(
() =>
(props.headerMode === 'auto' || props.headerMode === 'auto-scroll') &&
!isMixedNav.value &&
!isFullContent.value,
);
const headerHasShadow = computed(() => scrollY.value > 20);
2024-05-19 21:20:42 +08:00
const headerWrapperHeight = computed(() => {
let height = 0;
2024-06-01 22:17:52 +08:00
if (props.headerVisible && !props.headerHidden) {
2024-08-14 20:37:21 +08:00
height += props.headerHeight;
2024-05-19 21:20:42 +08:00
}
2024-06-09 15:39:11 +08:00
if (props.tabbarEnable) {
height += props.tabbarHeight;
2024-05-19 21:20:42 +08:00
}
return height;
});
const getSideCollapseWidth = computed(() => {
const {
sidebarCollapseShowTitle,
sidebarExtraCollapsedWidth,
sideCollapseWidth,
} = props;
2024-06-30 22:58:57 +08:00
return sidebarCollapseShowTitle ||
isSidebarMixedNav.value ||
isHeaderMixedNav.value
? sidebarExtraCollapsedWidth
2024-05-19 21:20:42 +08:00
: sideCollapseWidth;
});
/**
* 动态获取侧边区域是否可见
*/
2024-06-09 15:39:11 +08:00
const sidebarEnableState = computed(() => {
return !isHeaderNav.value && sidebarEnable.value;
2024-05-19 21:20:42 +08:00
});
/**
* 侧边区域离顶部高度
*/
const sidebarMarginTop = computed(() => {
2024-08-14 20:37:21 +08:00
const { headerHeight, isMobile } = props;
return isMixedNav.value && !isMobile ? headerHeight : 0;
2024-05-19 21:20:42 +08:00
});
/**
* 动态获取侧边宽度
*/
2024-06-09 15:39:11 +08:00
const getSidebarWidth = computed(() => {
const { isMobile, sidebarHidden, sidebarMixedWidth, sidebarWidth } = props;
2024-05-19 21:20:42 +08:00
let width = 0;
2024-06-09 15:39:11 +08:00
if (sidebarHidden) {
2024-06-01 22:17:52 +08:00
return width;
}
2024-05-19 21:20:42 +08:00
if (
2024-06-09 15:39:11 +08:00
!sidebarEnableState.value ||
(sidebarHidden &&
!isSidebarMixedNav.value &&
!isMixedNav.value &&
!isHeaderMixedNav.value)
2024-05-19 21:20:42 +08:00
) {
return width;
}
if ((isHeaderMixedNav.value || isSidebarMixedNav.value) && !isMobile) {
2024-06-09 15:39:11 +08:00
width = sidebarMixedWidth;
} else if (sidebarCollapse.value) {
2024-05-19 21:20:42 +08:00
width = isMobile ? 0 : getSideCollapseWidth.value;
} else {
2024-06-09 15:39:11 +08:00
width = sidebarWidth;
2024-05-19 21:20:42 +08:00
}
return width;
});
/**
* 获取扩展区域宽度
*/
const sidebarExtraWidth = computed(() => {
const { sidebarExtraCollapsedWidth, sidebarWidth } = props;
2024-06-30 22:58:57 +08:00
return sidebarExtraCollapse.value ? sidebarExtraCollapsedWidth : sidebarWidth;
2024-05-19 21:20:42 +08:00
});
/**
* 是否侧边栏模式包含混合侧边
*/
2024-08-14 20:37:21 +08:00
const isSideMode = computed(
() =>
currentLayout.value === 'mixed-nav' ||
currentLayout.value === 'sidebar-mixed-nav' ||
currentLayout.value === 'sidebar-nav' ||
currentLayout.value === 'header-mixed-nav' ||
currentLayout.value === 'header-sidebar-nav',
2024-05-19 21:20:42 +08:00
);
/**
* header fixed值
*/
const headerFixed = computed(() => {
2024-07-16 22:07:28 +08:00
const { headerMode } = props;
2024-05-19 21:20:42 +08:00
return (
isMixedNav.value ||
2024-07-16 22:07:28 +08:00
headerMode === 'fixed' ||
headerMode === 'auto-scroll' ||
headerMode === 'auto'
2024-05-19 21:20:42 +08:00
);
});
2024-08-14 20:37:21 +08:00
const showSidebar = computed(() => {
return isSideMode.value && sidebarEnable.value && !props.sidebarHidden;
2024-08-14 20:37:21 +08:00
});
/**
* 遮罩可见性
*/
const maskVisible = computed(() => !sidebarCollapse.value && props.isMobile);
2024-05-19 21:20:42 +08:00
const mainStyle = computed(() => {
let width = '100%';
2024-06-09 15:39:11 +08:00
let sidebarAndExtraWidth = 'unset';
2024-05-19 21:20:42 +08:00
if (
headerFixed.value &&
2024-08-14 20:37:21 +08:00
currentLayout.value !== 'header-nav' &&
currentLayout.value !== 'mixed-nav' &&
currentLayout.value !== 'header-sidebar-nav' &&
2024-06-09 15:39:11 +08:00
showSidebar.value &&
2024-05-19 21:20:42 +08:00
!props.isMobile
) {
2024-06-09 15:39:11 +08:00
// fixed模式下生效
2024-05-19 21:20:42 +08:00
const isSideNavEffective =
(isSidebarMixedNav.value || isHeaderMixedNav.value) &&
2024-06-09 15:39:11 +08:00
sidebarExpandOnHover.value &&
sidebarExtraVisible.value;
2024-05-19 21:20:42 +08:00
if (isSideNavEffective) {
const sideCollapseWidth = props.sidebarMixedWidth;
2024-06-09 15:39:11 +08:00
const sideWidth = sidebarExtraCollapse.value
2024-08-14 20:37:21 +08:00
? props.sidebarExtraCollapsedWidth
2024-06-09 15:39:11 +08:00
: props.sidebarWidth;
2024-05-19 21:20:42 +08:00
// 100% - 侧边菜单混合宽度 - 菜单宽度
2024-06-09 15:39:11 +08:00
sidebarAndExtraWidth = `${sideCollapseWidth + sideWidth}px`;
width = `calc(100% - ${sidebarAndExtraWidth})`;
2024-05-19 21:20:42 +08:00
} else {
let sidebarWidth = getSidebarWidth.value;
if (sidebarExpandOnHovering.value && !sidebarExpandOnHover.value) {
sidebarWidth =
isSidebarMixedNav.value || isHeaderMixedNav.value
? props.sidebarMixedWidth
: getSideCollapseWidth.value;
}
sidebarAndExtraWidth = `${sidebarWidth}px`;
2024-06-09 15:39:11 +08:00
width = `calc(100% - ${sidebarAndExtraWidth})`;
2024-05-19 21:20:42 +08:00
}
}
return {
2024-06-09 15:39:11 +08:00
sidebarAndExtraWidth,
2024-05-19 21:20:42 +08:00
width,
};
});
// 计算 tabbar 的样式
2024-06-09 15:39:11 +08:00
const tabbarStyle = computed((): CSSProperties => {
let width: string;
2024-05-19 21:20:42 +08:00
let marginLeft = 0;
// 如果不是混合导航tabbar 的宽度为 100%
if (!isMixedNav.value || props.sidebarHidden) {
2024-05-19 21:20:42 +08:00
width = '100%';
2024-06-09 15:39:11 +08:00
} else if (sidebarEnable.value) {
// 鼠标在侧边栏上时,且侧边栏展开时的宽度
const onHoveringWidth = sidebarExpandOnHover.value
? props.sidebarWidth
: getSideCollapseWidth.value;
// 设置 marginLeft根据侧边栏是否折叠来决定
2024-06-09 15:39:11 +08:00
marginLeft = sidebarCollapse.value
2024-05-19 21:20:42 +08:00
? getSideCollapseWidth.value
: onHoveringWidth;
2024-07-16 00:14:24 +08:00
// 设置 tabbar 的宽度,计算方式为 100% 减去侧边栏的宽度
width = `calc(100% - ${sidebarCollapse.value ? getSidebarWidth.value : onHoveringWidth}px)`;
2024-05-19 21:20:42 +08:00
} else {
// 默认情况下tabbar 的宽度为 100%
2024-05-19 21:20:42 +08:00
width = '100%';
}
return {
marginLeft: `${marginLeft}px`,
width,
};
});
const layoutScrollStyle = computed((): CSSProperties => {
2024-05-19 21:20:42 +08:00
const fixed = headerFixed.value;
if (!fixed) {
return {
marginTop: 0,
paddingTop: 0,
};
}
if (isHeaderOverlayModeActive.value) {
return {
marginTop: 0,
paddingTop: isFullContent.value ? 0 : `${headerWrapperHeight.value}px`,
};
}
2024-05-19 21:20:42 +08:00
return {
marginTop:
fixed &&
2024-08-14 20:37:21 +08:00
!isFullContent.value &&
2024-05-19 21:20:42 +08:00
!headerIsHidden.value &&
(!isHeaderAutoActive.value || scrollY.value < headerWrapperHeight.value)
2024-05-19 21:20:42 +08:00
? `${headerWrapperHeight.value}px`
: 0,
paddingTop: 0,
};
});
const contentStyle = computed((): CSSProperties => {
const { footerEnable, footerFixed, footerHeight } = props;
return {
paddingBottom: `${footerEnable && footerFixed ? footerHeight : 0}px`,
2024-05-19 21:20:42 +08:00
};
});
const headerZIndex = computed(() => {
const { zIndex } = props;
const offset = isMixedNav.value ? 1 : 0;
return zIndex + offset;
});
const headerWrapperStyle = computed((): CSSProperties => {
const fixed = headerFixed.value;
const hidden = headerIsHidden.value || isFullContent.value;
2024-05-19 21:20:42 +08:00
return {
2024-08-14 20:37:21 +08:00
height: isFullContent.value ? '0' : `${headerWrapperHeight.value}px`,
2024-06-09 15:39:11 +08:00
left: isMixedNav.value ? 0 : mainStyle.value.sidebarAndExtraWidth,
2024-05-19 21:20:42 +08:00
position: fixed ? 'fixed' : 'static',
top: 0,
transform: fixed
? `translate3d(0, ${hidden ? '-100%' : '0'}, 0)`
: undefined,
transitionDuration: fixed ? undefined : '0ms',
2024-05-19 21:20:42 +08:00
width: mainStyle.value.width,
willChange: fixed ? 'transform' : undefined,
2024-05-19 21:20:42 +08:00
'z-index': headerZIndex.value,
};
});
/**
* 侧边栏z-index
*/
2024-06-09 15:39:11 +08:00
const sidebarZIndex = computed(() => {
2024-05-19 21:20:42 +08:00
const { isMobile, zIndex } = props;
let offset = isMobile || isSideMode.value ? 1 : -1;
if (isMixedNav.value) {
offset += 1;
}
2024-05-19 21:20:42 +08:00
return zIndex + offset;
});
2024-06-09 15:39:11 +08:00
const footerWidth = computed(() => {
if (!props.footerFixed) {
return '100%';
}
return mainStyle.value.width;
});
2024-05-19 21:20:42 +08:00
const maskStyle = computed((): CSSProperties => {
2024-06-09 15:39:11 +08:00
return { zIndex: props.zIndex };
2024-05-19 21:20:42 +08:00
});
/**
* 侧边栏 Logo 区域是否显示
*/
const sidebarHeaderHeight = computed(() => {
if (isMixedNav.value || !props.sidebarLogoVisible) {
return 0;
}
return props.headerHeight;
});
2024-05-19 21:20:42 +08:00
const showHeaderToggleButton = computed(() => {
return (
props.isMobile ||
(props.headerToggleSidebarButton &&
isSideMode.value &&
!isSidebarMixedNav.value &&
!isMixedNav.value &&
!props.isMobile)
2024-05-19 21:20:42 +08:00
);
});
const showHeaderLogo = computed(() => {
return !isSideMode.value || isMixedNav.value || props.isMobile;
});
watch(
() => props.isMobile,
(val) => {
if (val) {
sidebarCollapse.value = true;
}
},
{
immediate: true,
2024-05-19 21:20:42 +08:00
},
);
watch(
[() => headerWrapperHeight.value, () => isFullContent.value],
([height]) => {
setLayoutHeaderHeight(isFullContent.value ? 0 : height);
},
{
immediate: true,
},
);
watch(
() => props.footerHeight,
(height: number) => {
setLayoutFooterHeight(height);
},
{
immediate: true,
},
);
watch(
[() => props.headerMode, () => isMixedNav.value, () => isFullContent.value],
() => {
headerIsHidden.value = false;
},
);
2026-01-12 21:40:26 +08:00
useEventListener(mainRef, 'mousemove', handleHeaderMouseMove, {
passive: true,
});
useEventListener(mainRef, 'wheel', handleLayoutWheel, {
passive: true,
});
2026-01-12 21:40:26 +08:00
function handleLayoutWheel(event: WheelEvent) {
lastMouseY = event.clientY;
2024-05-19 21:20:42 +08:00
}
function handleHeaderMouseMove(event: MouseEvent) {
lastMouseY = event.clientY;
2024-05-19 21:20:42 +08:00
if (!isHeaderAutoActive.value) {
return;
}
updateHeaderVisibilityFromMouse(lastMouseY);
}
function updateHeaderVisibilityFromMouse(mouseY: null | number) {
if (arrivedState.top || scrollY.value < headerWrapperHeight.value) {
headerIsHidden.value = false;
return;
}
if (mouseY === null) {
return;
}
const isInTriggerZone = mouseY <= HEADER_TRIGGER_DISTANCE;
const isInHeaderZone =
!headerIsHidden.value && mouseY <= headerWrapperHeight.value;
headerIsHidden.value = !(isInTriggerZone || isInHeaderZone);
}
function handleLayoutScroll() {
if (isHeaderAutoActive.value) {
updateHeaderVisibilityFromMouse(lastMouseY);
return;
}
if (
props.headerMode !== 'auto-scroll' ||
isMixedNav.value ||
isFullContent.value
) {
return;
}
resolveHeaderVisibilityOnScroll();
}
function resolveHeaderVisibilityOnScroll() {
headerIsHidden.value = resolveHeaderHiddenOnScroll({
arrivedTop: arrivedState.top,
currentHidden: headerIsHidden.value,
directionDown: directions.bottom,
directionUp: directions.top,
headerHeight: headerWrapperHeight.value,
scrollTop: scrollY.value,
});
2024-05-19 21:20:42 +08:00
}
function handleClickMask() {
2024-06-09 15:39:11 +08:00
sidebarCollapse.value = true;
2024-05-19 21:20:42 +08:00
}
function handleHeaderToggle() {
if (props.isMobile) {
sidebarCollapse.value = false;
} else {
emit('toggleSidebar');
}
2024-05-19 21:20:42 +08:00
}
const idMainContent = ELEMENT_ID_MAIN_CONTENT;
const idLayoutScroll = ELEMENT_ID_LAYOUT_SCROLL;
const idLayoutStaticHeader = `${ELEMENT_ID_LAYOUT_SCROLL}__static_header`;
const layoutStaticHeaderTarget = `#${idLayoutStaticHeader}`;
2024-05-19 21:20:42 +08:00
</script>
<template>
<div class="relative flex h-full min-h-0 w-full overflow-hidden">
2024-06-09 15:39:11 +08:00
<LayoutSidebar
v-if="sidebarEnableState"
v-model:draggable="sidebarDraggable"
2024-06-09 15:39:11 +08:00
v-model:collapse="sidebarCollapse"
v-model:expand-on-hover="sidebarExpandOnHover"
v-model:expand-on-hovering="sidebarExpandOnHovering"
v-model:extra-collapse="sidebarExtraCollapse"
v-model:extra-visible="sidebarExtraVisible"
:show-collapse-button="sidebarCollapsedButton"
:show-fixed-button="sidebarFixedButton"
2024-06-09 13:31:43 +08:00
:collapse-width="getSideCollapseWidth"
2024-05-19 21:20:42 +08:00
:dom-visible="!isMobile"
:extra-width="sidebarExtraWidth"
2024-06-09 15:39:11 +08:00
:fixed-extra="sidebarExpandOnHover"
:header-height="sidebarHeaderHeight"
:extra-title-height="
isSidebarMixedNav || isHeaderMixedNav ? sidebarExtraTitleHeight : 0
"
:is-sidebar-mixed="isSidebarMixedNav || isHeaderMixedNav"
:margin-top="sidebarMarginTop"
2024-06-09 15:39:11 +08:00
:mixed-width="sidebarMixedWidth"
:show="showSidebar"
2024-08-14 20:37:21 +08:00
:theme="sidebarTheme"
:theme-sub="sidebarThemeSub"
2024-06-09 15:39:11 +08:00
:width="getSidebarWidth"
:z-index="sidebarZIndex"
2024-05-19 21:20:42 +08:00
@leave="() => emit('sideMouseLeave')"
2026-06-06 16:21:25 +08:00
@update:width="(val) => emit('update:sidebarWidth', val)"
2024-05-19 21:20:42 +08:00
>
<template v-if="isSideMode && !isMixedNav && sidebarLogoVisible" #logo>
2024-05-19 21:20:42 +08:00
<slot name="logo"></slot>
</template>
<template v-if="isSidebarMixedNav || isHeaderMixedNav">
2024-05-19 21:20:42 +08:00
<slot name="mixed-menu"></slot>
</template>
<template v-else>
<slot name="menu"></slot>
</template>
<template #extra>
<slot name="side-extra"></slot>
</template>
<template #extra-title>
<slot name="side-extra-title"></slot>
</template>
2024-06-09 15:39:11 +08:00
</LayoutSidebar>
2024-05-19 21:20:42 +08:00
2024-05-28 23:38:36 +08:00
<div
ref="mainRef"
class="relative flex min-h-0 flex-1 flex-col overflow-hidden transition-all duration-300 ease-in"
2024-05-28 23:38:36 +08:00
>
<Teleport defer :disabled="headerFixed" :to="layoutStaticHeaderTarget">
<div
:class="[
{
'shadow-[0_16px_24px_hsl(var(--background))]': headerHasShadow,
},
SCROLL_FIXED_CLASS,
]"
:style="headerWrapperStyle"
class="shrink-0 overflow-hidden transition-[transform,left,width] duration-200"
>
<LayoutHeader
v-if="headerVisible"
:full-width="!isSideMode"
:height="headerHeight"
:is-mobile="isMobile"
:show="!isFullContent && !headerHidden"
:sidebar-width="sidebarWidth"
:theme="headerTheme"
:width="mainStyle.width"
:z-index="headerZIndex"
:logo-visible="sidebarLogoVisible"
>
<template v-if="showHeaderLogo" #logo>
<slot name="logo"></slot>
</template>
<template #toggle-button>
<VbenIconButton
v-if="showHeaderToggleButton"
class="my-0 mr-1 rounded-md"
@click="handleHeaderToggle"
>
<IconifyIcon v-if="showSidebar" icon="ep:fold" />
<IconifyIcon v-else icon="ep:expand" />
</VbenIconButton>
</template>
<slot name="header"></slot>
</LayoutHeader>
<LayoutTabbar
v-if="tabbarEnable"
:height="tabbarHeight"
:style="tabbarStyle"
>
<slot name="tabbar"></slot>
</LayoutTabbar>
</div>
</Teleport>
2024-05-28 23:38:36 +08:00
<div
:id="idLayoutScroll"
ref="contentRef"
:style="layoutScrollStyle"
class="flex min-h-0 flex-1 flex-col overflow-x-hidden overflow-y-auto"
2024-05-28 23:38:36 +08:00
>
<div :id="idLayoutStaticHeader" class="contents"></div>
<LayoutContent
:id="idMainContent"
:content-compact="contentCompact"
:content-compact-width="contentCompactWidth"
:padding="contentPadding"
:padding-bottom="contentPaddingBottom"
:padding-left="contentPaddingLeft"
:padding-right="contentPaddingRight"
:padding-top="contentPaddingTop"
:style="contentStyle"
2024-05-19 21:20:42 +08:00
>
<slot name="content"></slot>
<template #overlay>
<slot name="content-overlay"></slot>
</template>
</LayoutContent>
<LayoutFooter
v-if="footerEnable"
:fixed="footerFixed"
:height="footerHeight"
:show="!isFullContent"
:width="footerWidth"
:z-index="zIndex"
2024-06-09 15:39:11 +08:00
>
<slot name="footer"></slot>
</LayoutFooter>
2024-05-19 21:20:42 +08:00
</div>
</div>
<slot name="extra"></slot>
2024-05-19 21:20:42 +08:00
<div
v-if="maskVisible"
:style="maskStyle"
class="fixed top-0 left-0 size-full bg-overlay transition-[background-color] duration-200"
2024-05-19 21:20:42 +08:00
@click="handleClickMask"
></div>
</div>
</template>