Browse Source

fix(BasicTable): BasicTable resize wrong in modal (#3549)

xachary 1 năm trước cách đây
mục cha
commit
a121b32252

+ 12 - 1
src/components/Modal/src/BasicModal.vue

@@ -84,6 +84,7 @@
     'ok',
     'register',
     'update:open',
+    'fullscreen',
   ]);
 
   const attrs = useAttrs();
@@ -119,7 +120,11 @@
     };
   });
 
-  const { handleFullScreen, getWrapClassName, fullScreenRef } = useFullScreen({
+  const {
+    handleFullScreen: handleFullScreenInner,
+    getWrapClassName,
+    fullScreenRef,
+  } = useFullScreen({
     modalWrapperRef,
     extHeightRef,
     wrapClassName: toRef(getMergeProps.value, 'wrapClassName'),
@@ -229,4 +234,10 @@
     e.stopPropagation();
     handleFullScreen(e);
   }
+
+  // 事件传递
+  function handleFullScreen(e) {
+    handleFullScreenInner(e);
+    emit('fullscreen');
+  }
 </script>

+ 132 - 41
src/components/Table/src/hooks/useTableScroll.ts

@@ -74,6 +74,12 @@ export function useTableScroll(
   let footerEl: HTMLElement | null;
   let bodyEl: HTMLElement | null;
 
+  /**
+   * table wrapper padding 的高度
+   * @description 来自于 .vben-basic-table .ant-table-wrapper
+   */
+  const tableWrapperPadding = 6;
+
   function handleScrollBar(bodyEl: HTMLElement, tableEl: Element) {
     const hasScrollBarY = bodyEl.scrollHeight > bodyEl.clientHeight;
     const hasScrollBarX = bodyEl.scrollWidth > bodyEl.clientWidth;
@@ -93,21 +99,33 @@ export function useTableScroll(
     }
   }
 
+  /**
+   * 计算分页器高度
+   * @param tableEl table element
+   * @returns number
+   */
   function caclPaginationHeight(tableEl: Element): number {
     const { pagination } = unref(propsRef);
-    // Pager height
-    let paginationHeight = 2;
+
+    let paginationHeight = 0;
     if (!isBoolean(pagination)) {
-      paginationEl = tableEl.querySelector('.ant-pagination') as HTMLElement;
+      // 从 Dom 获取
+      if (!paginationEl) {
+        paginationEl = tableEl.querySelector('.ant-pagination') as HTMLElement;
+      }
       if (paginationEl) {
+        // 分页 margin-top
+        const paginationElMarginTop = parseInt(getComputedStyle(paginationEl).marginTop);
+        // 分页高度
         const offsetHeight = paginationEl.offsetHeight;
-        paginationHeight += offsetHeight || 0;
+        paginationHeight = offsetHeight + paginationElMarginTop;
       } else {
-        // TODO First fix 24
-        paginationHeight += 24;
+        // 找不到分页组件,缺省给予默认分页 margin-top + 高度
+        paginationHeight = 10 + 24;
       }
     } else {
-      paginationHeight = -8;
+      // 不显示分页,pagination 为 false 的时候
+      paginationHeight = 0;
     }
     return paginationHeight;
   }
@@ -134,43 +152,105 @@ export function useTableScroll(
     return headerHeight;
   }
 
+  /**
+   * 计算从表头一直到body底部的总高度
+   * @param tableEl table element
+   * @param headEl table 页头 element
+   * @returns number
+   */
   function calcBottomAndPaddingHeight(tableEl: Element, headEl: Element) {
-    const { pagination, isCanResizeParent, useSearchForm } = unref(propsRef);
-    // Table height from bottom height-custom offset
-    let paddingHeight = 30;
+    const { isCanResizeParent } = unref(propsRef);
     let bottomIncludeBody = 0;
     if (unref(wrapRef) && isCanResizeParent) {
-      const tablePadding = 12;
-      const formMargin = 16;
-      let paginationMargin = 10;
+      // 继承父元素高度
       const wrapHeight = unref(wrapRef)?.offsetHeight ?? 0;
 
       let formHeight = unref(formRef)?.$el.offsetHeight ?? 0;
       if (formHeight) {
-        formHeight += formMargin;
-      }
-      if (isBoolean(pagination) && !pagination) {
-        paginationMargin = 0;
+        // 来自于 .vben-basic-table-form-container .ant-form 以及 .vben-basic-table-form-container
+        formHeight += 16 + 16 * 2;
       }
-      if (isBoolean(useSearchForm) && !useSearchForm) {
-        paddingHeight = 0;
-      }
-
-      const headerCellHeight =
-        (tableEl.querySelector('.ant-table-title') as HTMLElement)?.offsetHeight ?? 0;
 
-      console.log(wrapHeight - formHeight - headerCellHeight - tablePadding - paginationMargin);
-      bottomIncludeBody =
-        wrapHeight - formHeight - headerCellHeight - tablePadding - paginationMargin;
+      bottomIncludeBody = wrapHeight - tableWrapperPadding - formHeight;
     } else {
-      // Table height from bottom
+      // 缺省 wrapRef 情况下
       bottomIncludeBody = getViewportOffset(headEl).bottomIncludeBody;
     }
 
-    return {
-      paddingHeight,
-      bottomIncludeBody,
-    };
+    return bottomIncludeBody;
+  }
+
+  /**
+   * 计算 table 在 modal 内 modal 所占用的高度
+   * @param tableEl table element
+   * @returns number
+   */
+  function calcModalHeight(tableEl: Element) {
+    // 找一下 table 是否在 modal 内,获得 modal、wrap、footer,并考虑 fullscreen 的情况
+    let modalEl: Nullable<HTMLElement> = null;
+    let modalWrapEl: Nullable<HTMLElement> = null;
+    let modalFooterEl: Nullable<HTMLElement> = null;
+    let modalElIterator: HTMLElement = tableEl.parentElement!;
+    let modalIsFullscreen = false;
+    while (modalElIterator !== document.body) {
+      if (modalElIterator.classList.contains('ant-modal')) {
+        modalEl = modalElIterator;
+        modalWrapEl = modalEl.parentElement;
+        modalFooterEl = modalElIterator.querySelector('.ant-modal-content>.ant-modal-footer');
+        modalIsFullscreen = modalWrapEl?.classList.contains('fullscreen-modal') ?? false;
+        break;
+      }
+      modalElIterator = modalElIterator.parentElement!;
+    }
+
+    if (modalEl) {
+      // table 在 modal 内
+
+      // modal top
+      const { top: modalTop = 0 } = modalEl ? getViewportOffset(modalEl) : {};
+
+      // 来自于 .ant-modal,非全屏为 24,全屏为 0
+      const modalBottom = modalIsFullscreen ? 0 : 24;
+
+      //  modal footer 高度
+      const modalFooterHeight = modalFooterEl?.offsetHeight ?? 0;
+
+      // modal footer 边距,来自于 .ant-modal .ant-modal-footer
+      const modalFooterMarginTop = modalFooterEl
+        ? modalIsFullscreen
+          ? 0
+          : parseInt(getComputedStyle(modalFooterEl).marginTop)
+        : 0;
+
+      // 来自于 .ant-modal .ant-modal-body > .scrollbar
+      const modalScrollBarHeight = 14;
+
+      return (
+        (modalTop > modalBottom ? modalTop : modalBottom) +
+        modalFooterHeight +
+        modalFooterMarginTop +
+        modalScrollBarHeight
+      );
+    }
+
+    // table 不住 modal 内
+    return 0;
+  }
+
+  /**
+   * 根据样式返回一些间距高度
+   * @returns number
+   */
+  function getMarginPaddingHeight() {
+    const { isCanResizeParent } = unref(propsRef);
+
+    if (unref(wrapRef) && isCanResizeParent) {
+      // 继承父元素高度
+      return tableWrapperPadding;
+    }
+    return (
+      tableWrapperPadding + 16 // 来自于 .vben-basic-table-form-container 或是 .p-4
+    );
   }
 
   async function calcTableHeight() {
@@ -204,18 +284,29 @@ export function useTableScroll(
     const paginationHeight = caclPaginationHeight(tableEl);
     const footerHeight = caclFooterHeight(tableEl);
     const headerHeight = calcHeaderHeight(headEl);
-    const { paddingHeight, bottomIncludeBody } = calcBottomAndPaddingHeight(tableEl, headEl);
+    const bottomIncludeBody = calcBottomAndPaddingHeight(tableEl, headEl);
+
+    const modalHeight = calcModalHeight(tableEl);
 
-    let height =
+    const marginPaddingHeight = getMarginPaddingHeight();
+
+    // Math.floor 宁愿小1px,也不溢出
+    let height = Math.floor(
       bottomIncludeBody -
-      (resizeHeightOffset || 0) -
-      paddingHeight -
-      paginationHeight -
-      footerHeight -
-      headerHeight -
-      (getShowFooter.value ? layoutFooterHeight : 0) -
-      // 取高度ceil值
-      1;
+        (resizeHeightOffset || 0) -
+        paginationHeight -
+        footerHeight -
+        headerHeight -
+        // 弹窗(如果有)相关高度
+        modalHeight -
+        // 页面 footer 高度(非弹窗的时候)
+        (getShowFooter.value && modalHeight <= 0 ? layoutFooterHeight : 0) -
+        // 样式间距高度
+        marginPaddingHeight -
+        // 预留非整数高度溢出(如实际高度为100.5,offsetHeight 的值为101)
+        1,
+    );
+
     height = (height > maxHeight! ? (maxHeight as number) : height) ?? height;
     setHeight(height);
 

+ 18 - 0
src/utils/domUtils.ts

@@ -2,11 +2,29 @@ import type { FunctionArgs } from '@vueuse/core';
 import { upperFirst } from 'lodash-es';
 
 export interface ViewportOffsetResult {
+  /**
+   * 元素左边距离 body 左边的距离(和 getBoundingClientRect 的 left 一样)
+   */
   left: number;
+  /**
+   * 元素顶边距离 body 顶边的距离(和 getBoundingClientRect 的 top 一样)
+   */
   top: number;
+  /**
+   * 元素右边距离 body 右边的距离
+   */
   right: number;
+  /**
+   * 元素底边距离 body 底边的距离
+   */
   bottom: number;
+  /**
+   * 内容宽度 + 计算后的 right
+   */
   rightIncludeBody: number;
+  /**
+   * 内容高度 + 计算后的 bottom
+   */
   bottomIncludeBody: number;
 }
 

+ 46 - 0
src/views/demo/comp/modal/Modal5.vue

@@ -0,0 +1,46 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    title="Modal Title"
+    :helpMessage="['提示1', '提示2']"
+    width="1000px"
+    @fullscreen="onFullscreen"
+  >
+    <BasicTable @register="registerTable" ref="selectTable" />
+  </BasicModal>
+</template>
+<script lang="ts" setup>
+  import { ref, nextTick } from 'vue';
+  import { BasicModal } from '@/components/Modal';
+  import { BasicTable, ColumnChangeParam, useTable } from '@/components/Table';
+  import { getBasicColumns } from '../../table/tableData';
+  import { demoListApi } from '@/api/demo/table';
+
+  const [registerTable] = useTable({
+    canResize: true,
+    title: 'useTable示例',
+    titleHelpMessage: '使用useTable调用表格内方法',
+    api: demoListApi,
+    columns: getBasicColumns(),
+    defSort: {
+      field: 'name',
+      order: 'ascend',
+    },
+    rowKey: 'id',
+    showTableSetting: true,
+    rowSelection: {
+      type: 'checkbox',
+    },
+    onColumnsChange: (data: ColumnChangeParam[]) => {
+      console.log('ColumnsChanged', data);
+    },
+    showSelectionBar: true, // 显示多选状态栏
+  });
+
+  const selectTable = ref<InstanceType<typeof BasicTable> | undefined>();
+
+  const onFullscreen = async () => {
+    await nextTick();
+    selectTable.value?.redoHeight();
+  };
+</script>

+ 8 - 1
src/views/demo/comp/modal/index.vue

@@ -11,8 +11,12 @@
 
     <Alert message="内外同时同时显示隐藏" show-icon />
     <a-button type="primary" class="my-4" @click="openModal2"> 打开弹窗 </a-button>
+
     <Alert message="自适应高度" show-icon />
-    <a-button type="primary" class="my-4" @click="openModal3"> 打开弹窗 </a-button>
+    <Space>
+      <a-button type="primary" class="my-4" @click="openModal3"> 打开弹窗 </a-button>
+      <a-button type="primary" class="my-4" @click="openModal5"> 打开弹窗(BasicTable) </a-button>
+    </Space>
 
     <Alert message="内外数据交互" show-icon />
     <a-button type="primary" class="my-4" @click="send"> 打开弹窗并传递数据 </a-button>
@@ -42,6 +46,7 @@
     <Modal2 @register="register2" />
     <Modal3 @register="register3" />
     <Modal4 @register="register4" />
+    <Modal5 @register="register5" />
   </PageWrapper>
 </template>
 <script lang="ts" setup>
@@ -52,6 +57,7 @@
   import Modal2 from './Modal2.vue';
   import Modal3 from './Modal3.vue';
   import Modal4 from './Modal4.vue';
+  import Modal5 from './Modal5.vue';
   import { PageWrapper } from '@/components/Page';
   import { type Nullable } from '@vben/types';
   import { createPrompt } from '@/components/Prompt';
@@ -61,6 +67,7 @@
   const [register2, { openModal: openModal2 }] = useModal();
   const [register3, { openModal: openModal3 }] = useModal();
   const [register4, { openModal: openModal4 }] = useModal();
+  const [register5, { openModal: openModal5 }] = useModal();
   const modalOpen = ref<Boolean>(false);
   const userData = ref<any>(null);
 

+ 2 - 2
src/views/demo/table/RefTable.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="p-4">
+  <div class="p-4 flex flex-col">
     <div class="mb-4">
       <a-button class="mr-2" @click="reloadTable"> 还原 </a-button>
       <a-button class="mr-2" @click="changeLoading"> 开启loading </a-button>
@@ -17,7 +17,7 @@
       <a-button class="mr-2" @click="getPagination"> 获取分页信息 </a-button>
     </div>
     <BasicTable
-      :canResize="false"
+      :canResize="true"
       title="RefTable示例"
       titleHelpMessage="使用Ref调用表格内方法"
       ref="tableRef"

+ 3 - 3
src/views/demo/table/ResizeParentHeightTable.vue

@@ -1,11 +1,11 @@
 <template>
   <div class="h-full flex p-4">
     <div class="flex flex-col pr-4 w-1/2">
-      <div class="flex-1">
+      <div class="flex-1 h-0">
         <BasicTable @register="registerTable" />
       </div>
-      <div class="h-4"></div>
-      <div class="flex-1">
+      <div class="h-4 shrink-0"></div>
+      <div class="flex-1 h-0">
         <BasicTable @register="registerTable" />
       </div>
     </div>

+ 1 - 1
src/views/demo/table/UseTable.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="p-4">
+  <div class="p-4 flex flex-col">
     <div class="mb-4">
       <a-button class="mr-2" @click="reloadTable"> 还原 </a-button>
       <a-button class="mr-2" @click="changeLoading"> 开启loading </a-button>