diff --git a/src/components/Page/src/PageWrapper.vue b/src/components/Page/src/PageWrapper.vue index 7aa0c3ae97fb02e2060a889147746d97a3c8c65f..f502c7b114fb492eb1105b67366fdeb59b15e29e 100644 --- a/src/components/Page/src/PageWrapper.vue +++ b/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(null); - const headerRef = ref(null); - const contentRef = ref(null); - const footerRef = ref(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(); }, diff --git a/src/components/Page/src/types.ts b/src/components/Page/src/types.ts deleted file mode 100644 index 82dbf50e5d56d086f9bb12bd1bcc6bbd2d5c5fd6..0000000000000000000000000000000000000000 --- a/src/components/Page/src/types.ts +++ /dev/null @@ -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; -} diff --git a/src/components/Page/src/useContentHeight.ts b/src/components/Page/src/useContentHeight.ts deleted file mode 100644 index cdb62301bf87a861ad045241202d98386e9317fc..0000000000000000000000000000000000000000 --- a/src/components/Page/src/useContentHeight.ts +++ /dev/null @@ -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, - wrapperRef: Ref, - headerRef?: Ref, - contentRef?: Ref, - footerRef?: Ref, - layoutFooterHeightRef: Ref = ref(0), - offsetHeightRef: Ref = ref(0) -) { - const contentHeight: Ref> = 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 }; -} diff --git a/src/hooks/web/useContentHeight.ts b/src/hooks/web/useContentHeight.ts new file mode 100644 index 0000000000000000000000000000000000000000..5bc0f953c466447f67817a98af8535abd98e9077 --- /dev/null +++ b/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 + * @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 }; +}