用户自定义模板中数据区域
# 用户自定义模板中数据区域
注意
本文中展示的代码均为关键代码,复制粘贴到您的项目中,按照实际的情况,例如文档路径,用户名等做适当修改即可使用。
在涉及到处理word文档生成的需求时,一般采用编程将数据填充到word模板中动态生成文件的方式来实现。
如果一个项目或系统中所有的模板都是固定的,那么只需要在系统开发过程中由开发人员协助用户把模板做好,之后,开发人员根据业务逻辑编写程序,给模板中的数据位置填充具体的数据即可。但在实际的应用中,很多时候不是这样的,最终用户还是希望能自己随时新建和修改模板,以满足不断变化的业务需求,如果每次模板的变化都需要和开发人员一起来完成,那么这个项目就永远不会完工。
为了满足用户的这一需求,在项目中就需要提供一个模板制作和管理的模块,又为了让程序可以控制和识别用户定义模板中的数据位置,那么就需要开发人员来制定模板中数据位置的标记规则,让最终用户在新建或编辑模板时必须按照规则来制作模板。
一般来说,模板中的数据可以分为两种:
- 一对一,一个数据在模版中对应一个数据位置。
- 一对多,一个数据在同一个模版中可以使用一次,也可以使用多次。
PageOffice针对以上两种模板数据提供的解决方案:
- “一对一”的情况,使用数据区域(DataRegion)。数据区域是PageOffice创造的概念,本质上是用“PO_”开头的书签标记数据位置,比如一个名称为“PO_Name”的书签,在Word中显示的内容为“[姓名]”,这里的中括号不是必须的,只是为了有别于文件中的其他文字内容,便于肉眼很快的发现数据位置,所以也可以用其他符号。由于Word文档中的书签名称是唯一的,即同一个Word文件中只能有一个名称为“PO_Name”的书签,所以整个文件中只能有一个“[姓名]”的数据位置;
- “一对多”的情况,使用数据标签(DataTag)。数据标签本质上只是普通的文本,但是用一些特殊格式的文本做标记,比如“{日期}”、“{##日期##}”、“[日期]”、“【@日期】”等等,只要这些文本内容足够特殊,有别于文件中的正式内容,就可以当做数据标签。所以不管这个特殊文格式的文本字体、字号、样式、颜色等是不是不同,只要纯文本内容是相同的,就被认为是同一个数据标签,只是出现在文件中多个不同的位置,最终都会被替换为同一个数据值。
如下图所示文件模板中,包含了两个数据区域PO_Name(姓名)和PO_ID(身份证号),包含了一个数据标签“{##日期##}”,但是此数据标签出现在文件中的两个位置,如红圈所示。
虽然数据区域就是书签,数据标签就是一个特征字符串,比较简单,但是让普通用户手动在Word文档中插入这样的书签和特征字符串还是有难度的,并且普通用户的命名可能与开发人员约定的命名不一致或不规范,导致数据区域和数据标签无效,为了解决此问题,就需要使用PageOffice提供的数据区域选择框,这样就可以规范用户的操作,实现数据区域和数据标签命名的一致性。
在用户编辑模板环节,给用户弹出开发人员准备好的数据区域或数据标签的选择窗口,用户只需要选择使用这些数据区域和数据标签插入到word模版中,设置好数据的段落格式、字体、样式、颜色等等。这样以来,不但实现了开发人员与用户的“约定”,而且使得用户自定义模板的操作更加简单快捷。
本篇文章重点描述如何实现用户自定义模板功能。开发人员预定义Word模板可用的数据区域,用户通过数据区域选择框编辑Word模板。最后,编辑好的Word模板可以用在以下场景:
- 动态生成Word文件,请参考数据区域填充文本;
- 表单模式打开文件,用户提交数据;
- 用于指定不同用户在Word文档中的可编辑区域;
# 预定义可用的数据区域集合
开发人员需要预定义未来用户可能使用到的最大的数据区域的集合,用户编辑模板就在这个集合中去选择文档中所需要的的具体数据区域,这样一来,就可以满足用户需求的动态变化,以此来充分保证用户未来使用的灵活性。
弹出数据区域对话框后,用户可以选择使用数据区域集合中的数据区域,并设置数据区域在模板中的位置。用户不一定把所有的数据区域全部添加到模板中,用户是可以选择一部分数据区域使用的,这样的好处就是客户的模板即使和之前的模板大相径庭,但是动态定义的数据区域还是这个集合中的一部分。
如此一来,就实现了用户灵活自定义模板的功能了,在无需重新开发和编译项目的情况下就能应对用户需求的变化。遇到用户需要使用模板的场景,强烈推荐使用此功能以解决用户随时调整模板和新建模板的需求。比如在一个定义“采购合同”模板的需求中,开发人员定义了编辑模板可能用到的所有数据区域如下:
WordDocument doc = new WordDocument();
doc.getTemplate().defineDataRegion("Guarantor", "[担保人]");
doc.getTemplate().defineDataRegion("SupplierAddress", "[供货单位地址]");
doc.getTemplate().defineDataRegion("BuyerAddress", "[购货单位地址]");
doc.getTemplate().defineDataRegion("No", "[合同编号]");
doc.getTemplate().defineDataRegion("GuarantorPhone", "[担保人手机]");
doc.getTemplate().defineDataRegion("ProductName", "[产品名称]");
doc.getTemplate().defineDataRegion("Buyer", "[购货单位]");
doc.getTemplate().defineDataRegion("Supplier", "[供货单位]");
# 用户编辑保存模板
用户编辑模板时可以使用所有数据区域,也可以根据实际需求只使用部分数据区域,但不影响动态生成文档的功能。
# 后端代码
- 在后端编写代码调用defineDataRegion方法定义数据区域,用户编辑模板时就可以选择使用这些数据区域;
PageOfficeCtrl poCtrl = new PageOfficeCtrl(request);
WordDocument doc = new WordDocument();
doc.getTemplate().defineDataRegion("Guarantor", "[担保人]");
doc.getTemplate().defineDataRegion("SupplierAddress", "[供货单位地址]");
doc.getTemplate().defineDataRegion("BuyerAddress", "[购货单位地址]");
doc.getTemplate().defineDataRegion("No", "[合同编号]");
doc.getTemplate().defineDataRegion("GuarantorPhone", "[担保人手机]");
doc.getTemplate().defineDataRegion("ProductName", "[产品名称]");
doc.getTemplate().defineDataRegion("Buyer", "[购货单位]");
doc.getTemplate().defineDataRegion("Supplier", "[供货单位]");
poCtrl.setWriter(doc); //必须。
poCtrl.setSaveFilePage("SaveFile");
//webOpen的第一个参数支持能够输出下载文件的Url相对地址或者文件在服务器上的磁盘路径两种方式
//查看详细,请在本站搜索“PageOffice属性或方法中涉及到的URL路径或磁盘路径的说明”
poCtrl.webOpen("D:\\template1.docx", OpenModeType.docNormalEdit, "zhangsan");
// Make sure to add code blocks to your code group
- 在SaveFilePage属性指向的地址接口中,创建FileSaver对象处理文件的保存工作。
FileSaver fs = new FileSaver(request, response);
// 这里就得到了用户编辑保存得到的自定义模板文件,此文件就可以用来做数据填充了,请参考“数据区域填充文本”的章节。
fs.saveToFile("D:\\"+ fs.getFileName());
fs.close();
// Make sure to add code blocks to your code group
# 前端代码
开发人员定义数据区域选择界面DataRegionDlg,用户编辑模板时弹出。
<template>
<div class="Word">
<div style="width: 470px; height: 320px;">
<div style="float: left;font-size:12px;">
<span>待添加标签:</span><br/>
<input id="Text1" v-model="Text1" type="text" @propertychange="searchBookMark(Text1);"
@input="searchBookMark(Text1);"/>
<input id="Button1" type="button" value="搜索" @click="Button1_onclick()"/>
<div style="width: 230px; height: 300px; border: solid 1px #ccc; overflow-y: scroll; ">
<table id="bkmkTable" style=" font-size:12px;">
</table>
</div>
</div>
<div style="float: right;font-size:12px;">
<span>已添加标签:</span><br/>
<input id="Text2" v-model="Text2" type="text" @propertychange="searchBookMark2(Text2);"
@input="searchBookMark2(Text2);"/>
<input id="Button2" type="button" value="搜索" @click="Button2_onclick()"/>
<div style="width: 230px; height: 300px; border: solid 1px #ccc; overflow-y: scroll; ">
<table id="bkmkTable2" style=" font-size:12px;">
</table>
</div>
</div>
</div>
</div>
</template>
<script>
const axios=require('axios');
export default{
name: 'Word',
data(){
return {
names: '',
conts: '',
bkmkArr: '',
bkContArr: '',
addBkmkArr: '',
addBkmkContArr: '',
Text1:''
}
},
methods:{
//控件中的一些常用方法都在这里调用,比如保存,打印等等
//首次加载数据
// load() {
// pcheckBks();
// searchBookMark('');
// searchBookMark2('');
// return;
// },
//遍历Word文件中已存在的书签名称及其对应的文本内容
pcheckBks() {
var pBkNames = window.external.CallParentFunc("checkBkNames", "");
var pBkConts = window.external.CallParentFunc("checkBkConts", "");
if (pBkNames != "" && pBkNames != null && pBkConts != "" && pBkConts != null) {
var currBkmArr = pBkNames.split(",");
var currBkContArr = pBkConts.split(",");
var start = 0;
// addBkmkArr = new Array(0);
// addBkmkContArr = new Array(0);
for (var i = 0; i < currBkmArr.length; i++) {
if (currBkmArr[i] != "") {
//alert(currBkmArr[i]);
this.addBkmkArr.splice(0, 0, currBkmArr[i]); //添加
this.addBkmkContArr.splice(0, 0, currBkContArr[i]); //添加
for (var j = 0; j < this.bkmkArr.length; j++) {
if (this.bkmkArr[j] == currBkmArr[i]) {
start = j;
}
}
this.bkmkArr.splice(start, 1); //删除
this.bkContArr.splice(start, 1); //删除
}
}
}
},
//加载左侧数据列表
searchBookMark(s) {
var tb1 = document.getElementById("bkmkTable");
var rCount = tb1.rows.length;
for (var i = 0; i < rCount; i++) {
tb1.deleteRow(0);
}
var oTable = document.getElementById("bkmkTable");
for (var i = 0; i < this.bkmkArr.length; i++) {
if (this.bkmkArr[i] != null && this.bkmkArr[i] != "" && 0 == this.bkmkArr[i].toLocaleLowerCase().indexOf(s.toLocaleLowerCase())) {
var oTr = oTable.insertRow();
var oTd = oTr.insertCell();
oTd.innerHTML = this.bkmkArr[i];
oTd = oTr.insertCell();
oTd.innerHTML = this.bkContArr[i];
oTd = oTr.insertCell();
oTd.innerHTML = " <a href=\"javascript:void(0);\" onclick= \"add('" + this.bkmkArr[i] + "','" + this.bkContArr[i] + "')\"> 添加</a>";
}
}
},
//加载右侧数据列表
searchBookMark2(s) {
//删除所有行
var tb1 = document.getElementById("bkmkTable2");
var rCount = tb1.rows.length;
for (var i = 0; i < rCount; i++) {
tb1.deleteRow(0);
}
var oTable = document.getElementById("bkmkTable2");
if (this.addBkmkArr != null) {
for (var i = 0; i < this.addBkmkArr.length; i++) {
if (this.addBkmkArr[i] != null && 0 == this.addBkmkArr[i].toLocaleLowerCase().indexOf(s.toLocaleLowerCase()) && "" != this.addBkmkArr[i]) {
var oTr = oTable.insertRow();
var oTd = oTr.insertCell();
oTd.innerHTML = this.addBkmkArr[i];
oTd = oTr.insertCell();
oTd.innerHTML = this.addBkmkContArr[i];
oTd = oTr.insertCell();
oTd.innerHTML = " <a href=\"javascript:void(0);\" onclick= \"locate('PO_" + this.addBkmkArr[i] + "')\"> 定位</a>";
oTd = oTr.insertCell();
oTd.innerHTML = " <a href=\"javascript:void(0);\" onclick= \"del('" + this.addBkmkArr[i] + "','" + this.addBkmkContArr[i] + "')\"> 删除</a>";
}
}
}
},
//搜索待添加书签
Button1_onclick() {
var s = document.getElementById("Text1").value.toLocaleLowerCase();
var tb1 = document.getElementById("bkmkTable");
var rCount = tb1.rows.length;
for (var i = 0; i < rCount; i++) {
tb1.deleteRow(0);
}
var oTable = document.getElementById("bkmkTable");
for (var i = 0; i < this.bkmkArr.length; i++) {
if (this.bkmkArr[i] != null && this.bkmkArr[i] != "" && this.bkmkArr[i].toLocaleLowerCase().indexOf(s) >= 0) {
var oTr = oTable.insertRow();
var oTd = oTr.insertCell();
oTd.innerHTML = this.bkmkArr[i];
oTd = oTr.insertCell();
oTd.innerHTML = this.bkContArr[i];
oTd = oTr.insertCell();
oTd.innerHTML = " <a href=\"javascript:void(0);\" onclick= \"add('" + this.bkmkArr[i] + "','" + this.bkContArr[i] + "')\"> 添加</a>";
}
}
},
//搜索已添加书签
Button2_onclick() {
var s = document.getElementById("Text2").value.toLocaleLowerCase();
var tb1 = document.getElementById("bkmkTable2");
var rCount = tb1.rows.length;
for (var i = 0; i < rCount; i++) {
tb1.deleteRow(0);
}
var oTable = document.getElementById("bkmkTable2");
if (this.addBkmkArr != null) {
for (var i = 0; i < this.addBkmkArr.length; i++) {
if (this.addBkmkArr[i] != null && this.addBkmkArr[i].toLocaleLowerCase().indexOf(s) >= 0 && "" != this.addBkmkArr[i]) {
var oTr = oTable.insertRow();
var oTd = oTr.insertCell();
oTd.innerHTML = this.addBkmkArr[i];
oTd = oTr.insertCell();
oTd.innerHTML = this.addBkmkContArr[i];
oTd = oTr.insertCell();
oTd.innerHTML = " <a href=\"javascript:void(0);\" onclick= \"locate('PO_" + this.addBkmkArr[i] + "')\"> 定位</a>";
oTd = oTr.insertCell();
oTd.innerHTML = " <a href=\"javascript:void(0);\" onclick= \"del('" + this.addBkmkArr[i] + "','" + this.addBkmkContArr[i] + "')\"> 删除</a>";
}
}
}
},
//******** 书签操作 ************************************************************
//添加书签
add(name, content) {
if ("true" == window.external.CallParentFunc("addBookMark", "PO_" + name + "=" + content)) {
var start = 0;
for (var i = 0; i < this.bkmkArr.length; i++) {
if (this.bkmkArr[i] == name) {
start = i;
}
}
this.addBkmkArr.splice(0, 0, name); //向数组第一个位置(0坐标处)添加一个元素
this.addBkmkContArr.splice(0, 0, content);
this.bkmkArr.splice(start, 1); //删除数组中相应坐标处的一个元素
this.bkContArr.splice(start, 1);
searchBookMark('');
searchBookMark2('');
}
},
//删除书签
del(name, cont) {
if ("true" == window.external.CallParentFunc("delBookMark", "PO_" + name)) {
var start = 0;
for (var i = 0; i < this.addBkmkArr.length; i++) {
if (this.addBkmkArr[i] == name) {
start = i;
}
}
this.bkmkArr.splice(0, 0, name);
this.bkContArr.splice(0, 0, cont);
this.addBkmkArr.splice(start, 1);
this.addBkmkContArr.splice(start, 1);
searchBookMark('');
searchBookMark2('');
}
else {
alert(0)
}
},
//定位书签
locate(bkName) {
window.external.CallParentFunc("locateBK", bkName);
}
},
mounted: function(){
this.Text1 = document.getElementById("Text1").value;
this.Text2 = document.getElementById("Text2").value;
this.names = window.external.CallParentFunc("getBkNames", "");
this.bkmkArr = this.names.split(";");
this.conts = window.external.CallParentFunc("getBkConts", "");
this.bkContArr = this.conts.split(";");
this.addBkmkArr = new Array(0);
this.addBkmkContArr = new Array(0);
// 将vue中的方法赋值给window
window.load = this.load;
window.Save = this.Save;
window.locate = this.locate;
window.del = this.del;
window.add = this.add;
window.pcheckBks = this.pcheckBks;
window.searchBookMark = this.searchBookMark;
window.searchBookMark2 = this.searchBookMark2;
//首次加载数据
pcheckBks();
searchBookMark('');
searchBookMark2('');
}
}
</script>