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 * @param subtractHeightRefs 待减去高度的组件列表 Ref * @param substractSpaceRefs 待减去空闲空间(margins/paddings)的组件列表 Ref * @param offsetHeightRef 计算偏移的响应式高度,计算高度时将直接减去此值 * @returns 响应式高度 */ export function useContentHeight( flag: ComputedRef, anchorRef: Ref, subtractHeightRefs: Ref[], substractSpaceRefs: Ref[], offsetHeightRef: Ref = ref(0) ) { const contentHeight: Ref> = 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 { 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 }; }