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="{ x: '100%' }"
|
|
|
|
|
|
: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[];
|
|
|
|
|
|
// 请求数据的函数,由父组件传入
|
|
|
|
|
|
listUrl: (params: any) => Promise<any>;
|
|
|
|
|
|
// 是否开启行选择
|
|
|
|
|
|
enableRowSelection?: boolean;
|
|
|
|
|
|
// 行数据的 Key,用来优化 Table 的渲染
|
|
|
|
|
|
rowKey?: string;
|
|
|
|
|
|
// 外部传入的搜索/过滤参数,变化时会自动触发刷新
|
|
|
|
|
|
searchParams?: Record<string, any>;
|
|
|
|
|
|
// 默认每页显示数量
|
|
|
|
|
|
defaultPageSize?: number;
|
2026-04-27 10:35:06 +08:00
|
|
|
|
getCheckboxProps?: (record: any) => any;
|
2026-04-27 19:11:22 +08:00
|
|
|
|
transformData?: (res: any) => { records: any[]; total: number };
|
2026-04-22 17:53:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
|
|
|
|
enableRowSelection: false,
|
|
|
|
|
|
rowKey: "id",
|
|
|
|
|
|
searchParams: () => ({}),
|
|
|
|
|
|
defaultPageSize: 20,
|
2026-04-27 19:11:22 +08:00
|
|
|
|
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[]>([]);
|
|
|
|
|
|
|
|
|
|
|
|
// --- Row Selection ---
|
|
|
|
|
|
const rowSelection = computed(() => ({
|
|
|
|
|
|
selectedRowKeys: selectedRowKeys.value,
|
|
|
|
|
|
onChange: (keys: string[], rows: any[]) => {
|
|
|
|
|
|
selectedRowKeys.value = keys;
|
|
|
|
|
|
selectedRows.value = rows;
|
|
|
|
|
|
emit("selection-change", keys, rows);
|
|
|
|
|
|
},
|
2026-04-27 10:35:06 +08:00
|
|
|
|
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>) => {
|
2026-04-22 17:53:20 +08:00
|
|
|
|
loading.value = true;
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 合并基础分页参数、外部搜索参数和临时参数
|
|
|
|
|
|
const params = {
|
2026-04-24 15:31:32 +08:00
|
|
|
|
// ...props.searchParams,
|
2026-04-22 17:53:20 +08:00
|
|
|
|
skip: page.value,
|
|
|
|
|
|
take: size.value,
|
2026-04-24 15:31:32 +08:00
|
|
|
|
filter: filter,
|
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);
|
2026-04-27 19:11:22 +08:00
|
|
|
|
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-27 19:11:22 +08:00
|
|
|
|
|
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();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// --- Expose Methods to Parent ---
|
|
|
|
|
|
defineExpose({
|
|
|
|
|
|
getList,
|
|
|
|
|
|
reset,
|
|
|
|
|
|
refresh,
|
|
|
|
|
|
// 也可以暴露选中的数据供父组件获取
|
|
|
|
|
|
getSelected: () => ({
|
|
|
|
|
|
keys: selectedRowKeys.value,
|
|
|
|
|
|
rows: selectedRows.value,
|
|
|
|
|
|
}),
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// --- Watchers ---
|
|
|
|
|
|
|
|
|
|
|
|
// 监听外部搜索参数变化,自动重置页码并刷新
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => props.searchParams,
|
|
|
|
|
|
() => {
|
|
|
|
|
|
page.value = 1; // 搜索时通常重置到第一页
|
|
|
|
|
|
getList();
|
|
|
|
|
|
},
|
|
|
|
|
|
{ deep: true }
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// --- Lifecycle ---
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
/* 如有必要,添加少量样式 */
|
2026-04-24 15:31:32 +08:00
|
|
|
|
</style>
|