需求:针对页面的表头是有分组的。因此在原先的导出的功能的基础上去实现导出也实现分组,并且发现网上少有这种需求技术实现思路,因此自己实现一个
思路
针对这种分组。初步确定数据结构是是像菜单一样的树结构一样。同时也要考虑到部份场景可能是有三级。四级分组
tablesGroupThree方法参数
tablesGroupThree(
field,
data,
{
filename,//导出文件名
wx_name = `Sheet${new Date().getTime()}`,//excel的工作表名称
isLab = false,//是否显示字段名
width = 23,//单元格的宽度
Group = [],//分组
isBeforeGroup = true,//是否分组排在最前面
},
)
``
#### Group的结构
```js
[{
title:"一级", //分组名称
column:['字段A1','字段B1'], //当前一级所属字段
children:[{ //如果还有分组。则需要
title:"二级",
column:['字段C1','字段D1'],
children:[{title:"三级",column:[],children}]
}]
}]
field数组结构,每个字段的具体信息。比如字段的匹配名称。类型是数字还是文字。是否保留小数点
Filed字段
FieldText //字段名称
FieldName //字段名
FieldType //字段类型
EnumTypeString, //枚举格式
FieldMerger // 是否合并上下单元格,值相同且合并
FieldBackGround //单元格背景颜色
FieldFontColor //单元格字体颜色
FieldDefaultColor ////默认合并字段颜色(黄色)
tablesGroupThree的逻辑
- 首先要卡控field和Group的参数。同时Group的分组的字段在field字段集合不存在时会报错
const GetchildColumnError = (Group, field,) => {
let fieldArr = field.map((item) => {
return item.FieldName;
});
for (let i = 0; i < Group.length; i++) {
if (Group[i].column && Group[i].column.length) {
let sliceArr = []
for (let j = 0; j < Group[i].column.length; j++) {
if (!fieldArr.includes(Group[i].column[j])) {
sliceArr.push(j)
console.log(`当前分组字段${Group[i].column[j]}不存在。已经在分组中删除`)
// throw Error('当前分组字段在field不存在。请检查');
}
}
sliceArr.map(item => {
Group[i].column.splice(item, 1)
})
}
if (Group[i].children && Group[i].children.length) {
for (let k = 0; k < Group[i].children.length; k++) {
GetchildColumnError(Group[i].children, field,);
}
}
}
};
2.同时卡控一级分组的title。因为用一级分组标题作为key存诸
if (
new Set(
Group.map((item) => {
return item.title;
}),
).size !== Group.length
) {
throw new Error('分组标题相同');
}
3.定义excel的实例对象
let workbook = new Excel.Workbook();//实例化excel对象
let worksheet = workbook.addWorksheet(wx_name);//添加工作表
const columns = []; //获取所有分组列的集合
const GruopColumnsObj= {}; //所有列的key
const maxChildArr = []; //每一个分组的最大层级,最终取最大值
4.遍历分组,去获取需要的columns,GruopColumnsObj,maxChildArr
for (let i = 0; i < Group.length; i++) {
GruopColumnsObj[Group[i].title] = [];//存诸当前分组的所有列名
const allColumn = GetchildColumn(Group[i]);//获取当前分组的所有列
const childreNumber = GetchildColumnTier(Group[i]);//获取分组共有多少层级
maxChildArr.push(childreNumber);
allColumn.map((item) => {
let newItem = field.find((items) => {
return item == items.FieldName;
});
if (newItem) {
GruopColumnsObj[Group[i].title].push(newItem.FieldName);
columns.push({
header: newItem.FieldText,
key: newItem.FieldName,
width: width,
FieldType: newItem.FieldType,
FieldMerger: newItem.hasOwnProperty('FieldMerger') ? newItem.FieldMerger : false, //合并属性
FieldBackGround: newItem.hasOwnProperty('FieldBackGround')
? newItem.FieldBackGround
: null, //单元格背影
FieldFontColor: newItem.hasOwnProperty('FieldFontColor')
? newItem.FieldFontColor
: null, //单元格字体颜色
FieldDefaultColor: newItem.hasOwnProperty('FieldDefaultColor')
? newItem.FieldFontColor
: false, //默认合并字段颜色(黄色)
});
}
});
}
4.获取未分组的列并处理信息
let noGroup = field.filter((item) => {
//处理未分组的是否启用和隐藏 还有不在分组
return !columns.find((items) => {
return items.key == item.FieldName;
});
});
noGroup = noGroup
.sort((a, b) => {
//栏目排序
return a.iColPos - b.iColPos;
})
.map((item) => {
return {
header: item.FieldText,
key: item.FieldName,
width: width,
FieldType: item.FieldType,
FieldMerger: item.hasOwnProperty('FieldMerger') ? item.FieldMerger : false, //合并属性
FieldBackGround: item.hasOwnProperty('FieldBackGround') ? item.FieldBackGround : null, //单元格背影
FieldFontColor: item.hasOwnProperty('FieldFontColor') ? item.FieldFontColor : null, //单元格字体颜色
FieldDefaultColor: item.hasOwnProperty('FieldDefaultColor') ? item.FieldFontColor : false, //默认合并字段颜色(黄色)
};
});
5.操作worksheet,用图来展示思路
处理表头的列
let worksheetColumns = [];
if (noGroup.length) {
if (isBeforeGroup) {
worksheetColumns = [...columns, ...noGroup];
} else {
worksheetColumns = [...noGroup, ...columns];
}
} else {
worksheetColumns = [...columns];
}
worksheet.columns = worksheetColumns;
//获取最高的层级
const MaxChild = Math.max(...maxChildArr);
for (let i = 0; i < MaxChild - 1; i++) {
//有多少层级就添加多少行
let rowValue = {};
columns.map((item) => {
rowValue[item.key] = item.key;
});
noGroup.map((item) => {
rowValue[item.key] = item.key;
});
worksheet.addRow(rowValue);
if (isLab && i == MaxChild - 1) {
//如果isLab为true,则添加一段字段
worksheet.addRow(rowValue);
}
}
处理表头的导出效果,此时未合并

合并表头组件的逻辑代码,这里在处理分组时我们采用的层级遍历。也就是广度优先,通过每次兄弟节点优先遍历,没有兄弟节点时,在遍历子节点的兄弟节点,以此类推。代码逻辑如下
- 将组进行层级遍历。也就是说先遍历所有一级分组,再遍历所有二级分级
- 在遍历过程中,要判断当前层级是否有上一层级与下一层级的逻辑处理。
条件为如果有上一层级
- 如果有上一层级。且需要合并上下的单元格
- 如果有上一层级,且有下一层级,先获取当前分组所有列并且拿最后一个,进行左右的合并单元格
- 如果有上一层级。且没有下一层级。哪么判定为最后一层转化字段为名称渲染
条件为如果没有上一层级
- 如果没有上一层级,且有下一层级,先获取当前分组所有列并且拿最后一个,进行左右的合并单元格
- 如果没有上一层级。且没有一层级,哪么判定为最后一层转化字段为名称渲染
- 如果没有上一级级。且有下一级但没有当前的列,且合并左右。且右为当前组的最大长度的letter

可以了解下广度优化的大致代码,实现的步聚就是通过这个形式去实现的
// 广度优先搜索
bfs (uuid) {
// 1.先初始化一个空队列
const queue = []
this.marked[uuid] = true
// 2.将起始节点放入队列
queue.push(uuid)
console.log('bfs', uuid)
while (queue.length) {
// 3.弹出队列第一个节点,并且访问它子节点
const uuid_ = queue.shift()
this.edges[uuid_].forEach(edge => {
// 4.如果没有被标记,那就标记它,并放入队列
if (!this.marked[edge]) {
this.marked[edge] = true
console.log('bfs', edge)
queue.push(edge)
}
})
}
}
具体实现如下,
function MegerCell2(Group, GruopAllColumns, MaxChild, fieldColumn) {
if (!Group) return [];
let res = {};
let handle_Columns = []; //记录合并防止遍历时又合并
let queue = [Group];
let count = 1; // 记录当前层级
while (queue.length) {
let countNum = queue.length; // 当前层级的节点数
while (countNum--) {
// 遍历当前层级的节点数
let current = queue.shift(); // 从前开始取当前层级的节点
//获取上一级的节点
let beforeGroup = res[count] ? res[count] : null;
let before_columns = beforeGroup && beforeGroup.column ? beforeGroup.column : []; //上一节点的column
let cur_columns = current.column && current.column.length ? current.column : []; //当前节点column
const Columns_end = worksheet.getColumn(
GruopAllColumns[GruopAllColumns.length - 1],
).letter; //获取当前组的最大长度的letter (excel的单元络A,B,C标志)
if (before_columns && before_columns.length) {
// 如果有,则需要合并当前的cur_columns与下面共有多少层级就行了
for (let i = 0; i < before_columns.length; i++) {
let merge_top = worksheet.getColumn(before_columns[i]).letter;
let mergeValue = `${merge_top}${count}:${merge_top}${MaxChild}`; //合并的位置
if (!handle_Columns.includes(mergeValue)) {
worksheet.mergeCells(mergeValue);
let columnTitle = fieldColumn.find((item) => {
return item.FieldName == before_columns[i];
});
//修改单元格标题
worksheet.getCell(`${merge_top}${count}`).value = columnTitle
? columnTitle.FieldText
: '标题';
cellStyles(merge_top, count);
handle_Columns.push(mergeValue);
}
}
//如果有cur_columns。则合并二级标题 这里要注意的是你二级还有三级,4级有多少层级 end要获取所有分组column的最后一个
if (cur_columns.length && current.children && current.children.length) {
let lastColumn = GetchildColumn(current);
let start = worksheet.getColumn(cur_columns[0]).letter;
let end = worksheet.getColumn(lastColumn[lastColumn.length - 1]).letter;
let mergeValue = `${start}${count}:${end}${count}`; //合并的位置
if (!handle_Columns.includes(mergeValue)) {
worksheet.mergeCells(mergeValue);
worksheet.getCell(`${start}${count}`).value = current.title
? current.title
: '标题';
//修改单元格样式
cellStyles(start, count);
//防止遍历又执行一次
handle_Columns.push(mergeValue);
}
}
if ((!current.children || !current.children.length) && cur_columns.length) {
//标题
let start = worksheet.getColumn(cur_columns[0]).letter;
let end = worksheet.getColumn(cur_columns[cur_columns.length - 1]).letter;
let mergeValue = `${start}${count}:${end}${count}`; //合并的位置
if (!handle_Columns.includes(mergeValue)) {
worksheet.mergeCells(mergeValue);
worksheet.getCell(`${start}${count}`).value = current.title
? current.title
: '标题';
//修改单元格样式
cellStyles(start, count);
handle_Columns.push(mergeValue);
}
//最后一层转化字段为名称
let number = count + 1;
for (let i = 0; i < cur_columns.length; i++) {
let merge_top = worksheet.getColumn(cur_columns[i]).letter;
let columnTitle = fieldColumn.find((item) => {
return item.FieldName == cur_columns[i];
});
let mergeValue = `${merge_top}${number}:${merge_top}${MaxChild}`; //合并的位置
if (!handle_Columns.includes(mergeValue)) {
worksheet.mergeCells(mergeValue);
//修改标题
worksheet.getCell(`${merge_top}${number}`).value = columnTitle
? columnTitle.FieldText
: '标题';
//修改单元格样式
cellStyles(merge_top, number);
handle_Columns.push(mergeValue);
}
}
}
} else {
//如果没有上一级且没有下一级
if (!current.children && cur_columns.length) {
//存在如果分组的字段在field是没有的情况下
let start = worksheet.getColumn(cur_columns[0]).letter;
let end = worksheet.getColumn(cur_columns[cur_columns.length - 1]).letter;
let mergeValue = `${start}${count}:${end}${count}`; //合并的位置
if (!handle_Columns.includes(mergeValue)) {
worksheet.mergeCells(mergeValue);
worksheet.getCell(`${start}${count}`).value = current.title
? current.title
: '标题';
//修改单元格样式
cellStyles(start, count);
handle_Columns.push(mergeValue);
}
//最后一层转化字段为名称
let number = count + 1;
for (let i = 0; i < cur_columns.length; i++) {
//如果不存在则跳过 原因在于分组与field有一个不存在会导致合并出错
// if(!worksheet.getColumn(cur_columns[i])) break;
let merge_top = worksheet.getColumn(cur_columns[i]).letter;
let columnTitle = fieldColumn.find((item) => {
return item.FieldName == cur_columns[i];
});
let mergeValue = `${merge_top}${number}:${merge_top}${MaxChild}`; //合并的位置
if (!handle_Columns.includes(mergeValue)) {
worksheet.mergeCells(mergeValue);
//修改标题
worksheet.getCell(`${merge_top}${number}`).value = columnTitle
? columnTitle.FieldText
: '标题';
//修改单元格样式
cellStyles(merge_top, number);
handle_Columns.push(mergeValue);
}
}
} else if (current.children && current.children.length && cur_columns.length) {
//没有上一级且有下一级
// let start = worksheet.getColumn(GruopAllColumns[0]).letter;
// let mergeValue = `${start}${count}:${Columns_end}${count}`;//合并的位置
let lastColumn = GetchildColumn(current);
let start = worksheet.getColumn(cur_columns[0]).letter;
let end = worksheet.getColumn(lastColumn[lastColumn.length - 1]).letter;
let mergeValue = `${start}${count}:${end}${count}`; //合并的位置
if (!handle_Columns.includes(mergeValue)) {
worksheet.mergeCells(mergeValue);
//合并赋值单元格的值
worksheet.getCell(`${start}${count}`).value = current.title
? current.title
: '标题';
//修改单元格样式
cellStyles(start, count);
handle_Columns.push(mergeValue);
}
} else if (current.children && current.children.length && !cur_columns.length) {
let start = worksheet.getColumn(GruopAllColumns[0]).letter;
let mergeValue = `${start}${count}:${Columns_end}${count}`; //合并的位置
if (!handle_Columns.includes(mergeValue)) {
worksheet.mergeCells(mergeValue);
//合并赋值单元格的值
worksheet.getCell(`${start}${count}`).value = current.title
? current.title
: '标题';
//修改单元格样式
cellStyles(start, count);
handle_Columns.push(mergeValue);
}
}
}
//这里是层级遍历
if (current.children && current.children.length) {
//如果有子级的话。则存当前count的下一位为当前的node;
let nextCount = count + 1;
res[nextCount] = current;
//层级遍历
for (let i = 0; i < current.children.length; i++) {
queue.push(current.children[i]);
// queue.unshift(current.children[i])
}
}
}
count++; // 层级+1
}
return res;
}
合并后的列头

之后就是处理单元格式和遍历添加数据的问题了。这里就不一一展开了。代码其实可以在优化的。但目前已经够用