<template>
|
<div :class="getClass">
|
<PageHeader
|
:ghost="ghost"
|
:title="title"
|
v-bind="$attrs"
|
ref="headerRef"
|
v-if="content || $slots.headerContent || title || getHeaderSlots.length"
|
>
|
<template #default>
|
<template v-if="content">
|
{{ content }}
|
</template>
|
<slot name="headerContent" v-else></slot>
|
</template>
|
<template #[item]="data" v-for="item in getHeaderSlots">
|
<slot :name="item" v-bind="data"></slot>
|
</template>
|
</PageHeader>
|
<div
|
class="overflow-hidden"
|
:class="[`${prefixCls}-content`, contentClass]"
|
:style="getContentStyle"
|
>
|
<slot></slot>
|
</div>
|
<PageFooter v-if="getShowFooter" ref="footerRef">
|
<template #left>
|
<slot name="leftFooter"></slot>
|
</template>
|
<template #right>
|
<slot name="rightFooter"></slot>
|
</template>
|
</PageFooter>
|
</div>
|
</template>
|
<script lang="ts">
|
import type { CSSProperties, PropType } from 'vue';
|
|
import { defineComponent, computed, watch, nextTick, ref, unref } from 'vue';
|
import PageFooter from './PageFooter.vue';
|
import { usePageContext } from '/@/hooks/component/usePageContext';
|
|
import { useDesign } from '/@/hooks/web/useDesign';
|
import { propTypes } from '/@/utils/propTypes';
|
import { omit } from 'lodash-es';
|
import { PageHeader } from 'ant-design-vue';
|
import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated';
|
export default defineComponent({
|
name: 'PageWrapper',
|
components: { PageFooter, PageHeader },
|
inheritAttrs: false,
|
props: {
|
title: propTypes.string,
|
dense: propTypes.bool,
|
ghost: propTypes.bool,
|
content: propTypes.string,
|
contentStyle: {
|
type: Object as PropType<CSSProperties>,
|
},
|
contentBackground: propTypes.bool,
|
contentFullHeight: propTypes.bool,
|
contentClass: propTypes.string,
|
fixedHeight: propTypes.bool,
|
},
|
setup(props, { slots }) {
|
const headerRef = ref<ComponentRef>(null);
|
const footerRef = ref<ComponentRef>(null);
|
const footerHeight = ref(0);
|
const { prefixCls, prefixVar } = useDesign('page-wrapper');
|
const { contentHeight, setPageHeight, pageHeight } = usePageContext();
|
|
const getClass = computed(() => {
|
return [
|
prefixCls,
|
{
|
[`${prefixCls}--dense`]: props.dense,
|
},
|
];
|
});
|
|
const getShowFooter = computed(() => slots?.leftFooter || slots?.rightFooter);
|
|
const getHeaderSlots = computed(() => {
|
return Object.keys(omit(slots, 'default', 'leftFooter', 'rightFooter', 'headerContent'));
|
});
|
|
const getContentStyle = computed(
|
(): CSSProperties => {
|
const { contentBackground, contentFullHeight, contentStyle, fixedHeight } = props;
|
const bg = contentBackground ? { backgroundColor: '#fff' } : {};
|
if (!contentFullHeight) {
|
return { ...bg, ...contentStyle };
|
}
|
const height = `${unref(pageHeight)}px`;
|
return {
|
...bg,
|
...contentStyle,
|
minHeight: height,
|
...(fixedHeight ? { height } : {}),
|
paddingBottom: `${unref(footerHeight)}px`,
|
};
|
}
|
);
|
|
watch(
|
() => [contentHeight?.value, getShowFooter.value],
|
() => {
|
calcContentHeight();
|
},
|
{
|
flush: 'post',
|
immediate: true,
|
}
|
);
|
|
onMountedOrActivated(() => {
|
nextTick(() => {
|
calcContentHeight();
|
});
|
});
|
|
function calcContentHeight() {
|
if (!props.contentFullHeight) {
|
return;
|
}
|
//fix:in contentHeight mode: delay getting footer and header dom element to get the correct height
|
const footer = unref(footerRef);
|
const header = unref(headerRef);
|
footerHeight.value = 0;
|
const footerEl = footer?.$el;
|
|
if (footerEl) {
|
footerHeight.value += footerEl?.offsetHeight ?? 0;
|
}
|
let headerHeight = 0;
|
const headerEl = header?.$el;
|
if (headerEl) {
|
headerHeight += headerEl?.offsetHeight ?? 0;
|
}
|
// fix:subtract content's marginTop and marginBottom value
|
let subtractHeight = 0;
|
let marginBottom = '0px';
|
let marginTop = '0px';
|
const classElments = document.querySelectorAll(`.${prefixVar}-page-wrapper-content`);
|
if (classElments && classElments.length > 0) {
|
const contentEl = classElments[0];
|
const cssStyle = getComputedStyle(contentEl);
|
marginBottom = cssStyle?.marginBottom;
|
marginTop = cssStyle?.marginTop;
|
}
|
if (marginBottom) {
|
const contentMarginBottom = Number(marginBottom.replace(/[^\d]/g, ''));
|
subtractHeight += contentMarginBottom;
|
}
|
if (marginTop) {
|
const contentMarginTop = Number(marginTop.replace(/[^\d]/g, ''));
|
subtractHeight += contentMarginTop;
|
}
|
setPageHeight?.(unref(contentHeight) - unref(footerHeight) - headerHeight - subtractHeight);
|
}
|
|
return {
|
getContentStyle,
|
footerRef,
|
headerRef,
|
getClass,
|
getHeaderSlots,
|
prefixCls,
|
getShowFooter,
|
pageHeight,
|
omit,
|
};
|
},
|
});
|
</script>
|
<style lang="less">
|
@prefix-cls: ~'@{namespace}-page-wrapper';
|
|
.@{prefix-cls} {
|
position: relative;
|
|
.@{prefix-cls}-content {
|
margin: 16px;
|
}
|
|
.ant-page-header {
|
&:empty {
|
padding: 0;
|
}
|
}
|
|
&--dense {
|
.@{prefix-cls}-content {
|
margin: 0;
|
}
|
}
|
}
|
</style>
|