Files
profitManage1.2/src/components/table/index.vue
T

554 lines
25 KiB
Vue
Raw Normal View History

2025-11-11 14:06:13 +08:00
<template>
<div class="w100 h100">
<!-- 表格头部按钮 -->
<el-row :gutter="10" style="margin: 0 0 8px 0;">
<template v-if="config && config.tableButton && config.tableButton.top">
<el-col :span="1.5" v-for="item of config.tableButton.top">
<!-- <el-button :type="item.type" plain size="mini" :icon="item.icon" @click="handleClick(item,{})" :hasPermi="[item.hasPermi]">{{item.content}}</el-button>-->
<el-button :type="item.type" plain size="mini" :icon="item.icon" @click="handleClick(item,{})" v-if="checkPermi([item.hasPermi])">{{item.content}}</el-button>
2025-11-11 14:06:13 +08:00
</el-col>
</template>
<right-toolbar v-if="!(config && config.colTopHiddenIcon)" :modelIdent="this.modelIdent" :showSearch.sync="showSearch" @queryTable="renderList" @columnsChange="columnsChange" :columns="columns"></right-toolbar>
</el-row>
<!-- 表格数据 -->
2025-12-18 19:00:38 +08:00
<el-table v-loading="loading" :data="tableList" :ref="config && config.tableKey ? `tableRef_${config.tableKey}` : `selChangeList`" :key="tableKey" :highlight-current-row="config && config.currentSel || false" @current-change="(val) => handleClick({fnCode: 'currentData'},val)" highlight-selection-row @select-all="handleSelectAll" @select="handleSelectionChange" @sort-change="handleSortChange" :row-class-name="tableRowClassName">
2025-11-11 14:06:13 +08:00
<!-- :selectable="() => config && config.selectable ? false : true" -->
<el-table-column v-if="!(config && config.colHiddenCheck)" fixed="left" type="selection" width="55" :selectable="() => config && config.selectable ? false : true" align="center" />
<!-- 展开列内容 -->
<el-table-column v-if="config && config.expand" type="expand">
<template #default="props">
<slot
name="tableExpand"
:row="props.row"
:rowIndex="props.$index"
:props="props"
></slot>
</template>
</el-table-column>
<template v-for="(column, key, index) of columns">
<!-- customTooltip:true 自定义tooltip 展示 -->
<template v-if="column.customTooltip">
<el-table-column v-if="column && column.visible" :label="column.label" :key="key" :prop="key" :sortable="column.sortable" :width="column.width" :min-width="column.minWidth || '100px'" align="left">
<template #header="{ column }" v-if="column.sortable">
<div style="display: flex; align-items: center;">
<span>{{ column.label }}</span>
<template v-if="column.order === 'ascending'">
<i class="el-icon-sort-down" style="margin-left: 2px;margin-top: 2px;font-weight: 700;color: #c0c4cc;"></i>
<i class="el-icon-sort-up" style="color: #409EFF; margin-left: -8px;margin-top: 2px;font-weight: 700;"></i>
</template>
<template v-else-if="column.order === 'descending'">
<i class="el-icon-sort-down" style="color: #409EFF;margin-left: 2px;margin-top: 2px;font-weight: 700;"></i>
<i class="el-icon-sort-up" style="margin-left: -8px;margin-top: 2px;font-weight: 700;color: #c0c4cc;"></i>
</template>
<i v-else class="el-icon-sort" style="color: #C0C4CC;margin-left: 4px;margin-top: 2px;font-weight: 700;"></i>
</div>
</template>
<template #default="scope">
<el-tooltip
:disabled="!isCellOverflow(scope.row[key], `cell-${key}-${scope.row.id}`)"
placement="top"
effect="dark"
popper-class="my-custom-tooltip">
<div slot="content" v-html="formatCellContent(scope.row[key])"></div>
<div :ref="`cell-${key}-${scope.row.id}`" class="cell-content">
{{ scope.row[key] }}
</div>
</el-tooltip>
</template>
</el-table-column>
</template>
<!-- 原tooltip展示逻辑 -->
<template v-else>
<el-table-column v-if="column && column.visible" :label="column.label" :key="key" :prop="key" :sortable="column.sortable" :width="column.width" :min-width="column.minWidth || '100px'" align="left" :show-overflow-tooltip="true">
2025-11-11 18:58:05 +08:00
<!-- 插槽 自定义列表表头数据格式 -->
2025-11-11 14:06:13 +08:00
<template #header v-if="column && column.slotHeaderName">
<span>{{column.label}}</span>
<el-tooltip trigger="click" effect="dark" placement="top">
<template #content>{{column.slotHeaderName}}</template>
<i class="el-icon-question"></i>
</el-tooltip>
</template>
<!-- 插槽 自定义列表表头的排序图标按钮 -->
<template #header="{ column }" v-if="column.sortable">
<div style="display: flex; align-items: center;">
<span>{{ column.label }}</span>
<template v-if="column.order === 'ascending'">
<i class="el-icon-sort-down" style="margin-left: 2px;margin-top: 2px;font-weight: 700;color: #c0c4cc;"></i>
<i class="el-icon-sort-up" style="color: #409EFF;margin-left: -8px;margin-top: 2px;font-weight: 700;"></i>
</template>
<template v-else-if="column.order === 'descending'">
<i class="el-icon-sort-down" style="color: #409EFF;margin-left: 2px;margin-top: 2px;font-weight: 700;"></i>
<i class="el-icon-sort-up" style="margin-left: -8px;margin-top: 2px;font-weight: 700;color: #c0c4cc;"></i>
</template>
<i v-else class="el-icon-sort" style="color: #C0C4CC;margin-left: 4px;margin-top: 2px;font-weight: 700;"></i>
</div>
</template>
<!-- 插槽 自定义列表内数据格式 -->
<template #default="scope" v-if="column && column.slotName">
<!-- 传递行数据行索引等信息到父组件的插槽 -->
<slot
:name="column.slotName"
:valueKey="key"
:row="scope.row"
:rowIndex="scope.$index"
:column="column"
:scope="scope"
></slot>
2025-11-11 14:06:13 +08:00
</template>
</el-table-column>
</template>
2025-11-11 18:58:05 +08:00
</template>
2025-11-11 14:06:13 +08:00
<!-- 表格行内按钮 -->
<el-table-column v-if="config && config.tableButton && config.tableButton.line" :width="calculateOperationColumnWidth(config.tableButton.line, tableList)" label="操作" fixed="right" align="center" class-name="small-padding fixed-width handleContSty">
2025-11-11 14:06:13 +08:00
<template #default="scope">
<template v-for="item of config.tableButton.line">
<template v-if="item && item.more && item.more.length > 0 && checkPermi(item.hasPermi)">
2025-11-11 14:06:13 +08:00
<el-dropdown size="mini" trigger="click" @command="(command) => handleClick({fnCode: command}, scope.row)">
<el-button size="mini" type="primary" plain icon="el-icon-d-arrow-right"><span style="margin-left: -5px;">{{item && item.title ? item.title : '更多'}}</span></el-button>
2025-11-11 14:06:13 +08:00
<el-dropdown-menu slot="dropdown">
<!-- <el-dropdown-item v-for="dropBut of item.more" :command="dropBut.fnCode" :icon="dropBut.icon" :hasPermi="[dropBut.hasPermi]">{{dropBut.content}}</el-dropdown-item>-->
2025-12-19 18:56:21 +08:00
<el-dropdown-item v-for="dropBut of item.more" :command="dropBut.fnCode" :icon="dropBut.icon" v-show="dropBut && dropBut.showName ? dropBut.showVal.includes(scope.row[dropBut.showName]) : true" v-if="checkPermi([dropBut.hasPermi])">{{dropBut.content}}</el-dropdown-item>
2025-11-11 14:06:13 +08:00
</el-dropdown-menu>
</el-dropdown>
</template>
<template v-else>
<!-- <el-tooltip v-if="item && item.content" :content="item.content" placement="top">-->
<template v-if="item && item.content">
<el-button size="mini" plain link :type="item.type || 'primary'" :icon="item.icon" v-show="item && item.showName ? item.showVal.includes(scope.row[item.showName]) : true" @click="handleClick(item, scope.row)" v-if="item && item.hasPermi ? checkPermi([item.hasPermi]) : true">{{item.content}}</el-button>
</template>
<!-- <el-button v-if="item && item.content" size="mini" link :type="item.type" :icon="item.icon" v-show="item && item.showName ? item.showVal.includes(scope.row[item.showName]) : true" @click="handleClick(item, scope.row)" :hasPermi="[item.hasPermi]">{{item.content}}</el-button>-->
2025-11-11 14:06:13 +08:00
<!-- </el-tooltip>-->
</template>
</template>
</template>
</el-table-column>
</el-table>
<pagination v-show="queryParams.total > 0" :layout="queryParams && queryParams.layout" :total="queryParams.total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="renderList" />
<el-dialog title="导出项" :visible.sync="dialogOpen" width="900px" append-to-body>
<el-checkbox-group v-model="checkColumns">
<template v-for="(item, key, index) of columns">
<el-checkbox v-if="key !== 'id'" :label="key" :key="key" style="width: 25%;margin-right: 0px;">{{item.label}}</el-checkbox>
</template>
</el-checkbox-group>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="diaColSubmit"> </el-button>
<el-button @click="diaColCancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { checkPermi } from "@/utils/permission";
export default {
2025-11-11 14:06:13 +08:00
name: 'table-list',
props: {
modelIdent: {
type: String,
default: ''
},
tableList: {
type: Array,
default: () => []
},
columns: {
type: Object,
default: () => {}
},
queryParams: {
type: Object,
2025-12-02 18:41:22 +08:00
default: () => ({
2025-11-11 14:06:13 +08:00
total: 0,
pageNum: 1,
pageSize: 10,
layout: 'total, sizes, prev, pager, next, jumper'
2025-12-02 18:41:22 +08:00
})
2025-11-11 14:06:13 +08:00
},
config: {
type: Object,
default: () => ({
colHiddenCheck: false,
colTopHiddenIcon: false,
currentSel: false,
})
}
},
data() {
return {
tableKey: 0,
loading: false,
showSearch: true,
dialogOpen: false,
ids: [],
selectList: [],
isProcessing: false, // 防止事件循环的标志位
checkColumns: []
}
},
// 监听showSearch的变化,并且把变化后的值传给父组件
watch: {
showSearch: {
handler(val) {
this.$emit('value-change', val)
}
},
queryParams: {
handler(val) {
// 重置后,重新渲染组件,起到排序图标重置功能
if (val && val.resetVal) {
this.tableKey++;
}
},
deep: true,
immediate: true
2025-11-11 14:06:13 +08:00
}
},
created() {
if (!(this.config && this.config.colTopHiddenIcon)) {
// this.$modal.loading();
2025-11-11 14:06:13 +08:00
}
},
methods: {
checkPermi,
2025-11-11 14:06:13 +08:00
// 1. 格式化单元格内容:将\n替换为<br>,支持HTML渲染
formatCellContent(content) {
if (!content) return '';
// 若内容是对象/数组,先转为字符串(避免[object Object]
const strContent = typeof content === 'object' ? JSON.stringify(content) : String(content);
return strContent.replace(/\n/g, '<br/>');
},
// 2. 判断单元格内容是否溢出(决定是否显示tooltip)
isCellOverflow(content, refName) {
// 获取单元格DOM(注意:ref在v-for中是数组,需取[0]
const cellDom = this.$refs[refName]?.[0];
if (!cellDom || !content) return false;
// 内容实际宽度 > 单元格可见宽度 → 溢出
return cellDom.scrollWidth > cellDom.offsetWidth;
},
setCurrent(row) {
this.$nextTick(() => {
const targetRow = this.tableList.find(item => item.id === row.id); // 用id匹配原对象
this.$refs.selChangeList.setCurrentRow(targetRow); // 使用传入的row参数
});
},
2025-11-12 18:39:01 +08:00
// 计算操作列宽度
calculateOperationColumnWidth(buttonConfigs, rowData) {
let maxTotalWidth = 0;
// 遍历每一行数据
rowData.forEach(row => {
let currentLineWidth = 0;
let visibleButtonCount = 0;
// 计算当前行所有可见按钮的总宽度
buttonConfigs.forEach(config => {
// 判断按钮是否显示
if (this.isButtonVisible(config, row)) {
// 12是图标的宽度 8是按钮padding宽度
const btnWidth = this.calculateButtonWidth(config) + 14 + 8;
2025-11-12 18:39:01 +08:00
currentLineWidth += btnWidth;
visibleButtonCount++;
}
});
// 加上按钮间距(假设每个按钮左右有5px间距)
if (visibleButtonCount > 0) {
currentLineWidth += (visibleButtonCount - 1) * 10;
}
// 更新最大宽度
if (currentLineWidth > maxTotalWidth) {
maxTotalWidth = currentLineWidth;
}
});
if (maxTotalWidth === 0) {
maxTotalWidth = 20;
}
2025-11-12 18:39:01 +08:00
// 加上单元格内边距
return Math.max(maxTotalWidth + 40); // 最小宽度120px
},
// 计算单个按钮宽度(根据字符数)
calculateButtonWidth(buttonConfig) {
const text = buttonConfig.content || buttonConfig.title || '';
let width = 0;
// 计算文本宽度(根据中英文字符不同宽度)
for (const char of text) {
if (char >= '\u4e00' && char <= '\u9fa5') {
// 中文字符约15px
width += 12;
} else {
// 英文字符约8px
width += 4;
}
}
// 加上按钮内边距和边框(约60px基础宽度)
return Math.max(width);
},
// 判断按钮是否显示
isButtonVisible(buttonConfig, row) {
if (buttonConfig && buttonConfig.more) {
if (!checkPermi(buttonConfig.hasPermi)) {
return false;
}
if (buttonConfig.showName) {
return buttonConfig.showVal.includes(row[buttonConfig.showName]);
}
return true;
} else {
if (buttonConfig && buttonConfig.hasPermi && !checkPermi([buttonConfig.hasPermi])) {
return false;
}
if (buttonConfig.showName) {
return buttonConfig.showVal.includes(row[buttonConfig.showName]);
}
return true;
2025-11-12 18:39:01 +08:00
}
},
2025-11-11 14:06:13 +08:00
// 所有方法都抛出到父组件里去
handleClick(result, row) {
if (result && result.fnCode) {
switch (result.fnCode) {
case 'export':
this.dialogOpen = true;
this.diaColumnCheck();
break;
default:
this.$emit("fnClick", result, row, this.ids, this.selectList);
}
}
},
// 导出弹窗 默认选中项
diaColumnCheck() {
this.checkColumns = [];
Object.keys(this.columns).forEach(item => {
if (item !== 'id' && this.columns[item] && this.columns[item].visible) {
this.checkColumns.push(item);
}
});
},
// 弹窗确认
diaColSubmit(){
this.$emit("fnClick", {fnCode: 'export'}, {properties: this.checkColumns});
this.dialogOpen = false;
},
// 弹窗取消
diaColCancel() {
this.dialogOpen = false;
},
/** 多选框选中数据 */
handleSelectionChange(selection) {
if (this.config && this.config['tableKey']) {
this.selectList = [];
this.ids = [];
if (this.isProcessing) return;
this.isProcessing = true;
// 当选中行有关联值时,默认把所关联的的行选中
selection.forEach(res => {
if (res && res.relevance) {
let selArr = [res];
Object.keys(res.relevance).forEach(item => {
res.relevance[item].forEach(val => {
this.tableList.some(tabVal => {
if (tabVal[item] === val) {
selArr.push(tabVal);
return true;
}
});
});
});
this.selectList = this.selectList.concat(selArr);
this.ids = this.ids.concat(selArr.map(item => item.id));
} else {
if (!this.ids.includes(res.id)) {
this.selectList = this.selectList.concat(res);
this.ids = this.ids.concat(res.id);
}
}
});
this.selectList.forEach(val => {
this.$refs[`tableRef_${this.config.tableKey}`].toggleRowSelection(val,true);
});
this.isProcessing = false;
} else {
this.selectList = selection;
this.ids = selection.map(item => item.id);
}
},
// 全选按钮操作
handleSelectAll(tabAll){
// 资源监控策略和资源监控模块使用 selectable: 控制列是否可选,值为false则不可选--this.config.selectable为判断
if (this.config && this.config.tableKey && this.config.selectable === true) {
if (tabAll && tabAll.length > 0) {
this.defaultSelectRows([]); // 取消全选
} else {
this.selectAllRows(); // 全选
}
} else {
if (tabAll && tabAll.length > 0) {
this.selectList = tabAll;
this.ids = tabAll.map(tabId => tabId.id);
} else {
// 清空
this.selectList = [];
this.ids = [];
}
}
},
// 提供给父组件调用的方法:返回当前表格的选中数据 资源监控策略和资源监控模块使用
getSelectedData() {
return {
tableKey: this.config.tableKey, // 表格唯一标识
data: [...this.selectList] // 选中的数据
};
},
// 提供给父组件调用的方法:全选所有行 资源监控策略和资源监控模块使用
selectAllRows() {
const table = this.config && this.config.tableKey ? this.$refs[`tableRef_${this.config.tableKey}`] : this.$refs.selChangeList;
if (!table) return;
// 清空现有选中状态
this.selectList = [];
this.ids = [];
// 遍历所有行,执行选中
this.tableList.forEach(row => {
// 跳过不可选的行(根据selectable配置)
// const isSelectable = !(this.config && this.config.selectable);
// if (isSelectable) {
table.toggleRowSelection(row, true); // 选中行
this.selectList.push(row); // 同步到选中列表
this.ids.push(row.id); // 同步到选中ID列表
// }
});
},
// 提供给父组件调用的方法:默认选中值
defaultSelectRows(rows){
const tableInstance = this.config && this.config.tableKey ? this.$refs[`tableRef_${this.config.tableKey}`] : this.$refs.selChangeList;
this.selectList = [];
this.ids = [];
if (tableInstance) {
if (rows && rows.length > 0) {
rows.forEach(row => {
tableInstance.toggleRowSelection(row, true);
this.selectList.push(row);
this.ids.push(row.id);
});
} else {
tableInstance.clearSelection();
}
}
},
// 提供给父组件调用的方法:默认选中值 otherRow:新的回显方式,防止破坏原来rows判断的逻辑与回显
defaultSelectRowsAll(rows,otherRow){
const tableInstance = this.config && this.config.tableKey ? this.$refs[`tableRef_${this.config.tableKey}`] : this.$refs.selChangeList;
this.selectList = [];
this.ids = [];
if (tableInstance) {
if (otherRow && otherRow.length > 0) {
otherRow.forEach(rowVal => {
this.tableList.some(tabVal => {
if (rowVal.id === tabVal.id) {
tableInstance.toggleRowSelection(tabVal, true);
this.selectList.push(tabVal);
this.ids.push(tabVal.id);
return true;
}
});
});
} else if (rows && rows.length > 0) {
rows.forEach(row => {
tableInstance.toggleRowSelection(row, true);
this.selectList.push(row);
this.ids.push(row.id);
});
} else {
tableInstance.clearSelection();
2025-11-11 14:06:13 +08:00
}
}
},
renderList() {
this.$emit("fnRenderList");
},
// columns改变时触发,重新计算样式并渲染列表(解决:新增列,多选框和操作按钮错位)
columnsChange(val, col) {
if (col) {
this.$modal.closeLoading();
}
this.tableKey++;
},
// 排序发生变化时
handleSortChange({column, prop, order}) {
let sortType = 3;
if (order === 'ascending') {
sortType = 1;
} else if (order === 'descending') {
sortType = 2;
}
this.$emit("fnClick", {fnCode: 'sortTable'}, {sortType: sortType});
},
2025-12-18 19:00:38 +08:00
// 高亮展示列
tableRowClassName({row, rowIndex}) {
if (row.alarmFlag) {
return 'tabColBgc'
}
},
2025-11-11 14:06:13 +08:00
}
}
</script>
<style scoped>
/* 单元格内容样式:强制单行显示,溢出截断 */
.cell-content {
width: 100%;
white-space: nowrap; /* 关键:确保内容单行显示 */
overflow: hidden;
text-overflow: ellipsis; /* 溢出显示... */
word-break: keep-all; /* 避免英文单词截断 */
}
::v-deep .el-table .fixed-width .el-button--mini {
padding: 4px!important;
}
2025-11-11 14:06:13 +08:00
/* 自定义tooltip样式:支持换行+限制宽度 */
::v-deep .el-tooltip__popper {
max-width: 400px; /* 限制tooltip宽度,避免过宽 */
white-space: normal; /* 允许自动换行 */
line-height: 1.6; /* 调整行高,提升可读性 */
}
::v-deep .caret-wrapper {
display: none !important;
}
::v-deep .handleContSty .cell{
white-space: nowrap !important;
}
2025-12-18 19:00:38 +08:00
/* 高亮闪烁 */
::v-deep .tabColBgc {
/*background-color: #fa9550!important;*/
animation: flashing 1s infinite ease-in-out;
}
@keyframes flashing {
0% { background-color: #fa9550; } /* 起始颜色 */
50% { background-color: #fff; } /* 中间颜色 */
100% { background-color: #fa9550; } /* 结束颜色 */
}
::v-deep .el-table__body .tabColBgc.hover-row > td.el-table__cell,
.el-table__body .tabColBgc.hover-row.current-row > td.el-table__cell,
.el-table__body .tabColBgc.hover-row.selection-row > td.el-table__cell,
.el-table__body .tabColBgc.hover-row.el-table__row--striped > td.el-table__cell,
.el-table__body .tabColBgc.hover-row.el-table__row--striped.current-row > td.el-table__cell,
.el-table__body .tabColBgc.hover-row.el-table__row--striped.selection-row > td.el-table__cell {
background-color: #ffb98c!important;
}
2025-11-11 14:06:13 +08:00
</style>
<style>
.my-custom-tooltip {
box-sizing: border-box !important;
2025-11-21 18:38:36 +08:00
max-height: 300px !important; /* 设置最大高度 */
overflow-y: auto !important; /* 内容超出时显示垂直滚动条 */
line-height: 1.5 !important; /* 设置行高,提升可读性 */
padding: 8px 12px !important; /* 设置内边距 */
}
</style>