218 lines
4.9 KiB
Vue
Raw Normal View History

2025-10-30 16:34:29 +08:00
<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 $slots" #[name]="slotProps">
<slot :name="name" v-bind="slotProps || {}" />
</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>