docx的使用,纯前端进行文档生成和导出

933 阅读5分钟

前提:业务需求

最近在公司进行一个项目,有一个需求需要进行报告的导出,其中有图有表。虽然不知道为什么一般这种给后端要给前端,但公司有需求就做呗。这个库需要和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版)有具体导出步骤和好的编程思想

后话

建议下次还是后端来做,做一个文档生成导出,能写二千行代码。。。前端地位有待加强