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-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">
|
|
|
|
|
|
<!-- :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">
|
|
|
|
|
|
<el-table-column v-if="column && column.visible" :label="column.label" :key="key" :prop="key" :width="column.width" :min-width="column.minWidth || '100px'" align="left">
|
|
|
|
|
|
<!-- 插槽 自定义列表表头数据格式 -->
|
|
|
|
|
|
<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>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 单元格内容:自定义tooltip支持换行 -->
|
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
|
<!-- 1. 有自定义插槽时,插槽内容仍用原逻辑,但包裹tooltip -->
|
|
|
|
|
|
<template v-if="column && column.slotName">
|
|
|
|
|
|
<el-tooltip
|
|
|
|
|
|
:disabled="!isCellOverflow(scope.row[key], `cell-${key}-${scope.row.id}`)"
|
|
|
|
|
|
placement="top"
|
|
|
|
|
|
effect="dark">
|
|
|
|
|
|
<div slot="content" v-html="formatCellContent(scope.row[key])"></div>
|
|
|
|
|
|
<div :ref="`cell-${key}-${scope.row.id}`" class="cell-content">
|
|
|
|
|
|
<slot
|
|
|
|
|
|
:name="column.slotName"
|
|
|
|
|
|
:valueKey="key"
|
|
|
|
|
|
:row="scope.row"
|
|
|
|
|
|
:rowIndex="scope.$index"
|
|
|
|
|
|
:column="column"
|
|
|
|
|
|
:scope="scope"
|
|
|
|
|
|
></slot>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-tooltip>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<!-- 2. 无自定义插槽时,直接渲染内容+tooltip -->
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
<el-tooltip
|
|
|
|
|
|
:disabled="!isCellOverflow(scope.row[key], `cell-${key}-${scope.row.id}`)"
|
|
|
|
|
|
placement="top"
|
|
|
|
|
|
effect="dark">
|
|
|
|
|
|
<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>
|
|
|
|
|
|
</template>
|
2025-11-11 18:44:01 +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 #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>-->
|
2025-11-11 14:06:13 +08:00
|
|
|
|
</el-table-column>
|
2025-11-11 18:44:01 +08:00
|
|
|
|
</template>
|
2025-11-11 14:06:13 +08:00
|
|
|
|
<!-- 表格行内按钮 -->
|
|
|
|
|
|
<el-table-column v-if="config && config.tableButton && config.tableButton.line" :width="config.tableButton.line.length * 70 + `px`" 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-menu>
|
|
|
|
|
|
</el-dropdown>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
<!-- <el-tooltip v-if="item && item.content" :content="item.content" placement="top">-->
|
|
|
|
|
|
<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>
|
|
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
created() {
|
|
|
|
|
|
if (!(this.config && this.config.colTopHiddenIcon)) {
|
|
|
|
|
|
this.$modal.loading();
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
// 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参数
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
// 所有方法都抛出到父组件里去
|
|
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
renderList() {
|
|
|
|
|
|
this.$emit("fnRenderList");
|
|
|
|
|
|
},
|
|
|
|
|
|
// columns改变时触发,重新计算样式并渲染列表(解决:新增列,多选框和操作按钮错位)
|
|
|
|
|
|
columnsChange(val, col) {
|
|
|
|
|
|
if (col) {
|
|
|
|
|
|
this.$modal.closeLoading();
|
|
|
|
|
|
}
|
|
|
|
|
|
this.tableKey++;
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</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; /* 调整行高,提升可读性 */
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|