vue 打印 前台用户自己设计模版 打印报表实现记录
一 需求
公司产品由cs转向bs,我前端使用vue技术栈 ,具体的难点
1.在vue的基础上让用户自己设计模版
2.设计的模版 与 后台请求的数据相结合
3.打印
二 功能实现
2.1 先说打印
问百度 web打印 出来的 基本是两种方案 ,一是 js插件 二是 lodop 控件实现web打印功能
注: lodop 控件 需要下载一个程序。对于我们客户来说或许有些麻烦 那就选js插件吧 lodop 官网 : http://www.mtsoftware.cn/LodopDemo.html 实现方式 百度好多 略过!!!
问百度 vue 项目实现打印 也是两种方式
一是:通过npm 安装插件
二是:手动下载插件到本地
扩展:详细说下 npm安装方式
//1.安装 npm install vue-print-nb --save //2 main.js文件中引入 import Print from 'vue-print-nb' Vue.use(Print); //注册 //3.使用 <div id="printTest" > <p>锄禾日当午</p> <p>汗滴禾下土 </p> <p>谁知盘中餐</p> <p>粒粒皆辛苦</p> </div> <button v-print="'#printTest'">打印</button> //目前看 不到 手动调 的方法 //
2.2 接下来 如何设计模版
做这个模版设计,百度找了一顿的 就看见一个 注册组件方式 ,写的不详细,自己没有实现出来,(┬_┬)先回到jq的思维 !!
让用户或者实施人员自己设计模版,这听起来有点之前的拖拽建站的感觉呀,拖拽元素生成对应的html代码(不想做个这东西!!),想想有啥可以借用的东西呢?一下子想到之前用过的富文本编译器o(∩_∩)o 哈哈!!
那就百度下-----确实找到一篇类似的文章 https://www.cnblogs.com/s0611163/p/4885833.html 文章中很多数据看不到,没法具体的尝试!!只能借鉴思路了 ,既然提到了 ueditor 那就也用这个吧 !!
ueditor 的官网 有不少例子 http://ueditor.baidu.com/website/onlinedemo.html
初试的时候 用的jq版本 (这个插件就是jq版的)vue的盛行 到目前已经好久不更新了,试验完 jq版本 又 尝试往 vue项目上 转移 。
拓展 :Vue 中使用UEditor富文本编辑器
参考 https://blog.csdn.net/haochuan9421/article/details/81975966
//1.安装 cnpm i vue-ueditor-wrap //2.下载处理后的UEditor,下载地址 https://github.com/HaoChuan9421/vue-ueditor-wrap/tree/master/assets/downloads //解压,重命名文件夹为UEditor,放入public文件夹下(如果是旧项目对应static文件夹)。 //3.引用组件、注册组件 import VueUeditorWrap from 'vue-ueditor-wrap' // ES6 Module // 或者 const VueUeditorWrap = require('vue-ueditor-wrap') // CommonJS //4. v-model绑定数据 <vue-ueditor-wrap v-model="msg"></vue-ueditor-wrap> data () { return { msg: '<h2><img src="http://img.baidu.com/hi/jx2/j_0003.gif"/>Vue + UEditor + v-model双向绑定</h2>' } } //5. 修改配置 具体参考如下详细代码
//详细代码 <template> <div> <div id="app"> <vue-ueditor-wrap v-model="msg" :config="myConfig"></vue-ueditor-wrap> <div class="tembtn"> <!-- <el-button type="primary" @click="showOne();">获取编辑器内容</el-button> --> <el-button type="primary" @click="saveTemplate();">保存编辑模版</el-button> </div> </div> </div> </template> <script> import VueUeditorWrap from 'vue-ueditor-wrap' export default { name: "setPrintTemplate", components: { VueUeditorWrap }, data() { return { msg:'', myConfig:{ // 编辑器不自动被内容撑高 autoHeightEnabled: false, // 初始容器高度 initialFrameHeight: 480, // 初始容器宽度 initialFrameWidth: '100%', // 上传文件接口(这个地址是我为了方便各位体验文件上传功能搭建的临时接口,请勿在生产环境使用!!!) serverUrl: '', // UEditor 资源文件的存放路径,如果你使用的是 vue-cli 生成的项目,通常不需要设置该选项,vue-ueditor-wrap 会自动处理常见的情况,如果需要特殊配置,参考下方的常见问题2 UEDITOR_HOME_URL: '/static/UEditor/' } } }, mounted() { this.msg = localStorage.setPrintTemplate || '' }, methods: { showOne(){ alert(this.msg); }, saveTemplate(){ alert('初步计划:此处需要将保存的模版保存到数据库中,可以设置多个,先本地模拟一个保存'); localStorage.setPrintTemplate = this.msg; alert('模版保存成功') } }, } </script>
注意:这里的msg 就是 我需要的html代码
附件:显示一下 测试中设计的模版 与模拟的json数据 (模版字段与json字段相同)
2.3 重点来了-----将上面的 html内容与 json想结合 需要一个函数,两个参数 分别是模版代码 与 json内容
这个功能算是核心功能吧 费了点尽!~~~(由于不确定哪些用循环?哪些是简单的单独的字段替换?表格的位置都在最后么??等等可扩展功能)
补充:在做这个功能的时候 先百度了下 js模版引擎原理 来提高点思路
文章一 : https://www.cnblogs.com/c2016c/articles/9343042.html
文章二:https://www.jianshu.com/p/9091e8a343e4
文章三:https://www.cnblogs.com/hustskyking/p/principle-of-javascript-template.html
确实提供一些思路 但我需要的功能也不近相同 ,他们都知道 循环体是哪~~我这个没法具体的 ,写出来循环体 只能去判断!!
上来给自己定了个难点的 模版 (⊙﹏⊙) 如图示:
图中 分为5个部分,
第一部分是 单纯的 p标签 内容替换
第二四部分是 一个 table 带有循环体(这个循环体的table我加了个 class="for"做标记)
第三部分 是一个 不带循环的table形式(其实他的性质和 单纯的p标签一样 算是一类)
第五部分 是结尾 没啥!!!!
大体思路:
1.将模版分段(依据循环的表格),显示顺序不能变,放入到一个大的数组arr中
2.将arr中的每个 模版进行处理
2.1 如是 非循环部分 直接 正则替换 后形成 html内容
2.2 若是 循环部分 再次正则找出循环体, 循环替换后与该table的其他部分 组成新的html内容
设计模版的时候 要注意:
如果是循环展示列表数据 那么这个列表 应该加class="for",(编译器切换到html模式),模版字段必须与 接口返回的数据字段一样
对于接口:
返回的数据格式要参照上面形式
直接代码
<script> var html = '<p>商品快递单</p><p>时间:{{time}}</p><p>地点:{{address}}</p><p>内容:</p><p><br/></p><table class="for"><tbody><tr class="firstRow"><td width="105" valign="top" style="word-break: break-all;"> <span style="font-size: 18px;"> <strong><span style="font-size: 16px;">名称</span></strong></span></td><td width="105" valign="top" style="word-break: break-all;">颜色</td><td width="105" valign="top" style="word-break: break-all;">大小</td><td width="105" valign="top" style="word-break: break-all;">数量</td><td width="105" valign="top" style="word-break: break-all;">途径</td><td width="105" valign="top" style="word-break: break-all;">价格</td><td width="105" valign="top" style="word-break: break-all;">优惠价格</td><td width="105" valign="top" style="word-break: break-all;">实施人员</td></tr><tr><td width="105" valign="top" style="word-break: break-all;">{{name}}</td><td width="105" valign="top" style="word-break: break-all;">{{color}}</td><td width="105" valign="top" style="word-break: break-all;">{{size}}</td><td width="105" valign="top" style="word-break: break-all;">{{num}}</td><td width="105" valign="top" style="word-break: break-all;">{{tj}}</td><td width="105" valign="top" style="word-break: break-all;">{{price}}</td><td width="105" valign="top" style="word-break: break-all;">{{aoutprice}}</td><td width="105" valign="top" style="word-break: break-all;">{{cname}}</td></tr></tbody></table><p><br/></p><table><tbody><tr class="firstRow"><td width="483" valign="top" style="word-break: break-all;">国家</td><td width="483" valign="top" style="word-break: break-all;">{{country}}</td></tr><tr><td width="483" valign="top" style="word-break: break-all;">省份</td><td width="483" valign="top" style="word-break: break-all;">{{province}}</td></tr></tbody></table><p><br/></p><table class="for"><tbody><tr class="firstRow"><td width="483" valign="top" style="word-break: break-all;">城市</td><td width="483" valign="top" style="word-break: break-all;">姓名</td></tr><tr><td width="483" valign="top" style="word-break: break-all;">{{city}}</td><td width="483" valign="top" style="word-break: break-all;">{{name}}</td></tr></tbody></table><p><br/></p>'; var json = { time: '2018-15-15', address: '山东青岛', data: [{ name: '苹果', color: '红色', size: '3存', num: '4', tj: '5', price: '6', aoutprice: '1', cname: '刘一', }, { name: '桃子', color: '绿色', size: '6', num: '4', tj: '5', price: '6', aoutprice: '1', cname: '刘二', }], country: '中国', province: '山东', } var efg = /<table class="for">.*?<\/table>/g; var regtr = /<tr>.*?<\/tr>/g; attachTemplateToData = function(template, data) { var i = 0, len = data.length, fragment = '', tmeparr = [], //总的模版 tempforarr = [], //用 replace 处理 得到 字段中需要循环的模版------最多就一个哈哈哈哈哈 tempsplit = []; //用 split 处理 得到的 不需要循环的模版 function strReplace(temps, obj) { var t, key, reg; //遍历该数据项下所有的属性,将该属性作为key值来查找标签,然后替换 for (key in obj) { reg = new RegExp('{{' + key + '}}', 'ig'); if (typeof(obj[key]) === 'string') { t = (t || temps).replace(reg, obj[key]); } } return t; } function forReplace(temps, obj) { var t, key, reg; //遍历该数据项下所有的属性,将该属性作为key值来查找标签,然后替换 for (key in obj) { reg = new RegExp('{{' + key + '}}', 'ig'); t = (t || temps).replace(reg, obj[key]); } console.log(t) return t; } tempsplit = template.split(efg); template.replace(efg, function(str) { tempforarr.push(str) }) for (var i = 0; i < tempsplit.length - 1; i++) { //for 循环 把 tempforarr 和 tempsplit 的模版 组成一个完整的 组数 (按照原先显示顺序的,其实就是在 tempsplit 中元素之间依次插入 tempforarr) //鉴于就一个 for循环的表格 的直接中间 插入就好 tmeparr.push(tempsplit[i]); tmeparr.push(tempforarr[i]) } tmeparr.push(tempsplit[tempsplit.length - 1]); console.log(tmeparr) //处理模版 var forTempHtml = '', forTempCont = ''; for (var m = 0; m < tmeparr.length; m++) { if (regtr.test(tmeparr[m]) && (tmeparr[m].search('table class="for"') != -1)) { //for for循环数据 //得到 循环体的模版 tmeparr[m].replace(regtr, function(str) { forTempHtml = str; }) // 将循环体的模版 与 数据结合得到 相应的 html结构 for (var n = 0; n < json.data.length; n++) { forTempCont += forReplace(forTempHtml, json.data[n]); } //得到循环后的html结构 将此结构 替换 原先的模版 fragment += tmeparr[m].replace(regtr, forTempCont) } else { //普通的字段替换 fragment += strReplace(tmeparr[m], json); } } return fragment; }; $('#cont').html(attachTemplateToData(html, json)) </script>
拓展补充:正则与字符串处理
a.*?b就是a开始b结束的匹配
//利用正则分割,str.split(/reg/); js同时使用多个分隔符分割字符串. var mystring = "jb51.net,google.com,baidu.com_weibo.com_haotu.net"; var myarray = mystring.split(/[,_]/);
//代码中 常用的几个正则 var re = /<%([^%>]+)?%>/g, 正则全局匹配以<%开头,中间不是%或>并以%>结尾的配配项 var efg = /<table class="for">.*?<\/table>/g;
//目前还不知道 有啥函数可以一次性的 将模版分成5段!! //split 函数 得到的结果 不带有 分隔符 //replace 函数可以找到 对应的分隔符 //两者相结合 ,你来一个元素 我来一个元素 最终巧妙的 按照顺序 得到了 完整的 分段模版
6666