Browse Source

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

pull/3551/head
xachary 3 years ago
committed by GitHub
parent
commit
a121b32252
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      src/components/Modal/src/BasicModal.vue
  2. 173
      src/components/Table/src/hooks/useTableScroll.ts
  3. 18
      src/utils/domUtils.ts
  4. 46
      src/views/demo/comp/modal/Modal5.vue
  5. 9
      src/views/demo/comp/modal/index.vue
  6. 4
      src/views/demo/table/RefTable.vue
  7. 6
      src/views/demo/table/ResizeParentHeightTable.vue
  8. 2
      src/views/demo/table/UseTable.vue

13
src/components/Modal/src/BasicModal.vue

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

173
src/components/Table/src/hooks/useTableScroll.ts

@ -74,6 +74,12 @@ export function useTableScroll(
let footerEl: HTMLElement | null; let footerEl: HTMLElement | null;
let bodyEl: 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) { function handleScrollBar(bodyEl: HTMLElement, tableEl: Element) {
const hasScrollBarY = bodyEl.scrollHeight > bodyEl.clientHeight; const hasScrollBarY = bodyEl.scrollHeight > bodyEl.clientHeight;
const hasScrollBarX = bodyEl.scrollWidth > bodyEl.clientWidth; const hasScrollBarX = bodyEl.scrollWidth > bodyEl.clientWidth;
@ -93,21 +99,33 @@ export function useTableScroll(
} }
} }
/**
* 计算分页器高度
* @param tableEl table element
* @returns number
*/
function caclPaginationHeight(tableEl: Element): number { function caclPaginationHeight(tableEl: Element): number {
const { pagination } = unref(propsRef); const { pagination } = unref(propsRef);
// Pager height
let paginationHeight = 2; let paginationHeight = 0;
if (!isBoolean(pagination)) { if (!isBoolean(pagination)) {
paginationEl = tableEl.querySelector('.ant-pagination') as HTMLElement; // 从 Dom 获取
if (!paginationEl) {
paginationEl = tableEl.querySelector('.ant-pagination') as HTMLElement;
}
if (paginationEl) { if (paginationEl) {
// 分页 margin-top
const paginationElMarginTop = parseInt(getComputedStyle(paginationEl).marginTop);
// 分页高度
const offsetHeight = paginationEl.offsetHeight; const offsetHeight = paginationEl.offsetHeight;
paginationHeight += offsetHeight || 0; paginationHeight = offsetHeight + paginationElMarginTop;
} else { } else {
// TODO First fix 24 // 找不到分页组件,缺省给予默认分页 margin-top + 高度
paginationHeight += 24; paginationHeight = 10 + 24;
} }
} else { } else {
paginationHeight = -8; // 不显示分页,pagination 为 false 的时候
paginationHeight = 0;
} }
return paginationHeight; return paginationHeight;
} }
@ -134,43 +152,105 @@ export function useTableScroll(
return headerHeight; return headerHeight;
} }
/**
* 计算从表头一直到body底部的总高度
* @param tableEl table element
* @param headEl table 页头 element
* @returns number
*/
function calcBottomAndPaddingHeight(tableEl: Element, headEl: Element) { function calcBottomAndPaddingHeight(tableEl: Element, headEl: Element) {
const { pagination, isCanResizeParent, useSearchForm } = unref(propsRef); const { isCanResizeParent } = unref(propsRef);
// Table height from bottom height-custom offset
let paddingHeight = 30;
let bottomIncludeBody = 0; let bottomIncludeBody = 0;
if (unref(wrapRef) && isCanResizeParent) { if (unref(wrapRef) && isCanResizeParent) {
const tablePadding = 12; // 继承父元素高度
const formMargin = 16;
let paginationMargin = 10;
const wrapHeight = unref(wrapRef)?.offsetHeight ?? 0; const wrapHeight = unref(wrapRef)?.offsetHeight ?? 0;
let formHeight = unref(formRef)?.$el.offsetHeight ?? 0; let formHeight = unref(formRef)?.$el.offsetHeight ?? 0;
if (formHeight) { if (formHeight) {
formHeight += formMargin; // 来自于 .vben-basic-table-form-container .ant-form 以及 .vben-basic-table-form-container
} formHeight += 16 + 16 * 2;
if (isBoolean(pagination) && !pagination) {
paginationMargin = 0;
} }
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 - tableWrapperPadding - formHeight;
bottomIncludeBody =
wrapHeight - formHeight - headerCellHeight - tablePadding - paginationMargin;
} else { } else {
// Table height from bottom // 缺省 wrapRef 情况下
bottomIncludeBody = getViewportOffset(headEl).bottomIncludeBody; bottomIncludeBody = getViewportOffset(headEl).bottomIncludeBody;
} }
return { return bottomIncludeBody;
paddingHeight, }
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() { async function calcTableHeight() {
@ -204,18 +284,29 @@ export function useTableScroll(
const paginationHeight = caclPaginationHeight(tableEl); const paginationHeight = caclPaginationHeight(tableEl);
const footerHeight = caclFooterHeight(tableEl); const footerHeight = caclFooterHeight(tableEl);
const headerHeight = calcHeaderHeight(headEl); 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 - bottomIncludeBody -
(resizeHeightOffset || 0) - (resizeHeightOffset || 0) -
paddingHeight - paginationHeight -
paginationHeight - footerHeight -
footerHeight - headerHeight -
headerHeight - // 弹窗(如果有)相关高度
(getShowFooter.value ? layoutFooterHeight : 0) - modalHeight -
// 取高度ceil值 // 页面 footer 高度(非弹窗的时候)
1; (getShowFooter.value && modalHeight <= 0 ? layoutFooterHeight : 0) -
// 样式间距高度
marginPaddingHeight -
// 预留非整数高度溢出(如实际高度为100.5,offsetHeight 的值为101)
1,
);
height = (height > maxHeight! ? (maxHeight as number) : height) ?? height; height = (height > maxHeight! ? (maxHeight as number) : height) ?? height;
setHeight(height); setHeight(height);

18
src/utils/domUtils.ts

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

46
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>

9
src/views/demo/comp/modal/index.vue

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

4
src/views/demo/table/RefTable.vue

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

6
src/views/demo/table/ResizeParentHeightTable.vue

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

2
src/views/demo/table/UseTable.vue

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

Loading…
Cancel
Save