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

271 lines
7.5 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>
<div class="table-container" ref="tableContainerRef">
<a-table size="small" :loading="loading" :row-selection="enableRowSelection ? rowSelection : undefined"
:data-source="tableData" :columns="enhancedColumns" :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, h } from "vue";
import { Tooltip } from "ant-design-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 };
emptyPlaceholder?: string; // 新增:空值占位符
processEmptyValues?: boolean; // 新增:是否开启空值处理
enableEllipsis?: boolean; // 新增:是否开启超出隐藏
enableSort?: boolean; // 新增:是否启用排序功能
}
const props = withDefaults(defineProps<Props>(), {
enableRowSelection: false,
rowKey: "id",
isPage: true,
data: () => [],
searchParams: () => ({}),
defaultPageSize: 20,
getCheckboxProps: undefined,
transformData: undefined,
scrollY: undefined,
emptyPlaceholder: "-",
processEmptyValues: true, // 默认开启
enableEllipsis: true, // 默认开启
enableSort: false, // 默认关闭,确保旧代码不受影响
});
const emit = defineEmits<{
(e: "data-loaded", params: any, data: any): void;
(e: "selection-change", selectedRowKeys: string[], selectedRows: any[]): void;
(e: "sort-change", sortInfo: { field: string; order: 'ascend' | 'descend' | null }): 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 = 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 = processData(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, filters: any, sorter: any) => {
page.value = pag.current;
size.value = pag.pageSize;
if (props.enableSort) {
if (sorter && sorter.field) {
emit("sort-change", { field: sorter.field, order: sorter.order });
} else {
emit("sort-change", { field: '', order: null });
}
}else {
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);
});
// --- 空值处理工具函数 ---
const processData = (records: any[]) => {
if (!props.processEmptyValues || !Array.isArray(records)) return records;
return records.map(record => {
const processedRecord: any = { ...record };
for (const key in processedRecord) {
if (Object.prototype.hasOwnProperty.call(processedRecord, key)) {
const val = processedRecord[key];
if (val === null || val === undefined || val === "") {
processedRecord[key] = props.emptyPlaceholder;
}
}
}
return processedRecord;
});
};
// --- 列配置增强(超出隐藏 + Tooltip---
const enhancedColumns = computed(() => {
if (!props.enableEllipsis) return props.columns;
return props.columns.map(col => {
if (col.customRender) {
return col; // 如果已有自定义渲染,则不覆盖
}
return {
...col,
ellipsis: true, // 启用 Ant Design Vue 原生的 ellipsis 支持
customRender: ({ text }: any) => {
const content = String(text ?? props.emptyPlaceholder);
return h(
Tooltip,
{ title: content },
() => h('div', {
class: 'table-cell-ellipsis',
style: { overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }
}, content)
);
},
};
});
});
onMounted(() => {
nextTick(() => {
tableScrollY.value = calcTableScrollY(tableContainerRef.value);
if (tableContainerRef.value) {
observer.observe(tableContainerRef.value);
}
});
if (props.data && props.data.length > 0) {
tableData.value = processData(props.data);
}
});
</script>
<style scoped lang="scss"></style>