在Node.js生态中,处理Excel文件最常用的库之一就是Exceljs。它功能强大,支持读写.xlsx、.xlsm等格式,但许多开发者都曾遇到一个令人困惑的情况:明明Excel单元格里显示的是文本"123",用Exceljs读取时却返回了数字123;或者读取日期时,返回的是一串数字时间戳。这种“类型自动转换”常常导致后续字符串拼接、数据校验等逻辑出错。

那么,如何正确地获取单元格的原始字符串值?本文将深入解析Exceljs的读取机制,并提供一份可靠的解决方案。

一、问题重现:为什么getValue()不总是字符串?

先看一段典型的代码:

const ExcelJS = require('exceljs');
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.readFile('sample.xlsx');
const worksheet = workbook.getWorksheet(1);
const cell = worksheet.getCell('A1');
console.log(cell.value); // 可能返回 123 (number) 或 "2023-01-01T00:00:00.000Z" (Date)

Exceljs的cell.value返回的是JavaScript原生类型。如果单元格格式为“常规”或“数字”,它就会自动解析为number;如果格式为日期,则解析为Date对象。这种智能解析在大多数场景下很方便,但在需要保留原始输入字符串(例如身份证号、电话号码、数字编码)时就成了障碍。

二、核心方案:使用 cell.text 属性

Exceljs其实已经提供了获取格式化显示字符串的属性——cell.text。这个属性返回的是单元格在Excel中实际显示的文本,保留所有格式,且永远是字符串。

示例:

const cell = worksheet.getCell('B2');
console.log(cell.text); // 例如 "00123"(即使原始值是数字123)
console.log(typeof cell.text); // "string"

对于日期单元格,cell.text会返回Excel格式化后的日期字符串(例如“2024/1/15”),而不是时间戳。对于带有自定义格式(如“#,##0.00”)的数字,也会返回格式化后的文本。

注意事项:

  • cell.text依赖于单元格的numFmt(数字格式)。如果单元格没有指定格式,可能会返回科学计数法等奇怪结果。
  • 对于公式单元格cell.text返回的是计算后的显示结果,而不是公式本身。
  • 如果单元格为空,cell.text返回空字符串""

三、进阶方案:显式获取原始字符串值

在某些极端情况下,你可能需要读取Excel文件中存储的原始字符串,而不经过任何格式化处理。这时可以使用cell.value的自带属性:

const value = cell.value;
if (value === null) {
  // 空单元格
} else if (typeof value === 'object' && 'richText' in value) {
  // 富文本,需遍历value.richText
} else if (typeof value === 'object' && 'formula' in value) {
  // 公式,value.result才是计算后的值
} else {
  // 直接使用value.toString()强制转字符串
  const stringValue = String(value);
}

但更推荐使用cell.text,因为它已经帮我们处理了大部分格式问题。

四、完整示例代码

下面是一个完整的函数,读取Excel文件并返回所有单元格的字符串值(保留原样):

const ExcelJS = require('exceljs');

async function readSheetAsStrings(filePath, sheetName) {
  const workbook = new ExcelJS.Workbook();
  await workbook.xlsx.readFile(filePath);
  const worksheet = workbook.getWorksheet(sheetName);

  const result = [];
  worksheet.eachRow((row, rowNumber) => {
    const rowData = [];
    row.eachCell((cell) => {
      rowData.push(cell.text); // 关键:使用text属性
    });
    result.push(rowData);
  });
  return result;
}

// 使用示例
(async () => {
  const data = await readSheetAsStrings('data.xlsx', 'Sheet1');
  console.log(data);
})();

如果你需要处理超大文件,可以结合stream方式逐行读取,但核心逻辑不变。

五、常见误区与最佳实践

  1. 不要用cell.value + ''强转:对于空单元格,cell.value为null,会变成字符串"null";日期对象会转成toString()格式,如"Sun Jan 15 2023...",并非所需要的格式。

  2. 日期处理:若需自定义日期格式,可以先读取cell.value,再用moment或原生Intl.DateTimeFormat格式化,或者直接使用cell.text

  3. 长数字(如身份证号):Excel默认会将超过15位的数字显示为科学计数法,且cell.text可能返回“1.23457E+17”。最佳实践是在Excel源文件中提前将单元格格式设置为“文本”。若无法修改源文件,可在读取后手动处理:检查字符串长度和是否包含“E+”等。

  4. 合并单元格:Exceljs中合并单元格区域只有左上角有值,其余单元格cell.text为空字符串。需通过cell.mastercell.isMerged判断。

六、官方文档与版本说明

该方案基于Exceljs v4.x(当前主流版本)。如果使用旧版(如v3.x),cell.text行为可能略有差异,但基本一致。建议始终使用最新稳定版。

官方文档中关于cell.text的描述:

“Returns the text of the cell. If the cell contains a string, it returns the string. If the cell contains a number, it returns the number formatted using the number format of the cell.”

结语

在Node.js中通过Exceljs读取Excel文件时,永远优先使用cell.text,它能最简洁地获得单元格的“所见即所得”字符串。理解cell.valuecell.text的区别,可以避免90%的读取类型问题。如果你正在构建数据导入、报表生成或ERP系统,请将此技巧加入你的技术备忘录。

希望这篇技术报道能帮助各位开发者少踩一个坑,高效完成Excel数据处理任务。