Files
klp-oa/klp-ui/src/components/KLPUI/KLPTable/index.vue

228 lines
6.9 KiB
Vue
Raw Normal View History

2025-08-25 11:46:03 +08:00
<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>
<div class="el-table-container" ref="elTableWrapper" @mouseleave="handleTableLeave">
<!-- 原生 Table 核心透传 props/事件/插槽 -->
<el-table :ref="tableRef" v-bind="$attrs" v-on="$listeners" :class="['my-table', customClass]"
@cell-mouse-enter="handleCellEnter" @row-mouseleave="handleRowLeave">
<!-- 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>
2025-08-28 15:06:03 +08:00
<!-- 3. 透传自定义列插槽直接接收<el-table-column> 嵌套的情况 -->
<slot v-bind:tableRef="tableRef"></slot>
2025-08-28 15:06:03 +08:00
<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>
<!-- 浮层组件 -->
<KLPTableFloatLayer v-if="floatLayer" :columns="floatLayerColumns" :data="hoveredRow" :tooltipVisible="tooltipVisible"
:tooltipStyle="tooltipStyle" />
</div>
<!-- 扩展层可后续统一添加分页如与 MyPagination 组件联动 -->
<slot name="pagination"></slot>
2025-08-25 11:46:03 +08:00
</div>
</template>
<script>
import ColumnRender from './ColumnRender.vue';
2025-08-28 15:06:03 +08:00
import Eclipse from './renderer/eclipse.vue';
import KLPTableFloatLayer from './FloatLayer/index.vue';
2025-08-28 15:06:03 +08:00
2025-08-25 11:46:03 +08:00
export default {
2025-08-28 15:06:03 +08:00
name: "KLPTable", // 组件名,便于调试和文档生成
components: {
ColumnRender,
Eclipse,
KLPTableFloatLayer
2025-08-28 15:06:03 +08:00
},
2025-08-25 11:46:03 +08:00
props: {
// 1. 扩展 props新增原生 Table 没有的属性(如加载状态)
2025-08-25 11:46:03 +08:00
loading: {
type: Boolean,
default: false
},
loadingText: {
2025-08-25 11:46:03 +08:00
type: String,
default: "加载中..."
2025-08-25 11:46:03 +08:00
},
// 2. 兼容原生 class 用法(原生 el-table 支持 class 属性,此处显式接收避免 $attrs 冲突)
customClass: {
2025-08-25 11:46:03 +08:00
type: String,
default: ""
2025-08-28 15:06:03 +08:00
},
selectionColumn: {
type: Boolean,
default: false
},
indexColumn: {
type: Boolean,
default: false
},
// floatLayer是否显示浮层
floatLayer: {
type: Boolean,
default: false
},
floatLayerConfig: {
type: Object,
default: () => ({
columns: [],
title: '详细信息'
})
2025-08-25 11:46:03 +08:00
}
},
data() {
return {
tableRef: "myTableRef", // 表格 ref便于后续通过 ref 调用原生方法
// 浮层相关
tooltipVisible: false,
tooltipStyle: {
top: '0px',
left: '0px'
},
hoveredRow: null,
columns: []
};
},
computed: {
floatLayerColumns() {
console.log(this.floatLayerConfig?.columns?.length > 1)
if (this.floatLayerConfig?.columns?.length > 1) {
return this.floatLayerConfig.columns
}
return this.columns;
}
},
2025-08-25 11:46:03 +08:00
methods: {
2025-08-28 15:06:03 +08:00
// 核心方法净化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);
2025-08-28 15:06:03 +08:00
// 避免处理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() 调用原生方法
2025-08-25 11:46:03 +08:00
getTableInstance() {
return this.$refs[this.tableRef];
2025-08-25 11:46:03 +08:00
},
// 示例:透传原生 clearSelection 方法
2025-08-25 11:46:03 +08:00
clearSelection() {
const table = this.getTableInstance();
if (table && table.clearSelection) {
table.clearSelection();
2025-08-25 11:46:03 +08:00
}
},
// 可根据需要扩展更多原生方法(如 toggleRowSelection、sort 等)
2025-08-25 11:46:03 +08:00
toggleRowSelection(row, selected) {
const table = this.getTableInstance();
if (table && table.toggleRowSelection) {
table.toggleRowSelection(row, selected);
2025-08-25 11:46:03 +08:00
}
},
// 浮层相关
handleCellEnter(row, column, cell, event) {
if (!row || !event) return
this.hoveredRow = row
this.tooltipVisible = true
this.updateTooltipPosition(event)
},
handleRowLeave() {
this.tooltipVisible = false
this.hoveredRow = null
},
handleTableLeave() {
this.tooltipVisible = false
this.hoveredRow = null
},
updateTooltipPosition(event) {
this.$nextTick(() => {
const wrapper = this.$refs.elTableWrapper
if (!wrapper) return
const wrapperRect = wrapper.getBoundingClientRect()
let left = event.clientX - wrapperRect.left + 16
let top = event.clientY - wrapperRect.top + 12
this.tooltipStyle = {
top: `${top}px`,
left: `${left}px`
}
})
},
2025-08-25 11:46:03 +08:00
},
mounted() {
// 扩展点:后续可统一添加初始化逻辑(如权限控制、默认排序等)
// console.log("MyTable 初始化完成,原生实例:", this.getTableInstance());
// 几个特殊的列,需要特殊处理 index, selection, action
const columns = this.$slots.default.filter(item => item.tag?.includes('ElTableColumn') && item.componentInstance && item.componentInstance.columnConfig && item.componentOptions && item.componentOptions.propsData).map(item => ({
...item.componentInstance.columnConfig,
...item.componentOptions.propsData
}))
this.columns = columns
2025-08-25 11:46:03 +08:00
}
};
2025-08-25 11:46:03 +08:00
</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 {
2025-08-25 11:46:03 +08:00
width: 100%;
}
</style>