怎么利用node生成word文檔?下面本篇文章給大家介紹一下使用node生成word文檔的方法,分享一個(gè)實(shí)用庫(kù),聊聊該庫(kù)的使用方法,希望對(duì)大家有所幫助!
最近有項(xiàng)目需要用到生成word文檔,平時(shí)經(jīng)常用的都是通過模板生成,里面變量使用占位符替換,好處是快捷、方便、簡(jiǎn)單、不需要通過代碼調(diào)word樣式,確定是很多庫(kù)不支持圖片繪制(很多都是付費(fèi)功能),找一圈,發(fā)現(xiàn)一個(gè)很有意思的庫(kù),正好也滿足我們的需求,特此分享一下
依賴
// https://docx.js.org/#/ npm i docx // https://www.npmjs.com/package/download npm i download
說明,因?yàn)閐ocx繪圖只支持文件流,所以要把網(wǎng)絡(luò)文件下載到本地轉(zhuǎn)成buffer
代碼
話不多說,上代碼
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[] } // 所有數(shù)據(jù) 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) // 比較粗糙的控制單元格長(zhǎng)度邏輯 const longHeaders = ['身份證號(hào)', '備注'] // 下載遠(yuǎn)程資源到本地 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 } } } // 需要多個(gè)文件合一 const sections = groupNames.map(groupName => { const info = data[groupName] const { institution, leader, phone, coach, doctor, players } = info // 標(biāo)頭內(nèi)容 // let headers = ['序號(hào)', '照片', '姓名', '性別', '出生年月', '體重', '級(jí)別', '備注'] let headers = ['序號(hào)', '照片', '姓名', '性別', '身份證號(hào)', '級(jí)別', '備注'] // 表格數(shù)據(jù) 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: { // 設(shè)置寬度 dxa長(zhǎng)度單位 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({ // 將圖片轉(zhuǎn)化為buffer data: fs.readFileSync(cell), transformation: { width: 100, height: 129, }, }) ] })]: [new Paragraph({ alignment: AlignmentType.CENTER, children:[ new TextRun(cell || '') ] })] }) }) }) }) // 渲染報(bào)名表格 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: `自 由 搏 擊 比 賽 報(bào) 名 表(${groupName === Gender.Male ? '男子' : '女子'})`, heading: HeadingLevel.TITLE, alignment: AlignmentType.CENTER }), // 隊(duì)伍信息 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(` 領(lǐng)隊(duì): `), ], }), 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(` 聯(lián)系電話: `), ], }), 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(` 隊(duì)醫(yī): `), ], }), new TableCell({ width: { size: 1300, type: WidthType.DXA, }, borders: noBoder, children: [ new Paragraph(`${doctor}`) ], }), ], }), ] }), // 用于段落距離(table無法設(shè)置spacing屬性) new Paragraph({ spacing: { // 通過調(diào)整before值來調(diào)整段落漸進(jìn) before: 400, }, text: ``, }), // 選手信息 table, // 印章和時(shí)間 new Paragraph({ style: "wellSpaced", children: [ new TextRun({ text: 'tttt報(bào)名單位章:tttttt', }), new TextRun({ text: '年tt' }), new TextRun({ text: '月tt' }), new TextRun({ text: '日' }) ] }) ] } }) // 創(chuàng)建整個(gè)文檔 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: '江蘇省南京市舜禹集團(tuán)總部', leader: '王猛(男)', phone: '18861856665', coach: '劉國(guó)梁(男)', 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)