Files
profitManage1.2/src/components/table/index.vue
T
康冉冉 618b9138ef 服务器管理模块新增批量分组功能;为列表查询新增分组筛选条件,支持按分组过滤数据
1、解决服务器管理模块中,下架设备标签页下列表按钮状态未正确缓存的问题
2、服务器管理模块新增批量分组功能
3、为列表查询新增分组筛选条件,支持按分组过滤数据
2026-04-02 17:55:56 +08:00

584 lines
26 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="w100">
<!-- 表格头部按钮 -->
<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 style="padding: 7px 12px;" :ref="`${item.content}`" :type="item.type" plain size="mini" :icon="item.icon" @click="handleClick(item,{})" v-if="checkPermi([item.hasPermi])">{{item.content}}</el-button>
</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>
<!-- 表格数据 -->
<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">
<!-- :selectable="() => config && config.selectable ? false : true" -->
<el-table-column v-if="!(config && config.colHiddenCheck)" fixed="left" type="selection" width="40" :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-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">
<!-- 插槽 自定义列表表头数据格式 -->
<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-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>
</template>
</el-table-column>
</template>
</template>
<!-- 表格行内按钮 -->
<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">
<template #default="scope">
<template v-for="item of config.tableButton.line">
<template v-if="item && item.more && item.more.length > 0 && checkPermi(item.hasPermi)">
<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>
<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>-->
<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>
</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 :ref="`${item.content}_${scope.row.id}`" 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>-->
<!-- </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 {
name: 'table-list',
props: {
modelIdent: {
type: String,
default: ''
},
tableList: {
type: Array,
default: () => []
},
columns: {
type: Object,
default: () => {}
},
queryParams: {
type: Object,
default: () => ({
total: 0,
pageNum: 1,
pageSize: 10,
layout: 'total, sizes, prev, pager, next, jumper'
})
},
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
}
},
created() {
if (!(this.config && this.config.colTopHiddenIcon)) {
// this.$modal.loading();
}
},
methods: {
checkPermi,
// 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参数
});
},
// 计算操作列宽度
calculateOperationColumnWidth(buttonConfigs, rowData) {
let maxTotalWidth = 0;
// 遍历每一行数据
rowData.forEach(row => {
let currentLineWidth = 0;
let visibleButtonCount = 0;
// 计算当前行所有可见按钮的总宽度
buttonConfigs.forEach(config => {
// 判断按钮是否显示
if (this.isButtonVisible(config, row)) {
let iconWid = 0;
if (config.icon || config.more) {
iconWid = 14;
}
// iconWid:是图标的宽度 8是按钮padding宽度
const btnWidth = this.calculateButtonWidth(config) + iconWid + 8;
currentLineWidth += btnWidth;
visibleButtonCount++;
}
});
// 加上按钮间距(假设每个按钮左右有5px间距)
if (visibleButtonCount > 0) {
currentLineWidth += (visibleButtonCount - 1) * 5;
}
// 更新最大宽度
if (currentLineWidth > maxTotalWidth) {
maxTotalWidth = currentLineWidth;
}
});
if (maxTotalWidth === 0) {
maxTotalWidth = 20;
}
// 加上单元格内边距
return Math.max(maxTotalWidth + 30); // 最小宽度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;
}
},
// 所有方法都抛出到父组件里去
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);
}
// 列表行内按钮 点击按钮后,通过设置失去焦点使其按钮恢复点击前状态
if (row && this.$refs && this.$refs[result.content +'_'+row.id]) {
if (Array.isArray(this.$refs[result.content +'_'+row.id])) {
this.$refs[result.content +'_'+row.id].forEach(refItem => {
refItem.$el.blur();
});
} else {
this.$refs[result.content +'_'+row.id].$el.blur();
}
} else if (this.$refs && this.$refs[result.content]) {
// 列表顶部的按钮
if (Array.isArray(this.$refs[result.content])) {
this.$refs[result.content].forEach(refItem => {
refItem.$el.blur();
});
} else {
this.$refs[result.content].$el.blur();
}
}
}
},
// 导出弹窗 默认选中项
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();
}
}
},
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 = this.columns[prop]['ascending'];
} else if (order === 'descending') {
sortType = this.columns[prop]['descending'];
}
this.$emit("fnClick", {fnCode: 'sortTable'}, {sortType: sortType});
},
// 高亮展示列
tableRowClassName({row, rowIndex}) {
if (row.alarmFlag) {
return 'tabColBgc'
}
},
}
}
</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;
}
/* 自定义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;
}
/* 高亮闪烁 */
::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;
}
::v-deep .el-table-column--selection .cell {
padding-left: 2px!important;
padding-right: 2px!important;
}
::v-deep .el-button + .el-button {
margin-left: 5px!important;
}
</style>
<style>
.my-custom-tooltip > div {
box-sizing: border-box !important;
max-height: 300px !important; /* 设置最大高度 */
overflow-y: auto !important; /* 内容超出时显示垂直滚动条 */
line-height: 1.5 !important; /* 设置行高,提升可读性 */
/*padding: 8px 12px !important; !* 设置内边距 *!*/
}
</style>