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

517 lines
22 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 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>
</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">
<!-- :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" :render-header="renderHeader" :width="column.width" :min-width="column.minWidth || '100px'" align="left">
<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" :render-header="renderHeader" :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 #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">
<template #default="scope">
<template v-for="item of config.tableButton.line">
<template v-if="item && item.more && item.more.length > 0">
<el-dropdown size="mini" trigger="click" @command="(command) => handleClick({fnCode: command}, scope.row)">
<el-button size="mini" type="text" 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-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 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)" v-if="checkPermi([item.hasPermi])">{{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)) {
// 12是图标的宽度
const btnWidth = this.calculateButtonWidth(config) + 14;
currentLineWidth += btnWidth;
visibleButtonCount++;
}
});
// 加上按钮间距(假设每个按钮左右有5px间距)
if (visibleButtonCount > 0) {
currentLineWidth += (visibleButtonCount - 1) * 10;
}
// 更新最大宽度
if (currentLineWidth > maxTotalWidth) {
maxTotalWidth = currentLineWidth;
}
});
if (maxTotalWidth === 0) {
maxTotalWidth = 20;
}
// 加上单元格内边距
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.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);
}
}
},
// 导出弹窗 默认选中项
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 = 1;
} else if (order === 'descending') {
sortType = 2;
}
this.$emit("fnClick", {fnCode: 'sortTable'}, {sortType: sortType});
},
// 排序图标替换
renderHeader(h, { column, $index }) {
if (column && column.sortable) {
if (column.order === 'ascending') {
// 升序图标
return (
<div style="display: flex; align-items: center;">
<span>{column.label}</span>
<i class="el-icon-sort-down" style="margin-left: 2px;font-weight: 700;color: #c0c4cc;"></i>
<i class="el-icon-sort-up" style="color: #409EFF; margin-left: -8px;font-weight: 700;"></i>
</div>
);
} else if (column.order === 'descending') {
// 降序图标
return (
<div style="display: flex; align-items: center;">
<span>{column.label}</span>
<i class="el-icon-sort-down" style="color: #409EFF; margin-left: 2px;font-weight: 700;"></i>
<i class="el-icon-sort-up" style="margin-left: -8px;font-weight: 700;color: #c0c4cc;"></i>
</div>
);
} else {
// 默认状态(无序)
return (
<div style="display: flex; align-items: center;">
<span>{column.label}</span>
<i class="el-icon-sort" style="color: #C0C4CC; margin-left: 4px;font-weight: 700;"></i>
</div>
);
}
}
return h('div', [
h('span', column.label), // 标题文本
// h('i', { class: iconClass }) // 排序图标
]);
},
}
}
</script>
<style scoped>
/* 单元格内容样式:强制单行显示,溢出截断 */
.cell-content {
width: 100%;
white-space: nowrap; /* 关键:确保内容单行显示 */
overflow: hidden;
text-overflow: ellipsis; /* 溢出显示... */
word-break: keep-all; /* 避免英文单词截断 */
}
/* 自定义tooltip样式:支持换行+限制宽度 */
::v-deep .el-tooltip__popper {
max-width: 400px; /* 限制tooltip宽度,避免过宽 */
white-space: normal; /* 允许自动换行 */
line-height: 1.6; /* 调整行高,提升可读性 */
}
::v-deep .caret-wrapper {
display: none !important;
}
</style>
<style>
.my-custom-tooltip {
box-sizing: border-box !important;
max-height: 300px !important; /* 设置最大高度 */
overflow-y: auto !important; /* 内容超出时显示垂直滚动条 */
line-height: 1.5 !important; /* 设置行高,提升可读性 */
padding: 8px 12px !important; /* 设置内边距 */
}
</style>