今天看到前端页面感觉还挺神奇,决定学习一下这个功能是怎么写的。
这是报价单生成页面,当该填的内容填完之后,点击立即创建。然后就会生成一个报价单。是不是挺有意思的,一瞬间发现了前端的乐趣。
先说一下里面用的到几个知识点,一个就是自动生成表单,还有一个是输完单价和数量后会自动算出总价,还有一个就是总价转成大写。页面那些框框我们就不说了,我们先说一下,输完单价和数量自动算出总价。
输入单价和数量计算总价
<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>
散了散了,公司官网还没写完呢,下次再会。