221 lines
5.0 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
ref="containerRef"
class="dynamic-table-container"
:class="{ 'auto-height-enabled': autoHeight }"
>
<!-- 工具栏 -->
<TableToolbar
v-if="toolbar"
:buttons="toolbar.buttons"
:selected-keys="selectedRowKeys"
:data-source="dataSource"
:align="toolbar.align"
/>
<!-- 表格 - 直接透传所有原生属性 -->
<el-table
ref="tableRef"
:data="dataSource"
:height="computedHeight"
v-bind="$attrs"
@selection-change="handleSelectionChange"
>
<!-- 渲染列 -->
<el-table-column
v-for="(col, idx) in processedColumns"
:key="col.prop || col.label || idx"
v-bind="getColumnProps(col)"
>
<!-- 自定义渲染render slot -->
<template v-if="col.render || col.slot" #default="scope">
<component
v-if="col.render"
:is="col.render(scope.row, scope.column, scope.$index)"
/>
<slot v-else-if="col.slot" :name="col.slot" v-bind="scope" />
</template>
</el-table-column>
<!-- 透传所有插槽 -->
<template v-for="name in Object.keys($slots)" #[name]="scope">
<slot :name="name" v-bind="typeof scope === 'object' ? scope : {}" />
</template>
</el-table>
<!-- 分页 -->
<el-pagination
v-if="pagination"
:current-page="pagination.current"
:page-size="pagination.pageSize"
:total="pagination.total"
:page-sizes="pagination.pageSizes || [10, 20, 50, 100]"
:layout="pagination.layout || 'total, sizes, prev, pager, next, jumper'"
@current-change="handlePageChange"
@size-change="handleSizeChange"
/>
</div>
</template>
<script setup>
import { ref, computed } from "vue";
import TableToolbar from "./TableToolbar.vue";
import { useAutoHeight } from "./useAutoHeight";
defineOptions({
name: "DynamicTable",
inheritAttrs: false, // 手动控制属性透传
});
const props = defineProps({
/**
* 数据源
*/
dataSource: {
type: Array,
default: () => [],
},
/**
* 列配置
*/
columns: {
type: Array,
default: () => [],
},
/**
* 工具栏配置
*/
toolbar: {
type: Object,
default: null,
},
/**
* 是否启用自适应高度
*/
autoHeight: {
type: Boolean,
default: false,
},
/**
* 分页配置false 表示不分页)
*/
pagination: {
type: [Object, Boolean],
default: false,
},
});
const emit = defineEmits(["selection-change"]);
const containerRef = ref(null);
const tableRef = ref(null);
const selectedRowKeys = ref([]);
// ==================== 自适应高度 ====================
const { tableHeight } = useAutoHeight(containerRef, {
enabled: props.autoHeight,
minHeight: 200,
});
const computedHeight = computed(() => {
return props.autoHeight ? tableHeight.value : undefined;
});
// ==================== 列配置处理 ====================
/**
* 处理列配置,设置合理的默认值
* 原则: 仅设置默认值,不改变结构,用户配置优先
*/
const processedColumns = computed(() => {
return props.columns.map((col) => {
// 如果是特殊列类型(selection/index/expand),直接返回
if (col.type) return col;
// 为普通列添加默认值
return {
showOverflowTooltip: true, // 默认启用省略提示
align: "center", // 默认居中对齐
minWidth: col.width ? undefined : 100, // 无固定宽度时设置最小宽度
...col, // 用户配置覆盖默认值
};
});
});
/**
* 获取列的 props用于 v-bind
* 过滤掉自定义属性 (render, slot)
*/
const getColumnProps = (col) => {
const { render, slot, ...restProps } = col;
return restProps;
};
// ==================== 事件处理 ====================
/**
* 处理行选择改变
*/
const handleSelectionChange = (selection) => {
// 提取选中行的 key
const rowKey = props.$attrs?.rowKey || props.$attrs?.["row-key"] || "id";
selectedRowKeys.value = selection.map((row) =>
typeof rowKey === "function" ? rowKey(row) : row[rowKey]
);
emit("selection-change", selection);
};
/**
* 处理分页页码改变
*/
const handlePageChange = (page) => {
props.pagination?.onChange?.(page, props.pagination.pageSize);
};
/**
* 处理每页条数改变
*/
const handleSizeChange = (size) => {
// 每页条数改变时,重置到第一页
props.pagination?.onChange?.(1, size);
};
// ==================== 暴露实例 ====================
defineExpose({
/**
* el-table 实例引用
*/
tableRef,
/**
* 已选中的行 key 数组
*/
selectedRowKeys,
/**
* 手动重新计算表格高度
*/
recalculate: () => {
if (props.autoHeight) {
setTimeout(() => {
tableHeight.recalculate?.();
}, 100);
}
},
});
</script>
<style scoped>
.dynamic-table-container {
display: flex;
flex-direction: column;
height: 100%;
}
.auto-height-enabled {
overflow: hidden;
}
.el-pagination {
margin-top: 16px;
justify-content: flex-end;
}
</style>