基于vxe-table实现翻转表格、固定列、展开收缩行、行列样式定制、合并行和列
起因
前阵子处理了个需求,做出来的效果如下图:
需求分析
我们的表格都是基于vxe-table
实现,整理了下一共需要处理以下几点:
table
数据行列倒置:按照一般逻辑,接口返回的数据是以时间为统计维度,但是页面的展示需要以类别为统计维度,所以需要用到行列倒置。- 行列合并:如上图,<账本1>、<账本2>做了行合并,第一、五行中的类型<支出>、<收入>以及最后一行的<合计>做了列合并
- 展开收缩行
- 特殊行、列样式定义:如合计行,类型列 需要定制样式
fixed
固定列
需求处理
按照难易程度,先把问题具体化,然后按顺序依次解决
table
数据行列倒置
共需要完成以下:
-
定义原始的表头数据
data() { return { // 未进行翻转时的原始表头数据 originalTitle:[ {field: 'outAll', title:'支出', sub: '支出'}, {field: 'transferOut', title:'支出', sub: ' 其中:转账'}, {field: 'redbagOut', title:'支出', sub: '其中:红包'}, {field: 'shopping', title:'支出', sub: '其中:购物'}, {field: 'inAll', title:'收入', sub: '收入'}, {field: 'wages', title:'收入', sub: ' 其中:工资'}, {field: 'transferIn', title:'收入', sub: ' 其中:转账'}, {field: 'redbagIn', title:'收入', sub: ' 其中:红包'}, {field: 'all', title:'合计', sub: '合计'}, ], } }
-
根据表头数据以及接口返回的原始数据,计算拿到页面要展示的表格渲染参数以及表体数据
表体数据:遍历原始表头数据,将
title
放在col0
列,sub
放在col1
列,之后遍历接口拿到的数据,按顺序将field
字段中的数据存储到对象中 表格渲染参数:前两列固定写死col0
、col1
,剩下的遍历原始数据生成// 表格翻转 reverseTable(table) { // 构建初始化表格数据 // 表体参数 const buildData = this.originalTitle.map(col => { const item = {col0: col.title, col1: col.sub} table.forEach((row, index) => { item[`col${index+2}`] = row[col.field] }) return item }) // 表格渲染参数 const buildColumns = [{ field: 'col0', width: 80, headerAlign: 'center', title: '' }, { field: 'col1', width: 120, headerAlign: 'center', title: '类型', }] table.forEach((item,index) => { buildColumns.push({ field: `col${index+2}`, minWidth: 180, align: 'right', title: item.title }) }) this.tableData = buildData; this.dataColumn = buildColumns; },
-
template
中使用高级表格,渲染看看<vxe-grid border show-overflow resizable max-height="670" :show-header="true" :columns="dataColumn" :data="tableData" :auto-resize="true" :sync-resize="true" ></vxe-grid>
行列合并
vxe-table
行列合并的实现是通过:span-method="rowSpanMethod"
在rowSpanMethod
中计算行列数
- 只对第一、二列进行计算
- 列合并的处理:判断第一列和第二列的值是否相同,若相同,其中一个列数设为2,另一个列数设为0
- 行合并的处理:判断当前行和下一行,若值相同,行数+1(该循环跑至下一行的值与当前行值不同,结束循环)
- 在
template
中增加配置:span-method="rowSpanMethod"
代码如下:
// 计算合并行列
rowSpanMethod({row, _rowIndex, column, visibleData}) {
// 合并行和列 范围
const fields = ['col0', 'col1'];
const cellValue = row[column.property];
if(cellValue && fields.includes(column.property)) {
// 处理列合并
if(column.property === 'col0' && row['col1'] === cellValue) {
return { rowspan:0, colspan:0 }
} else if(column.property === 'col1' && row['col0'] === cellValue) {
return { rowspan:1, colspan:2 }
}
// 处理行合并
let prevRow = visibleData[_rowIndex - 1];
let nextRow = visibleData[_rowIndex + 1];
if(prevRow && prevRow[column.property] === cellValue) {
return { rowspan:1, colspan:1 }
} else {
let countRow = 1; // 用于计算下一行
while(nextRow && nextRow[column.property] === cellValue) {
nextRow = visibleData[++countRow + _rowIndex]
}
return {rowspan:countRow, colspan:1}
}
}
},
展开收缩行
vxe-table
的展开收缩核心是通过tree-config
来配置
- 数据处理:将要展开收缩的数据放到父级的
children
中,这里的children
命名是可配置,在原先的数据处理中,追加以下逻辑:// 处理收缩展开行 buildData[0].children = buildData.slice(1, 4) buildData.splice(1, 3); buildData[1].children = buildData.slice(2, 5) buildData.splice(2, 3);
template
中增加配置:tree-config="{children:'children', toggleMethod: toggleMethod, indent: 0}"
- 增加了展开收缩后,行列合并也需要对应处理下
- 手动给展开行打上标记:展开时
expaned: true/false
// 手动给展开行打标记 最后要有return toggleMethod(e) { e.row.expanded = e.expanded; return true; },
- 行列合并时,兼容展开收缩功能。
- 列合并不影响
- 行合并处理:基于原先行合并的逻辑,通过
debugger
了解到:- 展开行的
_rowIndex
都为-1 - 通过
expaned
来判断是否为展开行 - 若为展开行,通过
children.length
来追加跨行数
- 展开行的
// 处理行合并 let prevRow = visibleData[_rowIndex - 1]; let nextRow = visibleData[_rowIndex + 1]; // 处理展开行,展开的子行 _rowIndex 都为-1 if(_rowIndex === -1) { if(column.property === 'col0') return { rowspan:0, colspan:0 }; else return { rowspan:1, colspan:1 } } else if(prevRow && prevRow[column.property] === cellValue) { return { rowspan:1, colspan:1 } } else { let countRow = 1; // 用于计算下一行 let rowspan = 1; // 用于计算实际跨行数目 if(row.expanded && row.children?.length && column.property === 'col0') { rowspan += row.children.length } while(nextRow && nextRow[column.property] === cellValue) { if(nextRow.expanded && nextRow.children?.length && nextRow.property === 'col0') { rowspan += nextRow.children.length } nextRow = visibleData[++countRow + _rowIndex] rowspan++ } return {rowspan:rowspan, colspan:1} }
- 手动给展开行打上标记:展开时
特殊行、列样式定义
行列样式定义的,就很简单了,通过:cell-style="cellStyle"
配置即可,cellStyle
方法如下:
// 计算行列样式
cellStyle({row, column}) {
// 合计行 样式调整
if(row.col1 === '合计') {
return {
backgroundColor: '#ffffcc',
color: '#606266',
fontWeight: 'bold'
}
}
// 固定展示的两列 样式定制
if(['col0', 'col1'].includes(column.property)) {
return {
backgroundColor:'#f8f8f9',
color:'#606266'
}
}
}
fixed
固定列
最后一个,在表格渲染参数中,对应列增加fixed: left
即可
最后,附上以上功能的核心代码,感兴趣的话可以理解下逻辑,copy下来改改就能跑通啦
核心代码如下:
<vxe-grid
border
show-overflow
resizable
max-height="670"
:tree-config="{children:'children', toggleMethod: toggleMethod, indent: 0}"
:show-header="true"
:span-method="rowSpanMethod"
:columns="dataColumn"
:data="tableData"
:auto-resize="true"
:sync-resize="true"
:cell-style="cellStyle"
></vxe-grid>
data() {
return {
// 计算后的表头
dataColumn:[],
// 计算后的表格数据
tableData: [],
// 未进行翻转时的原始表头数据
originalTitle:[
{field: 'outAll', title:'支出', sub: '支出'},
{field: 'transferOut', title:'支出', sub: ' 其中:转账'},
{field: 'redbagOut', title:'支出', sub: '其中:红包'},
{field: 'shopping', title:'支出', sub: '其中:购物'},
{field: 'inAll', title:'收入', sub: '收入'},
{field: 'wages', title:'收入', sub: ' 其中:工资'},
{field: 'transferIn', title:'收入', sub: ' 其中:转账'},
{field: 'redbagIn', title:'收入', sub: ' 其中:红包'},
{field: 'all', title:'合计', sub: '合计'},
],
}
},
methods: {
// 列表查询
async getList() {
let { data } = await handleGetList();
this.reverseTable(data.list)
},
// 表格翻转
reverseTable(table) {
// 构建初始化表格数据
const buildData = this.originalTitle.map(col => {
const item = {col0: col.title, col1: col.sub}
table.forEach((row, index) => {
item[`col${index+2}`] = row[col.field]
})
return item
})
// 处理收缩展开行
buildData[0].children = buildData.slice(1, 4)
buildData.splice(1, 3);
buildData[1].children = buildData.slice(2, 5)
buildData.splice(2, 3);
const buildColumns = [{
field: 'col0',
fixed: 'left', // 定义固定列
width: 80,
headerAlign: 'center',
title: ''
}, {
field: 'col1',
fixed: 'left', // 定义固定列
width: 120,
headerAlign: 'center',
title: '类型',
treeNode: true // 定义展开收缩行
}]
table.forEach((item,index) => {
buildColumns.push({
field: `col${index+2}`,
minWidth: 180,
align: 'right',
title: item.title,
formatter:this.formatterMoney
})
})
this.tableData = buildData;
this.dataColumn = buildColumns;
},
// 手动给展开行打标记 最后要有return
toggleMethod(e) {
e.row.expanded = e.expanded;
return true;
},
// 计算合并行列
rowSpanMethod({row, _rowIndex, column, visibleData}) {
// 合并行和列 范围
const fields = ['col0', 'col1'];
const cellValue = row[column.property];
if(cellValue && fields.includes(column.property)) {
// 处理列合并
if(column.property === 'col0' && row['col1'] === cellValue) {
return { rowspan:0, colspan:0 }
} else if(column.property === 'col1' && row['col0'] === cellValue) {
return { rowspan:1, colspan:2 }
}
// 处理行合并
let prevRow = visibleData[_rowIndex - 1];
let nextRow = visibleData[_rowIndex + 1];
// 处理展开行,展开的子行 _rowIndex 都为-1
if(_rowIndex === -1) {
if(column.property === 'col0') return { rowspan:0, colspan:0 };
else return { rowspan:1, colspan:1 }
} else if(prevRow && prevRow[column.property] === cellValue) {
return { rowspan:1, colspan:1 }
} else {
let countRow = 1; // 用于计算下一行
let rowspan = 1; // 用于计算实际跨行数目
if(row.expanded && row.children?.length && column.property === 'col0') {
rowspan += row.children.length
}
while(nextRow && nextRow[column.property] === cellValue) {
if(nextRow.expanded && nextRow.children?.length && nextRow.property === 'col0') {
rowspan += nextRow.children.length
}
nextRow = visibleData[++countRow + _rowIndex]
rowspan++
}
return {rowspan:rowspan, colspan:1}
}
}
},
// 计算行列样式
cellStyle({row, column}) {
// 合计行 样式调整
if(row.col1 === '合计') {
return {
backgroundColor: '#ffffcc',
color: '#606266',
fontWeight: 'bold'
}
}
// 固定展示的两列 样式定制
if(['col0', 'col1'].includes(column.property)) {
return {
backgroundColor:'#f8f8f9',
color:'#606266'
}
}
}
}
转载自:https://juejin.cn/post/7215851270342131772