Bläddra i källkod

refactor(contentHeight): 重构,将PageWrapper的useContentHeight抽象为公共hook,满足自定义扩展组件时的自动高度计算需求。 同时优化高度计算算法。 (#826)

Lan 3 år sedan
förälder
incheckning
9de6ac1119

+ 13 - 17
src/components/Page/src/PageWrapper.vue

@@ -42,9 +42,7 @@
   import { propTypes } from '/@/utils/propTypes';
   import { omit } from 'lodash-es';
   import { PageHeader } from 'ant-design-vue';
-  import { useLayoutHeight } from '/@/layouts/default/content/useContentViewHeight';
-  import { useContentHeight } from './useContentHeight';
-  import { WrapperProps } from './types';
+  import { useContentHeight } from '/@/hooks/web/useContentHeight';
 
   export default defineComponent({
     name: 'PageWrapper',
@@ -64,25 +62,23 @@
       fixedHeight: propTypes.bool,
     },
     setup(props, { slots }) {
-      const wrapperRef = ref<ElRef>(null);
-      const headerRef = ref<ComponentRef>(null);
-      const contentRef = ref<ElRef>(null);
-      const footerRef = ref<ComponentRef>(null);
+      const wrapperRef = ref(null);
+      const headerRef = ref(null);
+      const contentRef = ref(null);
+      const footerRef = ref(null);
       const { prefixCls } = useDesign('page-wrapper');
-      const { footerHeightRef } = useLayoutHeight();
 
-      const getProps = computed(() => {
-        return props as WrapperProps;
+      const getIsContentFullHeight = computed(() => {
+        return props.contentFullHeight;
       });
 
-      const { redoHeight, contentHeight } = useContentHeight(
-        getProps,
+      const { redoHeight, setCompensation, contentHeight } = useContentHeight(
+        getIsContentFullHeight,
         wrapperRef,
-        headerRef,
-        contentRef,
-        footerRef,
-        footerHeightRef
+        [headerRef, footerRef],
+        [contentRef]
       );
+      setCompensation({ useLayoutFooter: true, elements: [footerRef] });
 
       const getClass = computed(() => {
         return [
@@ -125,7 +121,7 @@
       });
 
       watch(
-        () => [getShowFooter.value, footerHeightRef.value],
+        () => [getShowFooter.value],
         () => {
           redoHeight();
         },

+ 0 - 13
src/components/Page/src/types.ts

@@ -1,13 +0,0 @@
-import { CSSProperties } from 'vue';
-
-export interface WrapperProps {
-  title?: string;
-  dense: boolean;
-  ghost: boolean;
-  content: string;
-  contentStyle?: CSSProperties;
-  contentBackground: boolean;
-  contentFullHeight: boolean;
-  contentClass?: string;
-  fixedHeight: boolean;
-}

+ 0 - 93
src/components/Page/src/useContentHeight.ts

@@ -1,93 +0,0 @@
-import { ComputedRef, nextTick, Ref, ref, unref } from 'vue';
-import { WrapperProps } from './types';
-import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated';
-import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
-import { getViewportOffset } from '/@/utils/domUtils';
-
-export function useContentHeight(
-  propsRef: ComputedRef<WrapperProps>,
-  wrapperRef: Ref<ElRef>,
-  headerRef?: Ref<ComponentRef>,
-  contentRef?: Ref<ElRef>,
-  footerRef?: Ref<ComponentRef>,
-  layoutFooterHeightRef: Ref<number> = ref(0),
-  offsetHeightRef: Ref<number> = ref(0)
-) {
-  const contentHeight: Ref<Nullable<number>> = ref(null);
-
-  const redoHeight = () => {
-    nextTick(() => {
-      calcContentHeight();
-    });
-  };
-
-  const subtractMargin = (element: HTMLElement | null | undefined): number => {
-    let subtractHeight = 0;
-    const ZERO_PX = '0px';
-    let marginBottom = ZERO_PX;
-    let marginTop = ZERO_PX;
-    if (element) {
-      const cssStyle = getComputedStyle(element);
-      marginBottom = cssStyle?.marginBottom ?? ZERO_PX;
-      marginTop = cssStyle?.marginTop ?? ZERO_PX;
-    }
-    if (marginBottom) {
-      const contentMarginBottom = Number(marginBottom.replace(/[^\d]/g, ''));
-      subtractHeight += contentMarginBottom;
-    }
-    if (marginTop) {
-      const contentMarginTop = Number(marginTop.replace(/[^\d]/g, ''));
-      subtractHeight += contentMarginTop;
-    }
-    return subtractHeight;
-  };
-
-  const calcContentHeight = async () => {
-    const { contentFullHeight } = unref(propsRef);
-    if (!contentFullHeight) {
-      return;
-    }
-    // Add a delay to get the correct height
-    await nextTick();
-
-    const wrapperEl = unref(wrapperRef);
-    if (!wrapperEl) {
-      return;
-    }
-    const { bottomIncludeBody } = getViewportOffset(wrapperEl);
-    const headerHeight = unref(headerRef)?.$el.offsetHeight ?? 0;
-    const footerHeight = unref(footerRef)?.$el.offsetHeight ?? 0;
-
-    // content's subtract
-    const substractHeight = subtractMargin(unref(contentRef));
-    let height =
-      bottomIncludeBody -
-      unref(layoutFooterHeightRef) -
-      unref(offsetHeightRef) -
-      headerHeight -
-      footerHeight -
-      substractHeight;
-
-    // fix: compensation height both layout's footer and page's footer was shown
-    if (unref(layoutFooterHeightRef) > 0 && footerHeight > 0) {
-      height += footerHeight;
-    }
-
-    contentHeight.value = height;
-  };
-
-  onMountedOrActivated(() => {
-    nextTick(() => {
-      calcContentHeight();
-    });
-  });
-  useWindowSizeFn(
-    () => {
-      calcContentHeight();
-    },
-    50,
-    { immediate: true }
-  );
-
-  return { redoHeight, contentHeight };
-}

+ 147 - 0
src/hooks/web/useContentHeight.ts

@@ -0,0 +1,147 @@
+import { ComputedRef, nextTick, Ref, ref, unref, watch } from 'vue';
+import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated';
+import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
+import { useLayoutHeight } from '/@/layouts/default/content/useContentViewHeight';
+import { getViewportOffset } from '/@/utils/domUtils';
+
+export interface CompensationHeight {
+  // 使用 layout Footer 高度作为判断补偿高度的条件
+  useLayoutFooter: boolean;
+  // refs HTMLElement
+  elements?: Ref[];
+}
+
+/**
+ * 动态计算内容高度,根据锚点dom最下坐标到屏幕最下坐标,根据传入dom的高度、padding、margin等值进行动态计算
+ * 最终获取合适的内容高度
+ *
+ * @param flag 用于开启计算的响应式标识
+ * @param anchorRef 锚点组件 Ref<ElRef | ComponentRef>
+ * @param subtractHeightRefs 待减去高度的组件列表 Ref<ElRef | ComponentRef>
+ * @param substractSpaceRefs 待减去空闲空间(margins/paddings)的组件列表 Ref<ElRef | ComponentRef>
+ * @param offsetHeightRef 计算偏移的响应式高度,计算高度时将直接减去此值
+ * @returns 响应式高度
+ */
+export function useContentHeight(
+  flag: ComputedRef<Boolean>,
+  anchorRef: Ref,
+  subtractHeightRefs: Ref[],
+  substractSpaceRefs: Ref[],
+  offsetHeightRef: Ref<number> = ref(0)
+) {
+  const contentHeight: Ref<Nullable<number>> = ref(null);
+  const { footerHeightRef: layoutFooterHeightRef } = useLayoutHeight();
+  let compensationHeight: CompensationHeight = {
+    useLayoutFooter: true,
+  };
+
+  const setCompensation = (params: CompensationHeight) => {
+    compensationHeight = params;
+  };
+
+  function redoHeight() {
+    nextTick(() => {
+      calcContentHeight();
+    });
+  }
+
+  function calcSubtractSpace(element: HTMLDivElement | null | undefined): number {
+    let subtractHeight = 0;
+    const ZERO_PX = '0px';
+    let marginBottom = ZERO_PX;
+    let marginTop = ZERO_PX;
+    if (element) {
+      const cssStyle = getComputedStyle(element);
+      marginBottom = cssStyle?.marginBottom ?? ZERO_PX;
+      marginTop = cssStyle?.marginTop ?? ZERO_PX;
+    }
+    if (marginBottom) {
+      const contentMarginBottom = Number(marginBottom.replace(/[^\d]/g, ''));
+      subtractHeight += contentMarginBottom;
+    }
+    if (marginTop) {
+      const contentMarginTop = Number(marginTop.replace(/[^\d]/g, ''));
+      subtractHeight += contentMarginTop;
+    }
+    return subtractHeight;
+  }
+
+  function getEl(element: any): Nullable<HTMLDivElement> {
+    if (element == null) {
+      return null;
+    }
+    return (element instanceof HTMLDivElement ? element : element.$el) as HTMLDivElement;
+  }
+
+  async function calcContentHeight() {
+    if (!flag.value) {
+      return;
+    }
+    // Add a delay to get the correct height
+    await nextTick();
+
+    const wrapperEl = getEl(unref(anchorRef));
+    if (!wrapperEl) {
+      return;
+    }
+    const { bottomIncludeBody } = getViewportOffset(wrapperEl);
+
+    // substract elements height
+    let substractHeight = 0;
+    subtractHeightRefs.forEach((item) => {
+      substractHeight += getEl(unref(item))?.offsetHeight ?? 0;
+    });
+
+    // subtract margins / paddings
+    let substractSpaceHeight = 0;
+    substractSpaceRefs.forEach((item) => {
+      substractSpaceHeight += calcSubtractSpace(getEl(unref(item)));
+    });
+
+    let height =
+      bottomIncludeBody -
+      unref(layoutFooterHeightRef) -
+      unref(offsetHeightRef) -
+      substractHeight -
+      substractSpaceHeight;
+
+    // compensation height
+    const calcCompensationHeight = () => {
+      compensationHeight.elements?.forEach((item) => {
+        height += getEl(unref(item))?.offsetHeight ?? 0;
+      });
+    };
+    if (compensationHeight.useLayoutFooter && unref(layoutFooterHeightRef) > 0) {
+      calcCompensationHeight();
+    } else {
+      calcCompensationHeight();
+    }
+
+    contentHeight.value = height;
+  }
+
+  onMountedOrActivated(() => {
+    nextTick(() => {
+      calcContentHeight();
+    });
+  });
+  useWindowSizeFn(
+    () => {
+      calcContentHeight();
+    },
+    50,
+    { immediate: true }
+  );
+  watch(
+    () => [layoutFooterHeightRef.value],
+    () => {
+      calcContentHeight();
+    },
+    {
+      flush: 'post',
+      immediate: true,
+    }
+  );
+
+  return { redoHeight, setCompensation, contentHeight };
+}