Bootstrap

前端生成指定模板的word文件

需求:前端生成word文件

实现步骤:

  1. 安装依赖
npm install jszip-utils docxtemplater file-saver pizzip --save
npm install angular-expressions --save
npm install docxtemplater-image-module-free --save

docxtemplater:这个插件可以通过预先写好的word,excel等文件模板生成对应带数据的文件。
file-saver:适合在客户端生成文件的工具,它提供的接口saveAs(blob, “1.docx”)将会使用到,方便我们保存文件。
pizzip:这个插件用来创建,读取或编辑.zip的文件,同步的(还有一个插件是jszip,异步的)。
jszip-utils:与jszip/pizzip一起使用,jszip-utils 提供一个getBinaryContent(path, data)接口,path即是文件的路径,支持AJAX get请求,data为读取的文件内容。
docxtemplater-image-module-free:需要导出图片的话需要这个插件
docxtemplater:不支持jszip,会有报错,因此要使用PizZip

  1. 编辑Word模板
    模板内容如下
    例如:
  • 后台出参:
{
   tableData:[{name:'张三',age:18},{name:'李四',age:19}],
   school:'实验中学',
   logo:'图片的base64格式'
}
  • 模板内容:
    在这里插入图片描述
  1. 保存模板
    在这里插入图片描述

  2. 代码实现

  • ①生成Word文档的触发按钮
<bt-button type="secondary" @click="handleGetCheckReport">生成报告</bt-button>
  • ②定义公共生成文件方法(固定内容,可直接复制),路径:src/utils/doc.js
/**
 * 导出word文档(带图片)
 */
import Docxtemplater from 'docxtemplater'
import PizZip from 'pizzip'
import JSZipUtils from 'jszip-utils'
import { saveAs } from 'file-saver'
// import * as ImageModule from 'docxtemplater-image-module-free'

import * as expressions from 'angular-expressions'
/**
 * 将base64格式的数据转为ArrayBuffer
 * @param {Object} dataURL base64格式的数据
 */
function base64DataURLToArrayBuffer(dataURL) {
    const base64Regex = /^data:image\/(png|jpg|jpeg|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 = Buffer.from(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
}

export const exportWord = (tempDocxPath, data, fileName, imgSize) => {
    console.log(111, tempDocxPath, data, fileName, imgSize)
    //这里要引入处理图片的插件
    // var ImageModule = require('docxtemplater-image-module-free')
    // var expressions = require('angular-expressions')
    // var assign = require('lodash/assign')
    // var last = require('lodash/last')
    expressions.filters.lower = function (input) {
        // This condition should be used to make sure that if your input is
        // undefined, your output will be undefined as well and will not
        // throw an error
        if (!input) return input
        // toLowerCase() 方法用于把字符串转换为小写。
        return input.toLowerCase()
    }

    import('docxtemplater-image-module-free')
        .then(({ default: ImageModule }) => {
            // 使用 ImageModule
            JSZipUtils.getBinaryContent(tempDocxPath, (error, content) => {
                if (error) {
                    console.log(error)
                }
                expressions.filters.size = function (input, width, height) {
                    return {
                        data: input,
                        size: [width, height],
                    }
                }

                let opts = {}

                opts = {
                    //图像是否居中
                    centered: true,
                }
                opts.getImage = chartId => {
                    //将base64的数据转为ArrayBuffer
                    return base64DataURLToArrayBuffer(chartId)
                }
                opts.getSize = function (img, tagValue, tagName) {
                    //自定义指定图像大小
                    if (tagName == 'signature') {
                        return [80, 40]
                    } else {
                        return [400, 500]
                    }
                }

                // 创建一个JSZip实例,内容为模板的内容
                const zip = new PizZip(content)
                // 创建并加载 Docxtemplater 实例对象
                // 设置模板变量的值
                let doc = new Docxtemplater()
                doc.attachModule(new ImageModule(opts))
                doc.loadZip(zip)
                // doc.setOptions({ parser: angularParser })
                doc.setData(data)
                try {
                    // 呈现文档,会将内部所有变量替换成值,
                    doc.render()
                } catch (error) {
                    const e = {
                        message: error.message,
                        name: error.name,
                        stack: error.stack,
                        properties: error.properties,
                    }
                    console.log('err', { error: e })
                    // 当使用json记录时,此处抛出错误信息
                    throw error
                }
                // 生成一个代表Docxtemplater对象的zip文件(不是一个真实的文件,而是在内存中的表示)
                const out = doc.getZip().generate({
                    type: 'blob',
                    mimeType:
                        'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
                })
                // 将目标文件对象保存为目标类型的文件,并命名
                saveAs(out, fileName)
            })
        })
        .catch(error => {
            console.error('Cannot load the module', error)
        })
}

/**
 * 将图片的url路径转为base64路径
 * 可以用await等待Promise的异步返回
 * @param {Object} imgUrl 图片路径
 */
export function getBase64Sync(imgUrl) {
    return new Promise(function (resolve, reject) {
        console.log(reject)
        // 一定要设置为let,不然图片不显示
        let image = new Image()
        //图片地址
        image.src = imgUrl
        // 解决跨域问题
        image.setAttribute('crossOrigin', '*') // 支持跨域图片
        // image.onload为异步加载
        image.onload = function () {
            let canvas = document.createElement('canvas')
            canvas.width = image.width
            canvas.height = image.height
            let context = canvas.getContext('2d')
            context.drawImage(image, 0, 0, image.width, image.height)
            //图片后缀名
            let ext = image.src.substring(image.src.lastIndexOf('.') + 1).toLowerCase()
            //图片质量
            let quality = 0.8
            //转成base64
            let dataurl = canvas.toDataURL(`image/${ext}`, quality)
            //返回
            console.log(dataurl)
            resolve(dataurl)
        }
    })
}
  • ③调用接口获取模板内容数据,生成word文件
import { createFile } from '@/src/utils/doc.js'//引用公共方法(即步骤②中的文件)

// 生成检测记录
const handleGetCheckRecord = () => {
    // 1. 调用接口返回数据
    let query = {
        recordId: 'b97d3f05d0d9d4d3607956057a999d8c',
    }
    const loadingInstance = ElLoading.service({
        target: '.rankingListBtTable',
        text: '',
    })
    QmsNdeOtherRecordMtApi.checkDetails(query)
        .then(res => {
            let resData = res.data.ndeMtDataDto
            nextTick(() => {
                // 2. 整理后端返回数据,用于生成word文件(后台返回数据格式如下)
                let data = {
                    //常规文本数据
                    title: '检测规程/版本',
                    // 附件图片文字数据
                    imgList: [
                        {
                            remark: '这是一条备注信息111',
                            img: 'http://gateway-develop.ever-ghsb-develop.svc.cluster.local:8888/system22/app/fileObject/preview?id=4d86dd9a6f84d984b78775a0de891efb',
                        },
                        {
                            remark: '这是一条备注信息2222',
                            img: 'https://images.pexels.com/photos/3291250/pexels-photo-3291250.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load',
                        },
                    ],
                    // 表格数据
                    tableData: [
                        { index: '1', materialNo: '检测部位编号1', texture: '' },
                        { index: '2', materialNo: '检测部位编号2', texture: '' },
                    ],
                }
                // 3. 图片转base64
                exportWordFile('./docxs/VTRecords.docx', data, '检查记录.docx')
            })
        })
        .catch(err => {
            E_Msg.warn(err.msg || err)
        })
        .finally(() => {
            loadingInstance.close()
        })
}

// 多个图片遍历转base64
const exportWordFile = async (path, datas, fileName) => {
    //多个图片遍历转base64
    for (let i in datas.imgList) {
        datas.imgList[i].fileUrl = await getBase64Sync(datas.imgList[i].fileUrl)
    }
    let imgSize = {
        //控制导出的word图片大小
        imgurl: [400, 500],
    }
    nextTick(() => {
        // 4. 生成文档
        exportWord(path, datas, fileName, imgSize)
    })
}

注:
1. 后台数据格式说明
在这里插入图片描述
2. 多个图片显示的情况
在这里插入图片描述
3. 勾选框的实现
传入的变量category1 为 true 时,才会渲染打√的效果。此时要传入另一个变量category_1 ,值为 category1 取反。
在这里插入图片描述

;