一、合并第一列
<template>
<el-table :data="tableData" border>
<el-table-column prop="type" label="种类" />
<el-table-column prop="code" label="产品编号">
<template v-slot="scope">
{{ scope.row.code }}
</template>
</el-table-column>
<el-table-column prop="name" label="产品名称" />
<el-table-column prop="price" label="价格(元)" />
</el-table>
</template>
tableData: [
{
type: '水果',
code: 'apple',
name: '苹果',
price: '54'
},
{
type: '水果',
code: 'banana',
name: '香蕉',
price: '74'
},
{
type: '水果',
code: 'banana',
name: '香蕉',
price: '40'
},
{
type: '水果',
code: 'banana',
name: '香蕉',
price: '70'
},
{
type: '水果',
code: 'orange',
name: '橙子',
price: '44'
},
{
type: '水果',
code: 'orange',
name: '橙子',
price: '95'
},
{
type: '水果',
code: 'orange',
name: '橙子',
price: '23'
},
{
type: '水果',
code: 'pear',
name: '梨子',
price: '64'
},
{
type: '零食',
code: 'chocolate',
name: '巧克力',
price: '25'
},
{
type: '零食',
code: 'pear',
name: '梨子',
price: '25'
}
],
需要将上面这个表格合并单元格,变成下面这样
el-table添加属性:span-method="spanMethod"
created() {
this.spanArrOne = this.getSpanArr(this.tableData, 'type')
},
methods: {
getSpanArr(data, params) {
let arr = [] // 接收重构数组
let spanArr = [] // 控制合并的数组
let pos = 0 // 设置索引
// 排序
const list = this.groupBy(data, params)
list.map(v => (arr = arr.concat(v)))
arr.map(res => {
data.shift()
data.push(res)
})
const redata = arr.map(v => v[params])
redata.reduce((old, cur, i) => {
if (cur === old) {
spanArr[pos] += 1
spanArr.push(0)
} else {
spanArr.push(1)
pos = i
}
return cur
}, {})
return spanArr
},
groupBy(data, params) {
// 根据某个字段进行排序 输出二维数组
const groups = {}
data.forEach(row => {
const group = row[params]
groups[group] = groups[group] || []
groups[group].push(row)
})
return Object.values(groups)
},
spanMethod({ row, column, rowIndex, columnIndex }) {
if (columnIndex === 0) {
const _row = this.spanArrOne[rowIndex]
const _col = _row > 0 ? 1 : 0
return { rowspan: _row, colspan: _col }
}
}
}
二、合并第二列
created() {
this.spanArrTwo = this.getSpanArr(this.tableData, 'code')
},
spanMethod({ row, column, rowIndex, columnIndex }) {
if (columnIndex === 0) {
const _row = this.spanArrOne[rowIndex]
const _col = _row > 0 ? 1 : 0
return { rowspan: _row, colspan: _col }
} else if (columnIndex === 1) {
const _row = this.spanArrTwo[rowIndex]
const _col = _row > 0 ? 1 : 0
return { rowspan: _row, colspan: _col }
}
}
但是这里有问题,第二列的pear分别属于水果和零食种类下的,它俩不应该合并到一起
这是由于code不唯一导致的,为了达到目的,改造数据
for (const item of this.tableData) {
item.code = `${item.type}****${item.code}`
}
现在第二列的合并也正确了,稍微改下DOM结构
<template slot-scope="scope">
<!-- {{ scope.row.code }} -->
{{ scope.row.code.split('****')[1] }}
</template>
</el-table-column>
最终效果:
完整代码:
<template>
<el-table :data="tableData" :span-method="spanMethod" border>
<el-table-column prop="type" label="种类" />
<el-table-column prop="code" label="产品编号">
<template slot-scope="scope">
<!-- {{ scope.row.code }} -->
{{ scope.row.code.split('****')[1] }}
</template>
</el-table-column>
<el-table-column prop="name" label="产品名称" />
<el-table-column prop="price" label="价格(元)" />
</el-table>
</template>
<script>
export default {
data() {
return {
tableData: [
{
type: '水果',
code: 'apple',
name: '苹果',
price: '54'
},
{
type: '水果',
code: 'banana',
name: '香蕉',
price: '74'
},
{
type: '水果',
code: 'banana',
name: '香蕉',
price: '40'
},
{
type: '水果',
code: 'banana',
name: '香蕉',
price: '70'
},
{
type: '水果',
code: 'orange',
name: '橙子',
price: '44'
},
{
type: '水果',
code: 'orange',
name: '橙子',
price: '95'
},
{
type: '水果',
code: 'orange',
name: '橙子',
price: '23'
},
{
type: '水果',
code: 'pear',
name: '梨子',
price: '64'
},
{
type: '零食',
code: 'chocolate',
name: '巧克力',
price: '25'
},
{
type: '零食',
code: 'pear',
name: '梨子',
price: '25'
}
],
spanArrOne: [],
spanArrTwo: []
}
},
created() {
for (const item of this.tableData) {
item.code = `${item.type}****${item.code}`
}
this.spanArrOne = this.getSpanArr(this.tableData, 'type')
this.spanArrTwo = this.getSpanArr(this.tableData, 'code')
},
methods: {
getSpanArr(data, params) {
let arr = [] // 接收重构数组
let spanArr = [] // 控制合并的数组
let pos = 0 // 设置索引
// 排序
const list = this.groupBy(data, params)
list.map(v => (arr = arr.concat(v)))
arr.map(res => {
data.shift()
data.push(res)
})
const redata = arr.map(v => v[params])
redata.reduce((old, cur, i) => {
if (cur === old) {
spanArr[pos] += 1
spanArr.push(0)
} else {
spanArr.push(1)
pos = i
}
return cur
}, {})
return spanArr
},
groupBy(data, params) {
// 根据某个字段进行排序 输出二维数组
const groups = {}
data.forEach(row => {
const group = row[params]
groups[group] = groups[group] || []
groups[group].push(row)
})
return Object.values(groups)
},
spanMethod({ row, column, rowIndex, columnIndex }) {
if (columnIndex === 0) {
const _row = this.spanArrOne[rowIndex]
const _col = _row > 0 ? 1 : 0
return { rowspan: _row, colspan: _col }
} else if (columnIndex === 1) {
const _row = this.spanArrTwo[rowIndex]
const _col = _row > 0 ? 1 : 0
return { rowspan: _row, colspan: _col }
}
}
}
}
</script>
三、代码优化
合并单元格的这个代码,要是有第三列要合并,不得再加个spanArrThree数组???
而且其他同事需要合并的时候,这一坨方法又要重新写一遍,麻烦...
于是就封装了一下
1、src/utils/kits.js
// 返回合并的数据,如[2, 0, 2, 0]
const getSpanArr = (tableData, prop) => {
const spanArr = [] // 控制合并的数组
let index = 0 // 设置索引
const values = tableData.map(v => v[prop])
// 将 ['1001', '1001', '1002', '1002'] 转换为 [2, 0, 2, 0]
values.reduce((old, cur, i) => {
if (cur === old) {
spanArr[index] += 1
spanArr.push(0)
} else {
spanArr.push(1)
index = i
}
return cur
}, '')
return spanArr
}
/*
根据prop属性,合并内容相同的行
入参:
1. el-table span-method属性自带的{ row, column, rowIndex, columnIndex }
2. 数据源
3. 需要合并的列
*/
const mergeRowByProp = (
{ row, column, rowIndex, columnIndex },
tableData,
mergeList
) => {
for (const { column, prop } of mergeList) {
if (columnIndex === column) {
const spanArr = getSpanArr(tableData, prop)
const rowspan = spanArr[rowIndex]
const colspan = rowspan > 0 ? 1 : 0
return { rowspan, colspan }
}
}
}
export { mergeRowByProp }
2、使用
span-method属性传入mergeRowByProp方法
:span-method="(...rest) => mergeRowByProp(...rest, tableData, mergeList)"
需要在data中定义mergeList
mergeList: [
{ column: 0, prop: 'type' },
// { column: 1, prop: 'code' },
// { column: 2, prop: 'name' }
// { column: 3, prop: 'price' },
]
3、效果
合并第一列
mergeList: [
{ column: 0, prop: 'type' },
// { column: 1, prop: 'code' },
// { column: 2, prop: 'name' }
// { column: 3, prop: 'price' },
]
合并第一列和第二列
mergeList: [
{ column: 0, prop: 'type' },
{ column: 1, prop: 'code' },
// { column: 2, prop: 'name' }
// { column: 3, prop: 'price' },
]
合并一二三列
mergeList: [
{ column: 0, prop: 'type' },
{ column: 1, prop: 'code' },
{ column: 2, prop: 'name' }
// { column: 3, prop: 'price' },
]
如果希望第二列基于第一列进行合并,需要在获取到tableData后,对第二列的数据进行改造
created() {
for (const item of this.tableData) {
item.code = `${item.type}****${item.code}`
}
}
4、完整代码
<template>
<el-table
:data="tableData"
:span-method="(...rest) => mergeRowByProp(...rest, tableData, mergeList)"
border
>
<el-table-column prop="type" label="种类" />
<el-table-column prop="code" label="产品编号">
<template slot-scope="scope">
{{ scope.row.code }}
<!-- {{ scope.row.code.split('****')[1] }} -->
</template>
</el-table-column>
<el-table-column prop="name" label="产品名称" />
<el-table-column prop="price" label="价格(元)" />
</el-table>
</template>
<script>
import { mergeRowByProp } from './kits'
export default {
name: 'MergeTable',
data() {
return {
mergeRowByProp,
tableData: [
{ type: '水果', code: 'apple', name: '苹果', price: '10' },
{ type: '水果', code: 'banana', name: '苹果', price: '10' },
{ type: '水果', code: 'banana', name: '香蕉', price: '20' },
{ type: '水果', code: 'banana', name: '香蕉', price: '20' },
{ type: '水果', code: 'orange', name: '橙子', price: '30' },
{ type: '水果', code: 'orange', name: '橙子', price: '30' },
{ type: '水果', code: 'orange', name: '橙子', price: '40' },
{ type: '水果', code: 'pear', name: '梨子', price: '40' },
{ type: '零食', code: 'pear', name: '梨子', price: '50' },
{ type: '零食', code: 'chocolate', name: '巧克力', price: '50' }
],
mergeList: [
{ column: 0, prop: 'type' },
{ column: 1, prop: 'code' },
{ column: 2, prop: 'name' }
// { column: 3, prop: 'price' },
]
}
},
created() {
for (const item of this.tableData) {
item.code = `${item.type}****${item.code}`
}
},
methods: {}
}
</script>
5、优化
如果希望第二列基于第一列进行合并,第三列基于第二列进行合并...依此类推,如果有很多列都需要合并,或者是很多个表格都需要这样处理,那么每个表格的数据都需要对tableData进行处理,并且在模板中使用split进行切割,未免有些太麻烦,所以将【item.code = ${item.type}****${item.code}】这种操作放到封装的方法中进行处理
src/utils/kits.js改造代码
const separator = '****' // 分隔符
const mergeRowByProp = ({ row, column, rowIndex, columnIndex }, tableData, mergeList) => {
// 如果合并的列数大于1,需要对数据进行改造,使后面的列依赖于前面的列
if (mergeList.length > 1) {
tableData = JSON.parse(JSON.stringify(tableData))
tableData.forEach(item => {
mergeList.forEach((m, i) => {
const { prop } = m
if (i) {
// 当前列的内容拼接上前一列的内容,使用分隔符隔开
// 分隔符的作用只是为了打印数据时看的方便点,加不加都行
item[prop] = `${item[mergeList[i - 1].prop]}${separator}${item[mergeList[i].prop]}`
}
})
})
}
for (const { column, prop } of mergeList) {
if (columnIndex === column) {
const spanArr = getSpanArr(tableData, prop)
const rowspan = spanArr[rowIndex]
const colspan = rowspan > 0 ? 1 : 0
return { rowspan, colspan }
}
}
}
在进行多列合并时,方法内部会帮我们处理好依赖关系~
四、mergeRowByProp 源码
/xtt-tools/elementUI/el-table.js
妈妈再也不用担心我合并单元格了...