Ben Lin
2024-06-25 4969a965adc32d3194763edfa4a4f74a7f26cd06
src/components/Modal/src/components/ModalWrapper.vue
@@ -1,30 +1,22 @@
<template>
  <ScrollContainer ref="wrapperRef">
  <ScrollContainer ref="wrapperRef" :scrollHeight="realHeight">
    <div ref="spinRef" :style="spinStyle" v-loading="loading" :loading-tip="loadingTip">
      <slot></slot>
    </div>
  </ScrollContainer>
</template>
<script lang="ts">
<script lang="ts" setup>
  import type { CSSProperties } from 'vue';
  import {
    defineComponent,
    computed,
    ref,
    watchEffect,
    unref,
    watch,
    onMounted,
    nextTick,
    onUnmounted,
  } from 'vue';
  import { computed, ref, watchEffect, unref, watch, onMounted, nextTick, onUnmounted } from 'vue';
  import { useWindowSizeFn } from '@vben/hooks';
  import { type AnyFunction } from '@vben/types';
  import { ScrollContainer } from '/@/components/Container';
  import { ScrollContainer } from '@/components/Container';
  import { createModalContext } from '../hooks/useModalContext';
  import { useMutationObserver } from '@vueuse/core';
  const props = {
  defineOptions({ name: 'ModalWrapper', inheritAttrs: false });
  const props = defineProps({
    loading: { type: Boolean },
    useWrapper: { type: Boolean, default: true },
    modalHeaderHeight: { type: Number, default: 57 },
@@ -32,139 +24,131 @@
    minHeight: { type: Number, default: 200 },
    height: { type: Number },
    footerOffset: { type: Number, default: 0 },
    visible: { type: Boolean },
    open: { type: Boolean },
    fullScreen: { type: Boolean },
    loadingTip: { type: String },
  };
  export default defineComponent({
    name: 'ModalWrapper',
    components: { ScrollContainer },
    inheritAttrs: false,
    props,
    emits: ['height-change', 'ext-height'],
    setup(props, { emit }) {
      const wrapperRef = ref(null);
      const spinRef = ref(null);
      const realHeightRef = ref(0);
      const minRealHeightRef = ref(0);
      let realHeight = 0;
      let stopElResizeFn: AnyFunction = () => {};
      useWindowSizeFn(setModalHeight.bind(null, false));
      useMutationObserver(
        spinRef,
        () => {
          setModalHeight();
        },
        {
          attributes: true,
          subtree: true,
        },
      );
      createModalContext({
        redoModalHeight: setModalHeight,
      });
      const spinStyle = computed((): CSSProperties => {
        return {
          minHeight: `${props.minHeight}px`,
          [props.fullScreen ? 'height' : 'maxHeight']: `${unref(realHeightRef)}px`,
        };
      });
      watchEffect(() => {
        props.useWrapper && setModalHeight();
      });
      watch(
        () => props.fullScreen,
        (v) => {
          setModalHeight();
          if (!v) {
            realHeightRef.value = minRealHeightRef.value;
          } else {
            minRealHeightRef.value = realHeightRef.value;
          }
        },
      );
      onMounted(() => {
        const { modalHeaderHeight, modalFooterHeight } = props;
        emit('ext-height', modalHeaderHeight + modalFooterHeight);
      });
      onUnmounted(() => {
        stopElResizeFn && stopElResizeFn();
      });
      async function scrollTop() {
        nextTick(() => {
          const wrapperRefDom = unref(wrapperRef);
          if (!wrapperRefDom) return;
          (wrapperRefDom as any)?.scrollTo?.(0);
        });
      }
      async function setModalHeight() {
        // 解决在弹窗关闭的时候监听还存在,导致再次打开弹窗没有高度
        // 加上这个,就必须在使用的时候传递父级的visible
        if (!props.visible) return;
        const wrapperRefDom = unref(wrapperRef);
        if (!wrapperRefDom) return;
        const bodyDom = (wrapperRefDom as any).$el.parentElement;
        if (!bodyDom) return;
        bodyDom.style.padding = '0';
        await nextTick();
        try {
          const modalDom = bodyDom.parentElement && bodyDom.parentElement.parentElement;
          if (!modalDom) return;
          const modalRect = getComputedStyle(modalDom as Element).top;
          const modalTop = Number.parseInt(modalRect);
          let maxHeight =
            window.innerHeight -
            modalTop * 2 +
            (props.footerOffset! || 0) -
            props.modalFooterHeight -
            props.modalHeaderHeight;
          // 距离顶部过进会出现滚动条
          if (modalTop < 40) {
            maxHeight -= 26;
          }
          await nextTick();
          const spinEl: any = unref(spinRef);
          if (!spinEl) return;
          await nextTick();
          // if (!realHeight) {
          realHeight = spinEl.scrollHeight;
          // }
          if (props.fullScreen) {
            realHeightRef.value =
              window.innerHeight - props.modalFooterHeight - props.modalHeaderHeight - 28;
          } else {
            realHeightRef.value = props.height
              ? props.height
              : realHeight > maxHeight
              ? maxHeight
              : realHeight;
          }
          emit('height-change', unref(realHeightRef));
        } catch (error) {
          console.log(error);
        }
      }
      return { wrapperRef, spinRef, spinStyle, scrollTop, setModalHeight };
    },
  });
  const emit = defineEmits(['height-change', 'ext-height']);
  const wrapperRef = ref(null);
  const spinRef = ref(null);
  const realHeightRef = ref(0);
  const minRealHeightRef = ref(0);
  const realHeight = ref(0);
  let stopElResizeFn: AnyFunction = () => {};
  useWindowSizeFn(setModalHeight.bind(null));
  useMutationObserver(
    spinRef,
    () => {
      setModalHeight();
    },
    {
      attributes: true,
      subtree: true,
    },
  );
  createModalContext({
    redoModalHeight: setModalHeight,
  });
  const spinStyle = computed((): CSSProperties => {
    return {
      minHeight: `${props.minHeight}px`,
      [props.fullScreen ? 'height' : 'maxHeight']: `${unref(realHeightRef)}px`,
    };
  });
  watchEffect(() => {
    props.useWrapper && setModalHeight();
  });
  watch(
    () => props.fullScreen,
    (v) => {
      setModalHeight();
      if (!v) {
        realHeightRef.value = minRealHeightRef.value;
      } else {
        minRealHeightRef.value = realHeightRef.value;
      }
    },
  );
  onMounted(() => {
    const { modalHeaderHeight, modalFooterHeight } = props;
    emit('ext-height', modalHeaderHeight + modalFooterHeight);
  });
  onUnmounted(() => {
    stopElResizeFn && stopElResizeFn();
  });
  async function scrollTop() {
    nextTick(() => {
      const wrapperRefDom = unref(wrapperRef);
      if (!wrapperRefDom) return;
      (wrapperRefDom as any)?.scrollTo?.(0);
    });
  }
  async function setModalHeight() {
    // 解决在弹窗关闭的时候监听还存在,导致再次打开弹窗没有高度
    // 加上这个,就必须在使用的时候传递父级的open
    if (!props.open) return;
    const wrapperRefDom = unref(wrapperRef);
    if (!wrapperRefDom) return;
    const bodyDom = (wrapperRefDom as any).$el.parentElement;
    if (!bodyDom) return;
    bodyDom.style.padding = '0';
    await nextTick();
    try {
      const modalDom = bodyDom.parentElement && bodyDom.parentElement.parentElement;
      if (!modalDom) return;
      const modalRect = getComputedStyle(modalDom as Element).top;
      const modalTop = Number.parseInt(modalRect);
      let maxHeight =
        window.innerHeight -
        modalTop * 2 +
        (props.footerOffset! || 0) -
        props.modalFooterHeight -
        props.modalHeaderHeight;
      // 距离顶部过进会出现滚动条
      if (modalTop < 40) {
        maxHeight -= 26;
      }
      await nextTick();
      const spinEl: any = unref(spinRef);
      if (!spinEl) return;
      await nextTick();
      // if (!realHeight) {
      realHeight.value = spinEl.scrollHeight;
      // }
      if (props.fullScreen) {
        realHeightRef.value =
          window.innerHeight - props.modalFooterHeight - props.modalHeaderHeight - 28;
      } else {
        realHeightRef.value = props.height
          ? props.height
          : realHeight.value > maxHeight
            ? maxHeight
            : realHeight.value;
      }
      emit('height-change', unref(realHeightRef));
    } catch (error) {
      console.log(error);
    }
  }
  defineExpose({ scrollTop, setModalHeight });
</script>