• 技术文章 >web前端 >js教程

    怎么利用node生成word文档?使用库分享

    青灯夜游青灯夜游2022-07-28 19:48:37转载176
    怎么利用node生成word文档?下面本篇文章给大家介绍一下使用node生成word文档的方法,分享一个实用库,聊聊该库的使用方法,希望对大家有所帮助!

    最近有项目需要用到生成word文档,平时经常用的都是通过模板生成,里面变量使用占位符替换,好处是快捷、方便、简单、不需要通过代码调word样式,确定是很多库不支持图片绘制(很多都是付费功能),找一圈,发现一个很有意思的库,正好也满足我们的需求,特此分享一下

    依赖

    // https://docx.js.org/#/
    npm i docx 
    
    // https://www.npmjs.com/package/download
    npm 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)
        }
      }
    }
    
    // 生成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)

    更多node相关知识,请访问:nodejs 教程

    以上就是怎么利用node生成word文档?使用库分享的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:segmentfault,如有侵犯,请联系admin@php.cn删除
    专题推荐:Node.js node node框架
    上一篇:JavaScript字典与集合(总结分享) 下一篇:一文了解JavaScript栈
    VIP课程(WEB全栈开发)

    相关文章推荐

    • 【活动】充值PHP中文网VIP即送云服务器• 火了!新的JavaScript运行时:Bun,性能完爆Node• 一文了解Node中的文件模块和核心模块• 聊聊Node.js中的进程、线程、协程与并发模型• 深入聊聊Node 异步和事件循环的底层实现和执行机制• 聊聊Node.js中的多进程和多线程
    1/1

    PHP中文网