Vue根据word模板导出页面所需文档

今天看到前端页面感觉还挺神奇,决定学习一下这个功能是怎么写的。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

这是报价单生成页面,当该填的内容填完之后,点击立即创建。然后就会生成一个报价单。是不是挺有意思的,一瞬间发现了前端的乐趣。
先说一下里面用的到几个知识点,一个就是自动生成表单,还有一个是输完单价和数量后会自动算出总价,还有一个就是总价转成大写。页面那些框框我们就不说了,我们先说一下,输完单价和数量自动算出总价。

输入单价和数量计算总价

<el-col :span="2">
                  <el-form-item prop="quant">
                    <el-input placeholder="数量" type='number' v-model="item.quant" @change="QuantOrPriceChange(item.id)"></el-input>
                  </el-form-item>
                </el-col>
                <el-col :span="3">
                  <el-form-item prop="unitPrice">
                    <el-input placeholder="单价" type='number' v-model="item.unitPrice" @change="QuantOrPriceChange(item.id)">
                      <template slot="append"></template>
                    </el-input>
                  </el-form-item>
                </el-col>
                <el-col :span="3">
                  <el-form-item prop="totalPrice">
                    <el-input placeholder="总价" type='number' v-model="item.totalPrice">
                      <template slot="append"></template>
                    </el-input>
                  </el-form-item>
                </el-col>

重点来了,这里可以看到我们的单价和数量都调用了这个方法,这是为了实现实时更新总价,无论改动单价还是数量,总价是一直变化的。

QuantOrPriceChange(val){
        var arr = []
        var totalPrice = 0;
        this.BJForm.list.forEach(function (v, i,arry) {
          if (v.id == val) {
              v.totalPrice = v.quant * v.unitPrice;
          }
          arr.push(v);        
        });
        ============================================================
        this.BJForm.list = arr
        arr.forEach(function (v, i,arry) {
          totalPrice = parseInt(v.totalPrice) + totalPrice
        });
        this.BJForm.totalPrice = totalPrice
        this.BJForm.totalPriceA = this.number_chinese(totalPrice.toString())
      },

大家可以看到,我把这块内容分成了上下两部分,上面的那部门是计算横行的总价,下面的那部分是计算纵列的总价。
totalPrice = parseInt(v.totalPrice) + totalPrice
这个totalPrice 是总价,v.totalPrice是这一行的总价
计算这个点不是太难,所以再赠送个点击+号出现一行的源码。

//+按钮的点击事件
      add() {
        var list = this.BJForm.list;
        var arr = {
            id:list.length + 1,
            name:'',
            munit:'',
            quant: null,
            unitPrice:null,
            totalPrice:null,
            remark:''
          }
        this.BJForm.list.push(arr)
        console.log(this.BJForm.list)
      },

金额转成大写

这个地方传过来的值是string类型

/**
       * 将数字转换成中文大写
      */
      number_chinese(str) {
        var num = parseFloat(str);
        var strOutput = "",
            strUnit = '仟佰拾亿仟佰拾万仟佰拾元角分';
        num += "00";
        var intPos = num.indexOf('.');
        if (intPos >= 0) {
            num = num.substring(0, intPos) + num.substr(intPos + 1, 2);
        }
        strUnit = strUnit.substr(strUnit.length - num.length);
        for (var i = 0; i < num.length; i++) {
            strOutput += '零壹贰叁肆伍陆柒捌玖'.substr(num.substr(i, 1), 1) + strUnit.substr(i, 1);
        }
        console.log(strOutput.replace(/零角零分$/, '整').replace(/[仟佰拾]/g, '零').replace(/{2,}/g, '零').replace(/([亿|])/g, '$1').replace(/+/, '元').replace(/亿零{0,3}/, '亿').replace(/^/, "零元"))
        return strOutput.replace(/零角零分$/, '整').replace(/[仟佰拾]/g, '零').replace(/{2,}/g, '零').replace(/([亿|])/g, '$1').replace(/+/, '元').replace(/亿零{0,3}/, '亿').replace(/^/, "零元")
      },

不知道你们能不能看懂这段代码哈,如果看不懂的话也没关系,比较都是造好的轮子了,直接拿来用就行。看不懂上面代码的话看下面的也行,下面的是我搜了一下注释还是挺全的。下面代码源地址

 /****************************************  金额转换为大写(汉字) ************************************************/
     Vue.prototype.amountConvertToUpperCase = function (money) {
       if (!money) { return '';}
       //数字的汉字数组
       let numberCNList = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
       //数字的基础单位汉字数组
       let numberBaseUnit = ['', '拾', '佰', '仟'];
       //数字的高级单位汉字数组
       let numberHighUnit = ['', '万', '亿', '兆'];// 一兆等于一万亿
       //小数部分单位汉字数组
       let floatUnit = ['角', '分', '毫', '厘'];
       //整数金额时后面跟的字符
       let integerLastCN = '整';
       //整数后的单位汉字
       let integerLastUnit = '元';
       //最大处理的数字
       let maxNum = 999999999999999.9999;
       //以小数点拆分金额产生的数组
       let decimalPointSplitList;
       //金额整数部分
       let integerNum;
       //金额小数部分
       let decimalNum;
       //返回的大写金额
       let moneyCNString = '';
       money = parseFloat(money);// 转为数字
       if(typeof money !== 'number' || isNaN(money)){this.$message('检测到非数字字符');return '';}
       //超出最大处理数字
       if (money >= maxNum) {this.$message('超出最大处理数字');return '';}
       // 等于0时 返回零元整
       if (money === 0) {
         moneyCNString = numberCNList[0] + integerLastUnit + integerLastCN;
         return moneyCNString;
       }
       //转换为字符串
       money = money.toString();
       /******金额数字的处理,转为字符串,判断有没有小数位,获取整数部分和小数部分*******/
       if (money.indexOf('.') === -1) {// 如果没有小数点
         integerNum = money;// money就都等于整数部分,小数部分为空
         decimalNum = '';
       } else {// 有小数点
         decimalPointSplitList = money.split('.');// 以小数点拆分数组
         if(decimalPointSplitList[1].length > 4)this.$message('金额精确仅支持到厘');
         integerNum = decimalPointSplitList[0];// 整数部分
         decimalNum = decimalPointSplitList[1].substr(0, 4);// 小数部分只支持4位
       }
       /******整数部分处理*******/
       if (parseInt(integerNum, 10) > 0) {// 以十进制转为int类型 大于零
         let zeroCount = 0;// 记录零的个数
         for (let i = 0; i < integerNum.length; i++) {
           let p = integerNum.length - 1 - i;// 当前数字的位数, 比如10000 ,1的位数是4 , 10001000,第一个1的位数是7,第二个1的位数是3
           console.log(p,integerNum[i]);
           let q = p / 4;// 当前数字位数 除以 4 商0余1,则值为0,控制其高级单位
           let m = p % 4;// 取余,比如1%4 商0余1 ,则值为1, 控制其基础单位
           // 如果当前数字等于零 则++
           if (integerNum[i] === '0') {zeroCount++;}
           // 否则当前数字不等于零时
           else {
             // 如果记录的零的个数大于0 返回结果中加一个汉字零
             if (zeroCount > 0) {moneyCNString += numberCNList[0];}
             //把零的个数归零,重新计算零的个数
             zeroCount = 0;
             // 然后把返回结果 加上 当前数字的汉字以及其基础单位
             moneyCNString += numberCNList[parseInt(integerNum[i])] + numberBaseUnit[m];
           }
           // 如果当前数字所在位数取余4等于0时,那么除以4一定是整数,并且记录的零的个数小于4个,则拼接当前位数对应的高级单位
           if (m === 0 && zeroCount < 4) {moneyCNString += numberHighUnit[q];}
         }
         moneyCNString += integerLastUnit;// 整数部分结束后 拼接上整数部分单位 元
       }
       /******小数部分处理 --- 小数部分只考虑是否有值,以及当前值所在单位,没有零的概念,所以只需拼接当前值对应的汉字,以及当前值的单位即可*******/
       if (decimalNum !== '') {
         for (let i = 0; i < decimalNum.length; i++) {
           if (decimalNum[i] !== '0') {// 如果当前数字不等于零 则加上当前数字的汉字 以及 其小数单位
             moneyCNString += numberCNList[Number(decimalNum[i])] + floatUnit[i];
           }
         }
       }else {// 如果没有小数部分则加上汉字 整
         moneyCNString += integerLastCN;
       }
       return moneyCNString;
     };

表单生成

接下来就是表单生成了,其实这个也不是特别难,因为都封装好了,所以我们也是可以拿过来用的,首先我们需要一个模板。
在这里插入图片描述
这个{}类似占位符,在模板里面占好位置,当我们在前端输入完或选择完数据后,这个数据就会过来自己找位置。

      /**
       *报价单生成
       */
      submitForm(formName) {
        this.$refs[formName].validate((valid) => {
          if (valid) {
            this.BJForm.bjOrder = 'GD-BJ-' + Date.parse(new Date());
            exportDocx('static/gdbj_model.docx', this.BJForm, this.BJForm.pmName+'报价文件.docx');
          } else {
            console.log('error submit!!');
            return false;
          }
        });
      },

调用写好的轮子,需要传递三个参数, exportDocx(路径, 表单, 文件名称);
调用这个轮子还需要三个插件,因为我今天拉取代码发布的时候会提示我缺少三个插件。
在这里插入图片描述
npm install xx 就可以下载
既然用到了,那就具体了解一下这三个插件的作用吧。

docxtemplater

docxtemplater是一个从 docx/pptx 模板生成 docx/pptx 文档的库,使用 JSON作为数据输入处理 docx 和 pptx 模板,可以使用条件、循环在文档中插入表格、html以及图像等任何内容。其中html及图像模块收费,可以用docxtemplater-image-module-free代替图像模块。官网
在这里插入图片描述

这个的用法就是上文中我说的类似于占位符的那个用法。
在这里插入图片描述
这个我看网上挺多博客写的,但是我觉着看官方文档好一点。

PizZip

PizZip 是一个使用 Javascript 创建、读取和编辑 .zip 文件的库,具有可爱而简单的 API。
官网上的实例代码粘一下

var zip = new PizZip();

zip.file("Hello.txt", "Hello World\n");

var img = zip.folder("images");
img.file("smile.gif", imgData, { base64: true });

var content = zip.generate({ type: "blob" });

// see FileSaver.js
saveAs(content, "example.zip");

/*
Results in a zip containing
Hello.txt
images/
    smile.gif
*/

JSZipUtils

这个插件好像是使用js来进行一个压缩解压的功能的,我不是太了解这个插件。

表单生成的JS代码

import docxtemplater from 'docxtemplater';
import PizZip from 'pizzip';
import JSZipUtils from 'jszip-utils';
import { saveAs } from 'file-saver';
/**
 4. 导出docx
 5. @param { String } tempDocxPath 模板文件路径
 6. @param { Object } data 文件中传入的数据
 7. @param { String } fileName 导出文件名称
*/
export const exportDocx = (tempDocxPath, data, fileName) => {
  // 读取并获得模板文件的二进制内容
  JSZipUtils.getBinaryContent(tempDocxPath, (error, content) => {
    if (error) {
      throw error;
    }
    let zip = new PizZip(content);
    let doc = new docxtemplater().loadZip(zip);
    doc.setData(data);
    try {
      // render the document (replace all occurences of {first_name} by John, {last_name} by Doe, ...)
      doc.render();
    } catch (error) {
      let e = {
        message: error.message,
        name: error.name,
        stack: error.stack,
        properties: error.properties,
      };
      console.log({
        error: e
      });
      // The error thrown here contains additional information when logged with JSON.stringify (it contains a property object).
      throw error;
    }
    let out = doc.getZip().generate({
      type: "blob",
      mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
    }); //Output the document using Data-URI
    saveAs(out, fileName);
  });
}

刚刚刷到一个比这个代码更适合做轮子的,确实不错,拿来借鉴一下,代码如下:以下代码原作者传送门

<script>
import Docxtemplater from "docxtemplater";
import PizZip from "pizzip";
import PizZipUtils from "pizzip/utils/index.js";
import { saveAs } from "file-saver";
import JSZipUtils from "jszip-utils";
export default {
  data() {
    return {
      personInfo: {
        name: "张三",
        sex: "男",
        usedname: "张三丰",
        born: "湖北武汉",
        nation: "汉族",
        date: "2020年1月",
        age: "20",
        id: "422123200101024325",
      },
      classTable: [
        {
          subject: "语文",
          teacher: "刘老师",
          score: "98",
          grade: "优",
        },
        {
          subject: "数学",
          teacher: "王老师",
          score: "59",
          grade: "不及格",
        },
        {
          subject: "英语",
          teacher: "蔡老师",
          score: "88",
          grade: "良",
        },
      ],
    };
  },
  methods: {
    loadFile(url, callback) {
      PizZipUtils.getBinaryContent(url, callback);
    },
    exportWord: function () {
      let _this = this;
      // 读取并获得模板文件的二进制内容
      JSZipUtils.getBinaryContent("table.docx", function (error, content) {
        // input.docx是模板。我们在导出的时候,会根据此模板来导出对应的数据
        // 抛出异常
        if (error) {
          throw error;
        }

        // 创建一个JSZip实例,内容为模板的内容
        const zip = new PizZip(content);
        // 创建并加载docxtemplater实例对象
        const doc = new Docxtemplater(zip, {
          paragraphLoop: true,
          linebreaks: true,
        });
        // 设置模板变量的值
        doc.setData({
          ..._this.personInfo,
          classTable: _this.classTable,
        });

        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, "个人信息.docx");
      });
    },
  },
};
</script>

散了散了,公司官网还没写完呢,下次再会。

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晚风偷吻云朵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值