一、需求

word自带图表不能满足需求,并且编写过程繁琐,需要一个页面,主要功能是能读取服务器word模板,根据后台给的数据自动生成echarts图表插入word指定位置然后点击能下载插入echarts后的最新word文档实现数据更新就生成新的word表格节省人力和时间。其次附带的一些功能比如预览,类似ctrl+f检索,批注内容什么的,记录一下问题

二、效果(demo)

这里只是实现大体效果页面样式没有设计

1.1 word模板部分示例

在这里插入图片描述

1.2 导出之后的word

在这里插入图片描述

1.3 页面预览效果(左侧目录,中间预览,右侧批注,实现目录检索并对位添加批注):

在这里插入图片描述

三、实现

1.1 需要安装的包
//-- 安装 docxtemplater
npm install docxtemplater pizzip  --save
//-- 安装 jszip-utils
npm install jszip-utils --save 
//-- 安装 jszip
npm install jszip --save
//-- 安装 FileSaver
npm install file-saver --save
//安装 docxtemplater-image-module-free
npm install --save docxtemplater-image-module-free 
//安装PizziP
npm install pizzip --save
//docx-preview预览插件
npm i docx-preview --save
//字体载入监测插件
npm i FontFaceObserver
1.2 需要引入相关文件和包

import PizZip frompizzip’;
import docxtemplater from ‘docxtemplater’;
import JSZipUtils from ‘jsziputils’;
import { defaultOptions, renderAsync } from ‘docxpreview’;
import { saveAs } from ‘filesaver’;
//请求数据api
import { getTreeData, getTextData, getChartsData, searchLocal, getCommentsInfo, updateSubDocx } from ‘./api/api.js’;
import FontFaceObserver from ‘fontfaceobserver’;
const echarts = require(‘echarts/lib/echarts’);
require(‘echarts/lib/component/tooltip’);
require(‘echarts/lib/component/legend’);
require(‘echarts/lib/chart/pie’);
import * as charts from ‘./charts’;
import axios from ‘axios’;
import Moment from ‘moment’;

1.3 主要功能
1.3.1 预览功能
    //日报刷新
    view() {
        let ImageModule = require('docxtemplater-image-module-free');
        // 点击导出word
        let _this = this;
        // 读取并获得模板文件二进制内容,本地的模板就放在public下面,然后直接文件名可以读取到
        //服务器文件,就需要一个可以访问地址,如:http:192.168.1.1:12034/doc/xxxxxxx.docx
        JSZipUtils.getBinaryContent('xxxxxxx.docx', function (error, content) {
            if (error) {
                throw error;
            }
            let opts = {};
            opts.centered = true;
            opts.fileType = 'docx';
            opts.getImage = function (chartId) {
                return _this.base64DataURLToArrayBuffer(chartId);
            };
            opts.getSize = function () { //设置预览和导出的echarts尺寸为650*350
                return [650, 350];
            };
            let imageModule = new ImageModule(opts);
            let zip = new PizZip(content);
            let doc = new docxtemplater();
            doc.attachModule(imageModule);
            doc.loadZip(zip);
            let urlList = [];
            //后端数据处理后形成的charts数据(多个图表集合),其中chartsUrl初始化echarts后,调用getDataURL方法得到的url链接
            //chartsUrl = myChart.getDataURL({pixelRatio: 2});
            _this.chartData.map((i, index) => {
                _this.$set(urlList, `img${i.type}`, i.chartsUrl);
            });
            doc.setData({
             //后台返回文字字段信息 ,对应模板的字段,
             //示例:{electric:'全市供电最大负荷100000万千瓦,.........',eventList:[title:'700多名红军战士背靠背坐着牺牲了',hot:19697,index:1]}
                ..._this.fieldInfo,
                //echarts生成的url列表
                ...urlList,
                periodsNum
            });
            try {
                doc.render();
                // doc.render(_this.wordContentData).then(() => {
                // });
            } catch (error) {
                let e = {
                    message: error.message,
                    name: error.name,
                    stack: error.stack,
                    properties: error.properties
                };
                throw error;
            }
            _this.outFiles = doc.getZip().generate({
                type: 'blob',
                mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
            });
            renderAsync(_this.outFiles, document.getElementById('dailyContent'));   //页面渲染
            _this.isAll = true;
            _this.getCommentsInfo('');//接口获取批注的信息
        });
    },
	// base64转arrayBuffer
    base64DataURLToArrayBuffer(dataURL) {
        const base64Regex = /^data:image/(png|jpg|svg|svg+xml);base64,/;
        if (!base64Regex.test(dataURL)) {
            return false;
        }
        const stringBase64 = dataURL.replace(base64Regex, '');
        let binaryString;
        if (typeof window !== 'undefined') {
            binaryString = window.atob(stringBase64);
        } else {
            binaryString = new Buffer(stringBase64, 'base64').toString('binary');
        }
        const len = binaryString.length;
        const bytes = new Uint8Array(len);
        for (let i = 0; i < len; i++) {
            const ascii = binaryString.charCodeAt(i);
            bytes[i] = ascii;
        }
        return bytes.buffer;
    },
1.3.2 下载功能
//导出
exportWord() {
    let month = new Date().getMonth() + 1;
    let date = new Date().getDate();
    saveAs(this.outFiles, `xxxxxx日报第${this.fieldInfo.num}期-${month}月${date}日.docx`);
},
1.3.3树形搜索右侧批注生成

实际上是实现了类似ctrl+f的功能点击目录,在渲染好的数据里面查找关键字,用新的带颜色标签替换掉原来的标签,这样就有了颜色,再使用scrollIntoView()方法跳转搜索结果第一个,同时请求批注接口,拿到批注后设置批注的位置与标颜色的关键字等高,

search(value, data) {
   let main = document.querySelector('.dailyContent'); //中间预览的容器
   let innerHTML = main.innerHTML;
   // 每次搜索之前都需要将em替换回来,不然就会出现em里面em的情况而导致em数量一直叠加
   let emReg1 = new RegExp('<em class="ele" style="background-color: yellow">', 'g');
   innerHTML = innerHTML.replace(emReg1, '');
   let emReg2 = new RegExp('</em>', 'g');
   innerHTML = innerHTML.replace(emReg2, '');
   // 设置搜索背景色,同时添加class为ele
   if (value.label != '') {
       let reg = new RegExp(value.label, 'g');
       innerHTML = innerHTML.replace(reg, '<em class="ele" style="background-color: yellow">' + value.label + '</em>');
   }
   // 替换配置
   main.innerHTML = innerHTML;
   let ele = document.querySelector('.ele');
   //必须能拿到元素,才能调用scrollIntoView()方法,不然会报错
   if (ele) {
       ele.scrollIntoView(true);
       this.isSearch = true;
       document.documentElement.scrollTop -= 150; //scrollIntoView()方法可能位置有偏差,根据情况做一些调整
       if (value.isLeaf) {//判断是不是子目录,子目录才有批注
           this.isAll = false;
           this.searchType = data.type;
           this.isAll = false;
           this.getCommentsInfo(this.searchType);
       }
       let comments = document.querySelector('.comments'); //获取批注的容器,设置容器的位置
       comments.style.top = ele.offsetTop + 280 + 'px';
   }
},    

四、问题总结

1.关于模板

1.1 下载下来的word文字大小字体,行间距样式都完全是根据模板文件来的,所以说模板的样式要求很高,如果渲染格式问题,多半都是因为模板不规范
1.2 模板的一些语法,必须要了解,主要有以下几种

2.echarts相关

2.1 echarts外部字体引入之后, 设置fontFamily后没有效果,因为渲染echarts的时候,字体文件可能没有引入进来,我开始使用了document.fonts.ready.then(()=>{}) 判断加载完成初始化echarts,发现没有作用,后面使用监测字体是否加载完成插件FontFaceObserver,等所有字体加载完成执行初始化

let fontA = new FontFaceObserver('fzfs_GBK');
let fontB = new FontFaceObserver('TimesNewRoman');
Promise.all([fontA.load(), fontB.load()]).then(() => {
    this.initLineCharts();
});

2.2 折线图柱状图,各种混合label数值重叠简单点就设置字号,透明度偏移。提供一种思路就是使用series中的labelLayout函数判断同一x轴位置上,柱1与柱2的差值大于或者等于0,即柱子更高的,label更靠上(返回的dx,dy代表了位置,dy为负数且越小,则label离柱子约远)
2.3 echarts横坐标汉字数字分开设置不同的字体,例如x轴为 01月01日,01月02日…,需要汉字为仿宋,数字为新罗马

axisLabel: {
    interval: 0,
    rich: {
      rich_Acenter: {
        color: '#000',
        fontFamily: 'fzfs_GBK',
        fontSize: 12,
      },
      rich_Bcenter: {
        color: 'rgba(89,89,89,1)',
        fontFamily: 'TimesNewRoman',
        fontSize: 12,
      },
    },
    formatter: function (params) {
      var arr = [];
      var tmp = '';
      for (var i = 0; i < params.length; i++) {
        if (params.charAt(i) >= '0' &amp;&amp; params.charAt(i) <= '9') {
          tmp += params.charAt(i);
        } else {
          //三种情况,这里判断非空的情况
          if (tmp) {
            arr.push(tmp);
            tmp = '';
          }
        }
      }
      //最后一位索引数字,需额外判断
      if (tmp) {
        arr.push(tmp);
        tmp = '';
      }
      return `{rich_Bcenter|${arr[0]}}` + `{rich_Acenter|月}` +`{rich_Bcenter|${arr[1]}}` + `{rich_Acenter|日}`
    },
  },
3.本地模板读取和服务器模板读取

JSZipUtils.getBinaryContent(‘xxxxxx.docx’,(error,content)=>{})
本地模板,只需要把文件放入public目录
服务器模板,则需要一个访问服务器地址可以打印回调函数的error信息,如果出现引入报错, 极有可能文件格式正确

4.代码执行的先后问题

很多时候,需要按顺序执行代码,否则会出现很多拿不到数据,拿不到dom元素的情况,这里进入页面就自动生成预览,顺序请求完所有的数据,载入字体后,初始化echarts,读取模板,添加模板数据,渲染页面,多使用Promise.all, $nextTick(),尽量避免使用定时器


后期可能还会有模板拆分,历史预览和下载这些功能遇到问题再总结

原文地址:https://blog.csdn.net/weixin_50561602/article/details/128127198

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_43856.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注