1 引言
上一章介绍了最简单的操作插件编写方法以后,往往开发者不会只是简单开发一个完全封闭的插件,一般来说,完成一项任务至少需要:
- 和DWF进行前端的数据输入和输出,例如:根据当前的登录用户信息、当前服务器信息进行相应的编码。
- 和DWF后端进行数据交换,例如:读出某个特定类型数据,比如“工单”数据,或者根据用户输入将特定数据更新回DWF的数据库。
因此,需要在插件的代码里调用DWF内部的接口,所以这一章,将接下来深入了解DWF提供给插件开发者的一些基础调用。
2 获得接口的总入口:dwf_ctx
DWF的前端插件配套接口最大限度的集成了本教程 第三部分:脚本开发培训中介绍的接口,也就是说,对于已经掌握脚本开发的程序员,可以不费力气的继续使用他们已经掌握的知识,进行高阶编码工作。
下面看一个简单的例子:
回到之前介绍的操作hello world模块操作插件,我们希望在操作激活的时候,显示当前登录用户名,例如:“hello admin”,可以这样修改代码:
<template> <div> <Alert show-icon>hello world!!!</Alert> <Alert type="success" show-icon>{{userName}}</Alert> </div> </template> <script> export default { name: "helloWorldOpr", data() { return { userName: "hello world!!!" } }, created() { let ctx = this.dwf_ctx; // 插件代码里对DWF调用的总入口 this.userName = `hello ${ctx.user.userName}!!!`; //获取当前用户名 } }; </script> <style> .ivu-alert{ margin-left: 10px; margin-top: 10px; } </style>
上面代码经过装配后得到的效果如下所示:
图-显示当前登录用户信息
上述代码中,定义了一个变量userName,并且在data()函数里返回了一个自带userName的JSON对象,该对象会自动传递到created函数。其中,data()和create()函数的作用是标准的vue文件自带的生命周期函数,这里不过多介绍。
重点关注的是created函数的内容,其中包含的this.dwf_ctx这个总入口,其作用和脚本中的this是一样的,因此,通过dwf_ctx可以访问到脚本提供的所有功能,当然,包括user.userName这样的属性。
有关脚本函数和代码之间的一一对应关系,可以参阅 前端脚本和插件开发调用的对应关系详细了解,这里不做过多介绍。
3 操作插件开发的典型案例
下面,结合两个比较有代表性的示例介绍如何在脚本中使用函数调用。
3.1 模块操作开发
针对设备管理系统,将工单列表从DWF数据库里取出来,显示到一个iView的时间线上,点击详情打开工单,如下图所示:
图-用时间线显示工单列表
通过restful api获取数据
这个例子中涉及到如何调用DWF数据库中的数据问题,在表单操作中这一目标可以通过调用DWF配套的restful api实现。取得工单对象列表的restful api调用方法如下面的代码所示:
let queryConditon = { condition: "order by obj.woDeadline" //按照要求完成时间进行排序 }; let that = this; // 注意:临时保存上下文以便异步调用 this.dwf_ctx.dwf_axios .post("/omf/entities/WorkOrder/objects", queryConditon) .then((res) => { console.log(res.data.data); ... });
在上面的例子中:
- this.dwf_ctx.dwf_axios变量表示dwf对象访问api的封装,这个封装只需要输入对象访问服务的相对地址,然后加上调用restful api调用的参数即可返回结果,用户不必输入token等初始化信息。
- post函数是一个异步调用,当后端返回结果以后,会调用.then方法,把结果调用是否成功以及成功后返回的数据返回,如果成功调用结果默认会存在res.data.data属性里。
- 因为是异步调用,所以在回调函数then被激活的时候,无法得到插件的上下文信息,因此需要在调用之前将其保留,所以出现了let that = this的设定
- 调用post需要传入的参数是一个JSON对象,DWF的restful api约定了参数对象必选属性,通过查阅Restful API的说明文档是可以看到需要的参数。
Restful API接口的说明文档可以通过在建模工具中点击登录用户,展开API说明查看,如下图所示:
图-打开接口文档
将工单查询结果展示到前端
数据加载的方法了解以后,剩下如何展示到前端的工作就取决于开发者对vue的和iView的熟悉程度了,本章一开始的例子里使用了报警(Alert)标签展示数据,在这个例子里我们将采用时间线(Timeline)控件实现希望展示的效果:
<template> <Timeline> <TimelineItem color="green" v-for="t in worklist" :key="t.oid"> <p>{{`${getdate(t.woDeadline || '未知')}`}}</p> <p>{{`${t.woTitle}`}}<a @click="openWorkOrder(t)">详情</a> </p> </TimelineItem> </Timeline> </template> <script> export default { name: "workOrderListOpr", data() { return { worklist: [], }; }, created() { console.log(this.dwf_ctx); let queryConditon = {}; let that = this; // 临时保存上下文以便异步调用 this.dwf_ctx.dwf_axios .post("/omf/entities/WorkOrder/objects", queryConditon) .then((res) => { if (res.data.success) { console.log(res.data.data); that.worklist = res.data.data; } }); }, methods: { openWorkOrder(wo) { // 打开工单对象 alert(`${wo.woTitle}`); }, // 日期转换辅助函数 getdate(ts) { if (ts == "") return "--"; var now = new Date(ts), y = now.getFullYear(), m = now.getMonth() + 1, d = now.getDate(); return ( y + "-" + (m < 10 ? "0" + m : m) + "-" +(d < 10 ? "0" + d : d) + " " + now.toTimeString().substr(0, 8) ); }, } }; </script> <style> .ivu-timeline { margin-left: 20px; margin-top: 20px; } </style>
在上述代码中:
- 标签内定义了一个worklist数组用于进行动态显示,利用iView标签遍历将数组中的每一个对象展示出来。
- 在脚本的中的create()方法里,通过调用dwf_aixos获得了工单对象,并且按照要求完成时间(woDeadline)排序。
- getdate()函数是为了将DWF中默认返回的时间戳数据类型转换为年月日以方便阅读。
- 当详情标签被点击的时候会自动调用openWorkOrder函数,并且将被点击的工单对象作为参数传入表单,并将其显示在界面上。
- 在样式<sytle>里增加了边沿设置,整体推进20个像素,以便显示边沿。
显示工单引用的设备和人员信息
在上面的例子中,仅仅显示了工单的内容,但是对于工单针对的设备、工单的负责人工程师的姓名,是没有显示的,接下来看看如何利用DWF提供的Restful API完成引用数据的加载。
在DWF的对象访问API:/omf/entities/WorkOrder/objects,中如果发现实体类中某些属性是其他实体类的对象,那么可以通过在参数中传递refs数组将这些属性标记出来,一旦标记出来以后DWF会自动根据属性的取值从后台将被引用的实体类对象以数组的形式返回回来。
对应到这个例子中,在工单类(WorkOrder)里,负责人对应的属性是:engineerOid,设备对应的是:assetOid,这两个属性分别记录了DWF内部的唯一代号,如果要将设备信息一并带回,可以这样拼写ref属性:
let queryConditon = { refs: [ {sourceAttr: "assetOid", targetAttr: "oid", targetClass: "Asset", sourceAttrSplit: ","}, {sourceAttr: "engineerOid", targetAttr: "oid", targetClass: "User", sourceAttrSplit: ","} ] }; let that = this; // 临时保存上下文以便异步调用 this.dwf_ctx.dwf_axios .post("/omf/entities/WorkOrder/objects", queryConditon) .then((res) => { console.log(res.data.data); that.worklist = res.data.data; });
在refs里每一个数组元素,代表一组引用关系,其中:
- sourceAttr:表示原始对象的属性名称,在这个例子里,分别是工单的目标设备属性assetOid和工程师属性engineerOid属性。
- targetClass:表示目标类的名称,在这个例子里,工单的assetOid对应设备实体类Asset,engineerOid对应是用户类User。
- targetAttr:表示用于检索目标类的属性,一般都是设置为oid,这个例子里表示用assetOid到设备中通过设备的oid找到对应的设备,用engineerOid到用户类中通过用户的oid找到对应的用户。
- sourceAttrSplit:如果sourceAttr里记录了多个对象的标识符,使用这个属性将分隔符标识出来,这里给出了一个“,”。
DWF的restful api会将工单对象作为数组全部返回,同时将所有返回结果中的工单对象引用的设备对象和用户对象作为另外的两个数组返回,如下所示:
图-带引用关系返回的结果
这个返回结果携带的信息更加丰富,其携带了3个数组,其中WorkOrder代表查出的工单,User代表工单中牵扯到的负责人,Asset代表工单涉及的所有设备。得到必要的信息以后,只需要在前端对数据进行初始化即可完成显示:
<template> <Timeline> <TimelineItem color="green" v-for="t in worklist" :key="t.oid"> <p>{{ `${getdate(t.woDeadline || "未知")}` }}</p> <p> {{ `设备代号为 ${t.assetId} 的 ${t.woTitle} 工作由 ${t.engineerName} 负责完成` }} <a @click="openWorkOrder(t)">详情</a> </p> </TimelineItem> </Timeline> </template> <script> export default { name: "workOrderListOpr", data() { return { worklist: [], userlist: [], //新增了用户列表备用 assetlist: [], //新增了设备列表备用 }; }, created() { console.log(this.dwf_ctx); let queryConditon = { condition: "order by obj.woDeadline", refs: [ { sourceAttr: "assetOid", targetAttr: "oid", targetClass: "Asset", sourceAttrSplit: ",", }, { sourceAttr: "engineerOid", targetAttr: "oid", targetClass: "User", sourceAttrSplit: ",", }, ], }; let that = this; // 临时保存上下文以便异步调用 this.dwf_ctx.dwf_axios .post("/omf/entities/WorkOrder/objects", queryConditon) .then((res) => { console.log(res); if (res.data.success) { let resArr = res.data.data; that.assetlist = resArr.refResult.assetOid; that.userlist = resArr.refResult.engineerOid; // 重新归置工单信息补充新的属性 that.worklist = resArr.queryResult.map((wo) => { let asset = that.assetlist.find((a) => { return a.oid === wo.assetOid; }); //找到工单提到的设备对象 let engineer = that.userlist.find((u) => { return u.oid === wo.engineerOid; }); //找到工单提到的用户对象 // 如果设备对象不为空则补充设备的代号到工单中 wo.assetId = typeof asset !== "undefined" ? asset.id : "未知"; // 如果用户对象不为空则补充用户的名称到工单中 wo.engineerName = typeof engineer !== "undefined" ? engineer.displayName : "未知"; return wo; }); } }); }, methods: { openWorkOrder(wo) { // 打开工单对象 alert(`${wo.woTitle}`); }, getdate(ts) { if (ts == "") return "--"; var now = new Date(ts), y = now.getFullYear(), m = now.getMonth() + 1, d = now.getDate(); return ( y + "-" + (m < 10 ? "0" + m : m) + "-" + (d < 10 ? "0" + d : d) + " " + now.toTimeString().substr(0, 8) ); }, }, }; </script> <style> .ivu-timeline { margin-left: 20px; margin-top: 20px; } </style>
上述代码经过装配后显示的结果如下所示:
图-引用其他类型数据后显示的工单
注:上述工单的最后一条里面是故意创建的一个未知设备和未知用户的工单,用于检查异常处理是否生效。
弹出表单显示工单详情
最后,如果希望在这个时间线里点击详情用特定的表单打开工单,可以使用openTab方法调用实现,结合上面的例子,只需要修改openWorkOrder方法增加打开页签的调用即可,具体的调用方法如下:
先引入DWF传入的变量:props: ["itemValue", "root", "store", "Message"],然后利用,this.root.openTab(opr)实现打开DWF表单的目的。
具体代码如下:
export default { ... props: ["itemValue", "root", "store", "Message"], // 从DWF传入的一些常量 ... methods: { openWorkOrder(wo) { // 打开工单对象 let opr = { targetClass: 'WorkOrder', // 设定目标类的名字 authority: 'editWO', // 设定需要执行的操作英文名 conditionExpre: `and obj.oid = '${wo.oid}'`, // 打开当前被传入的工单 displayName: '工单详情', // 设置弹出页签的标题 viewName: 'SingleWO', // 打开对象的表单名 action: 'edit', // 打开页签对应的动作:edit, visit, create params: "" // 前后处理设置 }; // 打开工单对象 this.root.openTab(opr); }, ... }, }
在这段代码中:props: ["itemValue", "root", "store", "Message"],DWF是利用VUE自动传入的一些常量,其中:
- root表示整个DWF界面的外壳,封装了弹出页签的方法openTab。
- store表示前端查询得到的对象共享的存储。
- Message是一些显示信息的常用类型。
- itemValue是当前打开的表单引用的对象。
在下面章节里,我们还将介绍其他更加简便的方法操作DWF中的概念。
3.2 表单按钮操作开发
在上面的例子里,通过使用模块操作插件实现与DWF互动的方法,包括:
- 如何调用DWF的restful api获得工单数据。
- 如何利用restful api参数查找引用的用户,设备对象。
- 如何操纵DWF外壳弹出指定表单的页签,查看工单详情。
如果整个界面的主体是由开发者完全控制,那么采用上面的开发方法制作插件就可以了,但是,如果希望表单的主体是DWF实现的,在表单中希望增加以类似于按钮的操作,通过弹出一些DWF无法定制的界面收集信息之后返回原有表单,那这样的场景如何实现呢?这就要采用表单中的按钮插件开发来实现了。
接下来,再举一个例子,在设备编辑的界面里增加一个按钮插件,弹框显示一个文本框,然后将文本框数据带回上级表单并且更新被选中的设备对象的描述属性,效果如下图所示:
在这个例子里,我们将接管表单绘制过程,绘制一个特殊样式的文字按钮“编码生成工具”,在按钮点击的时候使用iView的弹框示例,来展示对话框本文不对弹框示例进行过多解释,当用户点击确定以后,可以将输入编码返回到设备表单的备注中。
接管绘制逻辑
首先,继续在上一章buttonOpr.vue中继续修改,在单设备编辑表单中,放入一个按钮插件,并且将按钮事件对应的操作设置为动作为implement类型的操作,插件的名字设置为buttonOpr.vue。
在DWF表单引擎中,“按钮”插件允许用户接管其展示方式,这意味着表单引擎放弃绘制默认的按钮界面,而将界面展示的逻辑交给操作开发者实现。通过实现下面两个方法可以实现:
- canShow:返回布尔值,如果为true则表单引擎放弃绘制,否则表单引擎将根据操作插件的参数配置绘制按钮。
- setArgs:如果canShow设置为true,那么表单引擎会调用这个函数,将原来在表单引擎中给按钮控件设置的参数传进来,在我们的例子中不需要显示这个参数,直接返回即可。
<script> export default { ... methods: { // 通知DWF表单引擎接管绘制过程 canShow() { return true; }, // 一旦接管,DWF表单引擎传入当前表单的上下文 setArgs(args) { return this; }, ... }, }; </script>
绘制标签设置
接下来,编写template标记,将按钮和弹框的基本样式设置出来,标签的样式如下:
<template> <div> <Button type="text" @click="openDialog">编码工具</Button> <Modal v-model="showDlg" :title="`${asset.assetType}`" @on-ok="ok" @on-cancel="cancel"> <Input v-model="asset.id" :placeholder="asset.id" /> </Modal> </div> </template>
在这个标签里默认状态下会显示一个按钮和一个默认状态下不显示的对话框,其中:
- 按钮<Button>的点击事件调用openDialog方法。
- 弹框确定和取消分别绑定了ok和cancel两个函数。
- 弹框<Modal>是否显示由showDlg变量决定,需要在openDialog函数里实现。
- 弹框<Modal>的标题默认设置为设备的类型asset.assetType,因此使用了vue的标签绑定语法。
- 在输入框<Input>的内容里设置为设备的代号asset.id,同样使用了vue的标签绑定语法。
数据绑定asset对象和showDlg实现的代码如下所示:
export default { name: "buttonOpr", data() { return { showDlg: false, asset: { id: "unknown", assetType: "unknown" }, }; }, ...
通过函数实现交互
围绕openDialog,ok,cancel的实现如下所示:
openDialog() { // 初始化弹框内容 var a = this.dwf_ctx.obj(); if (typeof a !== "undefined") { this.asset = a; } // 显示对话框 this.showDlg = true; }, ok() { // 获得备注控件设置内容 var addin = this.dwf_ctx.getAddinById("TextInput1"); addin.setValue(this.asset.id); }, cancel() {},
其中
- 打开弹框的时候通过this.dwf_ctx.obj()得到当前表单对象,然后将其赋值给this.asset实现自动刷新。
- 点击ok按钮里,利用this.dwf_ctx.getAddinById取得单对象表单中的备注控件,然后设置其取值。
最后,整个代码如下所示:
<template> <div> <Button type="text" @click="openDialog">编码工具</Button> <Modal v-model="showDlg" :title="`${asset.assetType}`" @on-ok="ok" @on-cancel="cancel"> <Input v-model="asset.id" :placeholder="asset.id" /> </Modal> </div> </template> <script> export default { name: "buttonOpr", data() { return { showDlg: false, asset: { id: "unknown" }, }; }, methods: { // 通知DWF表单引擎接管绘制过程 canShow() { return true; }, // 一旦接管,DWF表单引擎传入当前表单的上下文 setArgs(args) { return this; }, openDialog() { // 初始化弹框内容 var a = this.dwf_ctx.obj(); if (typeof a !== "undefined") { this.asset = a; } // 显示对话框 this.showDlg = true; }, ok() { // 获得备注控件设置内容 var addin = this.dwf_ctx.getAddinById("TextInput1"); addin.setValue(this.asset.id); }, cancel() {}, }, }; </script> <style> </style>
4 小结
本章深入介绍了操作插件和按钮插件的开发,要点如下:
第一、this.dwf_ctx,表示DWF中前端api的总入口,具备第三部分脚本的绝大部分功能。
第二、介绍了如何通过后端restful api:/omf/entities/WorkOrder/objects,获取实体类对象以及如何指定实体类对象引用其他实体类对象的方法。
第三、在表单中引入root,itemValue,store,Message的方法,通过root打开新的页签的方法:openTab。
第四、通过canShow和setArgs接管表单引擎绘制过程,并通过弹框将数据带回DWF的表单。
关于VUE如何实现双向数据绑定,以及iView控件库基础知识,本文不做介绍,有兴趣的读者可以参阅:按钮示例,文本框示例,弹窗示例。