Skip to content

Commit

Permalink
fix(BasicTable): BasicTable resize wrong in modal (#3549)
Browse files Browse the repository at this point in the history
  • Loading branch information
xachary authored Jan 16, 2024
1 parent 0c1235e commit a121b32
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 49 deletions.
13 changes: 12 additions & 1 deletion src/components/Modal/src/BasicModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
'ok',
'register',
'update:open',
'fullscreen',
]);
const attrs = useAttrs();
Expand Down Expand Up @@ -119,7 +120,11 @@
};
});
const { handleFullScreen, getWrapClassName, fullScreenRef } = useFullScreen({
const {
handleFullScreen: handleFullScreenInner,
getWrapClassName,
fullScreenRef,
} = useFullScreen({
modalWrapperRef,
extHeightRef,
wrapClassName: toRef(getMergeProps.value, 'wrapClassName'),
Expand Down Expand Up @@ -229,4 +234,10 @@
e.stopPropagation();
handleFullScreen(e);
}
// 事件传递
function handleFullScreen(e) {
handleFullScreenInner(e);
emit('fullscreen');
}
</script>
173 changes: 132 additions & 41 deletions src/components/Table/src/hooks/useTableScroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
Expand All @@ -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() {
Expand Down Expand Up @@ -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);

Expand Down
18 changes: 18 additions & 0 deletions src/utils/domUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
46 changes: 46 additions & 0 deletions src/views/demo/comp/modal/Modal5.vue
Original file line number Diff line number Diff line change
@@ -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 changes: 8 additions & 1 deletion src/views/demo/comp/modal/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -42,6 +46,7 @@
<Modal2 @register="register2" />
<Modal3 @register="register3" />
<Modal4 @register="register4" />
<Modal5 @register="register5" />
</PageWrapper>
</template>
<script lang="ts" setup>
Expand All @@ -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';
Expand All @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions src/views/demo/table/RefTable.vue
Original file line number Diff line number Diff line change
@@ -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>
Expand All @@ -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"
Expand Down
Loading

0 comments on commit a121b32

Please sign in to comment.