Ben Lin
2024-07-01 f60c5156615626515bd6d84f151a1292b8b936c1
src/components/Page/src/PageWrapper.vue
@@ -1,9 +1,10 @@
<template>
  <div :class="getClass" ref="wrapperRef">
  <div :class="getClass" :style="getStyle" ref="wrapperRef">
    <PageHeader
      :ghost="ghost"
      :title="title"
      v-bind="omit($attrs, 'class')"
      :style="getHeaderStyle"
      ref="headerRef"
      v-if="getShowHeader"
    >
@@ -32,142 +33,161 @@
    </PageFooter>
  </div>
</template>
<script lang="ts">
<script lang="ts" setup>
  import { PageWrapperFixedHeightKey } from '@/enums/pageEnum';
  import { useContentHeight } from '@/hooks/web/useContentHeight';
  import { useDesign } from '@/hooks/web/useDesign';
  import { propTypes } from '@/utils/propTypes';
  import { PageHeader } from 'ant-design-vue';
  import { omit, debounce } from 'lodash-es';
  import { useElementSize } from '@vueuse/core';
  import {
    CSSProperties,
    PropType,
    provide,
    defineComponent,
    computed,
    watch,
    provide,
    ref,
    unref,
    useAttrs,
    useSlots,
    watch,
  } from 'vue';
  import PageFooter from './PageFooter.vue';
  import { useDesign } from '/@/hooks/web/useDesign';
  import { propTypes } from '/@/utils/propTypes';
  import { omit } from 'lodash-es';
  import { PageHeader } from 'ant-design-vue';
  import { useContentHeight } from '/@/hooks/web/useContentHeight';
  import { PageWrapperFixedHeightKey } from '/@/enums/pageEnum';
  export default defineComponent({
  defineOptions({
    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>,
  });
  const props = defineProps({
    title: propTypes.string,
    dense: propTypes.bool,
    ghost: propTypes.bool,
    headerSticky: propTypes.bool,
    headerStyle: Object as PropType<CSSProperties>,
    content: propTypes.string,
    contentStyle: {
      type: Object as PropType<CSSProperties>,
    },
    contentBackground: propTypes.bool,
    contentFullHeight: propTypes.bool.def(false),
    contentClass: propTypes.string,
    fixedHeight: propTypes.bool,
    upwardSpace: propTypes.oneOfType([propTypes.number, propTypes.string]).def(0),
  });
  const attrs = useAttrs();
  const slots = useSlots();
  const wrapperRef = ref(null);
  const headerRef = ref(null);
  const contentRef = ref(null);
  const footerRef = ref(null);
  const { height } = useElementSize(wrapperRef);
  const { prefixCls } = useDesign('page-wrapper');
  provide(
    PageWrapperFixedHeightKey,
    computed(() => props.fixedHeight),
  );
  const getIsContentFullHeight = computed(() => {
    return props.contentFullHeight;
  });
  const getUpwardSpace = computed(() => props.upwardSpace);
  const { redoHeight, setCompensation, contentHeight } = useContentHeight(
    getIsContentFullHeight,
    wrapperRef,
    [headerRef, footerRef],
    [contentRef],
    getUpwardSpace,
  );
  const debounceRedoHeight = debounce(redoHeight, 50);
  setCompensation({ useLayoutFooter: true, elements: [footerRef] });
  const getClass = computed(() => {
    return [
      prefixCls,
      {
        [`${prefixCls}--dense`]: props.dense,
      },
      contentBackground: propTypes.bool,
      contentFullHeight: propTypes.bool,
      contentClass: propTypes.string,
      fixedHeight: propTypes.bool,
      upwardSpace: propTypes.oneOfType([propTypes.number, propTypes.string]).def(0),
      attrs.class ?? {},
    ];
  });
  const getStyle = computed(() => {
    const { contentFullHeight, fixedHeight } = props;
    return {
      ...(contentFullHeight && fixedHeight ? { height: '100%' } : {}),
    };
  });
  const getHeaderStyle = computed((): CSSProperties => {
    const { headerSticky } = props;
    if (!headerSticky) {
      return {};
    }
    return {
      position: 'sticky',
      top: 0,
      zIndex: 99,
      ...props.headerStyle,
    };
  });
  const getShowHeader = computed(
    () => props.content || slots?.headerContent || props.title || getHeaderSlots.value.length,
  );
  const getShowFooter = computed(() => slots?.leftFooter || slots?.rightFooter);
  const getHeaderSlots = computed(() => {
    return Object.keys(omit(slots, 'default', 'leftFooter', 'rightFooter', 'headerContent'));
  });
  const getContentStyle = computed((): CSSProperties => {
    const { contentFullHeight, contentStyle, fixedHeight } = props;
    if (!contentFullHeight) {
      return { ...contentStyle };
    }
    const height = `${unref(contentHeight)}px`;
    return {
      ...contentStyle,
      minHeight: height,
      ...(fixedHeight ? { height } : {}),
    };
  });
  const getContentClass = computed(() => {
    const { contentBackground, contentClass } = props;
    return [
      `${prefixCls}-content`,
      contentClass,
      {
        [`${prefixCls}-content-bg`]: contentBackground,
      },
    ];
  });
  watch(
    () => [getShowFooter.value],
    () => {
      redoHeight();
    },
    setup(props, { slots, attrs }) {
      const wrapperRef = ref(null);
      const headerRef = ref(null);
      const contentRef = ref(null);
      const footerRef = ref(null);
      const { prefixCls } = useDesign('page-wrapper');
      provide(
        PageWrapperFixedHeightKey,
        computed(() => props.fixedHeight),
      );
      const getIsContentFullHeight = computed(() => {
        return props.contentFullHeight;
      });
      const getUpwardSpace = computed(() => props.upwardSpace);
      const { redoHeight, setCompensation, contentHeight } = useContentHeight(
        getIsContentFullHeight,
        wrapperRef,
        [headerRef, footerRef],
        [contentRef],
        getUpwardSpace,
      );
      setCompensation({ useLayoutFooter: true, elements: [footerRef] });
      const getClass = computed(() => {
        return [
          prefixCls,
          {
            [`${prefixCls}--dense`]: props.dense,
          },
          attrs.class ?? {},
        ];
      });
      const getShowHeader = computed(
        () => props.content || slots?.headerContent || props.title || getHeaderSlots.value.length,
      );
      const getShowFooter = computed(() => slots?.leftFooter || slots?.rightFooter);
      const getHeaderSlots = computed(() => {
        return Object.keys(omit(slots, 'default', 'leftFooter', 'rightFooter', 'headerContent'));
      });
      const getContentStyle = computed((): CSSProperties => {
        const { contentFullHeight, contentStyle, fixedHeight } = props;
        if (!contentFullHeight) {
          return { ...contentStyle };
        }
        const height = `${unref(contentHeight)}px`;
        return {
          ...contentStyle,
          minHeight: height,
          ...(fixedHeight ? { height } : {}),
        };
      });
      const getContentClass = computed(() => {
        const { contentBackground, contentClass } = props;
        return [
          `${prefixCls}-content`,
          contentClass,
          {
            [`${prefixCls}-content-bg`]: contentBackground,
          },
        ];
      });
      watch(
        () => [getShowFooter.value],
        () => {
          redoHeight();
        },
        {
          flush: 'post',
          immediate: true,
        },
      );
      return {
        getContentStyle,
        wrapperRef,
        headerRef,
        contentRef,
        footerRef,
        getClass,
        getHeaderSlots,
        prefixCls,
        getShowHeader,
        getShowFooter,
        omit,
        getContentClass,
      };
    {
      flush: 'post',
      immediate: true,
    },
  );
  watch(height, () => {
    const { contentFullHeight, fixedHeight } = props;
    contentFullHeight && fixedHeight && debounceRedoHeight();
  });
</script>
<style lang="less">