feat(KLPTable): 添加表格浮层功能以显示详细信息
实现表格行悬浮显示详细信息的浮层功能,支持自定义浮层列配置 重构KLPTable组件结构,将浮层逻辑独立为子组件 在用户管理和钢卷管理页面应用新浮层功能
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<transition name="el-fade-in-linear">
|
||||
<div v-if="tooltipVisible && data" class="row-tooltip" :style="tooltipStyle" ref="rowTooltip">
|
||||
<slot>
|
||||
<div class="tooltip-list">
|
||||
<div class="tooltip-item" v-for="field in visibleColumns" :key="field.prop">
|
||||
<span class="label">{{ field.label }}:</span>
|
||||
<span class="value">{{ formatTooltipValue(data, field) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "KLPTableFloatLayer",
|
||||
props: {
|
||||
columns: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
tooltipVisible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
tooltipStyle: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 一个列要同时有用 label 和 prop 才显示在浮层中, 并且prop有值时才显示
|
||||
visibleColumns() {
|
||||
return this.columns.filter(field => field.label && field.prop && this.data[field.prop])
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatTooltipValue(row, field) {
|
||||
// 辅助函数:递归/迭代获取多层嵌套的属性值
|
||||
const getNestedValue = (obj, path) => {
|
||||
if (!obj || !path) return undefined;
|
||||
// 将路径拆分为数组(支持 'dept.deptName' 或 ['dept', 'deptName'] 格式)
|
||||
const pathArr = Array.isArray(path) ? path : path.split('.');
|
||||
// 逐层取值,遇到null/undefined直接返回
|
||||
return pathArr.reduce((current, key) => {
|
||||
return current === null || current === undefined ? undefined : current[key];
|
||||
}, obj);
|
||||
};
|
||||
|
||||
// 获取多层数据的值(核心修改点)
|
||||
let value = getNestedValue(row, field.prop);
|
||||
|
||||
// 空值兜底处理(保留原有逻辑)
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-';
|
||||
}
|
||||
|
||||
// 字典匹配逻辑(保留原有逻辑)
|
||||
if (field.dict && this.dict && this.dict.type && this.dict.type[field.dict]) {
|
||||
const match = this.dict.type[field.dict].find(item => item.value === value);
|
||||
return match ? match.label : value;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.row-tooltip {
|
||||
position: absolute;
|
||||
background: #ffffff;
|
||||
border: 1px solid #dcdcdc;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
padding: 12px 14px;
|
||||
pointer-events: none;
|
||||
z-index: 5;
|
||||
max-height: 70%;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -6,88 +6,45 @@
|
||||
<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>
|
||||
<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>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 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 ColumnRender from './ColumnRender.vue';
|
||||
import Eclipse from './renderer/eclipse.vue';
|
||||
import KLPTableFloatLayer from './FloatLayer/index.vue';
|
||||
|
||||
export default {
|
||||
name: "KLPTable", // 组件名,便于调试和文档生成
|
||||
components: {
|
||||
ColumnRender,
|
||||
Eclipse
|
||||
Eclipse,
|
||||
KLPTableFloatLayer
|
||||
},
|
||||
props: {
|
||||
// 1. 扩展 props:新增原生 Table 没有的属性(如加载状态)
|
||||
@@ -104,10 +61,6 @@ export default {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
customColumns: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
selectionColumn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@@ -115,13 +68,42 @@ export default {
|
||||
indexColumn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// floatLayer:是否显示浮层
|
||||
floatLayer: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
floatLayerConfig: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
columns: [],
|
||||
title: '详细信息'
|
||||
})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableRef: "myTableRef" // 表格 ref,便于后续通过 ref 调用原生方法
|
||||
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) {
|
||||
@@ -136,7 +118,7 @@ export default {
|
||||
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 };
|
||||
|
||||
@@ -166,11 +148,48 @@ export default {
|
||||
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());
|
||||
// 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>
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<KLPTable v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
|
||||
<KLPTable v-loading="loading" :data="userList" @selection-change="handleSelectionChange" :floatLayer="true">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column label="用户编号" align="center" key="userId" prop="userId" v-if="columns[0].visible" />
|
||||
<el-table-column label="用户名称" align="center" key="userName" prop="userName" v-if="columns[1].visible" :show-overflow-tooltip="true" />
|
||||
|
||||
@@ -69,15 +69,15 @@
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="materialCoilList" @selection-change="handleSelectionChange">
|
||||
<KLPTable v-loading="loading" :data="materialCoilList" @selection-change="handleSelectionChange" :floatLayer="true" :floatLayerConfig="floatLayerConfig">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="入场钢卷号" align="center" prop="enterCoilNo" />
|
||||
<el-table-column label="当前钢卷号" align="center" prop="currentCoilNo" />
|
||||
<el-table-column label="厂家卷号" align="center" prop="supplierCoilNo" />
|
||||
<!-- <el-table-column label="厂家卷号" align="center" prop="supplierCoilNo" /> -->
|
||||
<el-table-column label="逻辑库位" align="center" prop="warehouseName" v-if="!hideWarehouseQuery" />
|
||||
<el-table-column label="实际库区" align="center" prop="actualWarehouseName" v-if="!hideWarehouseQuery" />
|
||||
<el-table-column label="物料类型" align="center" prop="materialType" />
|
||||
<el-table-column label="产品类型" align="center" prop="itemName">
|
||||
<!-- <el-table-column label="物料类型" align="center" prop="materialType" /> -->
|
||||
<el-table-column label="产品类型" align="center">
|
||||
<template slot-scope="scope">
|
||||
<ProductInfo v-if="scope.row.itemType == 'product'" :product="scope.row.product">
|
||||
<template slot-scope="{ product }">
|
||||
@@ -107,14 +107,14 @@
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="班组" align="center" prop="team" />
|
||||
<el-table-column label="毛重" align="center" prop="grossWeight" />
|
||||
<el-table-column label="净重" align="center" prop="netWeight" />
|
||||
<el-table-column v-if="querys.materialType === '成品'" label="质量状态" align="center" prop="qualityStatus" />
|
||||
<!-- <el-table-column label="班组" align="center" prop="team" /> -->
|
||||
<!-- <el-table-column label="毛重" align="center" prop="grossWeight" />
|
||||
<el-table-column label="净重" align="center" prop="netWeight" /> -->
|
||||
<!-- <el-table-column v-if="querys.materialType === '成品'" label="质量状态" align="center" prop="qualityStatus" />
|
||||
<el-table-column v-if="querys.materialType === '成品'" label="切边要求" align="center" prop="trimmingRequirement" />
|
||||
<el-table-column v-if="querys.materialType === '成品'" label="打包状态" align="center" prop="packingStatus" />
|
||||
<el-table-column v-if="querys.materialType === '成品'" label="包装要求" align="center" prop="packagingRequirement" />
|
||||
<el-table-column label="关联信息" align="center" prop="parentCoilNos" :show-overflow-tooltip="true">
|
||||
<el-table-column v-if="querys.materialType === '成品'" label="包装要求" align="center" prop="packagingRequirement" /> -->
|
||||
<el-table-column label="关联信息" align="center" :show-overflow-tooltip="true">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.parentCoilNos && scope.row.hasMergeSplit === 1 && scope.row.dataType === 1">
|
||||
<el-tag type="warning" size="mini">来自母卷:{{ scope.row.parentCoilNos }}</el-tag>
|
||||
@@ -128,7 +128,7 @@
|
||||
<span v-else>—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip/>
|
||||
<!-- <el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip/> -->
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" icon="el-icon-view" @click="handlePreviewLabel(scope.row)">
|
||||
@@ -139,7 +139,7 @@
|
||||
<el-button size="mini" type="text" icon="el-icon-search" @click="handleTrace(scope.row)">追溯</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</KLPTable>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
|
||||
@pagination="getList" />
|
||||
@@ -367,6 +367,25 @@ export default {
|
||||
visible: false,
|
||||
data: {},
|
||||
type: '2'
|
||||
},
|
||||
floatLayerConfig: {
|
||||
columns: [
|
||||
{ label: '入场钢卷号', prop: 'enterCoilNo' },
|
||||
{ label: '当前钢卷号', prop: 'currentCoilNo' },
|
||||
{ label: '厂家卷号', prop: 'supplierCoilNo' },
|
||||
{ label: '逻辑库位', prop: 'warehouseName' },
|
||||
{ label: '实际库位', prop: 'actualWarehouseName' },
|
||||
{ label: '物料类型', prop: 'materialType' },
|
||||
{ label: '班组', prop: 'team' },
|
||||
{ label: '净重', prop: 'netWeight' },
|
||||
{ label: '毛重', prop: 'grossWeight' },
|
||||
{ label: '备注', prop: 'remark' },
|
||||
{ label: '质量状态', prop: 'qualityStatus' },
|
||||
{ label: '打包状态', prop: 'packingStatus' },
|
||||
{ label: '切边要求', prop: 'edgeRequirement' },
|
||||
{ label: '包装要求', prop: 'packagingRequirement' }
|
||||
],
|
||||
title: '详细信息'
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user