前言
kk-utils 是一款我自己基于这几年开发封装出来的前端工具库
excel-js
excel-js
是kk-utils
里的工具之一(1.2.0版本开始支持)
,其作用是有多个针对导出导出的方法函数,是我在写项目时积累下来的,几乎可以覆盖日常所需,具体文档请查看
今天这篇主要说的是实践应用
实践 —— 导出【测试导入模版】
实现效果

比如我要做一个导入模版给用户填写数据导入进系统,第一行有说明和必填的红色,第二行是表头,后面的是表体数据,某些单元格还支持下拉选择
实现方法
import { BiMap } from 'kk-utils-library/bidirectional-mapping';
import { exportExcel } from 'kk-utils-library/excel-js';
// 说明行数据
const instructionData = [
'必填项\n请选择\n避免手动输入',
'必填项\n请填写',
'必填项\n请填写\n1、重复的客户编码将更新该客户的信息\n2、只允许输入英文字母或者阿拉伯数字\n3、编号一旦导入不允许修改',
'必填项\n请选择\n避免手动输入',
'必填项\n请填写',
'必填项\n请填写',
'必填项\n请填写',
'非必填项\n请填写'
];
// 表头数据
const tableHeader = [
'操作类型',
'客户名称',
'客户编码',
'客户类型',
'省',
'市',
'区',
'备注'
];
// 文本和key的映射关系
const keyMap = new BiMap([
['操作类型', 'operationType'],
['客户名称', 'customerName'],
['客户编码', 'customerCode'],
['客户类型', 'customerType'],
['省', 'provinceName'],
['市', 'cityName'],
['区', 'areaName'],
['备注', 'remark'],
['错误信息', 'errorMessage']
]);
// 必填key
const requireKeys = [
'操作类型',
'客户名称',
'客户编码',
'客户类型',
'省',
'市',
'区'
];
// 选项数据
const validationData = [
{
prop: 'operationType',
options: [
{
label: '新增',
value: 1
},
{
label: '修改',
value: 2
},
{
label: '删除',
value: 3
},
{
label: '不变',
value: 4
}
],
transformType: Number,
label: 'label',
value: 'value'
},
{
prop: 'customerType',
options: [
{
label: '客户',
value: 0
},
{
label: '供应商',
value: 1
},
{
label: '客户与供应商',
value: 2
}
],
transformType: Number,
label: 'label',
value: 'value'
}
];
// 表体数据
const bodyData = [
{
operationType: 1,
customerName: '客户名称',
customerCode: '客户编码',
customerType: 0,
provinceName: '省',
cityName: '市',
areaName: '区',
remark: '备注'
}
];
// 在这里把数组处理成二维数组的格式
function handleGetExportData() {
return new Promise((resolve) => {
const result = [
[...instructionData],
[...tableHeader],
...bodyData.map((item) => {
const processItem = {
...item,
// 这里要自己把枚举类型的value转成label
operationType: validationData[0].options.find(
(option) => option.value === item.operationType
)?.label,
// 这里要自己把枚举类型的value转成label
customerType: validationData[1].options.find(
(option) => option.value === item.customerType
)?.label
};
return tableHeader.map((label) => {
const valueKey = keyMap.get(label);
return processItem[valueKey];
});
})
];
// 打印一下result
// 可以看出最终要的结果就是二维数组,你可以不参照我的自己写方法处理,只要结果是如下就行
// [
// [
// '必填项\n请选择\n避免手动输入',
// '必填项\n请填写',
// '必填项\n请填写\n1、重复的客户编码将更新该客户的信息\n2、只允许输入英文字母或者阿拉伯数字\n3、编号一旦导入不允许修改',
// '必填项\n请选择\n避免手动输入',
// '必填项\n请填写',
// '必填项\n请填写',
// '必填项\n请填写',
// '非必填项\n请填写'
// ],
// [
// '操作类型',
// '客户名称',
// '客户编码',
// '客户类型',
// '省',
// '市',
// '区',
// '备注'
// ],
// [1, '客户名称', '客户编码', 0, '省', '市', '区', '备注']
// ];
resolve(result);
});
}
// 导出模版
function handleExportTemplate() {
handleGetExportData().then((data) => {
exportExcel({
filename: '测试导入模版',
sheets: [
{
processing: (worksheet) => {
worksheet.addRows(data);
},
extraProcessing: (worksheet) => {
// 处理单元格的样式
handleProcessingStyle(worksheet);
// 处理单元格的额外处理
handleExtraProcessing(worksheet);
}
}
]
});
});
}
// 处理单元格的样式
function handleProcessingStyle(worksheet) {
handleProcessingInstructionLineStyle(worksheet);
handleProcessingTableHeaderLineStyle(worksheet);
}
// 处理说明行的样式
function handleProcessingInstructionLineStyle(worksheet) {
const compareRowLine = 2; // 对比行 要知道列是否必填就要拿表头行的label去对比
const rowLine = 1; // 操作行 第一行
// 无论是否合并单元格 Excel的每一行列数你肯定是知道的 一般都是表头是多少列就是多少列
for (let i = 1; i <= tableHeader.length; i += 1) {
const cell = worksheet.getRow(compareRowLine).getCell(i); // 获取表头单元格
const isRequired = requireKeys.includes(cell.text); // 判断表头是否在必填key里
if (!isRequired) continue;
// 填充必填样式
worksheet.getRow(rowLine).getCell(i).style.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: {
argb: 'FFFF4949'
}
};
}
}
// 处理表头行的样式
function handleProcessingTableHeaderLineStyle(worksheet) {
const rowLine = 2; // 操作行 第二行 实际应用中你可以定义变量保存每个模块的行数 这样可以通过相加计算出来而不用写死
// 无论是否合并单元格 Excel的每一行的列数你肯定是知道的 一般都是表头是多少列就是多少列
for (let i = 1; i <= tableHeader.length; i += 1) {
// 填充表头样式
worksheet.getRow(rowLine).getCell(i).fill = {
type: 'pattern',
pattern: 'solid',
fgColor: {
argb: 'FF99CCFF'
}
};
}
}
// 处理单元格的额外处理 比如下拉框啊 数字金额格式化等这些
function handleExtraProcessing(worksheet) {
const instructionLineLength = 1; // 说明行的行数
const tableHeaderLineLength = 1; // 表头行的行数
const tableBodyLineLength = bodyData.length; // 表体行的行数
const tableBodyLineStart =
1 + instructionLineLength + tableHeaderLineLength; // 表体开始行
const tableBodyLineEnd = tableBodyLineStart + tableBodyLineLength - 1; // 表体结束行
// 遍历所有选项数据,给对应单元格设置
for (let i = 0; i < validationData.length; i += 1) {
const { prop, options, label } = validationData[i];
const valueKey = keyMap.get(prop);
const listLabel = options.map((item) => item.label).join(',');
// 表体每一行的对应单元格都要设置 所以从表体开始行遍历到结束行
for (let j = tableBodyLineStart; j <= tableBodyLineEnd; j += 1) {
worksheet
.getRow(j)
.getCell(tableHeader.indexOf(valueKey) + 1).dataValidation = {
type: 'list',
allowBlank: true,
formulae: [`"${listLabel}"`]
};
}
}
}
// 执行导出 就可以看到Excel下载好了 打开就是开头的效果
handleExportTemplate();
实践 —— 导入【测试导入模版】填写的数据
实现效果
这是使用上面导出的模版填写的数据

这是导入后拿到的数组对象

实现方法
先写一个input拿文件和导入按钮
<input type="file" id="file" accept=".xlsx" />
<button id="import">导入所选文件</button>
import { BiMap } from 'kk-utils-library/bidirectional-mapping';
import { importExcel } from 'kk-utils-library/excel-js';
const fileInput = document.getElementById('file');
const importButton = document.getElementById('import');
importButton.addEventListener('click', () => {
const file = fileInput.files[0];
if (!file) {
alert('请选择文件');
return;
}
importExcel(file, {
headerStartLine: 2, // 因为第一行是说明行,所以表头要设置参数从第二行开始
headerTotalLine: 1 // 总共一行表头 可以不写 默认是1
}).then((data) => {
console.log('data', data);
// 注意了 因为Excel是多工作表的 所以即使我们只要第一个工作表 返回的data还是嵌套多了一层数组
// [
// [
// {
// 操作类型: '新增',
// 客户名称: '测试1',
// 客户编码: 'TEST001',
// 客户类型: '供应商',
// 省: '辽宁省',
// 市: '沈阳市',
// 区: '辽中县'
// },
// {
// 操作类型: '修改',
// 客户名称: '测试2',
// 客户编码: 'TEST002',
// 客户类型: '客户',
// 省: '山西省',
// 市: '太原市',
// 区: '晋源区'
// },
// {
// 操作类型: '删除',
// 客户名称: '测试3',
// 客户编码: 'TEST003',
// 客户类型: '客户与供应商',
// 省: '吉林省',
// 市: '长春市',
// 区: '宽城区'
// },
// {
// 操作类型: '不变',
// 客户名称: '测试4',
// 客户编码: 'TEST004',
// 客户类型: '供应商',
// 省: '湖南省',
// 市: '长沙市',
// 区: '雨花区'
// }
// ]
// ];
});
});
因为传给后台接收的key不可能是中文,所以我们还要使用前面定义的keyMap把中文key转成后台要的key值才能传给后台写入数据库
import { BiMapConversion } from 'kk-utils-library/bidirectional-mapping'
function handleGetImportData(data) {
return new Promise((resolve) => {
const result = data.map((list) => {
return list.map((item) => {
const mapData = BiMapConversion(item, keyMap);
// 因为选项是中文的,要使用选项数据反向处理回对应的value传给后台
validationData.forEach((el) => {
const { prop, options, label, value } = el;
const targetValue = options.find([label, mapData[prop]])?.[
value
];
(targetValue || target === 0) && (mapData[prop] = targetValue);
});
return mapData;
});
});
resolve(result);
});
}
把importExcel拿到的data传入,就拿到映射后的数据啦
handleGetImportData(data).then(res=>{
console.log('res', res);
});
