Node.js需要借助第三方模块来读写EXCEL。
可以备选的模块有
Sheetjs(Node.js中模块名为xlsx)、exceljs、node-xlsx等。
这里选用xlsx进行EXCLE文件读写
官网docs.sheetjs.com
明显缺陷:默认的是社区版本(CE),无法保持EXCEL原有样式
演示环境:
1.Node.js v21.0.0、npm 10.2.0(以下代码支持Node.js v18.x以上)
2.系统Win11+MS Office2021。文章编辑Markdown主题awesome-green
3.[可选]VSCode查看EXCEL插件:Office Viewer
〇、学一点EXCLE基础知识
三个重要概念(由大到小)
工作簿→工作表→单元格
工作簿(Workbook):整个EXCEL文件,可包含一到多个工作表。
工作表(Worksheet):一个由列行构成的数据表格。
单元格(Cell):工作表中最小的组成单位。通常由列和行组成,
例如:
G5表示G列第5行的单元格。
一、读取EXCEL文件
1. Node.js安装xlsx模块
npm -v //查看 Node.js模块管理工具npm 版本,测试环境正常
npm install xlsx // npm install 模块名
2.读取EXCEL文件(一个工作簿 )
const XLSX = require('xlsx')
const 工作簿 = XLSX.readFile('百万.xlsx')
console.log( 工作簿 )
读取到的 工作簿 、工作簿.SheetNames、工作簿.Sheets属性,内容如下:
//工作簿
{
/* ...此处隐藏若干属性... */
SheetNames: [...], //所有工作表名
Sheets: {...}, //所有工作表数据
/* ...此处隐藏若干属性... */
}
//工作簿.SheetNames 所有工作表名 Array类型
[ '第一张工作表', '第二张工作表' ]
//工作簿.Sheets 所有工作表数据 Object类型
{
'第一张工作表': {
'!ref': 'A1:B3',
A1: {
t: 's', //数据类型 s 表示字符串
v: '序号',
r: '<t>序号</t><phoneticPr fontId="1" type="noConversion"/>',
h: '序号',
w: '序号'
},
B1: {
t: 's',
v: '姓名',
r: '<t>姓名</t><phoneticPr fontId="1" type="noConversion"/>',
h: '姓名',
w: '姓名'
},
A2: { t: 'n', v: 1, w: '1' },
B2: {
t: 's',
v: '百万',
r: '<t>百万</t><phoneticPr fontId="1" type="noConversion"/>',
h: '百万',
w: '百万'
},
A3: { t: 'n', v: 2, w: '2' },
B3: {
t: 's',
v: '牛牛',
r: '<t>牛牛</t><phoneticPr fontId="1" type="noConversion"/>',
h: '牛牛',
w: '牛牛'
},
'!margins': {
left: 0.7,
right: 0.7,
top: 0.75,
bottom: 0.75,
header: 0.3,
footer: 0.3
}
},
'第二张工作表': {
'!margins': {
left: 0.7,
right: 0.7,
top: 0.75,
bottom: 0.75,
header: 0.3,
footer: 0.3
}
}
}
2.1 工作表中 直接获取指定单元格的值
console.table(工作簿.Sheets) 打印的值如下
在工作表数据中,属性直接就是单元格的坐标。
// 截取的A1单元格的值
{
'第一张工作表': {
A1: {
t: 's', //数据类型 s 表示字符串
v: '序号',
r: '<t>序号</t><phoneticPr fontId="1" type="noConversion"/>',
h: '序号',
w: '序号'
}
}
}
// 1.获取第一个工作表的A1单元格的值
工作簿.Sheets['第一张工作表'].A1.v // 序号
// 2.获取第一个工作表的A1单元格的值
let 表名 = 工作簿.SheetNames[0] // 获取第一张工作表的名字
工作簿.Sheets[表名].A1.v // 序号
2.2 [推荐]工作表中 处理后 获取单元格的值
使用XLSX.utils.sheet_to_json( 工作表 ),将工作表数据转成JSON格式
const XLSX = require('xlsx')
const 工作簿 = XLSX.readFile('百万.xlsx')
let name = 工作簿.SheetNames[0]
let 工作表 = 工作簿.Sheets[name]
//sheet_to_json 将工作表数据转成JSON格式
let jsonArr = XLSX.utils.sheet_to_json( 工作表 )
console.table(jsonArr)
console.log(jsonArr)
//console.table(jsonArr)
┌─────────┬──────┬────────┐
│ (index) │ 序号 │ 姓名 │
├─────────┼──────┼────────┤
│ 0 │ 1 │ '百万' │
│ 1 │ 2 │ '牛牛' │
└─────────┴──────┴────────┘
//console.log(jsonArr)
[
{ '序号': 1, '姓名': '百万' },
{ '序号': 2, '姓名': '牛牛' }
]
二、写入EXCEL文件
1.获得一个空的 EXCEL文件
使用 xlsx 库来创建一个新的工作簿。并在其中添加一个工作表,然后将该工作簿写入一个名为 "文件名.xlsx" 的 Excel 文件
const XLSX = require('xlsx')
const 工作簿 = XLSX.utils.book_new( ) //创建一个新的工作簿
工作簿.SheetNames[0] = '第一张表名' //工作簿至少要有一个工作表
XLSX.writeFile(工作簿,'文件名.xlsx' )//输出到 指定文件名的EXCEL文件,注意必须要有.xlsx
2.创建有多张表的 EXCEL文件
采用了
XLSX.utils.aoa_to_sheet(arr) 将数组转化成表格数据
XLSX.utils.book_append_sheet(工作簿,工作表,'表名你的名字')向工作簿追加新表
const XLSX = require('xlsx')
// 第一步 创建一个新的工作簿
const 工作簿 = XLSX.utils.book_new()
// 第二步 创建数据,写入工作表
let arr = [
['序号','姓名'],
[1 ,'你的名字']
]
//aoa_to_sheet 将二维数组转换成 电子表格(sheet)
let 工作表 = XLSX.utils.aoa_to_sheet(arr)
// 第三步 把工作表添加到工作簿中
XLSX.utils.book_append_sheet(工作簿,工作表,'表名你的名字') //第一个表
XLSX.utils.book_append_sheet(工作簿,工作表,'表名室友名字') //第二个表
// 第四步 生成EXCEL文件
XLSX.writeFile(工作簿,'EXCEL文件名.xlsx' )
三、完整代码附录
1.XLSX对象
{
version: '0.18.5',
parse_xlscfb: [Function: parse_xlscfb],
parse_zip: [Function: parse_zip],
read: [Function: readSync],
readFile: [Function: readFileSync],
readFileSync: [Function: readFileSync],
write: [Function: writeSync],
writeFile: [Function: writeFileSync],
writeFileSync: [Function: writeFileSync],
writeFileAsync: [Function: writeFileAsync],
utils: {
encode_col: [Function: encode_col],
encode_row: [Function: encode_row],
encode_cell: [Function: encode_cell],
encode_range: [Function: encode_range],
decode_col: [Function: decode_col],
decode_row: [Function: decode_row],
split_cell: [Function: split_cell],
decode_cell: [Function: decode_cell],
decode_range: [Function: decode_range],
format_cell: [Function: format_cell],
sheet_add_aoa: [Function: sheet_add_aoa],
sheet_add_json: [Function: sheet_add_json],
sheet_add_dom: [Function: sheet_add_dom],
aoa_to_sheet: [Function: aoa_to_sheet],
json_to_sheet: [Function: json_to_sheet],
table_to_sheet: [Function: parse_dom_table],
table_to_book: [Function: table_to_book],
sheet_to_csv: [Function: sheet_to_csv],
sheet_to_txt: [Function: sheet_to_txt],
sheet_to_json: [Function: sheet_to_json],
sheet_to_html: [Function: sheet_to_html],
sheet_to_formulae: [Function: sheet_to_formulae],
sheet_to_row_object_array: [Function: sheet_to_json],
sheet_get_cell: [Function: ws_get_cell_stub],
book_new: [Function: book_new],
book_append_sheet: [Function: book_append_sheet],
book_set_sheet_visibility: [Function: book_set_sheet_visibility],
cell_set_number_format: [Function: cell_set_number_format],
cell_set_hyperlink: [Function: cell_set_hyperlink],
cell_set_internal_link: [Function: cell_set_internal_link],
cell_add_comment: [Function: cell_add_comment],
sheet_set_array_formula: [Function: sheet_set_array_formula],
consts: { SHEET_VISIBLE: 0, SHEET_HIDDEN: 1, SHEET_VERY_HIDDEN: 2 }
},
writeXLSX: [Function: writeSyncXLSX],
writeFileXLSX: [Function: writeFileSyncXLSX],
SSF: {
format: [Function: SSF_format],
load: [Function: SSF_load],
_table: {
'0': 'General',
'1': '0',
'2': '0.00',
'3': '#,##0',
'4': '#,##0.00',
'9': '0%',
'10': '0.00%',
'11': '0.00E+00',
'12': '# ?/?',
'13': '# ??/??',
'14': 'm/d/yy',
'15': 'd-mmm-yy',
'16': 'd-mmm',
'17': 'mmm-yy',
'18': 'h:mm AM/PM',
'19': 'h:mm:ss AM/PM',
'20': 'h:mm',
'21': 'h:mm:ss',
'22': 'm/d/yy h:mm',
'37': '#,##0 ;(#,##0)',
'38': '#,##0 ;[Red](#,##0)',
'39': '#,##0.00;(#,##0.00)',
'40': '#,##0.00;[Red](#,##0.00)',
'45': 'mm:ss',
'46': '[h]:mm:ss',
'47': 'mmss.0',
'48': '##0.0E+0',
'49': '@',
'56': '"上午/下午 "hh"時"mm"分"ss"秒 "'
},
load_table: [Function: SSF_load_table],
parse_date_code: [Function: SSF_parse_date_code],
is_date: [Function: fmt_is_date],
get_table: [Function: get_table]
},
stream: {
to_json: [Function: write_json_stream],
to_html: [Function: write_html_stream],
to_csv: [Function: write_csv_stream],
set_readable: [Function: set_readable]
},
CFB: {
version: '1.2.1',
find: [Function: find],
read: [Function: read],
parse: [Function: parse],
write: [Function: write],
writeFile: [Function: write_file],
utils: {
cfb_new: [Function: cfb_new],
cfb_add: [Function: cfb_add],
cfb_del: [Function: cfb_del],
cfb_mov: [Function: cfb_mov],
cfb_gc: [Function: cfb_gc],
ReadShift: [Function: ReadShift],
CheckField: [Function: CheckField],
prep_blob: [Function: prep_blob],
bconcat: [Function (anonymous)],
use_zlib: [Function: use_zlib],
_deflateRaw: [Function: _deflate],
_inflateRaw: [Function: _inflate],
consts: [Object]
}
}
}
2.工作簿对象
{
Directory: {
workbooks: [ '/xl/workbook.xml' ],
sheets: [ '/xl/worksheets/sheet1.xml', '/xl/worksheets/sheet2.xml' ],
charts: [],
dialogs: [],
macros: [],
rels: [],
strs: [],
comments: [],
threadedcomments: [],
links: [],
coreprops: [ '/docProps/core.xml' ],
extprops: [ '/docProps/app.xml' ],
custprops: [],
themes: [ '/xl/theme/theme1.xml' ],
styles: [ '/xl/styles.xml' ],
vba: [],
drawings: [],
metadata: [ '/xl/metadata.xml' ],
people: [],
TODO: [],
xmlns: 'http://schemas.openxmlformats.org/package/2006/content-types',
calcchain: '',
sst: '',
style: '/xl/styles.xml',
defaults: {
xml: 'application/xml',
bin: 'application/vnd.ms-excel.sheet.binary.macroEnabled.main',
vml: 'application/vnd.openxmlformats-officedocument.vmlDrawing',
data: 'application/vnd.openxmlformats-officedocument.model+data',
bmp: 'image/bmp',
png: 'image/png',
gif: 'image/gif',
emf: 'image/x-emf',
wmf: 'image/x-wmf',
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
tif: 'image/tiff',
tiff: 'image/tiff',
pdf: 'application/pdf',
rels: 'application/vnd.openxmlformats-package.relationships+xml'
}
},
Workbook: {
AppVersion: {},
WBProps: {
CodeName: 'ThisWorkbook',
allowRefreshQuery: false,
autoCompressPictures: true,
backupFile: false,
checkCompatibility: false,
date1904: false,
defaultThemeVersion: 0,
filterPrivacy: false,
hidePivotFieldList: false,
promptedSolutions: false,
publishItems: false,
refreshAllConnections: false,
saveExternalLinkValues: true,
showBorderUnselectedTables: true,
showInkAnnotation: true,
showObjects: 'all',
showPivotChartFilter: false,
updateLinks: 'userSet'
},
WBView: [],
Sheets: [ [Object], [Object] ],
CalcPr: {
calcCompleted: 'true',
calcMode: 'auto',
calcOnSave: 'true',
concurrentCalc: 'true',
fullCalcOnLoad: 'false',
fullPrecision: 'true',
iterate: 'false',
iterateCount: '100',
iterateDelta: '0.001',
refMode: 'A1'
},
Names: [],
xmlns: 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
Views: [ {} ]
},
Props: {
Application: 'SheetJS',
HyperlinksChanged: false,
SharedDoc: false,
LinksUpToDate: false,
ScaleCrop: false,
Worksheets: 2,
SheetNames: [ '表名你的名字', '表名室友名字' ]
},
Custprops: {},
Deps: {},
Sheets: {
'表名你的名字': {
'!ref': 'A1:B2',
A1: [Object],
B1: [Object],
A2: [Object],
B2: [Object]
},
'表名室友名字': {
'!ref': 'A1:B2',
A1: [Object],
B1: [Object],
A2: [Object],
B2: [Object]
}
},
SheetNames: [ '表名你的名字', '表名室友名字' ],
Strings: [],
Styles: {
NumberFmt: [
'General',
'0',
'0.00',
'#,##0',
'#,##0.00',
<4 empty items>,
'0%',
'0.00%',
'0.00E+00',
'# ?/?',
'# ??/??',
'm/d/yy',
'd-mmm-yy',
'd-mmm',
'mmm-yy',
'h:mm AM/PM',
'h:mm:ss AM/PM',
'h:mm',
'h:mm:ss',
'm/d/yy h:mm',
<14 empty items>,
'#,##0 ;(#,##0)',
'#,##0 ;[Red](#,##0)',
'#,##0.00;(#,##0.00)',
'#,##0.00;[Red](#,##0.00)',
<4 empty items>,
'mm:ss',
'[h]:mm:ss',
'mmss.0',
'##0.0E+0',
'@',
<6 empty items>,
'"上午/下午 "hh"時"mm"分"ss"秒 "'
],
Fonts: [ [Object] ],
Fills: [ [Object], [Object] ],
Borders: [ {} ],
CellXf: [ [Object] ]
},
Themes: {},
SSF: {
'0': 'General',
'1': '0',
'2': '0.00',
'3': '#,##0',
'4': '#,##0.00',
'9': '0%',
'10': '0.00%',
'11': '0.00E+00',
'12': '# ?/?',
'13': '# ??/??',
'14': 'm/d/yy',
'15': 'd-mmm-yy',
'16': 'd-mmm',
'17': 'mmm-yy',
'18': 'h:mm AM/PM',
'19': 'h:mm:ss AM/PM',
'20': 'h:mm',
'21': 'h:mm:ss',
'22': 'm/d/yy h:mm',
'37': '#,##0 ;(#,##0)',
'38': '#,##0 ;[Red](#,##0)',
'39': '#,##0.00;(#,##0.00)',
'40': '#,##0.00;[Red](#,##0.00)',
'45': 'mm:ss',
'46': '[h]:mm:ss',
'47': 'mmss.0',
'48': '##0.0E+0',
'49': '@',
'56': '"上午/下午 "hh"時"mm"分"ss"秒 "'
}
}