需要
最近有我的项目须要用到生成 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)