0


vue实现导出word文档(含多张图片)

**一、实现效果 **

以填写并导出房屋出租审批表为例,首先填写表格相应内容后,点击" 导出 "按钮实现word文档的导出功能,界面如下所示:

18f507430db54d04abf5a31280ee715d.gif

最后导出word文档如下所示:

ded5c5f01cc140abb3800fd187172662.jpeg

**二、所需插件 **

这里使用npm对以下所需依赖进行安装,并在后面封装的js文件(导出word文档主要实现方法)中引入 。

-- 安装 docxtemplater
npm install docxtemplater pizzip  --save
-- 安装 jszip-utils
npm install jszip-utils --save 
-- 安装 jszip
npm install jszip --save
-- 安装 FileSaver
npm install file-saver --save
-- 引入处理图片的插件1
npm install docxtemplater-image-module-free --save
-- 引入处理图片的插件2
npm install angular-expressions --save

**三、word文档模板 **

在导出word之前,需要准备一个word模板文件(按自己所需最后导出的样式),放到该vue项目public文件夹下, 房屋出租审批表模板word样式如下所示:

ed329decadf045c699760052e86b97ce.jpeg

需要填写的部分都被定义为变量或者json对象数组,具体格式如下:

  1. 单一变量使用 { } 包含,例如:
{ user } 、{ area }
  1. json数组格式,则包裹一个循环对象,例如:

原格式为:

"thinglist": [
      { time :"2022-4-1",thing: "在家"},
      { time :"2022-4-2",thing: "上班"},
 ]

在模板文件中表示为:

{#thinglist}
    {time}-{thing}
{/thinglist}

如果对象是图片地址时,需要在对象前加上% ,例如:

原格式为:

imglist:[
    { imgUrl: "  "},
    { imgUrl: "  "},
]

在模板文件中表示为:

{#imglist}
    {%imgUrl}
{/imglist}

四、封装js 文件

这部分主要是实现word文档导出含图片的主要实现方法,包括将图片的url路径转为base64路径、base64转二进制、以及导出图片的处理,可以直接复制粘贴在页面引入使用,具体代码如下:

import PizZip from 'pizzip'
import docxtemplater from 'docxtemplater'
import JSZipUtils from 'jszip-utils'
import {saveAs} from 'file-saver'
 
/**
 * 将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 = 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;
}
 
/**
 * 导出word,支持图片
 * @param {Object} tempDocxPath 模板文件路径
 * @param {Object} wordData 导出数据
 * @param {Object} fileName 导出文件名
 * @param {Object} imgSize 自定义图片尺寸
 */
export const exportWord = (tempDocxPath, wordData, fileName,imgSize) => {
    //这里要引入处理图片的插件
    var ImageModule = require('docxtemplater-image-module-free');
 
    const expressions = require("angular-expressions");
 
    // 读取并获得模板文件的二进制内容
    JSZipUtils.getBinaryContent(tempDocxPath, function(error, content) {
 
        if (error) {
            throw error;
        }
        expressions.filters.size = function(input, width, height) {
            return {
                data: input,
                size: [width, height],
            };
        };
        function angularParser(tag) {
            const expr = expressions.compile(tag.replace(/’/g, "'"));
            return {
                get(scope) {
                    return expr(scope);
                },
            };
        }
        // 图片处理
        let opts = {}
 
        opts = {
            //图像是否居中
            centered: true
        };
        opts.getImage = (chartId) => {
            //console.log(chartId);//base64数据
            //将base64的数据转为ArrayBuffer
            return base64DataURLToArrayBuffer(chartId);
        }
        opts.getSize = function(img, tagValue, tagName) {
            //自定义指定图像大小
            if(imgSize.hasOwnProperty(tagName)){
                return imgSize[tagName];
            }else{
                return [300, 300];
            }
        }
        // 创建一个PizZip实例,内容为模板的内容
        let zip = new PizZip(content);
        // 创建并加载docxtemplater实例对象
        let doc = new docxtemplater();
        doc.attachModule(new ImageModule(opts));
        doc.loadZip(zip);
        doc.setData(wordData);
        try {
            // 用模板变量的值替换所有模板变量
            doc.render();
        } catch (error) {
            // 抛出异常
            let e = {
                message: error.message,
                name: error.name,
                stack: error.stack,
                properties: error.properties
            };
            console.log(JSON.stringify({
                error: e
            }));
            throw error;
        }
        // 生成一个代表docxtemplater对象的zip文件(不是一个真实的文件,而是在内存中的表示)
        let out = doc.getZip().generate({
            type: "blob",
            mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
        });
        // 将目标文件对象保存为目标类型的文件,并命名
        saveAs(out, fileName);
    });
}

/**
 * 将图片的url路径转为base64路径
 * 可以用await等待Promise的异步返回
 * @param {Object} imgUrl 图片路径
 */
export function getBase64Sync(imgUrl) {    
    return new Promise(function(resolve, 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);
            //返回
            resolve(dataurl);
        };
    })
}

五、实现导出word文档

  1. 首先是前端部分,这里使用ElementPlus进行前端页面实现,代码如下:
<template>
    <div class="page-css">
           <el-card class="box-card" shadow="never">
               <div class="search-data">
                   <el-button type="success" @click="editVisible=true">填写审批表</el-button>
               </div> 
           </el-card>
           <el-dialog v-model="editVisible" title="房屋出租审批表" width="50%" custom-class="role-mask">
               <div>
                   <div class="tablename">
                       <h2>房屋出租审批表</h2>
                   </div>
                  <table class="tb" border="1">
                          <tr>
                              <td height="60">承租人</td>
                              <td colspan="2"  width="180">
                                <input class="inputone" v-model="user"/>
                            </td>
                              <td colspan="2" width="125">房屋面积</td>
                              <td colspan="2" width="175">
                                <input class="inputtwo" v-model="area" />
                                平方米
                            </td>
                          </tr>
                          <tr>
                              <td height="60">年租金</td>
                              <td colspan="2" >
                                <input class="inputtwo" v-model="annualrent" />
                                元/年
                            </td>
                              <td colspan="2" >出租用途</td>
                              <td colspan="2" >
                                <input class="inputone" v-model="purpose"/>
                            </td>
                          </tr>
                        <tr>
                            <td height="300">房屋平面示意图</td>
                            <td  colspan="6">
                                 <div v-for="(item,index) in imglist">
                                     <img style="width: 60%;" :src="item.imgUrl"/>
                                 </div>
                            </td>
                        </tr>
                      </table>
               </div>
               <template #footer>
                    <span class="dialog-footer">
                        <el-button type="info" @click="editVisible=false">取消</el-button>
                        <el-button type="primary" @click="exportWordFile" >导出</el-button>
                    </span>
              </template>
           </el-dialog> 
    </div>
</template>

实现过程遇到一个问题:使用el-dialog弹出框时,想固定其在页面居中、距离页面顶部以及底部的固定距离,但是里面的表格内容却超出其显示范围,该如何实现喃?css设置如下:

/* 弹出框居中显示 */
/deep/.el-dialog {
   left: 50%;
   top: 50%;
   transform: translate(-50%, -50%);
   margin: 0px !important;
}
/* 弹出框超出部分滑动 */
/deep/.el-dialog__body {
     height: 75vh;
     overflow: hidden;
     overflow-y: auto;
}

包括更改el-dialog弹出框头部以及底部区域样式,css设置如下:

/deep/.el-dialog__header {
    width: 100%;
    background-color:#f8f8f8 ;
}
/deep/ .el-dialog__footer {
    width: 100%;
    border-top: 1px #ebebeb solid ;
}

2.然后在页面内引入封装js里的exportWord以及getBase64Sync方法,data部分定义的是双向绑定填写的内容以及图片地址,考虑到图片可能不知一张,需要循环对其处理转为base64路径,代码如下:

// 引入将图片的url路径转为base64路径的方法
for (let i in this.imglist) {
    this.imglist[i].imgUrl = await getBase64Sync(this.imglist[i].imgUrl)
}

完整代码如下所示:

<script>
import {exportWord,getBase64Sync} from '@/assets/js/outword.js'
export default {
  data () {
      return {
        editVisible:false,
        user:'',
        area:'',
        annualrent:'',
        purpose:'',
        imglist:[
            {
                imgUrl: "https://img2.baidu.com/it/u=2709954499,581919391&fm=253&fmt=auto&app=138&f=JPEG?w=468&h=518"
            },
            {
                imgUrl: "https://img0.baidu.com/it/u=1462004956,1440895436&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=353"
            }
        ]
      }   
  },
  methods:{
        async exportWordFile (){
            for (let i in this.imglist) {
                this.imglist[i].imgUrl = await getBase64Sync(this.imglist[i].imgUrl)
            }
             let  data= {
                user:this.user,
                area:this.area,
                annualrent:this.annualrent,
                purpose:this.purpose,
                imglist:this.imglist
            }
            let imgSize = {
            //控制导出的word图片大小
              imgurl:[200, 200],
            };
            exportWord("/房屋出租审批表.docx", data, "房屋出租审批表.docx", imgSize);
          }    
  }
}
</script>
标签: 前端 vue.js

本文转载自: https://blog.csdn.net/zhanglinsen123/article/details/125984759
版权归原作者 薇森 所有, 如有侵权,请联系我们删除。

“vue实现导出word文档(含多张图片)”的评论:

还没有评论