refactor(useTableScroll): 重构表格滚动高度计算逻辑,适配表头高度扣除
1. 优化滚动高度计算方式,改为容器总高减去表头和分页器高度,计算更准确 2. 新增nextTick等待DOM渲染完成后再进行尺寸测量 3. 调整默认最小高度为100px,优化变量命名和注释 4. 简化菜单页面的模板代码格式,统一代码风格 5. 更新useTableScroll的调用参数,移除旧的自定义paginationHeight传参
This commit is contained in:
1 parent
4048aeb6fb
commit
026ec34471
2 files changed
+37
-61
No files matched your search
@@ -1,49 +1,56 @@
|
|||||||
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 监听表格父容器高度,动态计算 a-table 的 scroll.y。
|
* 正确计算 AntDV a-table 的 scroll.y 高度。
|
||||||
* 自动测量 a-table 内部的分页器高度,从而得到准确的 tbody 高度。
|
* 核心逻辑:scroll.y = 外层容器总高 - 表头真实高度 - 分页器真实高度
|
||||||
*/
|
*/
|
||||||
export function useTableScroll(minHeight = 120) {
|
export function useTableScroll(minHeight = 100) {
|
||||||
const wrapperRef = ref<HTMLElement | null>(null);
|
const wrapperRef = ref<HTMLElement | null>(null);
|
||||||
const tableHeight = ref<number>(minHeight);
|
const tableHeight = ref<number>(minHeight);
|
||||||
let observer: ResizeObserver | null = null;
|
let observer: ResizeObserver | null = null;
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
if (!wrapperRef.value) return;
|
if (!wrapperRef.value) return;
|
||||||
|
|
||||||
const wrapperHeight = wrapperRef.value.clientHeight;
|
const wrapperHeight = wrapperRef.value.clientHeight;
|
||||||
|
|
||||||
// 动态获取分页器高度(如果存在)
|
// 动态获取表头和分页器的真实高度
|
||||||
|
const headerEl = wrapperRef.value.querySelector('.ant-table-thead') as HTMLElement | null;
|
||||||
const paginationEl = wrapperRef.value.querySelector('.ant-table-pagination') as HTMLElement | null;
|
const paginationEl = wrapperRef.value.querySelector('.ant-table-pagination') as HTMLElement | null;
|
||||||
|
|
||||||
|
const headerHeight = headerEl ? headerEl.offsetHeight : 55; // 55 为兜底
|
||||||
const paginationHeight = paginationEl ? paginationEl.offsetHeight : 0;
|
const paginationHeight = paginationEl ? paginationEl.offsetHeight : 0;
|
||||||
|
|
||||||
// scroll.y = 容器总高 - 分页器高度(表头由 a-table 自动固定,无需扣除)
|
// 计算 tbody 的高度
|
||||||
const tbodyHeight = wrapperHeight - paginationHeight;
|
const tbodyHeight = wrapperHeight - headerHeight - paginationHeight;
|
||||||
tableHeight.value = Math.max(tbodyHeight, minHeight);
|
tableHeight.value = Math.max(tbodyHeight, minHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
if (!wrapperRef.value) return;
|
if (!wrapperRef.value) return;
|
||||||
|
|
||||||
|
// 等待 DOM 渲染完成后再测量
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
observer = new ResizeObserver(update);
|
observer = new ResizeObserver(update);
|
||||||
observer.observe(wrapperRef.value);
|
observer.observe(wrapperRef.value);
|
||||||
|
|
||||||
// 初始测量
|
// 初始计算
|
||||||
update();
|
update();
|
||||||
|
|
||||||
// MutationObserver 用于监听分页器的动态添加/移除
|
// 使用 MutationObserver 监听 DOM 变化(比如数据加载后表头/分页器出现)
|
||||||
const mutationObserver = new MutationObserver(() => {
|
const mutationObserver = new MutationObserver(() => {
|
||||||
update();
|
update();
|
||||||
});
|
});
|
||||||
mutationObserver.observe(wrapperRef.value, { childList: true, subtree: true });
|
mutationObserver.observe(wrapperRef.value, { childList: true, subtree: true });
|
||||||
// 保存以便卸载时使用
|
(wrapperRef.value as any).__mo = mutationObserver;
|
||||||
(wrapperRef.value as any).__mutationObserver = mutationObserver;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
observer?.disconnect();
|
observer?.disconnect();
|
||||||
observer = null;
|
observer = null;
|
||||||
if (wrapperRef.value && (wrapperRef.value as any).__mutationObserver) {
|
if (wrapperRef.value && (wrapperRef.value as any).__mo) {
|
||||||
(wrapperRef.value as any).__mutationObserver.disconnect();
|
(wrapperRef.value as any).__mo.disconnect();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,24 +3,19 @@
|
|||||||
<a-card title="菜单管理">
|
<a-card title="菜单管理">
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<a-button type="primary" @click="handleAdd()">
|
<a-button type="primary" @click="handleAdd()">
|
||||||
<template #icon><PlusOutlined /></template>
|
<template #icon>
|
||||||
|
<PlusOutlined />
|
||||||
|
</template>
|
||||||
新增菜单
|
新增菜单
|
||||||
</a-button>
|
</a-button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="table-wrapper" ref="wrapperRef">
|
<div class="table-wrapper" ref="wrapperRef">
|
||||||
<a-table
|
<a-table :columns="columns" :data-source="menuList" :loading="loading" :pagination="false" row-key="id"
|
||||||
:columns="columns"
|
:scroll="{ x: 'max-content', y: tableHeight }" :expandable="{
|
||||||
:data-source="menuList"
|
|
||||||
:loading="loading"
|
|
||||||
:pagination="false"
|
|
||||||
row-key="id"
|
|
||||||
:scroll="{ x: 'max-content', y: tableHeight }"
|
|
||||||
:expandable="{
|
|
||||||
defaultExpandAllRows: false,
|
defaultExpandAllRows: false,
|
||||||
showExpandColumn: true,
|
showExpandColumn: true,
|
||||||
}"
|
}">
|
||||||
>
|
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.dataIndex === 'menuType'">
|
<template v-if="column.dataIndex === 'menuType'">
|
||||||
<a-tag :color="record.menuType === 1 ? 'blue' : 'green'">
|
<a-tag :color="record.menuType === 1 ? 'blue' : 'green'">
|
||||||
@@ -42,12 +37,7 @@
|
|||||||
<a-button type="link" size="small" @click="handleEdit(record)">
|
<a-button type="link" size="small" @click="handleEdit(record)">
|
||||||
<EditOutlined />编辑
|
<EditOutlined />编辑
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-popconfirm
|
<a-popconfirm title="确定删除该菜单?" ok-text="确定" cancel-text="取消" @confirm="handleDelete(record)">
|
||||||
title="确定删除该菜单?"
|
|
||||||
ok-text="确定"
|
|
||||||
cancel-text="取消"
|
|
||||||
@confirm="handleDelete(record)"
|
|
||||||
>
|
|
||||||
<a-button type="link" size="small" danger>
|
<a-button type="link" size="small" danger>
|
||||||
<DeleteOutlined />删除
|
<DeleteOutlined />删除
|
||||||
</a-button>
|
</a-button>
|
||||||
@@ -59,30 +49,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</a-card>
|
</a-card>
|
||||||
|
|
||||||
<a-modal
|
<a-modal v-model:open="modalVisible" :title="modalTitle" :confirm-loading="modalLoading" width="640px"
|
||||||
v-model:open="modalVisible"
|
@ok="handleSubmit" @cancel="handleCancel">
|
||||||
:title="modalTitle"
|
<a-form ref="formRef" :model="formData" :rules="formRules" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||||
:confirm-loading="modalLoading"
|
|
||||||
width="640px"
|
|
||||||
@ok="handleSubmit"
|
|
||||||
@cancel="handleCancel"
|
|
||||||
>
|
|
||||||
<a-form
|
|
||||||
ref="formRef"
|
|
||||||
:model="formData"
|
|
||||||
:rules="formRules"
|
|
||||||
:label-col="{ span: 6 }"
|
|
||||||
:wrapper-col="{ span: 16 }"
|
|
||||||
>
|
|
||||||
<a-form-item label="上级菜单" name="parentId">
|
<a-form-item label="上级菜单" name="parentId">
|
||||||
<a-tree-select
|
<a-tree-select v-model:value="formData.parentId" :tree-data="parentOptions"
|
||||||
v-model:value="formData.parentId"
|
:field-names="{ label: 'menuName', value: 'id', children: 'children' }" allow-clear tree-default-expand-all
|
||||||
:tree-data="parentOptions"
|
placeholder="请选择上级菜单" />
|
||||||
:field-names="{ label: 'menuName', value: 'id', children: 'children' }"
|
|
||||||
allow-clear
|
|
||||||
tree-default-expand-all
|
|
||||||
placeholder="请选择上级菜单"
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="菜单类型" name="menuType">
|
<a-form-item label="菜单类型" name="menuType">
|
||||||
@@ -117,12 +90,8 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="可见性" name="visible">
|
<a-form-item label="可见性" name="visible">
|
||||||
<a-switch
|
<a-switch :checked="formData.visible === 1" checked-children="显示" un-checked-children="隐藏"
|
||||||
:checked="formData.visible === 1"
|
@change="(checked: boolean) => (formData.visible = checked ? 1 : 0)" />
|
||||||
checked-children="显示"
|
|
||||||
un-checked-children="隐藏"
|
|
||||||
@change="(checked: boolean) => (formData.visible = checked ? 1 : 0)"
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
@@ -136,7 +105,7 @@ import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons-vu
|
|||||||
import { menuApi } from '@/api/menu';
|
import { menuApi } from '@/api/menu';
|
||||||
import { useTableScroll } from '@/composables/useTableScroll';
|
import { useTableScroll } from '@/composables/useTableScroll';
|
||||||
|
|
||||||
const { wrapperRef, tableHeight } = useTableScroll({ paginationHeight: 0 });
|
const { wrapperRef, tableHeight } = useTableScroll();
|
||||||
|
|
||||||
interface MenuItem {
|
interface MenuItem {
|
||||||
id: number;
|
id: number;
|
||||||
|
|||||||
Reference in new issue
Block a user