214 lines
5.4 KiB
Vue
214 lines
5.4 KiB
Vue
<template>
|
||
<div class="table-container" ref="tableContainerRef">
|
||
<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>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed, onMounted, watch, nextTick } from "vue";
|
||
import { calcTableScrollY } from "@/utils/index";
|
||
|
||
// --- Types ---
|
||
interface Props {
|
||
columns: any[];
|
||
scrollY?: string | number; // 优先使用传入的固定值
|
||
listUrl: (params: any) => Promise<any>;
|
||
data?: any[];
|
||
enableRowSelection?: boolean;
|
||
rowKey?: string;
|
||
searchParams?: Record<string, any>;
|
||
defaultPageSize?: number;
|
||
isPage?: boolean;
|
||
getCheckboxProps?: (record: any) => any;
|
||
transformData?: (res: any) => { records: any[]; total: number };
|
||
}
|
||
|
||
const props = withDefaults(defineProps<Props>(), {
|
||
enableRowSelection: false,
|
||
rowKey: "id",
|
||
isPage: true,
|
||
data: () => [],
|
||
searchParams: () => ({}),
|
||
defaultPageSize: 20,
|
||
getCheckboxProps: undefined,
|
||
transformData: undefined,
|
||
scrollY: 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[]>([]);
|
||
const lastFilter = ref<Record<string, any> | undefined>(undefined);
|
||
const tableContainerRef = ref<any>(null);
|
||
const tableScrollY = ref<number>(0); // ✅ 新增:存储容器高度
|
||
|
||
// --- Computed Scroll Config ---
|
||
const scrollConfig = computed(() => {
|
||
const config: any = {
|
||
x: "100%",
|
||
y: 0,
|
||
};
|
||
|
||
// // 1. 如果父组件传入了固定的 scrollY,优先使用
|
||
if (props.scrollY !== undefined && props.scrollY !== null) {
|
||
config.y = props.scrollY;
|
||
}
|
||
// 2. 否则,如果检测到容器有高度,则使用容器高度作为滚动高度
|
||
else if (tableScrollY.value > 0) {
|
||
config.y = tableScrollY.value;
|
||
}
|
||
console.log(config);
|
||
|
||
return config;
|
||
});
|
||
|
||
// --- 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 ---
|
||
const getList = async (filter?: Record<string, any>) => {
|
||
if (props.data.length > 0) return;
|
||
loading.value = true;
|
||
tableData.value = [];
|
||
total.value = 0;
|
||
if (filter !== undefined) {
|
||
lastFilter.value = filter;
|
||
}
|
||
console.log(props.searchParams);
|
||
try {
|
||
const params = {
|
||
...props.searchParams,
|
||
skip: page.value,
|
||
take: size.value,
|
||
filter: lastFilter.value,
|
||
};
|
||
if (!props.isPage) {
|
||
delete params.take;
|
||
delete params.skip;
|
||
}
|
||
|
||
const res = await props.listUrl(params);
|
||
|
||
let finalRecords: any[] = [];
|
||
let finalTotal: number = 0;
|
||
|
||
if (props.transformData) {
|
||
const result = props.transformData(res);
|
||
finalRecords = result.records || [];
|
||
finalTotal = result.total || 0;
|
||
} else {
|
||
finalRecords = res?.data?.records || res?.data?.data || res?.data || [];
|
||
finalTotal = res.data?.data?.total || res?.data?.total || res?.total || 0;
|
||
}
|
||
|
||
tableData.value = finalRecords;
|
||
total.value = finalTotal;
|
||
emit("data-loaded", params, { records: finalRecords, total: finalTotal });
|
||
} 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 = [];
|
||
};
|
||
defineExpose({
|
||
loading,
|
||
getList,
|
||
reset,
|
||
refresh,
|
||
tableData,
|
||
clearSelection,
|
||
getSelected: () => ({
|
||
keys: selectedRowKeys.value,
|
||
rows: selectedRows.value,
|
||
}),
|
||
});
|
||
|
||
const observer = new ResizeObserver(() => {
|
||
tableScrollY.value = calcTableScrollY(tableContainerRef.value);
|
||
});
|
||
onMounted(() => {
|
||
nextTick(() => {
|
||
tableScrollY.value = calcTableScrollY(tableContainerRef.value);
|
||
if (tableContainerRef.value) {
|
||
observer.observe(tableContainerRef.value);
|
||
}
|
||
});
|
||
if (props.data && props.data.length > 0) {
|
||
tableData.value = props.data || [];
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<style scoped lang="scss"></style>
|