需要

最近有我的项目须要用到生成word文档,平时常常用的都是通过模板生成,外面变量应用占位符替换,益处是快捷、不便、简略、不须要通过代码调word款式,确定是很多库不反对图片绘制(很多都是付费性能),找一圈,发现一个很有意思的库,正好也满足咱们的需要,特此分享一下

依赖

// https://docx.js.org/#/npm i docx // https://www.npmjs.com/package/downloadnpm i download

阐明,因为docx绘图只反对文件流,所以要把网络文件下载到本地转成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[]}// 所有数据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)    }  }}// 生成wordasync 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)