209 lines
6.1 KiB
Vue
209 lines
6.1 KiB
Vue
<template>
|
||
<div class="my-table-container">
|
||
<!-- 扩展层:可后续统一添加(如加载动画、导出按钮等) -->
|
||
<div v-if="loading" class="table-loading">
|
||
<el-loading-spinner></el-loading-spinner>
|
||
<p class="loading-text">{{ loadingText || "加载中..." }}</p>
|
||
</div>
|
||
|
||
<!-- 原生 Table 核心:透传 props/事件/插槽 -->
|
||
<el-table
|
||
:ref="tableRef"
|
||
v-bind="$attrs"
|
||
v-on="$listeners"
|
||
:class="['my-table', customClass]"
|
||
>
|
||
<!-- 1. 透传“列定义插槽”(原生 columns 中通过 slot 自定义的表头/单元格) -->
|
||
<template
|
||
v-for="(column, index) in $attrs.columns || []"
|
||
v-slot:[`header-${column.prop}`]="scope"
|
||
>
|
||
<!-- 调用业务层定义的表头插槽 -->
|
||
<slot :name="`header-${column.prop}`" v-bind="scope"></slot>
|
||
</template>
|
||
<template
|
||
v-for="(column, index) in $attrs.columns || []"
|
||
v-slot:[column.prop]="scope"
|
||
>
|
||
<!-- 调用业务层定义的单元格插槽 -->
|
||
<slot :name="column.prop" v-bind="scope"></slot>
|
||
</template>
|
||
|
||
<!-- 2. 透传原生内置插槽(如 empty 空数据插槽、append 底部插槽等) -->
|
||
<template v-slot:empty="scope">
|
||
<slot name="empty" v-bind="scope"></slot>
|
||
</template>
|
||
<template v-slot:append="scope">
|
||
<slot name="append" v-bind="scope"></slot>
|
||
</template>
|
||
|
||
<!-- 3. 透传“自定义列”插槽(业务层直接用 <el-table-column> 嵌套的情况) -->
|
||
<slot v-bind:tableRef="tableRef"></slot>
|
||
|
||
<el-table-column
|
||
v-if="selectionColumn"
|
||
type="selection"
|
||
width="55"
|
||
align="center"
|
||
></el-table-column>
|
||
|
||
<el-table-column
|
||
v-if="indexColumn"
|
||
type="index"
|
||
width="55"
|
||
align="center"
|
||
></el-table-column>
|
||
|
||
<el-table-column
|
||
v-for="(column, index) in customColumns"
|
||
v-bind="column"
|
||
:width="column.width"
|
||
:prop="column.prop"
|
||
:align="column.align"
|
||
:label="column.label"
|
||
:min-width="column.minWidth"
|
||
:fixed="column.fixed"
|
||
:show-overflow-tooltip="column.showOverflowTooltip"
|
||
:sortable="column.sortable"
|
||
>
|
||
<template v-slot="scope">
|
||
<ColumnRender v-if="column.render" :column="column" :data="scope.row" :render="column.render"/>
|
||
<Eclipse v-else-if="column.eclipse" :text="scope.row[column.prop]"></Eclipse>
|
||
<span v-else>{{ scope.row[column.prop] }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 扩展层:可后续统一添加分页(如与 MyPagination 组件联动) -->
|
||
<slot name="pagination"></slot>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import ColumnRender from './ColumnRender.vue';
|
||
import Eclipse from './renderer/eclipse.vue';
|
||
|
||
export default {
|
||
name: "KLPTable", // 组件名,便于调试和文档生成
|
||
components: {
|
||
ColumnRender,
|
||
Eclipse
|
||
},
|
||
props: {
|
||
// 1. 扩展 props:新增原生 Table 没有的属性(如加载状态)
|
||
loading: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
loadingText: {
|
||
type: String,
|
||
default: "加载中..."
|
||
},
|
||
// 2. 兼容原生 class 用法(原生 el-table 支持 class 属性,此处显式接收避免 $attrs 冲突)
|
||
customClass: {
|
||
type: String,
|
||
default: ""
|
||
},
|
||
customColumns: {
|
||
type: Array,
|
||
default: () => []
|
||
},
|
||
selectionColumn: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
indexColumn: {
|
||
type: Boolean,
|
||
default: false
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
tableRef: "myTableRef" // 表格 ref,便于后续通过 ref 调用原生方法
|
||
};
|
||
},
|
||
methods: {
|
||
// 核心方法:净化scope,移除可能导致循环引用的属性
|
||
sanitizeScope(scope) {
|
||
if (!scope) return {};
|
||
// 只保留安全的属性(根据实际需求调整)
|
||
const { row, column, $index, store } = scope;
|
||
// 深拷贝避免引用传递,同时过滤循环引用
|
||
return this.deepClone({ row, column, $index, store });
|
||
},
|
||
// 安全的深拷贝方法(避免JSON.parse/stringify无法处理的类型)
|
||
deepClone(obj) {
|
||
if (obj === null || typeof obj !== "object") return obj;
|
||
if (obj instanceof Date) return new Date(obj);
|
||
if (obj instanceof RegExp) return new RegExp(obj);
|
||
|
||
// 避免处理Vue实例(通常带有循环引用)
|
||
if (obj._isVue) return { _isVue: true };
|
||
|
||
const clone = Array.isArray(obj) ? [] : {};
|
||
for (const key in obj) {
|
||
if (obj.hasOwnProperty(key)) {
|
||
clone[key] = this.deepClone(obj[key]);
|
||
}
|
||
}
|
||
return clone;
|
||
},
|
||
// 3. 透传原生 Table 实例方法(如 clearSelection、doLayout 等)
|
||
// 业务层可通过 this.$refs.myTable.xxx() 调用原生方法
|
||
getTableInstance() {
|
||
return this.$refs[this.tableRef];
|
||
},
|
||
// 示例:透传原生 clearSelection 方法
|
||
clearSelection() {
|
||
const table = this.getTableInstance();
|
||
if (table && table.clearSelection) {
|
||
table.clearSelection();
|
||
}
|
||
},
|
||
// 可根据需要扩展更多原生方法(如 toggleRowSelection、sort 等)
|
||
toggleRowSelection(row, selected) {
|
||
const table = this.getTableInstance();
|
||
if (table && table.toggleRowSelection) {
|
||
table.toggleRowSelection(row, selected);
|
||
}
|
||
}
|
||
},
|
||
mounted() {
|
||
// 扩展点:后续可统一添加初始化逻辑(如权限控制、默认排序等)
|
||
console.log("MyTable 初始化完成,原生实例:", this.getTableInstance());
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.my-table-container {
|
||
width: 100%;
|
||
position: relative;
|
||
}
|
||
|
||
/* 扩展样式:加载状态遮罩(后续可统一调整) */
|
||
.table-loading {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(255, 255, 255, 0.8);
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
z-index: 10;
|
||
}
|
||
|
||
.loading-text {
|
||
margin-top: 12px;
|
||
color: #666;
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* 原生 Table 样式兼容:避免封装层影响原生样式 */
|
||
.my-table {
|
||
width: 100%;
|
||
}
|
||
</style> |