功能需求来源:近期,客户提出了一项刁钻的需求,表格的行实现合并,并能展开折叠。antd虽然有行合并和展开折叠的实现,但并未有两者结合起来的写法。
结果截图:
表格初始是合并状态:点击“结构名称”列的“+”展开状态:
点击“结构名称”列的“-”折叠收起状态
实现代码如下:
<template>
<div style="width: 1300px; height: 800px">
<a-table
:columns="tableColumn"
:data-source="tableData"
:bordered="true"
:scroll="{ x: 900, y: 600 }"
:pagination="false"
:expandable="{ rows: true, childrenColumnName: 'children' }"
:expandIconColumnIndex="1"
@expand="expandRows"
>
<template slot="index" slot-scope="text, record">
<span :title="text"> {{ record.key }} </span>
</template>
</a-table>
</div>
</template>
<script>
export default {
name: 'technology',
data() {
return {
tableColumn: [
{
title: "序号",
dataIndex: 'index',
key: 'index',
width: 100,
scopedSlots: { customRender: 'index'}
},
{
title: '结构名称',
dataIndex: 'structName',
key: 'structName',
width: 200,
customRender: (value, row) => {
const obj = {
children: value,
attrs: {},
};
if (row.structShowFlag) {
obj.attrs.rowSpan = this.structIdCountMap.get(row.structId);
} else if (row.structShowFlag === false) {
obj.attrs.rowSpan = 0;
}
return obj;
}
},
{
title: '功能名称',
dataIndex: 'functionName',
key: 'functionName',
width: 200,
customRender: (value, row) => {
const obj = {
children: value,
attrs: {},
};
if (row.functionShowFlag) {
obj.attrs.rowSpan = this.functionIdCountMap.get(row.functionId);
} else if (row.functionShowFlag === false) {
obj.attrs.rowSpan = 0;
}
return obj;
}
},
{
title: '要求名称',
dataIndex: 'requireName',
key: 'requireName',
ellipsis: true,
width: 150
},
{
title: '规格值',
dataIndex: 'standerValue',
key: 'standerValue',
ellipsis: true
},
{
title: '功能种类',
dataIndex: 'functionType',
key: 'functionType',
ellipsis: true
}
],
tableData: [],
structIdCountMap: {},
functionIdCountMap: {},
standbyTableData: []
};
},
mounted() {
// 初始化默认折叠
this.tableData = this.buildTableData(this.tableData, false);
this.expandRows(false,null);
},
methods: {
/**
* 整理表格数据
* 构造合并单元格数据和展开折叠数据
* @param val
* @param expand
* @returns {({structId: number, functionId: number, functionName: string, structName: string, standerValue: string, requireName: string, functionType: string}|{structId: number, functionId: number, functionName: string, structName: string, standerValue: string, requireName: string, functionType: string}|{structId: number, functionId: number, functionName: string, structName: string, standerValue: string, requireName: string, functionType: string}|{structId: number, functionId: number, functionName: string, structName: string, standerValue: string, requireName: string, functionType: string}|{structId: number, functionId: number, functionName: string, structName: string, standerValue: string, requireName: string, functionType: string})[]}
*/
buildTableData(val,expand) {
val = [
{
structId: 1,
functionId: 1,
structName: '结构 1',
functionName: '功能 1',
requireName: '要求 1',
standerValue: '规格值 1',
functionType: '功能类型 1'
},
{
structId: 1,
functionId: 1,
structName: '结构 1',
functionName: '功能 1',
requireName: '要求 2',
standerValue: '规格值 1',
functionType: '功能类型 1'
},
{
structId: 1,
functionId: 1,
structName: '结构 1',
functionName: '功能 1',
requireName: '要求 3',
standerValue: '规格值 1',
functionType: '功能类型 1'
},
{
structId: 1,
functionId: 2,
structName: '结构 1',
functionName: '功能 2',
requireName: '要求 4',
standerValue: '规格值 2',
functionType: '功能类型 2'
},
{
structId: 1,
functionId: 2,
structName: '结构 1',
functionName: '功能 2',
requireName: '要求 5',
standerValue: '规格值 2',
functionType: '功能类型 2'
},
{
structId: 1,
functionId: 3,
structName: '结构 1',
functionName: '功能 3',
requireName: '要求 6',
standerValue: '规格值 3',
functionType: '功能类型 3'
},
{
structId: 1,
functionId: 3,
structName: '结构 1',
functionName: '功能 3',
requireName: '要求 7',
standerValue: '规格值 3',
functionType: '功能类型 3'
},
{
structId: 1,
functionId: 4,
structName: '结构 1',
functionName: '功能 4',
requireName: '要求 8',
standerValue: '规格值 4',
functionType: '功能类型 4'
},
{
structId: 2,
functionId: 5,
structName: '结构 2',
functionName: '功能 5',
requireName: '要求 9',
standerValue: '规格值 5',
functionType: '功能类型 5'
},
{
structId: 2,
functionId: 6,
structName: '结构 2',
functionName: '功能 6',
requireName: '要求 10',
standerValue: '规格值 6',
functionType: '功能类型 6'
},
{
structId: 2,
functionId: 7,
structName: '结构 2',
functionName: '功能 7',
requireName: '要求 11',
standerValue: '规格值 7',
functionType: '功能类型 7'
},
{
structId: 2,
functionId: 8,
structName: '结构 2',
functionName: '功能 8',
requireName: '要求 12',
standerValue: '规格值 8',
functionType: '功能类型 8'
},
{
structId: 2,
functionId: 9,
structName: '结构 2',
functionName: '功能 9',
requireName: '要求 13',
standerValue: '规格值 9',
functionType: '功能类型 9'
},
{
structId: 3,
functionId: 10,
structName: '结构 3',
functionName: '功能 10',
requireName: '要求 14',
standerValue: '规格值 10',
functionType: '功能类型 10'
},
{
structId: 3,
functionId: 11,
structName: '结构 3',
functionName: '功能 11',
requireName: '要求 16',
standerValue: '规格值 11',
functionType: '功能类型 11'
},
{
structId: 3,
functionId: 12,
structName: '结构 3',
functionName: '功能 12',
requireName: '要求 17',
standerValue: '规格值 12',
functionType: '功能类型 12'
},
{
structId: 3,
functionId: 13,
structName: '结构 3',
functionName: '功能 13',
requireName: '要求 18',
standerValue: '规格值 13',
functionType: '功能类型 13'
}
];
console.log("expand",expand);
if (val.length > 0) {
let index = 0;
val.forEach(item => {
index += 1;
item.key = index;
});
}
// 备用的tableData
this.standbyTableData = val;
let structIdCountMap = new Map();
let functionIdCountMap = new Map();
val.forEach( item => {
let structId = item.structId;
let functionId = item.functionId ? item.functionId : null;
if (structIdCountMap.has(structId)) {
structIdCountMap.set(structId, structIdCountMap.get(structId) + 1);
// 当点击展开时,才合并,否则赋值为null
item.structShowFlag = expand === false ? null : false;
item.isExpand = false;
} else {
structIdCountMap.set(structId, 1);
item.structShowFlag = expand === false ? null : true;
item.isExpand = true;
item.children = [];
}
if (functionId) {
if (functionIdCountMap.has(functionId)) {
functionIdCountMap.set(functionId, functionIdCountMap.get(functionId) + 1);
item.functionShowFlag = expand === false ? null : false;
} else {
functionIdCountMap.set(functionId, 1);
item.functionShowFlag = expand === false ? null : true;
}
} else {
item.functionShowFlag = null
}
});
this.structIdCountMap = structIdCountMap;
this.functionIdCountMap = functionIdCountMap;
return val;
},
/**
* 表格行折叠展开事件
* @param isExpand
* @param record
*/
expandRows(isExpand, record) {
// 初始化时
if (isExpand === false && record === null) {
let buildData = this.buildTableData(this.standbyTableData, false);
let children = [];
buildData.forEach(item => {
if (!item.isExpand) {
children.push(item);
}
});
buildData = buildData.filter(item => item.isExpand === true);
buildData.forEach(item => {
if (item.isExpand) {
item.children = children.filter(child => child.structId === item.structId);
}
});
this.tableData = buildData;
}
// 点击折叠时,构造children列
if (isExpand === false && record !== null) {
// 找到与点击行相同structId但不包括点击行的数据
let foldData = (this.tableData.filter(item => item.structId === record.structId)).slice(1);
foldData.forEach(item => {
item.structShowFlag = null;
item.functionShowFlag = null;
});
// 设置折叠数据
record.children = foldData;
record.structShowFlag = null;
record.functionShowFlag = null;
// let newArray = array.map(function(item) {
// return item === 2 ? 10 : item;
// });
this.tableData = this.tableData.map(item => {
return item.key === record.key ? record : item;
});
// this.tableData.splice(record.key - 1, 1, record);
// 删除被折叠数据
let afterDeleteData = this.tableData;
foldData.forEach(item => {
afterDeleteData = afterDeleteData.filter(afterDelete => afterDelete.key !== item.key);
});
this.tableData = afterDeleteData;
console.log(this.tableData);
}
// 点击展开时,合并单元格
if (isExpand && record !== null) {
// 先取出该行的children,再把该行的children置空
let children = record.children;
record.children = [];
// 设置结构合并数据
record.structShowFlag = true;
// 用于判断该结构的功能是否合并
let functionShowList = [record, ...children];
let functionIdCountMap = new Map();
// 设置功能是否合并
functionShowList.forEach(item => {
let functionId = item.functionId ? item.functionId : null;
if (functionId) {
if (functionIdCountMap.has(functionId)) {
functionIdCountMap.set(functionId, functionIdCountMap.get(functionId) + 1);
item.functionShowFlag = false;
} else {
functionIdCountMap.set(functionId, 1);
item.functionShowFlag = true;
}
} else {
item.functionShowFlag = null
}
});
// 替换元素
this.tableData = this.tableData.map(item => {
return item.key === record.key ? functionShowList[0] : item;
});
// this.tableData.splice(record.key - 1, 1, functionShowList[0]);
children = functionShowList.slice(1);
// 插入元素的索引
let insertIndex = -1;
for (let i = 0; i < this.tableData.length; i++) {
if (this.tableData[i].key === record.key) {
insertIndex = i + 1; // 在找到的对象后面插入,所以索引加1
break;
}
}
// 再将该行的children,插入tableData中。
if (insertIndex !== -1) {
children.forEach(item => {
item.structShowFlag = false;
this.tableData.splice(insertIndex, 0, item);
insertIndex += 1;
});
}
console.log("tableData",this.tableData);
// this.tableData = this.buildTableData(this.standbyTableData, true);
}
}
}
};
</script>
问题:antd要求合并表格行,只能合并同级的行(根据官方文档的样例和API,实践得出的结论,当然也有可能是我理解和实践错了)。
举个例子:合并行的实现是写在表格columns配置中,以插槽的形式配合官方文档提供的API实现,但一跟行的展开折叠结合起来实现就会有冲突。
因为行的展开折叠是需要columns配置中有children,这就跟合并表格行只能合并同级行,产生了矛盾。
解决方案:以上代码的方案就是,当点击折叠时候,需要构造children,点击展开的时候需要再把children中的数据拿出来,进行合并。
难点:代码之所以写的罗里吧嗦的,是因为在把构造children数据和拿出children数据这个过程中,要能对应上,哪些数据是哪一行的children。
待解决的问题:这一版本的解决方案中,我并未加上分页和表格列拖拽之后代码(公司项目中的表格都有分页和列拖拽)。因为加上之后,显得更加罗嗦了,不能很好的展示主要的代码。提示一下,分页不会有任何问题,但表格列拖拽,会跟展开折叠冲突,需要在table组件加上一个key,当表格数据更新,也就是表格重新渲染的时候,更改表格的key,让其重新渲染一次。
总结:不难,但很费时费力,比较繁琐的一个小功能,双商正常的客户都不会提这样的需求。