mirror of
https://github.com/jeecgboot/JeecgBoot.git
synced 2025-12-31 17:15:29 +08:00
236 lines
8.8 KiB
Vue
236 lines
8.8 KiB
Vue
import type { BasicTableProps, TableRowSelection, BasicColumn } from '../types/table';
|
||
import type { Ref, ComputedRef, Slots } from 'vue';
|
||
import { computed, unref, ref, nextTick, watch } from 'vue';
|
||
import { getViewportOffset } from '/@/utils/domUtils';
|
||
import { isBoolean } from '/@/utils/is';
|
||
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
|
||
import { useModalContext } from '/@/components/Modal';
|
||
import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated';
|
||
import { useDebounceFn } from '@vueuse/core';
|
||
import componentSetting from '/@/settings/componentSetting';
|
||
|
||
export function useTableScroll(
|
||
propsRef: ComputedRef<BasicTableProps>,
|
||
tableElRef: Ref<ComponentRef>,
|
||
columnsRef: ComputedRef<BasicColumn[]>,
|
||
rowSelectionRef: ComputedRef<TableRowSelection<any> | null>,
|
||
getDataSourceRef: ComputedRef<Recordable[]>,
|
||
slots: Slots,
|
||
getPaginationInfo: ComputedRef<any>
|
||
) {
|
||
const tableHeightRef: Ref<Nullable<number>> = ref(null);
|
||
|
||
const modalFn = useModalContext();
|
||
|
||
// Greater than animation time 280
|
||
const debounceRedoHeight = useDebounceFn(redoHeight, 100);
|
||
|
||
const getCanResize = computed(() => {
|
||
const { canResize, scroll } = unref(propsRef);
|
||
return canResize && !(scroll || {}).y;
|
||
});
|
||
|
||
watch(
|
||
() => [unref(getCanResize), unref(getDataSourceRef)?.length],
|
||
() => {
|
||
debounceRedoHeight();
|
||
},
|
||
{
|
||
flush: 'post',
|
||
}
|
||
);
|
||
|
||
function redoHeight() {
|
||
nextTick(() => {
|
||
calcTableHeight();
|
||
});
|
||
}
|
||
|
||
function setHeight(heigh: number) {
|
||
tableHeightRef.value = heigh;
|
||
// Solve the problem of modal adaptive height calculation when the form is placed in the modal
|
||
modalFn?.redoModalHeight?.();
|
||
}
|
||
|
||
// No need to repeat queries
|
||
let paginationEl: HTMLElement | null;
|
||
let footerEl: HTMLElement | null;
|
||
let bodyEl: HTMLElement | null;
|
||
|
||
async function calcTableHeight() {
|
||
const { resizeHeightOffset, pagination, maxHeight, minHeight } = unref(propsRef);
|
||
const tableData = unref(getDataSourceRef);
|
||
|
||
const table = unref(tableElRef);
|
||
if (!table) return;
|
||
|
||
const tableEl: Element = table.$el;
|
||
if (!tableEl) return;
|
||
|
||
if (!bodyEl) {
|
||
// 代码逻辑说明: issues/355 前端-jeecgboot-vue3 3.4.4版本,BasicTable高度自适应功能失效,设置BasicTable组件maxHeight失效; 原因已找到,请看详情
|
||
bodyEl = tableEl.querySelector('.ant-table-tbody');
|
||
if (!bodyEl) return;
|
||
}
|
||
|
||
const hasScrollBarY = bodyEl.scrollHeight > bodyEl.clientHeight;
|
||
const hasScrollBarX = bodyEl.scrollWidth > bodyEl.clientWidth;
|
||
|
||
if (hasScrollBarY) {
|
||
tableEl.classList.contains('hide-scrollbar-y') && tableEl.classList.remove('hide-scrollbar-y');
|
||
} else {
|
||
!tableEl.classList.contains('hide-scrollbar-y') && tableEl.classList.add('hide-scrollbar-y');
|
||
}
|
||
|
||
if (hasScrollBarX) {
|
||
tableEl.classList.contains('hide-scrollbar-x') && tableEl.classList.remove('hide-scrollbar-x');
|
||
} else {
|
||
!tableEl.classList.contains('hide-scrollbar-x') && tableEl.classList.add('hide-scrollbar-x');
|
||
}
|
||
|
||
bodyEl!.style.height = 'unset';
|
||
|
||
if (!unref(getCanResize) || ( !tableData || tableData.length === 0)) return;
|
||
|
||
await nextTick();
|
||
//Add a delay to get the correct bottomIncludeBody paginationHeight footerHeight headerHeight
|
||
|
||
const headEl = tableEl.querySelector('.ant-table-thead');
|
||
|
||
if (!headEl) return;
|
||
|
||
// Table height from bottom
|
||
const { bottomIncludeBody } = getViewportOffset(headEl);
|
||
// Table height from bottom height-custom offset
|
||
|
||
const paddingHeight = 32;
|
||
// Pager height
|
||
let paginationHeight = 2;
|
||
// 【issues/9217】当配置了pagination: true时,BasicTable组件自适应高度异常
|
||
if (pagination !== false) {
|
||
paginationEl = tableEl.querySelector('.ant-pagination') as HTMLElement;
|
||
if (paginationEl) {
|
||
const offsetHeight = paginationEl.offsetHeight;
|
||
paginationHeight += offsetHeight || 0;
|
||
} else {
|
||
// TODO First fix 24
|
||
paginationHeight += 24;
|
||
}
|
||
} else {
|
||
paginationHeight = -8;
|
||
}
|
||
|
||
let footerHeight = 0;
|
||
// 代码逻辑说明: 【issues/1137】BasicTable自适应高度计算没有减去尾部高度
|
||
footerEl = tableEl.querySelector('.ant-table-footer');
|
||
if (footerEl) {
|
||
const offsetHeight = footerEl.offsetHeight;
|
||
footerHeight = offsetHeight || 0;
|
||
}
|
||
|
||
let headerHeight = 0;
|
||
if (headEl) {
|
||
headerHeight = (headEl as HTMLElement).offsetHeight;
|
||
}
|
||
|
||
let height = bottomIncludeBody - (resizeHeightOffset || 0) - paddingHeight - paginationHeight - footerHeight - headerHeight;
|
||
// update-begin--author:liaozhiyang---date:20240603---for【TV360X-861】列表查询区域不可往上滚动
|
||
// 10+6(外层边距padding:10 + 内层padding-bottom:6)
|
||
height -= 16;
|
||
// 代码逻辑说明: 【issues/8880】BasicTable组件在modal中适应高度
|
||
try {
|
||
// 当BasicTable在BasicModal容器中时,扣减容器底部高度
|
||
const modalFooter = tableEl.closest('.ant-modal-root')?.querySelector('.ant-modal-footer');
|
||
if (modalFooter) {
|
||
const { bottomIncludeBody: modalFooterHeight } = getViewportOffset(modalFooter);
|
||
height = height - modalFooterHeight;
|
||
}
|
||
} catch (e) {}
|
||
height = (height < minHeight! ? (minHeight as number) : height) ?? height;
|
||
height = (height > maxHeight! ? (maxHeight as number) : height) ?? height;
|
||
setHeight(height);
|
||
|
||
bodyEl!.style.height = `${height}px`;
|
||
// update-begin--author:liaozhiyang---date:20240609---for【issues/8374】分页始终显示在底部
|
||
nextTick(() => {
|
||
if (maxHeight === undefined) {
|
||
if (unref(getPaginationInfo) && unref(getDataSourceRef).length) {
|
||
const pageSize = unref(getPaginationInfo)?.pageSize;
|
||
const current = unref(getPaginationInfo)?.current;
|
||
const total = unref(getPaginationInfo)?.total;
|
||
const tableBody = tableEl.querySelector('.ant-table-body') as HTMLElement;
|
||
const tr = tableEl.querySelector('.ant-table-tbody')?.children ?? [];
|
||
const lastrEl = tr[tr.length - 1] as HTMLElement;
|
||
const trHeight = lastrEl.offsetHeight;
|
||
const dataHeight = trHeight * pageSize;
|
||
if (tableBody && lastrEl) {
|
||
// table是否隐藏(隐藏的table不能吸底)
|
||
const isTableBodyHide = tableBody.offsetHeight == 0 && tableBody.offsetWidth == 0;
|
||
if (isTableBodyHide) {
|
||
return;
|
||
}
|
||
if (current === 1 && pageSize > unref(getDataSourceRef).length && total <= pageSize) {
|
||
tableBody.style.height = `${height}px`;
|
||
} else {
|
||
tableBody.style.height = `${dataHeight < height ? dataHeight : height}px`;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
});
|
||
// update-end--author:liaozhiyang---date:20240609---for【issues/8374】分页始终显示在底部
|
||
}
|
||
useWindowSizeFn(calcTableHeight, 280);
|
||
onMountedOrActivated(() => {
|
||
calcTableHeight();
|
||
nextTick(() => {
|
||
debounceRedoHeight();
|
||
});
|
||
});
|
||
|
||
const getScrollX = computed(() => {
|
||
let width = 0;
|
||
// if (unref(rowSelectionRef)) {
|
||
// width += 60;
|
||
// }
|
||
// 代码逻辑说明: 【issues/5411】BasicTable 配置maxColumnWidth 未生效
|
||
const { maxColumnWidth } = unref(propsRef);
|
||
// TODO props ?? 0;
|
||
const NORMAL_WIDTH = maxColumnWidth ?? 150;
|
||
// date-begin--author:liaozhiyang---date:20250716---for:【QQYUN-13122】有数十个字段时只展示2个字段,其余字段为ifShow:false会有滚动条
|
||
const columns = unref(columnsRef).filter((item) => !(item.defaultHidden == true || item.ifShow == false))
|
||
// date-end--author:liaozhiyang---date:20250716---for:【QQYUN-13122】有数十个字段时只展示2个字段,其余字段为ifShow:false会有滚动条
|
||
columns.forEach((item) => {
|
||
width += Number.parseInt(item.width as string) || 0;
|
||
});
|
||
const unsetWidthColumns = columns.filter((item) => !Reflect.has(item, 'width'));
|
||
|
||
const len = unsetWidthColumns.length;
|
||
if (len !== 0) {
|
||
width += len * NORMAL_WIDTH;
|
||
}
|
||
// 代码逻辑说明: 【TV360X-116】内嵌风格字段较多时表格错位
|
||
if (slots.expandedRowRender) {
|
||
width += propsRef.value.expandColumnWidth;
|
||
}
|
||
const table = unref(tableElRef);
|
||
const tableWidth = table?.$el?.offsetWidth ?? 0;
|
||
return tableWidth > width ? '100%' : width;
|
||
});
|
||
|
||
const getScrollRef = computed(() => {
|
||
const tableHeight = unref(tableHeightRef);
|
||
const { canResize, scroll } = unref(propsRef);
|
||
const { table } = componentSetting;
|
||
return {
|
||
x: unref(getScrollX),
|
||
y: canResize ? tableHeight : null,
|
||
// 代码逻辑说明: 【issues/1188】BasicTable加上scrollToFirstRowOnChange类型定义
|
||
scrollToFirstRowOnChange: table.scrollToFirstRowOnChange,
|
||
...scroll,
|
||
};
|
||
});
|
||
|
||
return { getScrollRef, redoHeight };
|
||
}
|