20260720215810
This commit is contained in:
1 parent
94207c77ff
commit
fd161dd302
9 files changed
+1242
-1102
No files matched your search
@@ -0,0 +1,197 @@
|
||||
<script setup>
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from "vue";
|
||||
|
||||
defineOptions({ inheritAttrs: false });
|
||||
|
||||
const props = defineProps({
|
||||
bordered: { type: Boolean, default: true },
|
||||
scroll: { type: Object, default: () => ({}) },
|
||||
scrollX: { type: [Boolean, Number, String], default: "max-content" },
|
||||
});
|
||||
|
||||
const tableAreaRef = ref(null);
|
||||
const bodyHeight = ref(240);
|
||||
const mergedScroll = computed(() => ({
|
||||
...props.scroll,
|
||||
x: props.scrollX,
|
||||
y: bodyHeight.value,
|
||||
}));
|
||||
|
||||
let resizeObserver;
|
||||
let mutationObserver;
|
||||
let frameId = 0;
|
||||
const observedElements = new Set();
|
||||
|
||||
function getOuterHeight(element) {
|
||||
if (!element) return 0;
|
||||
|
||||
const style = getComputedStyle(element);
|
||||
const marginTop = Number.parseFloat(style.marginTop) || 0;
|
||||
const marginBottom = Number.parseFloat(style.marginBottom) || 0;
|
||||
|
||||
return element.getBoundingClientRect().height + marginTop + marginBottom;
|
||||
}
|
||||
|
||||
function clipsOverflowY(element) {
|
||||
const { overflow, overflowY } = getComputedStyle(element);
|
||||
return [overflow, overflowY].some((value) => /^(auto|clip|hidden|scroll)$/.test(value));
|
||||
}
|
||||
|
||||
function getAvailableHeight(area) {
|
||||
const areaTop = area.getBoundingClientRect().top;
|
||||
let boundaryBottom = Number.POSITIVE_INFINITY;
|
||||
let ancestor = area.parentElement;
|
||||
|
||||
while (ancestor && ancestor !== document.documentElement) {
|
||||
if (clipsOverflowY(ancestor)) {
|
||||
const rect = ancestor.getBoundingClientRect();
|
||||
boundaryBottom = Math.min(boundaryBottom, rect.top + ancestor.clientTop + ancestor.clientHeight);
|
||||
}
|
||||
ancestor = ancestor.parentElement;
|
||||
}
|
||||
|
||||
if (Number.isFinite(boundaryBottom) && boundaryBottom > areaTop) {
|
||||
return Math.floor(boundaryBottom - areaTop);
|
||||
}
|
||||
return area.clientHeight;
|
||||
}
|
||||
|
||||
function updateBodyHeight() {
|
||||
const area = tableAreaRef.value;
|
||||
if (!area?.clientHeight) return;
|
||||
|
||||
const headerHeight = getOuterHeight(
|
||||
area.querySelector(".ant-table-header") || area.querySelector(".ant-table-thead"),
|
||||
);
|
||||
const paginationHeight = getOuterHeight(area.querySelector(".ant-table-pagination"));
|
||||
const titleHeight = getOuterHeight(area.querySelector(".ant-table-title"));
|
||||
const footerHeight = getOuterHeight(area.querySelector(".ant-table-footer"));
|
||||
|
||||
const nextHeight = Math.max(
|
||||
1,
|
||||
getAvailableHeight(area) - headerHeight - paginationHeight - titleHeight - footerHeight,
|
||||
);
|
||||
if (nextHeight !== bodyHeight.value) bodyHeight.value = nextHeight;
|
||||
}
|
||||
|
||||
function scheduleHeightUpdate() {
|
||||
cancelAnimationFrame(frameId);
|
||||
frameId = requestAnimationFrame(updateBodyHeight);
|
||||
}
|
||||
|
||||
function observe(element) {
|
||||
if (!element || observedElements.has(element)) return;
|
||||
resizeObserver?.observe(element);
|
||||
observedElements.add(element);
|
||||
}
|
||||
|
||||
function syncObservedElements() {
|
||||
const area = tableAreaRef.value;
|
||||
if (!area) return;
|
||||
|
||||
let ancestor = area.parentElement;
|
||||
while (ancestor && ancestor !== document.documentElement) {
|
||||
if (ancestor === area.parentElement || clipsOverflowY(ancestor)) observe(ancestor);
|
||||
ancestor = ancestor.parentElement;
|
||||
}
|
||||
observe(area.querySelector(".ant-table-header"));
|
||||
observe(area.querySelector(".ant-table-pagination"));
|
||||
observe(area.querySelector(".ant-table-title"));
|
||||
observe(area.querySelector(".ant-table-footer"));
|
||||
scheduleHeightUpdate();
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
resizeObserver = new ResizeObserver(scheduleHeightUpdate);
|
||||
mutationObserver = new MutationObserver(() => {
|
||||
nextTick(syncObservedElements);
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
syncObservedElements();
|
||||
mutationObserver.observe(tableAreaRef.value, { childList: true, subtree: true });
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
cancelAnimationFrame(frameId);
|
||||
resizeObserver?.disconnect();
|
||||
mutationObserver?.disconnect();
|
||||
observedElements.clear();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="tableAreaRef"
|
||||
class="auto-height-table"
|
||||
:style="{ '--auto-table-body-height': bodyHeight + 'px' }"
|
||||
>
|
||||
<a-table v-bind="$attrs" :bordered="bordered" :scroll="mergedScroll" size="small">
|
||||
<template v-for="(_, name) in $slots" :key="name" #[name]="slotProps">
|
||||
<slot :name="name" v-bind="slotProps || {}" />
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.auto-height-table {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.ant-table-wrapper),
|
||||
:deep(.ant-spin-nested-loading),
|
||||
:deep(.ant-spin-container),
|
||||
:deep(.ant-table),
|
||||
:deep(.ant-table-container) {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
:deep(.ant-table-wrapper) {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
:deep(.ant-spin-nested-loading),
|
||||
:deep(.ant-spin-container),
|
||||
:deep(.ant-table),
|
||||
:deep(.ant-table-container) {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
:deep(.ant-table-header),
|
||||
:deep(.ant-table-pagination) {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
:deep(.ant-table-body) {
|
||||
height: var(--auto-table-body-height);
|
||||
min-height: 0;
|
||||
max-height: var(--auto-table-body-height) !important;
|
||||
flex: 0 0 var(--auto-table-body-height);
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
/* scroll.y 模式下 antdv 不会渲染 body 底部外边框;且直接加在滚动容器 .ant-table-body
|
||||
上时,边框会被内部滚动内容的最后一行盖住、或被祖先 overflow:hidden 裁掉,表现为
|
||||
“看不到/被遮挡”。所以把底边框挂在不滚动的 .ant-table-container 上,并强制 border-box
|
||||
让边框计入盒子高度,避免溢出裁切。 */
|
||||
::deep(.ant-table-container),
|
||||
::deep(.ant-table-body) {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
::deep(.ant-table-bordered .ant-table-container) {
|
||||
border-bottom: 1px solid var(--ant-color-border-secondary, #f0f0f0);
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user