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

248 lines
6.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<a-table
size="small"
:loading="loading"
:row-selection="enableRowSelection ? rowSelection : undefined"
:data-source="tableData"
:columns="columns"
:pagination="paginationConfig"
:scroll="scrollConfig "
: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;
// 请求数据的函数,由父组件传入
listUrl: (params: any) => Promise<any>;
data?: any[];
// 是否开启行选择
enableRowSelection?: boolean;
// 行数据的 Key用来优化 Table 的渲染
rowKey?: string;
// 外部传入的搜索/过滤参数,变化时会自动触发刷新
searchParams?: Record<string, any>;
// 是否只加载一次,默认 false
isOneLoad?: boolean;
// 默认每页显示数量
defaultPageSize?: number;
getCheckboxProps?: (record: any) => any;
transformData?: (res: any) => { records: any[]; total: number };
}
const props = withDefaults(defineProps<Props>(), {
enableRowSelection: false,
rowKey: "id",
data: () => ([]),
isOneLoad: true,
searchParams: () => ({}),
defaultPageSize: 20,
getCheckboxProps: undefined,
transformData: undefined,
});
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[]>([]);
//保存最后一次使用的 filter
const lastFilter = ref<Record<string, any> | undefined>(undefined);
// 计算表格滚动高度
const scrollConfig = computed(() => ({
x: 'max-content',
y: props.scrollY, // 注意:如果 scrollY 是 undefined则不会启用垂直滚动
}))
// --- 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) => ({})
}));
// --- 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 额外的临时参数(可选)
*/
const getList = async (filter?: Record<string, any>) => {
if(props.data.length > 0) return
loading.value = true;
tableData.value = [];
total.value = 0;
// 如果传入了新 filter则更新否则保留旧的
if (filter !== undefined) {
lastFilter.value = filter;
}
try {
// 合并基础分页参数、外部搜索参数和临时参数
const params = {
...props.searchParams,
skip: page.value,
take: size.value,
filter: lastFilter.value,
// 如果后端需要 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;
}
tableData.value = records;
total.value = totalCount;
// 向父组件暴露当前请求参数和结果
emit("data-loaded", params, { records, total: totalCount });
} 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();
};
const clearSelection = () => {
selectedRowKeys.value = [];
selectedRows.value = [];
}
// --- Expose Methods to Parent ---
defineExpose({
getList,
reset,
refresh,
tableData,
clearSelection,
// 也可以暴露选中的数据供父组件获取
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 || [];
});
</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>