目录
依赖
代码
首页 web前端 js教程 怎么利用node生成word文档?使用库分享

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

Jul 28, 2022 pm 07:48 PM
node.js node node框架

怎么利用node生成word文档?下面本篇文章给大家介绍一下使用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中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

图文详解Node V8引擎的内存和GC 图文详解Node V8引擎的内存和GC Mar 29, 2023 pm 06:02 PM

本篇文章带大家深入了解NodeJS V8引擎的内存和垃圾回收器(GC),希望对大家有所帮助!

一文聊聊Node中的内存控制 一文聊聊Node中的内存控制 Apr 26, 2023 pm 05:37 PM

基于无阻塞、事件驱动建立的Node服务,具有内存消耗低的优点,非常适合处理海量的网络请求。在海量请求的前提下,就需要考虑“内存控制”的相关问题了。 1. V8的垃圾回收机制与内存限制 Js由垃圾回收机

node项目中如何使用express来处理文件的上传 node项目中如何使用express来处理文件的上传 Mar 28, 2023 pm 07:28 PM

怎么处理文件上传?下面本篇文章给大家介绍一下node项目中如何使用express来处理文件的上传,希望对大家有所帮助!

深入聊聊Node中的File模块 深入聊聊Node中的File模块 Apr 24, 2023 pm 05:49 PM

文件模块是对底层文件操作的封装,例如文件读写/打开关闭/删除添加等等 文件模块最大的特点就是所有的方法都提供的**同步**和**异步**两个版本,具有 sync 后缀的方法都是同步方法,没有的都是异

深入浅析Node的进程管理工具“pm2” 深入浅析Node的进程管理工具“pm2” Apr 03, 2023 pm 06:02 PM

本篇文章给大家分享Node的进程管理工具“pm2”,聊聊为什么需要pm2、安装和使用pm2的方法,希望对大家有所帮助!

Pi Node教学:什么是Pi节点?如何安装和设定Pi Node? Pi Node教学:什么是Pi节点?如何安装和设定Pi Node? Mar 05, 2025 pm 05:57 PM

PiNetwork节点详解及安装指南本文将详细介绍PiNetwork生态系统中的关键角色——Pi节点,并提供安装和配置的完整步骤。Pi节点在PiNetwork区块链测试网推出后,成为众多先锋积极参与测试的重要环节,为即将到来的主网发布做准备。如果您还不了解PiNetwork,请参考Pi币是什么?上市价格多少?Pi用途、挖矿及安全性分析。什么是PiNetwork?PiNetwork项目始于2019年,拥有其专属加密货币Pi币。该项目旨在创建一个人人可参与

一起聊聊Node中的事件循环 一起聊聊Node中的事件循环 Apr 11, 2023 pm 07:08 PM

事件循环是 Node.js 的基本组成部分,通过确保主线程不被阻塞来实现异步编程,了解事件循环对构建高效应用程序至关重要。下面本篇文章就来带大家深入了解Node中的事件循环 ,希望对大家有所帮助!

node无法用npm命令怎么办 node无法用npm命令怎么办 Feb 08, 2023 am 10:09 AM

node无法用npm命令是因为没有正确配置环境变量,其解决办法是:1、打开“系统属性”;2、找到“环境变量”->“系统变量”,然后编辑环境变量;3、找到nodejs所在的文件夹;4、点击“确定”即可。

See all articles