WholeProcessPlatform/frontend/src/components/BasicTable/index.vue

244 lines
6.2 KiB
Vue
Raw Normal View History

2026-04-22 17:53:20 +08:00
<template>
<a-table
size="small"
:loading="loading"
:row-selection="enableRowSelection ? rowSelection : undefined"
:data-source="tableData"
:columns="columns"
:pagination="paginationConfig"
:scroll="scrollConfig "
2026-04-22 17:53:20 +08:00
:row-key="rowKey"
@change="handleTableChange"
>
<!-- 透传插槽支持自定义列内容 -->
<template v-for="slot in Object.keys($slots)" #[slot]="scope" :key="slot">
<slot :name="slot" v-bind="scope"></slot>
</template>
</a-table>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, watch } from "vue";
// --- Types ---
interface Props {
columns: any[];
scrollY?: string | number;
2026-04-22 17:53:20 +08:00
// 请求数据的函数,由父组件传入
listUrl: (params: any) => Promise<any>;
data?: any[];
2026-04-22 17:53:20 +08:00
// 是否开启行选择
enableRowSelection?: boolean;
// 行数据的 Key用来优化 Table 的渲染
rowKey?: string;
// 外部传入的搜索/过滤参数,变化时会自动触发刷新
searchParams?: Record<string, any>;
// 默认每页显示数量
defaultPageSize?: number;
getCheckboxProps?: (record: any) => any;
transformData?: (res: any) => { records: any[]; total: number };
2026-04-22 17:53:20 +08:00
}
const props = withDefaults(defineProps<Props>(), {
enableRowSelection: false,
rowKey: "id",
data: () => ([]),
2026-04-22 17:53:20 +08:00
searchParams: () => ({}),
defaultPageSize: 20,
getCheckboxProps: undefined,
transformData: undefined,
2026-04-22 17:53:20 +08:00
});
const emit = defineEmits<{
// 每次数据请求后,向父组件返回当前的查询参数和数据结果
(e: "data-loaded", params: any, data: any): void;
// 选中项变化
(e: "selection-change", selectedRowKeys: string[], selectedRows: any[]): void;
}>();
// --- State ---
const loading = ref(false);
const tableData = ref<any[]>([]);
const total = ref(0);
const page = ref(1);
const size = ref(props.defaultPageSize);
const selectedRowKeys = ref<string[]>([]);
const selectedRows = ref<any[]>([]);
2026-05-05 22:38:24 +08:00
//保存最后一次使用的 filter
const lastFilter = ref<Record<string, any> | undefined>(undefined);
2026-04-22 17:53:20 +08:00
// 计算表格滚动高度
const scrollConfig = computed(() => ({
x: 'max-content',
y: props.scrollY, // 注意:如果 scrollY 是 undefined则不会启用垂直滚动
}))
2026-04-22 17:53:20 +08:00
// --- Row Selection ---
const rowSelection = computed(() => ({
selectedRowKeys: selectedRowKeys.value,
onChange: (keys: string[], rows: any[]) => {
selectedRowKeys.value = keys;
selectedRows.value = rows;
emit("selection-change", keys, rows);
},
getCheckboxProps: props.getCheckboxProps ? props.getCheckboxProps : (record: any) => ({})
2026-04-22 17:53:20 +08:00
}));
// --- Pagination Config ---
const paginationConfig = computed(() => ({
total: total.value,
current: page.value,
pageSize: size.value,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total: number) => `${total}`,
pageSizeOptions: ["20", "50", "100"],
}));
// --- Methods ---
/**
* 获取列表数据
* @param extraParams 额外的临时参数可选
*/
2026-04-24 15:31:32 +08:00
const getList = async (filter?: Record<string, any>) => {
if(props.data.length > 0) return
2026-04-22 17:53:20 +08:00
loading.value = true;
2026-05-07 15:40:18 +08:00
tableData.value = [];
total.value = 0;
2026-05-05 22:38:24 +08:00
// 如果传入了新 filter则更新否则保留旧的
if (filter !== undefined) {
lastFilter.value = filter;
}
2026-04-22 17:53:20 +08:00
try {
// 合并基础分页参数、外部搜索参数和临时参数
const params = {
2026-04-30 17:09:07 +08:00
...props.searchParams,
2026-04-22 17:53:20 +08:00
skip: page.value,
take: size.value,
2026-05-05 22:38:24 +08:00
filter: lastFilter.value,
2026-04-30 17:09:07 +08:00
2026-04-22 17:53:20 +08:00
// 如果后端需要 skip/take 格式,可以在此转换
// skip: (page.value - 1) * size.value,
// take: size.value,
};
const res = await props.listUrl(params);
let records: any[] = [];
let totalCount: number = 0;
// [!code ++] 核心逻辑:如果父组件传入了 transformData则使用父组件的逻辑
if (props.transformData) {
const result = props.transformData(res);
records = result.records || [];
totalCount = result.total || 0;
} else {
// [!code ++] 否则使用默认逻辑
records = res?.data?.records || res?.data || [];
totalCount = res?.data?.total || res?.total || 0;
}
2026-04-24 15:31:32 +08:00
2026-04-22 17:53:20 +08:00
tableData.value = records;
total.value = totalCount;
// 向父组件暴露当前请求参数和结果
emit("data-loaded", params, { records, total: totalCount });
2026-04-22 17:53:20 +08:00
} catch (error) {
console.error("Fetch table data error:", error);
tableData.value = [];
total.value = 0;
} finally {
loading.value = false;
}
};
/**
* 处理分页排序筛选变化
*/
const handleTableChange = (pag: any) => {
page.value = pag.current;
size.value = pag.pageSize;
getList();
};
/**
* 重置表格状态回到第一页清空选中
*/
const reset = () => {
page.value = 1;
size.value = props.defaultPageSize;
selectedRowKeys.value = [];
selectedRows.value = [];
getList();
};
/**
* 刷新当前页
*/
const refresh = () => {
getList();
};
2026-05-07 15:40:18 +08:00
const clearSelection = () => {
selectedRowKeys.value = [];
selectedRows.value = [];
}
2026-04-22 17:53:20 +08:00
// --- Expose Methods to Parent ---
defineExpose({
getList,
reset,
refresh,
2026-05-07 15:40:18 +08:00
tableData,
clearSelection,
2026-04-22 17:53:20 +08:00
// 也可以暴露选中的数据供父组件获取
getSelected: () => ({
keys: selectedRowKeys.value,
rows: selectedRows.value,
}),
});
// --- Watchers ---
// 监听外部搜索参数变化,自动重置页码并刷新
watch(
() => props.searchParams,
() => {
page.value = 1; // 搜索时通常重置到第一页
getList();
},
{ deep: true }
);
// --- Lifecycle ---
onMounted(() => {
if(props.data && props.data.length > 0) tableData.value = props.data || [];
2026-04-22 17:53:20 +08:00
});
</script>
<style scoped>
:deep(.ant-table-wrapper) {
height: 100%; /* 让表格容器填满父级 */
.ant-spin-nested-loading, .ant-spin-container {
height: 100%;
display: flex;
flex-direction: column;
}
.ant-table {
flex: 1; /* 表格主体区域自动撑满剩余空间 */
overflow: hidden; /* 防止内容溢出 */
}
.ant-table-container {
height: 100%;
display: flex;
flex-direction: column;
}
.ant-table-body {
flex: 1; /* 表体部分撑满,实现固定表头 + 滚动内容 */
overflow: auto !important; /* 出现滚动条的关键 */
}
}
</style>