现在的需求如下图, 完成的效果如下。 有这么几个关注点:
- 1、第一列显示的是当前大类, 第二列显示的是当前小类,第三列之后开始就是数据, 我们正常表格都是行数据, 这里我们使用列数据,列是动态的, 数量不一, 行是固定的。
- 2、我们可以配置每一列数据的插槽,这里面第一列的数据为基础数据,后面的数据每一列会和第一列的对比大小, 显示箭头。
- 3、el-table 组件 鼠标悬浮的时候会自动给增加一个悬浮背景色, 当我们这里用到了单元格合并以后,这个背景色就会把左边合并了的 悬浮样式和右边第一行的一起触发,这里下面代码在 css 中单独处理了,使 el-table的背景颜色不跟着改变。
那么在做这个需求的时候, 主要有几个关注点,第一个就是列合并的思路, 这里我写到业务代码里了, 没有做动态列合并, 因为这种场景不多见, 但是这个也是能够封装的, 第二个就是列的值的显示思路, 这里我们既然使用 el-table 修改, 那么肯定数据实际上还是按照行数据来的, 那数据的读取就不能够按照正常使用的思维来, 第三个是渲染时候的判定, 我们需要针对自己需要的显示进行一些动态判定。 代码如下:
- 代码当中关键的地方都写了注释, 这里最后一行 也就是 renderCompareValue 这里有一个渲染百分比的东西,这个是单独处理的,不需要关注。
- 箭头的图标可以自己找 icon 就行了。
这里是传入的参数, 实际上也就是前两列的表头的,数据长的大概就是上图的样子,后端还是按照行分类给我们的, 这里没有让后端更改,所以在代码里前端进行了改造。我们最终需要使用的数据长的样子如下图,这里面使用汉字为 key 的 就是我们的列表头,他们的值是对象, value 就是我们显示的值, effect 就是是否需要显示箭头的策略, 0 不显示, 2 上箭头, 1 下箭头这样。
basisColumns: [
{ label: '类别', prop: 'compareType', width: 140 },
{ label: '对比内容', prop: 'content', width: 220 },
]
下面是完整代码
<template>
<div>
<el-table
:data="tableData"
:span-method="objectSpanMethod"
border
stripe
class="compare-table"
style="margin-top: 20px"
:cell-style="cellStyleHandler"
:cell-class-name="cellStyleHandler2"
>
<el-table-column
v-for="item in columns"
:key="item.label"
:prop="item.prop"
:width="item?.width"
:label="item.label"
align="center"
>
<template slot-scope="scope">
<template v-if="renderOtherSlot(scope, item)">
<div>
{{ scope.row[item.prop].value ? '否' : '是'}}
</div>
</template>
<template v-else-if="renderSlot(scope) === 'RENDER'">
<div class="slot-class">
<div>
<el-tooltip effect="dark" content="1%" placement="top" :disabled="true">
<i class="el-icon--left" v-if="renderCompare(scope,2)" ><svg-icon icon-class="compare_big" /></i>
<i class="el-icon--left" v-if="renderCompare(scope,1)"><svg-icon icon-class="compare_small" /></i>
</el-tooltip>
</div>
<div>{{renderCompareValue(scope) ?? '-'}}</div>
</div>
</template>
<template v-else>
{{renderSlot(scope)}}
</template>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
props: {
columns: {
type: Array,
default: () => []
},
basisData: {
type: Array,
default: () => []
}
},
data() {
return {
arrSort: [],
tableData: [
{compareType: '第一列数据',type:'segmentNum',content:'数量1'},
{compareType: '第一列数据',type:'segmentUseLength',content:'输量2'},
{compareType: '第一列数据',type:'stationNum',content:'输量3' },
{compareType: '第一列数据',type:'loadRatio',content:'数量4'},
{compareType: '第一列数据',type:'power',content:'数量5'},
{compareType: '第一列数据',type:'stationInvestment',content:'数量6' },
{compareType: '第一列数据',type:'segmentInvestment',content:'数量7'},
{compareType: '第一列数据',type:'buildCost',content:'数量8'},
{compareType: '第一列数据',type:'operatingCost',content:'数量9'},
{compareType: '第一列数据',type:'presentCost',content:'数量10'},
{compareType: '第一列数据',type:'turnoverEnergyConsumption',content:'数量11'},
{compareType: '第二列数据',type:'loadRationRelaxMax',content:'测试1'},
{compareType: '第二列数据',type:'energyTarget',content:'测试2'},
{compareType: '第二列数据',type:'turnoverTarget',content:'测试3'},
{compareType: '第二列数据',type:'costTarget',content:'测试4'},
{compareType: '第三列数据',type:'minimumTotalTurnover',content:'测试5'},
{compareType: '第三列数据',type:'optimalLoadRatio',content:'测试6'},
{compareType: '第三列数据',type:'minimumReceptionCost',content:'测试7'},
{compareType: '第三列数据',type:'minimumEnergy',content:'测试8'},
{compareType: '第四列数据',type:'maxRunTime',content:'测试9(min)'},
{compareType: '第四列数据',type:'convergencePrecision',content:'测试10'},
{compareType: '第四列数据',type:'isWaterConservancy',content:'测试11'},
{compareType: '推荐排序',type:'sort',content:'测试12'},
]
};
},
// 数据重置
created() {
this.arrSort = []
// 改造数据源
this.basisData.forEach((item)=>{
const { modelName, constraints, executeConfig, optimizeTargetWeight, basIndicators} = item
this.arrSort.push(modelName)
const currentObj = { ...constraints, ...executeConfig, ...optimizeTargetWeight, ...basIndicators}
Object.keys(currentObj).forEach((big)=>{
// 循环,对照数据,找到 tableData 展示 数据中的对应项, 进行赋值。
const showCurrent = this.tableData.find((small)=>small.type === big)
if(showCurrent){
// 响应式复制, 给当前列 复制 value 对象
this.$set(showCurrent, modelName, { value: currentObj[big]})
}
})
})
const denominator = this.tableData.find(item=>item.type === 'presentCost')[this.arrSort[0]].value
// 第一层循环当前数据、第二层 找到当前列对应的数据, 与第一列对应的数据进行对比, 给当前列数据显示对象 复制对比结果
this.tableData.forEach((item)=>{
this.arrSort.map((it,index)=>{
const compare = this.arrSort[index]
const compareValue = item[compare]?.value || null // 当前列的值
const currentValue = item[this.arrSort[0]]?.value || null // 第一列需要对比的值
// 如果 需要对比的值为 null 或者当前列的值为 null 则不对比
// 找到第一列的 值, 需要用作分母的, 这里判定 如果分母是 0 , 则不渲染箭头
if(item.type === 'sort' && denominator === 0){
item[compare].effect = 0
}else if(currentValue === null || compareValue === null){ item[compare].effect = 0 }
else{
item[compare].effect = compareValue > currentValue ? 2 : compareValue < currentValue ? 1 : 0;
}
})
})
},
mounted() {
},
methods: {
// 渲染对比的值
renderCompareValue(data){
const { row, column:{ label } } = data
const { type } = row
const value = row[label]?.value
if(type === 'sort' && value){
const current = this.tableData.find(item=>item.type === 'presentCost')
// 计算百分比 使用当前列的值 / 第一列的值 * 100% 第一列的 label 为 this.arrSort[0] 当前 label 为 lable
// 如果 分母是 0 , 则不计算
const denominator = current[this.arrSort[0]].value // 分母
const molecule = current[label].value
if(denominator === 0) return '-'
return value + `(${(molecule / denominator * 100).toFixed(2)}%)`
}
return value ?? '-'
},
renderCompare(data,num){
// 渲染箭头方向
const { row, column:{ label } } = data
return row[label]?.effect === num
},
renderSlot(scope){
const { row, column } = scope
const { property } = column
// 排除第一列, 第二列之后的使用 箭头插槽渲染
let arr = [...this.arrSort].slice(1)
if(arr.includes(property)){
return 'RENDER'
}
// 值是对象的,标识不是类别 和 对比内容, 直接返回 value
const value = row[property]?.value
if(row.type === 'sort' && typeof row[property] === 'object') return value ? `${value}(100%)` : '-' // 渲染第一列, 经济评价指标的百分比
if(typeof row[property] === 'object') return value ?? '-'
return row[property] // 返回前两列的固定label
},
// 渲染不需要对比的值
renderOtherSlot(scope, item){
const { row, column: { property } } = scope
if(row.type === "isWaterConservancy" && property !== 'compareType' && property !== 'content') return true
return false
},
// 设置斑马线样式
cellStyleHandler2({row, column, rowIndex, columnIndex}){
if(columnIndex === 0) return 'cell-reset'
},
cellStyleHandler({row, column, rowIndex, columnIndex}){
// 设置第一列的样式
if(columnIndex === 0){
return {
backgroundColor: "#fff"
}
}
},
objectSpanMethod({ row, column, rowIndex, columnIndex }) {
// columnIndex 为当前列的索引, 我们只合并第一列, compareType 为当前列的值 rowIndex 为当前列的行数, rowspan 是我们要合并多少行, 这里拿第一个 switch 的条件距离,rowspan 11 表示合并11行, colspan 表示 显示1列。 那么我们在想要合并11行的话, 就在第 11 行 合并即可, 其他 10行的 行跨度 和 列跨度 都返回0 即可。
const { compareType, content } = row
if (columnIndex === 0) {
switch(compareType){
case "第一列数据":
// console.log('data', row, column, rowIndex, rowIndex % 11, '---',columnIndex);
if (rowIndex % 11 === 0) {
return {
rowspan: 11, // 行跨度
colspan: 1 // 列跨度
};
} else {
return {
rowspan: 0,
colspan: 0
};
}
case "第二列数据":
if (rowIndex % 11 === 0) {
return {
rowspan: 4,
colspan: 1
};
} else {
return {
rowspan: 0,
colspan: 0
};
}
case "第三列数据":
if (rowIndex % 15 === 0) {
return {
rowspan: 4,
colspan: 1
};
} else {
return {
rowspan: 0,
colspan: 0
};
}
case "第四列数据":
// console.log('data', row, column, rowIndex, rowIndex % 20, '---',columnIndex);
if (rowIndex % 19 === 0) {
return {
rowspan: 3,
colspan: 1
};
} else {
return {
rowspan: 0,
colspan: 0
};
}
default:
return {
rowspan: 1,
colspan: 1
}
}
}
}
},
};
</script>
<style lang="scss" scoped>
::v-deep
.el-table--striped
.el-table__body
tr.el-table__row--striped
td.el-table__cell {
background-color: #f6faff;
}
::v-deep .el-table--enable-row-hover .el-table__body tr:hover > td {
background-color: transparent !important;
}
::v-deep
.el-table--enable-row-hover
.el-table__body
tr.el-table__row--striped:hover
> td {
background-color: #f6faff !important;
}
.slot-class {
display: flex;
justify-content: center;
> div:first-child {
display: flex;
align-items: center;
}
}
// 修改鼠标悬浮时候,让第一列背景不跟着改变
::v-deep .el-table--enable-row-hover .el-table__body tr:hover > td.cell-reset {
background-color: transparent !important;
}
.el-icon--left {
width: 16px;
height: 16px;
}
.compare-table {
width: 100%;
overflow: auto;
}
</style>