mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-25 16:16:20 +08:00
fix(BasicTable): BasicTable resize wrong in modal (#3549)
This commit is contained in:
@@ -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>
|
||||
|
@@ -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;
|
||||
}
|
||||
if (isBoolean(useSearchForm) && !useSearchForm) {
|
||||
paddingHeight = 0;
|
||||
// 来自于 .vben-basic-table-form-container .ant-form 以及 .vben-basic-table-form-container
|
||||
formHeight += 16 + 16 * 2;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
let height =
|
||||
const modalHeight = calcModalHeight(tableEl);
|
||||
|
||||
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);
|
||||
|
||||
|
@@ -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
src/views/demo/comp/modal/Modal5.vue
Normal file
46
src/views/demo/comp/modal/Modal5.vue
Normal file
@@ -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>
|
@@ -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);
|
||||
|
||||
|
@@ -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"
|
||||
|
@@ -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,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>
|
||||
|
Reference in New Issue
Block a user