|
|
|
@ -3,9 +3,8 @@ import type { StyleValue } from 'vue'; |
|
|
|
|
|
|
|
import type { PageProps } from './types'; |
|
|
|
|
|
|
|
import { computed, nextTick, onMounted, ref, useTemplateRef } from 'vue'; |
|
|
|
import { computed } from 'vue'; |
|
|
|
|
|
|
|
import { CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT } from '@vben-core/shared/constants'; |
|
|
|
import { cn } from '@vben-core/shared/utils'; |
|
|
|
|
|
|
|
defineOptions({ |
|
|
|
@ -18,45 +17,27 @@ const { |
|
|
|
footerFixed = false, |
|
|
|
} = defineProps<PageProps>(); |
|
|
|
|
|
|
|
const headerHeight = ref(0); |
|
|
|
const footerHeight = ref(0); |
|
|
|
const shouldAutoHeight = ref(false); |
|
|
|
|
|
|
|
const headerRef = useTemplateRef<HTMLDivElement>('headerRef'); |
|
|
|
const footerRef = useTemplateRef<HTMLDivElement>('footerRef'); |
|
|
|
|
|
|
|
const contentStyle = computed<StyleValue>(() => { |
|
|
|
if (autoContentHeight) { |
|
|
|
return { |
|
|
|
height: `calc(var(${CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT}) - ${headerHeight.value}px - ${footerHeight.value}px - ${typeof heightOffset === 'number' ? `${heightOffset}px` : heightOffset})`, |
|
|
|
overflowY: shouldAutoHeight.value ? 'auto' : 'unset', |
|
|
|
}; |
|
|
|
} |
|
|
|
return {}; |
|
|
|
}); |
|
|
|
|
|
|
|
async function calcContentHeight() { |
|
|
|
if (!autoContentHeight) { |
|
|
|
return; |
|
|
|
return {}; |
|
|
|
} |
|
|
|
shouldAutoHeight.value = false; |
|
|
|
await nextTick(); |
|
|
|
headerHeight.value = headerRef.value?.offsetHeight || 0; |
|
|
|
|
|
|
|
footerHeight.value = footerFixed ? 0 : footerRef.value?.offsetHeight || 0; |
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
|
shouldAutoHeight.value = true; |
|
|
|
}, 30); |
|
|
|
} |
|
|
|
|
|
|
|
onMounted(() => { |
|
|
|
calcContentHeight(); |
|
|
|
return { |
|
|
|
'--page-content-height-offset': `${heightOffset}px`, |
|
|
|
marginBlockEnd: 'var(--page-content-height-offset)', |
|
|
|
}; |
|
|
|
}); |
|
|
|
</script> |
|
|
|
|
|
|
|
<template> |
|
|
|
<div class="relative flex h-full flex-col"> |
|
|
|
<div |
|
|
|
:class=" |
|
|
|
cn( |
|
|
|
'relative flex h-full min-h-0 flex-col', |
|
|
|
autoContentHeight && 'overflow-hidden', |
|
|
|
) |
|
|
|
" |
|
|
|
> |
|
|
|
<div |
|
|
|
v-if=" |
|
|
|
description || |
|
|
|
@ -65,10 +46,9 @@ onMounted(() => { |
|
|
|
$slots.title || |
|
|
|
$slots.extra |
|
|
|
" |
|
|
|
ref="headerRef" |
|
|
|
:class=" |
|
|
|
cn( |
|
|
|
'relative flex items-end border-b border-border bg-card px-6 py-4', |
|
|
|
'relative flex shrink-0 items-end border-b border-border bg-card px-6 py-4', |
|
|
|
headerClass, |
|
|
|
) |
|
|
|
" |
|
|
|
@ -93,15 +73,27 @@ onMounted(() => { |
|
|
|
</div> |
|
|
|
|
|
|
|
<div |
|
|
|
:class="cn(autoContentHeight ? 'h-full' : 'flex-1', 'p-4', contentClass)" |
|
|
|
data-layout-region="page-content" |
|
|
|
:class=" |
|
|
|
cn( |
|
|
|
autoContentHeight ? 'min-h-0 flex-1 overflow-y-auto' : 'flex-1', |
|
|
|
'p-4', |
|
|
|
contentClass, |
|
|
|
) |
|
|
|
" |
|
|
|
:style="contentStyle" |
|
|
|
> |
|
|
|
<slot></slot> |
|
|
|
</div> |
|
|
|
<div |
|
|
|
v-if="$slots.footer" |
|
|
|
ref="footerRef" |
|
|
|
:class="cn('align-center flex bg-card px-6 py-4', footerClass)" |
|
|
|
:class=" |
|
|
|
cn( |
|
|
|
'align-center flex bg-card px-6 py-4', |
|
|
|
footerFixed ? 'absolute inset-x-0 bottom-0' : 'shrink-0', |
|
|
|
footerClass, |
|
|
|
) |
|
|
|
" |
|
|
|
> |
|
|
|
<slot name="footer"></slot> |
|
|
|
</div> |
|
|
|
|