sheet.js可以很方便的在前端读取excel文件数据,一般读取文件后,每个单元格中的数据都会被解析为对象,后续我们可以使用该库提供的utils.sheet_to_string()方法将一张工作表转换成二维数组,方便操作处理。
但是如果单元格中包含日期数据,转换后拿到的将是一个数字。因为excel中对日期实际存储的是数字,通过格式化转换为日期。这就导致读取的日期也会被转成数字,无法与真正的数字区分。
解决方案
先说解决思路:区分单元格日期类型,遍历原始单元格对象,对日期类型单元格,读取格式化后的字符串。
- 将日期读取文件时就转为日期格式: XLSX.read(sheet,
{cellDates: true}) - 遍历原始的单元格对象,根据对象中类型做区分,类型为
d,则是日期类型,需要读取格式化后的值,其他类型仍读取真实值(单元格对象的v值)。
这种形式导致无法直接使用utils.sheet_to_json()转换的二维数组数据,必须从原始的单元格对象中遍历,对日期类型做特别处理。
问题现象
工作表中的数字与日期经sheet.js解析后的单元格对象,类型是相同的。经utils.sheet_to_json()转换后会得到数字数组,而没有期望的日期。
import {utils} from 'xlsx';
// t 表示数据类型,v 表示实际值,w 表示格式化后展示的值
const sheet = {
"A1": {t: "n", v: 44927, w: "2023年1月"},
"A2": {t: "n", v: 44958, w: "2023年2月"}
"B1": {t: "n", v: 100, w: "100"},
"B2": {t: "n", v: 200, w: "200"},
}
// 转换工作表为二维数组:
cosnt arr = utils.sheet_to_json(sheet);
// arr 为:
// [
// [44927, 100],
// [44958, 200]
// ]
这就导致当存在日期数据时,转换后的二维数组无法区分日期与数字,导致无法直接使用。
解决过程
sheet.js提供了读取文件时,直接将日期转为日期格式的方式,需要在读取时,增加配置项{cellDates: true}:
import {read} from 'xlsx';
// ...
// 以antd中Form提交方法为例(也可以在Upload组件的beforeUpload中处理):
const onFinish = async (values) => {
const file = values.files[0].originFileObj; // 得到原始文件对象
if (file) {
// 读取工作簿数据
const bufferData = await file.arrayBuffer();
const workbook = await read(bufferData, { cellDates: true });
// 打印第一张工作表
const firstSheetName = workbook.SheetNames[0];
const firstSheet = workbook.Sheets[firstSheetName]
console.log(firstSheet);
}
}
打印后会发现,日期格式已有专有的类型d,但是v值显示的js日期莫名少了3秒,utils.sheet_to_json()转换后会取v中的值,还是无法使用。
{t: 'd', v: Sat Dec 31 2022 23:59:17 GMT+0800 (中国标准时间), w: '2023年1月'}`
于是只能配合原始单元格对象、sheet_to_json()转换后的二维数组,一起过滤出真实的二维数组:遍历转换二维数组,得到对应的单元格位置,根据位置拿到原始单元格对象,将对象处理后使用。
核心示例代码:
// const sheet = ... // sheetjs解析的工作表对象
// const sheetArr = utils.sheet_to_json(sheet)
const records = [];
sheetArr.forEach((row, i) => {
const record = []
(row || []).forEach((val, j) => {
// 根据二维数组行列位置,得到单元格编码,如 A2、B4
const cellAddr = utils.encode_cell({r: i, c: j})
// 根据单元格编码,得到单元格原始对象
const cellObj = sheet[cellAddr];
if (cellObj.t === 'd') {
// 单元格为日期类型,取格式化后的字符串
record.push(cellObj.w)
} else {
// 其他类型仍取实际值
record.push(cellObj.v)
}
})
records.push(record)
});
// records即为取得日期字符串的二维数组
其他
看文档配置中有类似日期的配置 dateNF,介绍不详,也没找到使用示例。自己尝试添加后,并没有效果。