Files
xgy-oa/klp-ui/src/components/KLPUI/KLPTable/index.vue
砂糖 5501987fe5 feat(KLPTable): 添加表格浮层功能以显示详细信息
实现表格行悬浮显示详细信息的浮层功能,支持自定义浮层列配置
重构KLPTable组件结构,将浮层逻辑独立为子组件
在用户管理和钢卷管理页面应用新浮层功能
2025-11-29 14:10:04 +08:00

228 lines
6.9 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="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>
<!-- 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>
<!-- 浮层组件 -->
<KLPTableFloatLayer v-if="floatLayer" :columns="floatLayerColumns" :data="hoveredRow" :tooltipVisible="tooltipVisible"
:tooltipStyle="tooltipStyle" />
</div>
<!-- 扩展层可后续统一添加分页如与 MyPagination 组件联动 -->
<slot name="pagination"></slot>
</div>
</template>
<script>
import ColumnRender from './ColumnRender.vue';
import Eclipse from './renderer/eclipse.vue';
import KLPTableFloatLayer from './FloatLayer/index.vue';
export default {
name: "KLPTable", // 组件名,便于调试和文档生成
components: {
ColumnRender,
Eclipse,
KLPTableFloatLayer
},
props: {
// 1. 扩展 props新增原生 Table 没有的属性(如加载状态)
loading: {
type: Boolean,
default: false
},
loadingText: {
type: String,
default: "加载中..."
},
// 2. 兼容原生 class 用法(原生 el-table 支持 class 属性,此处显式接收避免 $attrs 冲突)
customClass: {
type: String,
default: ""
},
selectionColumn: {
type: Boolean,
default: false
},
indexColumn: {
type: Boolean,
default: false
},
// floatLayer是否显示浮层
floatLayer: {
type: Boolean,
default: false
},
floatLayerConfig: {
type: Object,
default: () => ({
columns: [],
title: '详细信息'
})
}
},
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;
}
},
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);
}
},
// 浮层相关
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`
}
})
},
},
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
}
};
</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>