mirror of
https://gitee.com/JavaLionLi/plus-ui.git
synced 2026-09-13 07:43:43 +08:00
update 补充漏提交文件
This commit is contained in:
parent
87bc5bea09
commit
58e32c468f
298
src/hooks/table/useFullHeightTable.ts
Normal file
298
src/hooks/table/useFullHeightTable.ts
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useSettingsStore } from '@/store/modules/settings';
|
||||||
|
|
||||||
|
interface VueBackedHTMLElement extends HTMLElement {
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TableComponentHost {
|
||||||
|
proxy?: {
|
||||||
|
doLayout?: () => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TableState {
|
||||||
|
height: string;
|
||||||
|
maxHeight: string;
|
||||||
|
bodyHeight: string;
|
||||||
|
bodyMaxHeight: string;
|
||||||
|
bodyOverflowY: string;
|
||||||
|
bodyOverflowX: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableStates = new WeakMap<HTMLElement, TableState>();
|
||||||
|
const vueParentComponentKey = '__vueParentComponent';
|
||||||
|
|
||||||
|
const getTableComponentHost = (table: HTMLElement): TableComponentHost | undefined => {
|
||||||
|
const host = (table as VueBackedHTMLElement)[vueParentComponentKey];
|
||||||
|
if (!host || typeof host !== 'object') return undefined;
|
||||||
|
return host as TableComponentHost;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOuterHeight = (element: HTMLElement) => {
|
||||||
|
const style = window.getComputedStyle(element);
|
||||||
|
const marginTop = Number.parseFloat(style.marginTop) || 0;
|
||||||
|
const marginBottom = Number.parseFloat(style.marginBottom) || 0;
|
||||||
|
return element.getBoundingClientRect().height + marginTop + marginBottom;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isVisible = (element: HTMLElement) => {
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
return rect.width > 0 && rect.height > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFollowingHeight = (table: HTMLElement) => {
|
||||||
|
const parent = table.parentElement;
|
||||||
|
if (!parent) return 0;
|
||||||
|
|
||||||
|
const children = Array.from(parent.children) as HTMLElement[];
|
||||||
|
const tableIndex = children.indexOf(table);
|
||||||
|
if (tableIndex === -1) return 0;
|
||||||
|
|
||||||
|
return children.slice(tableIndex + 1).reduce((height, child) => {
|
||||||
|
if (!isVisible(child)) return height;
|
||||||
|
return height + getOuterHeight(child);
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBottomPadding = (table: HTMLElement) => {
|
||||||
|
const parent = table.parentElement;
|
||||||
|
if (!parent) return 0;
|
||||||
|
const parentStyle = window.getComputedStyle(parent);
|
||||||
|
return Number.parseFloat(parentStyle.paddingBottom) || 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBodyWrapper = (table: HTMLElement) => {
|
||||||
|
return table.querySelector<HTMLElement>('.el-table__body-wrapper');
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTableComponent = (table: HTMLElement) => {
|
||||||
|
return getTableComponentHost(table)?.proxy;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPageOverflowHeight = () => {
|
||||||
|
const documentElement = document.documentElement;
|
||||||
|
return Math.max(0, documentElement.scrollHeight - documentElement.clientHeight);
|
||||||
|
};
|
||||||
|
|
||||||
|
const rememberTableState = (table: HTMLElement) => {
|
||||||
|
if (tableStates.has(table)) return;
|
||||||
|
|
||||||
|
const bodyWrapper = getBodyWrapper(table);
|
||||||
|
tableStates.set(table, {
|
||||||
|
height: table.style.height,
|
||||||
|
maxHeight: table.style.maxHeight,
|
||||||
|
bodyHeight: bodyWrapper?.style.height ?? '',
|
||||||
|
bodyMaxHeight: bodyWrapper?.style.maxHeight ?? '',
|
||||||
|
bodyOverflowY: bodyWrapper?.style.overflowY ?? '',
|
||||||
|
bodyOverflowX: bodyWrapper?.style.overflowX ?? ''
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetTable = (table: HTMLElement) => {
|
||||||
|
const originalState = tableStates.get(table);
|
||||||
|
if (!originalState) return;
|
||||||
|
|
||||||
|
const bodyWrapper = getBodyWrapper(table);
|
||||||
|
table.classList.remove('is-full-height-table');
|
||||||
|
table.style.height = originalState.height;
|
||||||
|
table.style.maxHeight = originalState.maxHeight;
|
||||||
|
if (bodyWrapper) {
|
||||||
|
bodyWrapper.style.height = originalState.bodyHeight;
|
||||||
|
bodyWrapper.style.maxHeight = originalState.bodyMaxHeight;
|
||||||
|
bodyWrapper.style.overflowY = originalState.bodyOverflowY;
|
||||||
|
bodyWrapper.style.overflowX = originalState.bodyOverflowX;
|
||||||
|
}
|
||||||
|
tableStates.delete(table);
|
||||||
|
getTableComponent(table)?.doLayout?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetAllTables = () => {
|
||||||
|
document.querySelectorAll<HTMLElement>('.is-full-height-table').forEach(resetTable);
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculateTableHeight = (table: HTMLElement) => {
|
||||||
|
const rect = table.getBoundingClientRect();
|
||||||
|
const viewportBottom = window.innerHeight;
|
||||||
|
const followingHeight = getFollowingHeight(table);
|
||||||
|
const bottomPadding = getBottomPadding(table);
|
||||||
|
const bottomOffset = window.innerWidth < 768 ? 28 : 32;
|
||||||
|
const minHeight = window.innerWidth < 768 ? 220 : 260;
|
||||||
|
|
||||||
|
return Math.max(minHeight, Math.floor(viewportBottom - rect.top - followingHeight - bottomPadding - bottomOffset));
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyTableHeightValue = (table: HTMLElement, tableHeight: number) => {
|
||||||
|
const nextHeight = `${tableHeight}px`;
|
||||||
|
if (table.style.height !== nextHeight) {
|
||||||
|
table.style.height = nextHeight;
|
||||||
|
}
|
||||||
|
if (table.style.maxHeight !== nextHeight) {
|
||||||
|
table.style.maxHeight = nextHeight;
|
||||||
|
}
|
||||||
|
table.classList.add('is-full-height-table');
|
||||||
|
|
||||||
|
const bodyWrapper = getBodyWrapper(table);
|
||||||
|
if (bodyWrapper) {
|
||||||
|
const headerHeight = table.querySelector<HTMLElement>('.el-table__header-wrapper')?.offsetHeight ?? 0;
|
||||||
|
const footerHeight = table.querySelector<HTMLElement>('.el-table__footer-wrapper')?.offsetHeight ?? 0;
|
||||||
|
const bodyHeight = `${Math.max(120, tableHeight - headerHeight - footerHeight)}px`;
|
||||||
|
if (bodyWrapper.style.height !== bodyHeight) {
|
||||||
|
bodyWrapper.style.height = bodyHeight;
|
||||||
|
}
|
||||||
|
if (bodyWrapper.style.maxHeight !== bodyHeight) {
|
||||||
|
bodyWrapper.style.maxHeight = bodyHeight;
|
||||||
|
}
|
||||||
|
bodyWrapper.style.overflowY = 'auto';
|
||||||
|
bodyWrapper.style.overflowX = 'auto';
|
||||||
|
}
|
||||||
|
|
||||||
|
getTableComponent(table)?.doLayout?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyTableHeight = (table: HTMLElement) => {
|
||||||
|
rememberTableState(table);
|
||||||
|
|
||||||
|
const minHeight = window.innerWidth < 768 ? 220 : 260;
|
||||||
|
const tableHeight = calculateTableHeight(table);
|
||||||
|
applyTableHeightValue(table, tableHeight);
|
||||||
|
|
||||||
|
const overflowHeight = getPageOverflowHeight();
|
||||||
|
if (overflowHeight > 0 && tableHeight > minHeight) {
|
||||||
|
applyTableHeightValue(table, Math.max(minHeight, tableHeight - overflowHeight - 4));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const canManageTable = (table: HTMLElement) => {
|
||||||
|
if (!isVisible(table)) return false;
|
||||||
|
if (!table.closest('.app-main')) return false;
|
||||||
|
if (!table.closest('.table-panel')) return false;
|
||||||
|
if (table.closest('.el-dialog')) return false;
|
||||||
|
if (table.dataset.fullHeightTable === 'false') return false;
|
||||||
|
|
||||||
|
const hasManualHeight = Boolean(table.style.height || table.style.maxHeight);
|
||||||
|
return !hasManualHeight || tableStates.has(table);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getManagedTables = () => {
|
||||||
|
return Array.from(document.querySelectorAll<HTMLElement>('.app-main .table-panel .data-table.el-table')).filter(
|
||||||
|
canManageTable
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useFullHeightTable() {
|
||||||
|
const route = useRoute();
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
const observedElements = new Set<Element>();
|
||||||
|
let resizeObserver: ResizeObserver | undefined;
|
||||||
|
let mutationObserver: MutationObserver | undefined;
|
||||||
|
let animationFrame = 0;
|
||||||
|
const timerIds = new Set<number>();
|
||||||
|
|
||||||
|
const refreshObservedElements = () => {
|
||||||
|
if (!resizeObserver) return;
|
||||||
|
|
||||||
|
const nextElements = new Set<Element>([
|
||||||
|
...Array.from(document.querySelectorAll('.app-main, .app-main .p-2, .app-main .table-panel')),
|
||||||
|
...Array.from(document.querySelectorAll('.app-main .search-panel, .app-main .tree-panel-shell')),
|
||||||
|
...getManagedTables()
|
||||||
|
]);
|
||||||
|
|
||||||
|
observedElements.forEach(element => {
|
||||||
|
if (!nextElements.has(element)) {
|
||||||
|
resizeObserver?.unobserve(element);
|
||||||
|
observedElements.delete(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
nextElements.forEach(element => {
|
||||||
|
if (!observedElements.has(element)) {
|
||||||
|
resizeObserver?.observe(element);
|
||||||
|
observedElements.add(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const recalculate = () => {
|
||||||
|
animationFrame = 0;
|
||||||
|
if (!settingsStore.fullHeightTable) {
|
||||||
|
resetAllTables();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tables = getManagedTables();
|
||||||
|
document.querySelectorAll<HTMLElement>('.is-full-height-table').forEach(table => {
|
||||||
|
if (!tables.includes(table)) {
|
||||||
|
resetTable(table);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tables.forEach(applyTableHeight);
|
||||||
|
refreshObservedElements();
|
||||||
|
};
|
||||||
|
|
||||||
|
const scheduleRecalculate = (delay = 0) => {
|
||||||
|
if (delay > 0) {
|
||||||
|
const timerId = window.setTimeout(() => {
|
||||||
|
timerIds.delete(timerId);
|
||||||
|
scheduleRecalculate();
|
||||||
|
}, delay);
|
||||||
|
timerIds.add(timerId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (animationFrame) return;
|
||||||
|
animationFrame = window.requestAnimationFrame(recalculate);
|
||||||
|
};
|
||||||
|
|
||||||
|
const scheduleLayoutRecheck = () => {
|
||||||
|
scheduleRecalculate();
|
||||||
|
[80, 180, 360].forEach(scheduleRecalculate);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
resizeObserver = new ResizeObserver(() => scheduleLayoutRecheck());
|
||||||
|
mutationObserver = new MutationObserver(() => {
|
||||||
|
refreshObservedElements();
|
||||||
|
scheduleLayoutRecheck();
|
||||||
|
});
|
||||||
|
|
||||||
|
const appMain = document.querySelector('.app-main');
|
||||||
|
if (appMain) {
|
||||||
|
mutationObserver.observe(appMain, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['class', 'style'],
|
||||||
|
childList: true,
|
||||||
|
subtree: true
|
||||||
|
});
|
||||||
|
appMain.addEventListener('transitionend', scheduleLayoutRecheck);
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', scheduleLayoutRecheck);
|
||||||
|
|
||||||
|
refreshObservedElements();
|
||||||
|
scheduleLayoutRecheck();
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (animationFrame) {
|
||||||
|
window.cancelAnimationFrame(animationFrame);
|
||||||
|
}
|
||||||
|
timerIds.forEach(timerId => window.clearTimeout(timerId));
|
||||||
|
timerIds.clear();
|
||||||
|
resizeObserver?.disconnect();
|
||||||
|
mutationObserver?.disconnect();
|
||||||
|
document.querySelector('.app-main')?.removeEventListener('transitionend', scheduleLayoutRecheck);
|
||||||
|
window.removeEventListener('resize', scheduleLayoutRecheck);
|
||||||
|
resetAllTables();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => settingsStore.fullHeightTable,
|
||||||
|
() => scheduleLayoutRecheck()
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.fullPath,
|
||||||
|
() => {
|
||||||
|
nextTick(() => scheduleLayoutRecheck());
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user