目录 |
---|
...
引言
前面由浅入深介绍了表单控件的基本开发方法、开发了两个最简单控件:“简单控件”和“进阶控件”,在这个过程中,涉及到利用DWF提供的自动识别配置选项,添加事件和触发事件调用操作。
...
在详细介绍控件开发方法之前,先了解一下DWF中的,控件是怎么在表单定制工具里访问数据的。
...
模型端数据访问
关于数据访问的问题,我们在第五章,操作插件开发6.4 操作插件开发-进阶中介绍了this.dwf_ctx的概念,它代表了一切函数的总入口并且和上一部分的脚本开发里提供几乎同样的函数.。
同样的,在建模工具中也包含了这样的入口,通过this.dwf_ctx可以访问。在 ctx可以访问。在6.4.1 脚本调用和插件调用的对应关系中详细介绍了这些在建模工具中可以使用的函数和对应的例程。
下面就this.dwf_ctx包含的几个关键的函数进行介绍:
...
(一)获取模型数据
接下来,是this.dwf_ctx中关于模型数据获取的函数:
...
代码块 |
---|
let assetAttrs = this.dwf_ctx.getAttributesByClass("Asset"); assetAttrs.forEach(a => { if (this.dwf_ctx.isEntitySysAttribute(a)){ console.log(a); } }); |
...
(二)获取对象数据
在建模工具一侧,如果需要是访问可以通过下面的函数
- handleQueryData(queryData):通过${queryData}获取数据,通过回调将结果异步返回。
...
- targetClass: 指定目标类
- query: 表示目标对象查询条件的参数设定,具体包括如下的属性:
- query: 表示目标查询条件,例如:and obj.id = "xxx",关于查询语句的语法详见: DWF内部查询条件语法,关于查询语句的语法详见: 附录(五)DWF内部查询条件语法
- startIndex: 翻页有关,从第几条开始
- pageSize: 翻页有关,每页大小
- fresh: 是否从前端缓存加载,默认不从后端加载,当为false的时候从后端重新刷新加载
...
代码块 |
---|
async getData(){ let targetClass = "Asset"; let queryParams = { query: "and obj.name = 'xxx'", pageSize: 4, startIndex: 0, }; let objs = await this.handleQueryData({ targetClass: targetClass, query: queryParams, fresh: true, }); } |
接下来,开始进入控件的开发。
...
(三)使用内置的选择器
除了上述函数的接口
...
准备基础代码框架
参照 第六章,表单控件开发6.5 表单控件开发-入门和 第七章,表单控件开发6.6 表单控件开发-进阶,两章介绍,在part-web/modeler/forms以及part-web/app/forms文件夹,分别新建一个名为simpleCellGroup.vue文件。
...
然后,分别在part-web/modeler以及part-web/app文件夹对应的assemble-to.yaml文件里增加对应的装配选项,将简单列表放入多对象控件分组“form/multi"中:
...
增加控件配置选项和事件
在了解了modeler端数据访问的方法之后,可以回头看看列表控件应该提供的配置选项了。我们希望这个控件在绑定数据的时候可配置的选项如下:
(一)控件args选项设计
4.1 控件args选项设计
在 第七章,表单控件开发在6.6 表单控件开发-进阶,已经介绍DWF的表单引擎和表单建模工具之间配置选项是通过名为args对象实现模型数据交换的,对于简单列表这个控件来说,args属性可以复用dwf内置选项,还需要新增一些个性化选项:
复用内置选项
...
(1)复用内置选项
对于即将开发的列表控件而言,有些通用选项是可以直接利用dwf的保留选项实现的,可以参考6.6.1 DWF可自动识别的args参数了解可以直接使用的保留选项
- 目标类(bindTargetClass):用于设置需要显示的类,这个属性的设置我们可以直接利用dwf提供的保留字实现,因此无需额外考虑编码。
- 整体标题(label):用于设置列表项外框的整体标题,这个属性页可以直接利用dwf提供的保留字实现,无需额外考虑外观的编码。
- 高度和类型(height,heightType):设置控件的整体高度和样式单位。
- 宽度和类型(width,widthType):设置控件的整体宽度和宽度单位。
- 背景色(back_color):控件的背景色。
- 文字色(txt_fontcolor):控件中文字的颜色。
...
(2)增加交互事件
为了支持用户可以设置事件和操作的绑定关系,需要增加两个属性
- 事件清单(eventRange):表示预置的时间类型,是一个数组。
- 事件列表(events):用于存储用户设置的事件和操作的绑定。
...
(3)增加个性选项
除了上述的选项之外,结合Cell控件自身的特点设置选项,在本章的例子中,设计两个属性
...
上述args对应的配置选项和事件,会在在控件被表单建模工具或者表单引擎内部实例化的时候,对应的setArgs()传递到控件内部。
...
(二)加载对象数据
除了配置选项数据之外,简单列表控件还需要存储对象数据,以便将内容正确显示给用户,因此,在控件的vue文件中的data部分,增加一个cellData数组存储查询得到的对象,实现如下:
...
代码块 |
---|
mounted() { // 生命周期函数,实现数据加载 this.freshData(); }, methods: { // 使用 `handleQueryData` 获取数据 ...mapActions("DWF_form", ["handleQueryData"]), // 此处省略若干函数的实现直接接入正题... freshData() { // 获取已经绑定的实体类、关联类类名,此类名会有一个后缀以便表明其类型,例如实体类:Asset&e,关联类:PartToPart&r const bindTargetClass = this.args.bindTargetClass; if (!bindTargetClass) { console.error("no bindTargetClass"); return; } // 在使用的时候,需要将后缀抹去,利用正则表达式实现如下 const regexForTargetClass = /^([0-9a-zA-Z_]+)&([er])$/; const glob = regexForTargetClass.exec(bindTargetClass); if (glob === null) { console.error( `handleRefresh: invalid targetClass value -> ${bindTargetClass}` ); return; } // 经过正则过滤后,得到干净的实体类/关联类的类名 let targetClass = glob[1]; let query = { query: this.args.filterQuery, pageSize: 4, //设计的时候可以根据需要设置翻页和大小 startIndex: 0, }; this.handleQueryData({ targetClass: targetClass, query: query, fresh: true, }).then((res) => { const attributeForTitle = this.args.attributeForTitle; const attributeForLabel = this.args.attributeForLabel; let objs = res; this.cellData = { title: this.args.label, icon: "ios-bookmarks-outline", list: res, }; }); }, }, }; |
...
(三)设置动态样式
在前面章节里提到了前景色和背景色的设置,在即将开发的dwf控件里,样式的设置可以实现为一个计算属性,并将其绑定在卡片自身:style属性上,如下所示:
代码块 | ||
---|---|---|
| ||
<template> <!-- 建模时的预览前端,即插件的实际显示样式 :addinName="name"和ref="main"一般情况不可去除 --> <section v-if="t_preview" :addinName="name" ref="main"> <!-- 在画布区的显示内容 --> <span> <!-- 关于Cell控件的使用,详见:https://www.iviewui.com/components/cell#JCYF --> <Card :style="cardStyle" class="cell-group-wrapper"> <!-- 省略代码.... --> </Card> </span> </section> <!-- 省略代码.... --> </template> <script> //... computed: { cardStyle() { // 外层卡片的样式 const result = {}; if (this.args.height) { result["height"] = String(this.args.height) + (this.args.heightType || "px"); } if (this.args.width) { result["width"] = String(this.args.width) + (this.args.widthType || "px"); } if (this.args.back_color) { result["backgroundColor"] = this.args.back_color; } if (this.args.txt_fontColor) { result["color"] = this.args.txt_fontColor; } return result; }, }, methods: { }, </script> <style scoped> /* 没有合适的数据的时候使用的样式 */ .no-result-prompt { display: flex; align-items: center; justify-content: center; background-color: #eeeeee; color: #00000022; font-weight: bold; font-size: 2em; } /* hack to respond to height change. see https://vue-loader.vuejs.org/guide/scoped-css.html#deep-selectors */ .cell-group-wrapper >>> .ivu-card-body { position: relative; height: calc(100% - 60px); } .cell-group-wrapper >>> .ivu-cell-group { height: 100%; overflow-y: auto; } </style> |
4.4 数据与标签的绑定
...
(四)数据与标签的绑定
(1)在表单定制工具中的标签
在表单建模工具中,args对应的配置界面可以直接利用dwf提供的EditBox组件自动实现,而个性化属性attrbuteForTile和attributeForLabel需要在EditBox标签内增加slot实现,具体的代码如下:
...
- 预览区中会根据控件的配置数据从cellData里将内容显示出来。
- 选项设置区中利用EditBox将args的数据显示出来,其中使用了一个组件AttributeSelector用于帮助用户设置属性,详细内容 DWF提供的内置选项设置组件中间描述。
- 最后在图表区里,显示了控件的图标
...
(2)在表单引擎中的标签
在app端的表单引擎里由于没有设置选项的界面,因此,标签可以大幅度简化,如下所示:
代码块 |
---|
<template> <!-- 建模时的预览前端,即插件的实际显示样式 :addinName="name"和ref="main"一般情况不可去除 --> <section :addinName="name" ref="main"> <!-- 在画布区的显示内容 --> <span> <!-- see https://www.iviewui.com/components/cell#JCYF --> <Card :style="cardStyle" class="cell-group-wrapper"> <p slot="title"> <!-- change to slot syntax to avoid a bug, where :title may not respond to change as a prop. --> <Icon :type="cellData.icon"></Icon> {{ cellData.title }} </p> <CellGroup v-if="cellData.list && cellData.list.length" @on-click="onClick" > <Cell v-for="(obj, idx) in cellData.list" :key="obj.oid" :name="idx" :title="String(obj[args.attributeForTitle])" :label="String(obj[args.attributeForLabel])" @click="onClick" > <Avatar :src="String(obj[args.attributeForIcon])" slot="icon" /> </Cell> </CellGroup> <div v-else class="no-result-prompt">无数据展示...</div> </Card> </span> </section> </template> |
...
(3)表单引擎中的事件响应
在这里的<CellGroup>标签里,增加了@on-click的实现,对应控件onClick()函数,需要激活操作,具体的调用和 第七章,表单控件开发6.6 表单控件开发-进阶提到的方法类似,在methods部分增加函数调用操作即可:
代码块 | ||
---|---|---|
| ||
// 单击事件 onClick() { this.triggerEvent("单击"); }, // 根据名字触发表单事件上配置的操作 triggerEvent(eventName) { let eventConfig = null; // 提取事件对应的操作参数 if (this.args.events && this.args.events.length > 0) { eventConfig = this.args.events.find((val) => { return val.name === eventName; }); } // 触发实际操作 if (eventConfig) { this.invokeOperation( eventConfig.opr_type, eventConfig.opr_path, this.itemValue, this.store ); } }, |
...
增加供DWF脚本使用的方法
为了给脚本开发者提供支持,控件一般会提供一些函数供脚本开发者调用,例如:freshData(),getAll(),getSelected()等,在本章开发的简单列表控件中,只需要在app端methods部分提供对应的方法即可实现对脚本的支持,实现如下:
代码块 |
---|
// Vue数据绑定的时候要求返回的结果 data() { return { // 插件的名字 name: "simpleCellGroup", // ... // 需要显示数据的集合,包括标题,图标,单元标题,单元内容,约定格式如下 cellData: {}, // 记录被点击选择的对象 cellSelected: {}, }; }, methods: { ... getSelected() { return this.cellSelected; }, getAll() { return this.cellData.list; }, // 单击事件,会传入被点击对象的索引,详见:https://www.iviewui.com/components/cell onClick(idx) { this.cellSelected = this.cellData.list[idx]; this.triggerEvent("单击"); }, } |
...
结果展示
上述代码汇总在一起之后,通过装配,可以在建模工具内看到新增的多对象控件“简单列表”,选择类、标题属性、内容属性,然后点击刷新数据,即可看到数据显示的结果。
...
在控件中可以添加单击事件,例如:绑定一个简单的消息弹框的事件之后,显示的内容如下:
...
最终代码
...
(一)建模工具
建模工具的代码如下
代码块 | ||||||
---|---|---|---|---|---|---|
| ||||||
<template> <!-- 建模时的预览前端,即插件的实际显示样式 :addinName="name"和ref="main"一般情况不可去除 --> <section v-if="t_preview" :addinName="name" ref="main"> <!-- 在画布区的显示内容 --> <span> <!-- see https://www.iviewui.com/components/cell#JCYF --> <Card :style="cardStyle" class="cell-group-wrapper"> <p slot="title"> <!-- change to slot syntax to avoid a bug, where :title may not respond to change as a prop. --> <Icon :type="cellData.icon"></Icon> {{ cellData.title }} </p> <CellGroup v-if="cellData.list && cellData.list.length"> <Cell v-for="obj in cellData.list" :key="obj.oid" :title="String(obj[args.attributeForTitle])" :label="String(obj[args.attributeForLabel])" > <Avatar :src="String(obj[args.attributeForIcon])" slot="icon" /> </Cell> </CellGroup> <div v-else class="no-result-prompt">无数据展示...</div> </Card> </span> <span v-show="t_edit" ref="edit"> <!-- v-bind. see https://vuejs.org/v2/api/#v-bind --> <EditBox v-model="args" :router="router" :route="route" :root="root" :itemValue="itemValue" :query_oprs="query_oprs" :targetclass="itemValue.data.targetClass" > <div slot="attribute"> <!-- 从外部引用的辅助标签,用于显示属性 --> <p>标题属性(Title)</p> <AttributeSelector :target-class="args.bindTargetClass" v-model="args.attributeForTitle" ></AttributeSelector> <p>详情属性(Label)</p> <AttributeSelector :target-class="args.bindTargetClass" v-model="args.attributeForLabel" ></AttributeSelector> <p>图标属性(Icon)</p> <AttributeSelector :target-class="args.bindTargetClass" v-model="args.attributeForIcon" ></AttributeSelector> <Button type="primary" long @click="freshData">刷新数据</Button> </div> </EditBox> </span> </section> <section v-else :addinName="name"> <span style="text-align: center"> <div class="form-addin-icon"> <!-- 控件拖放区图标的样式 see: http://ise.thss.tsinghua.edu.cn/font_857540_a2fo0rqai0f/demo_index.html--> <i class="iconfont"></i> </div> <!-- 在控件拖放区图标的名字 --> <div class="form-addin-name">简单列表</div> </span> </section> </template> <script> // 从本地引擎的一个自定义标签 import AttributeSelector from "./AttributeSelector"; import EditBox from "@/ext_components/form/_EditBox.vue"; import { mapGetters, mapActions } from "vuex"; export default { props: [ "widgetAnnotation", "itemValue", "attributes", "route", "router", "root", "query_oprs", ], components: { EditBox, AttributeSelector, }, // Vue数据绑定的时候要求返回的结果 data() { return { // 插件的名字 name: "simpleCellGroup", // 表示是否已经进入画布区 t_preview: false, // 是否显示控件的属性编辑区 t_edit: false, // 属性配置项,按需设置 args: { // 直接复用DWF默认的配置项,从而无需自行开发配置界面 bindTargetClass: "", // 标签作为列表标题 label: "", // 建模设置的过滤条件 filterQuery: "", // 高度取值和单位 height: "", heightType: "px", // 宽度取值和单位 width: "", widthType: "px", // 背景色设置 back_color: "", // 文字颜色设置 txt_fontColor: "", /* --------------------- * 下面是新增的自定义属性 * -------------------- */ // 单元格显示标题 attributeForTitle: "", // 单元格显示内容 attributeForLabel: "", // 显示图标对应属性 attributeForIcon: "", // 在配置区支持的事件列表,元素为事件中文名 eventRange: ["单击"], // 已被用户设置的事件列表,元素格式为 { opr_type: '', opr_path: '', name: '事件中文名' } events: [], }, cellData: {}, }; }, mounted() { // 生命周期函数,实现数据加载 this.freshData(); }, computed: { //...mapGetters("DWF_form", ["Entities", "Relations"]), cardStyle() { // 外层卡片的样式 const result = {}; if (this.args.height) { result["height"] = String(this.args.height) + (this.args.heightType || "px"); } if (this.args.width) { result["width"] = String(this.args.width) + (this.args.widthType || "px"); } if (this.args.back_color) { result["backgroundColor"] = this.args.back_color; } if (this.args.txt_fontColor) { result["color"] = this.args.txt_fontColor; } return result; }, }, methods: { // use `handleQueryData` to fetch data. ...mapActions("DWF_form", ["handleQueryData"]), /* 在建模的时候传入,提示控件是在画布区还是在控件列表中 0:表示在画布区,已经被拖入 1:表示在控件区,准备被拖入 2: 表示在拖动中,还未放下 */ setDisplayType(type) { if (type == 0) this.t_preview = true; return this; }, // 默认不用变化,返回编辑框供建模工具绘制,当控件拖入画布区以后被点击的时候触发 getEditBox() { this.t_edit = true; return this.$refs.edit; }, // 当插件无法直接通过style设置高度时,使用setHeight方法设置高度 setHeight() { if (!this.$refs.main) return; }, // 现有表单加载在画布区加载控件的时候,会将之前的配置传入 setArgs(args) { for (var i in args) { this.args[i] = args[i]; } return this; }, // 表单保存的时候将控件的设置合并到表单自身的JSON中 getArgs() { return this.args; }, // 是否允许其他子控件拖入,如果不允许则返回null getAllow() { return null; }, // 返回控件绑定的目标属性,如果没有绑定返回undefined或者null getFormName() { return null; }, freshData() { const bindTargetClass = this.args.bindTargetClass; if (!bindTargetClass) { console.error("no bindTargetClass"); return; } const regexForTargetClass = /^([0-9a-zA-Z_]+)&([er])$/; const result = regexForTargetClass.exec(bindTargetClass); if (result === null) { console.error( `handleRefresh: invalid targetClass value -> ${bindTargetClass}` ); return; } let targetClass = result[1]; let query = { query: this.args.filterQuery, pageSize: 4, startIndex: 0, }; let objs = this.handleQueryData({ targetClass: targetClass, query: query, fresh: true, }).then((res) => { let objs = res; this.cellData = { title: this.args.label, icon: "ios-bookmarks-outline", list: res, }; }); }, }, }; </script> <style scoped> /* 没有合适的数据的时候使用的样式 */ .no-result-prompt { display: flex; align-items: center; justify-content: center; background-color: #eeeeee; color: #00000022; font-weight: bold; font-size: 2em; } /* hack to respond to height change. see https://vue-loader.vuejs.org/guide/scoped-css.html#deep-selectors */ .cell-group-wrapper >>> .ivu-card-body { position: relative; height: calc(100% - 60px); } .cell-group-wrapper >>> .ivu-cell-group { height: 100%; overflow-y: auto; } </style> |
...
(二)应用前端
代码块 | ||||||
---|---|---|---|---|---|---|
| ||||||
<template> <!-- 建模时的预览前端,即插件的实际显示样式 :addinName="name"和ref="main"一般情况不可去除 --> <section :addinName="name" ref="main"> <!-- 在画布区的显示内容 --> <span> <!-- see https://www.iviewui.com/components/cell#JCYF --> <Card :style="cardStyle" class="cell-group-wrapper"> <p slot="title"> <!-- change to slot syntax to avoid a bug, where :title may not respond to change as a prop. --> <Icon :type="cellData.icon"></Icon> {{ cellData.title }} </p> <CellGroup v-if="cellData.list && cellData.list.length" @on-click="onClick" > <Cell v-for="(obj, idx) in cellData.list" :key="obj.oid" :name="idx" :title="String(obj[args.attributeForTitle])" :label="String(obj[args.attributeForLabel])" @click="onClick" > <Avatar :src="String(obj[args.attributeForIcon])" slot="icon" /> </Cell> </CellGroup> <div v-else class="no-result-prompt">无数据展示...</div> </Card> </span> </section> </template> <script> // 引入内部图标备用 import "@/styles/component/iconfont.css"; import { mapActions } from "vuex"; export default { props: [ "widgetAnnotation", "itemValue", "attributes", "route", "router", "root", "store", "query_oprs", ], // Vue数据绑定的时候要求返回的结果 data() { return { // 插件的名字 name: "simpleCellGroup", // 表示是否已经进入画布区 t_preview: false, // 是否显示控件的属性编辑区 t_edit: false, // 属性配置项,按需设置 args: { // 直接复用DWF默认的配置项,从而无需自行开发配置界面 bindTargetClass: "", // 标签作为列表标题 label: "", // 建模设置的过滤条件 filterQuery: "", // 高度取值和单位 height: "", heightType: "px", // 宽度取值和单位 width: "", widthType: "px", // 背景色设置 back_color: "", // 文字颜色设置 txt_fontColor: "", /* --------------------- * 下面是新增的自定义属性 * -------------------- */ // 单元格显示标题 attributeForTitle: "", // 单元格显示内容 attributeForLabel: "", // 已被用户设置的事件列表,元素格式为 { opr_type: '', opr_path: '', name: '事件中文名' } events: [], }, // 需要显示数据的集合,包括标题,图标,单元标题,单元内容,约定格式如下 cellData: {}, // 记录被点击选择的对象 cellSelected: {}, }; }, mounted() { // 生命周期函数,实现数据加载 this.freshData(); }, computed: { cardStyle() { // 外层卡片的样式 const result = {}; if (this.args.height) { result["height"] = String(this.args.height) + (this.args.heightType || "px"); } if (this.args.width) { result["width"] = String(this.args.width) + (this.args.widthType || "px"); } if (this.args.back_color) { result["backgroundColor"] = this.args.back_color; } if (this.args.txt_fontColor) { result["color"] = this.args.txt_fontColor; } return result; }, }, methods: { // use `handleQueryData` to fetch data. ...mapActions("DWF_form", ["handleQueryData"]), /* 在建模的时候传入,提示控件是在画布区还是在控件列表中 0:表示在画布区,已经被拖入 1:表示在控件区,准备被拖入 2: 表示在拖动中,还未放下 */ setDisplayType(type) { if (type == 0) this.t_preview = true; return this; }, // 默认不用变化,返回编辑框供建模工具绘制,当控件拖入画布区以后被点击的时候触发 getEditBox() { this.t_edit = true; return this.$refs.edit; }, // 当插件无法直接通过style设置高度时,使用setHeight方法设置高度 setHeight() { if (!this.$refs.main) return; }, // 现有表单加载在画布区加载控件的时候,会将之前的配置传入 setArgs(args) { for (var i in args) { this.args[i] = args[i]; } return this; }, // 表单保存的时候将控件的设置合并到表单自身的JSON中 getArgs() { return this.args; }, // 是否允许其他子控件拖入,如果不允许则返回null getAllow() { return null; }, // 返回控件绑定的目标属性,如果没有绑定返回undefined或者null getFormName() { return null; }, getSelected() { return this.cellSelected; }, getAll() { return this.cellData.list; }, // 单击事件 onClick(idx) { this.cellSelected = this.cellData.list[idx]; this.triggerEvent("单击"); }, // 根据名字触发表单事件上配置的操作 triggerEvent(eventName) { let eventConfig = null; // 提取事件对应的操作参数 if (this.args.events && this.args.events.length > 0) { eventConfig = this.args.events.find((val) => { return val.name === eventName; }); } // 触发实际操作 if (eventConfig) { this.invokeOperation( eventConfig.opr_type, eventConfig.opr_path, this.itemValue, this.store ); } }, // 刷新控件内部数据 freshData() { const bindTargetClass = this.args.bindTargetClass; if (!bindTargetClass) { console.error("no bindTargetClass"); return; } const regexForTargetClass = /^([0-9a-zA-Z_]+)&([er])$/; const glob = regexForTargetClass.exec(bindTargetClass); if (glob === null) { console.error( `handleRefresh: invalid targetClass value -> ${bindTargetClass}` ); return; } const targetClass = glob[1]; const query = this.args.filterQuery; console.log(this.dwf_ctx); this.handleQueryData({ targetClass: targetClass, query: query, fresh: true, }).then((res) => { const attributeForTitle = this.args.attributeForTitle; const attributeForLabel = this.args.attributeForLabel; let objs = res; this.cellData = { title: this.args.label, icon: "ios-bookmarks-outline", list: res, }; }); }, }, }; </script> <style scoped> /* 没有合适的数据的时候使用的样式 */ .no-result-prompt { display: flex; align-items: center; justify-content: center; background-color: #eeeeee; color: #00000022; font-weight: bold; font-size: 2em; } /* hack to respond to height change. see https://vue-loader.vuejs.org/guide/scoped-css.html#deep-selectors */ .cell-group-wrapper >>> .ivu-card-body { position: relative; height: calc(100% - 60px); } .cell-group-wrapper >>> .ivu-cell-group { height: 100%; overflow-y: auto; } </style> |
...
小结
本章主要结合vue列表控件介绍了如何在dwf中实现一个多对象控件。在这个过程中重点涉及了:
...