前提:业务需求
最近在公司进行一个项目,有一个需求需要进行报告的导出,其中有图有表。虽然不知道为什么一般这种给后端要给前端,但公司有需求就做呗。这个库需要和docxtemplater库进行对比,docxtemplater库是模板中字符串的替换,适用于固定模板的前端文档生成。
举个简单例子:
一个报告如果只需要每次生成的签名和照片不同,那肯定docxtemplater库好,只需要替换字串和图片。但如果还要求报告不仅根据每次数据不同添加表格和各种图,那就只能用docx了。 docx
具体使用
1.1段落的具体设置(全局)
import { Document} from "docx"
import { AlignmentType, Document, Paragraph, Border, UnderlineType } from 'docx'
export const document = {
creator: "Summy", // 作者
title: "Sample Document", // 标题
description: "A brief example of using docx", // 描述
// 以下是设置全局颜色的地方
styles: {
paragraphStyles: [ // 段落样式
{
id: "Heading1",
name: "Heading 1",
basedOn: "Normal",
next: "Normal",
quickFormat: true,
run: {
size: 36,
bold: true,
italics: true,
color: "#F87654",
margin: {
top: 500,
bottom: 300
}
},
paragraph: { // 段落
indent: { //空格符,有start,left,right常见
left: 720
},
spacing: { //字体间距
line: Number,
before: Number,
after: Number,
lineRule: Number
}
}
},
{
id: "Heading2",
name: "Heading 2",
basedOn: "Normal",
next: "Normal",
quickFormat: true, // 快速格式化
run: {
size: 18, // 字号
bold: true, // 加粗
color: '#CB0000'
// underline: { // 设置下划线 DOUBLE是双下划线
// type: UnderlineType.DOUBLE,
// color: "FF0000"
// }
font: {
name: "Garamond" // 设置字体
}
},
paragraph: {
spacing: {
before: 240,
after: 120
}
},
},
{
id: 'Heading3',
name: 'Heading 3',
basedOn: 'Normal',
next: 'Normal',
quickFormat: true,
run: {
size: 24, //字体大小
bold: false,
position: '1pt',//占据位置大小
color: '#010203', //颜色
},
paragraph: {
// 段落
alignment: AlignmentType.CENTER,
spacing: {
// 字间距
after: 20,
},
},
},
{
id: "aside",
name: "Aside",
basedOn: "Normal",
next: "Normal",
run: {
color: "999999",
italics: true
},
paragraph: {
indent: {
left: 720
},
spacing: {
line: 276
}
},
},
{
id: "Foote",
name: "Foote",
basedOn: "Normal",
quickFormat: true,
run: {
size: 14, // 字号
bold: true, // 加粗
color: '#CB0000'
}
}
]
},
numbering: { // 设置项目编号
config: [ // 配置
{
reference: "my-crazy-numbering", // 参考
levels: [ // 水平
{
level: 0,
format: "lowerLetter",
text: "%1)",
alignment: AlignmentType.LEFT // 左右居中 AlignmentType.LEFT(RIGHT CENTER)
}
]
}
]
}
}
1.2段落的具体设置(单独)
new Paragraph({
heading: 'Heading3', //1:可以使用库提供的默认几种样式,类似标题大小写,2:如果全局中定义的样式,这里Heading3就是id,样式即可生效
alignment: AlignmentType.DISTRIBUTE,//主要left,right,center,distribute四种,居左,居右,居中和两端对齐
//下面配置项参考上段代码
children: [
new TextRun({
text: `关键监测参数运行分析: `,
}),
new TextRun({
text: ` · · · · · · · · · · · · 5`,
}),
],
}),
2.页眉页脚
headers: {
//首页的页眉设置
first: new Header({ //不是首页直接设置 default:
children: [
new Paragraph({
bidirectional: true,
children: [
new ImageRun({ //页眉的图片设置
data: blobData, //图片的数据(注意格式,是blob)
transformation: { //图片的大小
width: 60,
height: 20,
},
floating: { //图片在页眉的位置
horizontalPosition: {
offset: 1014400,
},
verticalPosition: {
offset: 714400,
},
margins: {
left: 0,
// bottom: 0,
// bottom: 201440,
},
},
}),
],
}),
new Paragraph({
bidirectional: true,
children: [],
}),
new Paragraph({ //设置页眉文字
heading: 'Heading1',
bidirectional: true,
children: [
new TextRun({
text: `https:www.imotorlinx.com `,
// bold: true,
// rightToLeft: true,
}),
],
}),
],
}),
}
footers: {
//页脚的设置
first: new Footer({ //和页眉类似
children: [
new Paragraph({
alignment: AlignmentType.RIGHT,
children: [
new TextRun({
children: [PageNumber.CURRENT, '/', PageNumber.TOTAL_PAGES], //当前页数,/,总页数
}),
],
}),
new Paragraph({
alignment: AlignmentType.RIGHT,
children: [],
}),
new Paragraph({
alignment: AlignmentType.RIGHT,
children: [],
}),
],
}),
},
3.表格的插入
const table = new Table({
rows: [
new TableRow({
children: [
new TableCell({
children: [new Paragraph("Hello")],
}),
new TableCell({
children: [],
})
]
}),
new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [new Paragraph("World")],
})
]
})
]
})
new TableCell({
children: [new Paragraph("World")],
// width: { // 设置表格的宽度
// size: 24,
// type: WidthType, // type 类型: AUTO DXA NIL PCT NUMBER
// },
// cantSplit: true, // 防止分页
// verticalAlign: VerticalAlign.CENTER, // 单元格中文本垂直居中
// textDirection: TextDirection.BOTTOM_TO_TOP_LEFT_TO_RIGHT, // 单元格中的文本方向对齐
// textDirection: TextDirection.TOP_TO_BOTTOM_RIGHT_TO_LEFT, // 单元格中的文本方向对齐
// margins: { // 单元格中的距离
// top: convertInchesToTwip(0.69),
// bottom: convertInchesToTwip(0.69),
// left: convertInchesToTwip(0.69),
// right: convertInchesToTwip(0.69),
// },
// shading: { // 阴影设置
// fill: "b79c2f",
// val: ShadingType.REVERSE_DIAGONAL_STRIPE, // ShadingType.PERCENT_95 ShadingType.PERCENT_10 ShadingType.CLEAR
// color: "auto",
// },
// float: { // 浮动设置
// horizontalAnchor: TableAnchorType.MARGIN,
// verticalAnchor: TableAnchorType.MARGIN,
// relativeHorizontalPosition: RelativeHorizontalPosition.RIGHT,
// relativeVerticalPosition: RelativeVerticalPosition.BOTTOM,
// overlap: OverlapType.NEVER,
// },
// rowSpan: [NUMBER_OF_CELLS_TO_MERGE], // 行合并单元格 rowSpan:3 (合并行三个单元格)
// columnSpan: [NUMBER_OF_CELLS_TO_MERGE], // 列合并单元格
/SSSborders: { // 自定义边框
// top: {
// style: BorderStyle.DASH_DOT_STROKED,
// size: 3,
// color: "red"
// },
// bottom: {
// style: BorderStyle.DOUBLE,
// size: 3,
// color: "blue"
// },
// left: {
// style: BorderStyle.DASH_DOT_STROKED,
// size: 3,
// color: "green"
// },
// right: {
// style: BorderStyle.DASH_DOT_STROKED,
// size: 3,
// color: "#ff8000"
// }
// }
}),
// new TableCell({ // 添加图像
// children: [new Paragraph(image)],
// })
4.图表的插入
其实类似于图片 这题提到是因为遇到过如果是根据数据生成echart图,将其放入报告中的话,提供一下我的解决方法
if (ite == 'trendData') {
//处理接口返回的数据
let chartDomtrend: any = null;
const echartstrendData = item[ite];
let echart2trendData: any = null;
const data = { x: [] as any[], y: [] as any[] };
echartstrendData.forEach((it) => {
data.x.push(it.time);
data.y.push(it.value);
});
console.log('data', data);
chartDomtrend = document.getElementById('id');
// count++;
let myCharttrend: any = null;
let optionTrend: any = null;
myCharttrend = echarts.init(chartDomtrend);
optionTrend = {
animation: false, //重点,不加这个的话文档中图片是只有坐标轴
xAxis: {
type: 'category',
boundaryGap: false,
axisLine: {
onZero: false,
color: '#666669',
lineStyle: {
type: 'normal',
color: '#666669', //左边线的颜色
width: '1', //坐标线的宽度
},
},
axisLabel: {
formatter: '{value} ',
color: '#666669',
},
data: data.x,
},
yAxis: {
name: 'm/s2',
type: 'value',
splitLine: {
lineStyle: {
color: '#EEEEF1',
},
},
axisLine: {
color: '#666669',
lineStyle: {
type: 'normal',
color: '#666669', //左边线的颜色
width: '1', //坐标线的宽度
},
},
},
series: [
{
itemStyle: {
normal: {
lineStyle: {
width: 0.2, // 0.1的线条是非常细的了
},
},
},
type: 'line',
smooth: true,
data: data.y,
},
],
};
myCharttrend.setOption(optionTrend);
//图像数据转换
const imgDataPushTrend = async () => {
const img1 = new Image();
img1.src = myCharttrend.getDataURL({
pixelRatio: 2,
backgroundColor: '#fff',
});
const echartDataTrend = await fetch(img1.src);
echart2trendData = await echartDataTrend.blob();
return echart2trendData;
};
//将生成的图片插入children的数组内
temp.push(
new Paragraph({
children: [
new ImageRun({
data: imgDataPushTrend(),
transformation: {
width: 600,
height: 400,
},
}),
],
}),
);
}
总结
上述将文档一般用到的各个方法整理了一下,可能还有不全的,建议参考官方文档,不过官方文档确实难用。 官方总结很好的使用例子(React版)有具体导出步骤和好的编程思想
后话
建议下次还是后端来做,做一个文档生成导出,能写二千行代码。。。前端地位有待加强