How to use node to generate word documents? The following article will introduce to you how to use node to generate word documents, share a practical library, and talk about how to use the library. I hope it will be helpful to everyone!
Recently, there is a project that needs to generate word documents. Usually, they are generated through templates. The variables inside are replaced with placeholders. The advantage is that it is fast, convenient and simple. , there is no need to adjust the word style through code. It is sure that many libraries do not support image drawing (many of them are paid functions). After searching around, I found a very interesting library that just meets our needs. I would like to share it
// https://docx.js.org/#/ npm i docx // https://www.npmjs.com/package/download npm i download
Instructions, because docx drawing only supports file streams, so network files must be downloaded locally and converted into buffer
Without further ado, here’s the code
import * as fs from "fs" import { Document, Packer, Paragraph, TextRun, ImageRun, HeadingLevel, AlignmentType, convertInchesToTwip, Table, TableRow, TableCell, WidthType, VerticalAlign, BorderStyle } from "docx" const download = require('download') // 性别 enum Gender { Male = 'male', Female = 'female' } // 选手 type PlayerSchema = { name: string gender: string idCard?: string birthday?: string weight?: string remark?: string avatar?: string localAvatar?: string level: string } type GroupSchema = { // gender: Gender institution: string leader: string phone: string coach: string doctor: string players: PlayerSchema[] } // 所有数据 interface DataSchema { [key: string]: GroupSchema } // 表格无边框 const noBoder = { top: { style: BorderStyle.NIL, size: 0, color: 'FFFFFF' }, bottom: { style: BorderStyle.NIL, size: 0, color: 'FFFFFF' }, left: { style: BorderStyle.NIL, size: 0, color: 'FFFFFF' }, right: { style: BorderStyle.NIL, size: 0, color: 'FFFFFF' } } // 删除下载的照片及文件夹 function delStaticFile(groupNames: string[]) { for (let groupName of groupNames) { if (fs.existsSync(groupName)) { const files = fs.readdirSync(groupName) files.map((file: string) => { let curPath = groupName + "/" + file // 删除选手招聘 fs.unlinkSync(curPath) }) fs.rmdirSync(groupName) } } } // 生成word async function generate (data: DataSchema) { const groupNames = Object.keys(data) // 比较粗糙的控制单元格长度逻辑 const longHeaders = ['身份证号', '备注'] // 下载远程资源到本地 for (let groupName of groupNames) { if (!fs.existsSync(groupName)) { fs.mkdirSync(groupName) } const players = data[groupName].players for (let player of players) { if (player.avatar) { const avatarArr = player.avatar.split('/') const fileName = `${groupName}/${avatarArr[avatarArr.length - 1]}` if (!fs.existsSync(fileName)) { await download(player.avatar, groupName) } // 下载后的本地的资源路径 player.localAvatar = fileName } } } // 需要多个文件合一 const sections = groupNames.map(groupName => { const info = data[groupName] const { institution, leader, phone, coach, doctor, players } = info // 标头内容 // let headers = ['序号', '照片', '姓名', '性别', '出生年月', '体重', '级别', '备注'] let headers = ['序号', '照片', '姓名', '性别', '身份证号', '级别', '备注'] // 表格数据 let tableData: any[][] = [] tableData.push(headers) // 填充选手信息 let index = 1 for (let player of players) { tableData.push([ index.toString(), player.localAvatar || '', player.name, player.gender === Gender.Male ? '男' : '女', player.idCard, // player.birthday, // player.weight, player.level, player.remark, ]) index++ } // 表格渲染 const tableRows = tableData.map(colums => { return new TableRow({ children: colums.map(cell => { return new TableCell({ verticalAlign: VerticalAlign.CENTER, width: { // 设置宽度 dxa长度单位 https://stackoverflow.com/questions/14360183/default-wordml-unit-measurement-pixel-or-point-or-inches size: longHeaders.some(j => cell === j) ? 3000 : 800, type: WidthType.DXA, }, children: cell && colums.findIndex(i => i === cell) === 1 && cell !== '照片' ? [new Paragraph({ alignment: AlignmentType.CENTER, children: [ new ImageRun({ // 将图片转化为buffer data: fs.readFileSync(cell), transformation: { width: 100, height: 129, }, }) ] })]: [new Paragraph({ alignment: AlignmentType.CENTER, children:[ new TextRun(cell || '') ] })] }) }) }) }) // 渲染报名表格 const table = new Table({ alignment: AlignmentType.CENTER, rows: tableRows }) return { properties: {}, children: [ // new Paragraph({ // style: "wellSpaced", // children: [ // new TextRun({ // text: '附件 4', // color: '999999', // }) // ], // }), // 表头信息 new Paragraph({ spacing: { before: 400, after: 400 }, style: "Title", text: `自 由 搏 击 比 赛 报 名 表(${groupName === Gender.Male ? '男子' : '女子'})`, heading: HeadingLevel.TITLE, alignment: AlignmentType.CENTER }), // 队伍信息 new Table({ style: "wellSpaced", alignment: AlignmentType.CENTER, borders: noBoder, rows: [ new TableRow({ children: [ new TableCell({ width: { size: 600, type: WidthType.DXA, }, borders: noBoder, children: [ new Paragraph(`单位: `), ], }), new TableCell({ width: { size: 1800, type: WidthType.DXA, }, borders: noBoder, children: [ new Paragraph(`${institution}`) ], }), new TableCell({ width: { size: 700, type: WidthType.DXA, }, borders: noBoder, children: [ new Paragraph(` 领队: `), ], }), new TableCell({ width: { size: 1200, type: WidthType.DXA, }, borders: noBoder, children: [ new Paragraph(`${leader}`) ], }), new TableCell({ width: { size: 1100, type: WidthType.DXA, }, borders: noBoder, children: [ new Paragraph(` 联系电话: `), ], }), new TableCell({ width: { size: 1400, type: WidthType.DXA, }, borders: noBoder, children: [ new Paragraph(`${phone}`) ], }), new TableCell({ width: { size: 700, type: WidthType.DXA, }, borders: noBoder, children: [ new Paragraph(` 教练: `), ], }), new TableCell({ width: { size: 1300, type: WidthType.DXA, }, borders: noBoder, children: [ new Paragraph(`${coach}`) ], }), new TableCell({ width: { size: 700, type: WidthType.DXA, }, borders: noBoder, children: [ new Paragraph(` 队医: `), ], }), new TableCell({ width: { size: 1300, type: WidthType.DXA, }, borders: noBoder, children: [ new Paragraph(`${doctor}`) ], }), ], }), ] }), // 用于段落距离(table无法设置spacing属性) new Paragraph({ spacing: { // 通过调整before值来调整段落渐进 before: 400, }, text: ``, }), // 选手信息 table, // 印章和时间 new Paragraph({ style: "wellSpaced", children: [ new TextRun({ text: '\t\t\t\t报名单位章:\t\t\t\t\t\t', }), new TextRun({ text: '年\t\t' }), new TextRun({ text: '月\t\t' }), new TextRun({ text: '日' }) ] }) ] } }) // 创建整个文档 const doc = new Document({ styles: { paragraphStyles: [ { id: "Title", name: "title", basedOn: "Normal", next: "Normal", quickFormat: true, run: { size: 30, bold: true, color: "000000" } }, { id: "wellSpaced", name: "Well Spaced", basedOn: "Normal", quickFormat: true, paragraph: { indent: { left: convertInchesToTwip(0.5), }, spacing: { before: 400, }, }, }, ], }, sections }) // 生成word文档 Packer.toBuffer(doc).then((buffer) => { fs.writeFileSync("enrolls.docx", buffer) }) // 删除下载的选手照片 delStaticFile(groupNames) } const group: GroupSchema = { institution: '江苏省南京市舜禹集团总部', leader: '王猛(男)', phone: '18861856665', coach: '刘国梁(男)', doctor: '杨永信(女)', players: [ { name: '莱昂纳多迪卡普里奥', gender: Gender.Male, idCard: '320888199001019878', birthday: '1999-01-02', weight: '60kg', avatar: 'https://multi-xm.oss-cn-hangzhou.aliyuncs.com/atms/13.png', remark: '', level: '60kg' }, { name: '张三', gender: Gender.Male, idCard: '320888199001019878', birthday: '1999-01-02', weight: '60kg', avatar: 'https://multi-xm.oss-cn-hangzhou.aliyuncs.com/atms/7.png', remark: '', level: '60kg' }, { name: '张三', gender: Gender.Male, idCard: '320888199001019878', birthday: '1999-01-02', weight: '60kg', avatar: 'https://multi-xm.oss-cn-hangzhou.aliyuncs.com/atms/14.png', remark: '', level: '60kg' }, { name: '张三', gender: Gender.Male, idCard: '320888199001019878', birthday: '1999-01-02', weight: '60kg', avatar: 'https://multi-xm.oss-cn-hangzhou.aliyuncs.com/atms/14.png', remark: '', level: '60kg' }, { name: '张三', gender: Gender.Male, idCard: '320888199001019878', birthday: '1999-01-02', weight: '60kg', avatar: 'https://multi-xm.oss-cn-hangzhou.aliyuncs.com/atms/14.png', remark: '', level: '60kg' }, { name: '张三', gender: Gender.Male, idCard: '320888199001019878', birthday: '1999-01-02', weight: '60kg', avatar: 'https://multi-xm.oss-cn-hangzhou.aliyuncs.com/atms/14.png', remark: '', level: '60kg' }, { name: '张三', gender: Gender.Male, idCard: '320888199001019878', birthday: '1999-01-02', weight: '60kg', avatar: 'https://multi-xm.oss-cn-hangzhou.aliyuncs.com/atms/14.png', remark: '', level: '60kg' }, { name: '张三', gender: Gender.Male, idCard: '320888199001019878', birthday: '1999-01-02', weight: '60kg', avatar: 'https://multi-xm.oss-cn-hangzhou.aliyuncs.com/atms/14.png', remark: '', level: '60kg' }, { name: '张三', gender: Gender.Male, birthday: '1999-01-02', weight: '60kg', avatar: 'https://multi-xm.oss-cn-hangzhou.aliyuncs.com/atms/14.png', idCard: '320888199001019878', remark: '', level: '60kg' }, { name: '张三', gender: Gender.Male, idCard: '320888199001019878', birthday: '1999-01-02', weight: '60kg', avatar: 'https://multi-xm.oss-cn-hangzhou.aliyuncs.com/atms/14.png', remark: '', level: '60kg' }, { name: '张三', gender: Gender.Male, idCard: '320888199001019878', birthday: '1999-01-02', weight: '60kg', avatar: 'https://multi-xm.oss-cn-hangzhou.aliyuncs.com/atms/14.png', remark: '', level: '60kg' }, { name: '张三', gender: Gender.Male, idCard: '320888199001019878', birthday: '1999-01-02', weight: '60kg', avatar: 'https://multi-xm.oss-cn-hangzhou.aliyuncs.com/atms/14.png', remark: '', level: '60kg' } ] } const data: DataSchema = { [Gender.Male]: group, [Gender.Female]: group, } generate(data)
For more node-related knowledge, please visit: nodejs tutorial!
The above is the detailed content of How to use node to generate word documents? Share using libraries. For more information, please follow other related articles on the PHP Chinese website!