...
对于即将开发的列表控件而言,有些通用选项是可以直接利用dwf的保留选项实现的,可以参考 DWF可自动识别的args参数 了解可以直接使用的保留选项
- 名称(title):返回控件的名字,用于显示。
- 目标类(bindTargetClass):用于设置需要显示的类,这个属性的设置我们可以直接利用dwf提供的保留字实现,因此无需额外考虑编码。
- 整体标题(label):用于设置列表项外框的整体标题,这个属性页可以直接利用dwf提供的保留字实现,无需额外考虑外观的编码。
- 高度和类型(height,heightType):设置控件的整体高度和样式单位。
- 宽度和类型(width,widthType):设置控件的整体宽度和宽度单位。
- 背景色(back_color):控件的背景色。
- 文字色(txt_fontcolor):控件中文字的颜色。
...
为了支持用户可以设置事件和操作的绑定关系,需要增加两个属性
- 事件清单(eventRange):表示预置的时间类型,是一个数组。事件清单(eventRange):表示预置的事件类型,是一个数组。
- 事件列表(events):用于存储用户设置的事件和操作的绑定。
...
基于上述设计简单列表的控件在建模工具modeler侧和app侧的vue文件对应的配置选项的代码如下:
代码块 |
---|
args: { // 在选项区显示的名称 title: "简单列表", // 直接复用DWF默认的配置项,从而无需自行开发配置界面 bindTargetClass: "", // 标签作为列表标题 label: "", // 建模设置的过滤条件 filterQuery: "", // 高度取值和单位 height: "", heightType: "px", // 宽度取值和单位 width: "", widthType: "px", // 背景色设置 back_color: "", // 文字颜色设置 txt_fontColor: "", // 在配置区支持的事件列表,元素为事件中文名 eventRange: ["单击"], // 已被用户设置的事件列表,元素格式为 { opr_type: '', opr_path: '', name: '事件中文名' } events: [], /* --* ------------------------- * 下面是新增的自定义属性 * -------------------- */ // 单元格显示标题 attributeForTitle: "", // 单元格显示内容 attributeForLabel: "", // 显示图标对应属性 attributeForIcon: "", }, |
上述args对应的配置选项和事件,会在在控件被表单建模工具或者表单引擎内部实例化的时候,对应的setArgs()传递到控件内部。
4.2 加载对象数据
除了配置选项数据之外,简单列表控件还需要存储对象数据,以便将内容正确显示给用户,因此,在控件的vue文件中的data部分,增加一个cellData数组存储查询得到的对象,实现如下:
代码块 | ||
---|---|---|
| ||
// Vue数据绑定的时候要求返回的结果 data()在配置区支持的事件列表,元素为事件中文名 eventRange: ["单击"], // 已被用户设置的事件列表,元素格式为 { opr_type: '', opr_path: '', name: '事件中文名' } events: [], }, |
上述args对应的配置选项和事件,会在在控件被表单建模工具或者表单引擎内部实例化的时候,对应的setArgs()传递到控件内部。
4.2 加载对象数据
除了配置选项数据之外,简单列表控件还需要存储对象数据,以便将内容正确显示给用户,因此,在控件的vue文件中的data部分,增加一个cellData数组存储查询得到的对象,实现如下:
代码块 | ||
---|---|---|
| ||
// Vue数据绑定的时候要求返回的结果 data() { return { // 插件的名字 name: "simpleCellGroupSimpleCellGroup", // 表示是否已经进入画布区 t_preview: falsetrue, // 是否显示控件的属性编辑区 t_edit: false, // 属性配置项,按需设置 argsaddIcon: {true, // 前面已经描述,此处省略...actEdit: true, actIndex: -1, setModal: false, // 控件支持设置类型 dataTypes: ["String"], }, // 属性配置项,按需设置 args: { // 需要显示数据的集合,包括标题,图标,单元标题,单元内容,约定格式如下 控件自带的选项等,这里忽略 ... }, cellData: {}, }; }, |
其中cellData是未来需要和前端标签部分绑定的数据,其格式如下:
...
这些对象数据应该在vue控件的生命周期函数mounted()中实现,在dwf中习惯通过freshData()作为控件刷新默认实现,因此需要在created()作为控件刷新默认实现,因此只需要在mounted中提取表单的store属性,并且在mounted()函数中调用这个freshData()即可:
代码块 |
---|
mounted()import { // 生命周期函数,实现数据加载 this.freshData(); }, methods:mapGetters, mapActions } from "vuex"; export default { // 使用 `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)props: [ "addin", "basicArgs", "argsProps", "activeUUID", "store", "itemValue", "attributes", "relation", "editExtendInfo", "widgetAnnotation", "checkResult", "query_oprs", "route", "router", "root", "Message", "echarts", ], ... //省略部分代码 mounted() { // 生命周期函数,实现数据加载 this.freshData(); }, created() { // 将表单引擎传递下来的store赋值给当前控件的store,以便handleQueryData可以用到这个全局属性 this.$store = this.store; }, computed: { //...mapGetters("DWF_form", ["Entities", "Relations"]), cardStyle() { // 外层卡片的样式 const result = {}; if (glob === nullthis.args.height) { console.error(result["height"] = `handleRefresh: invalid targetClass value -> ${bindTargetClass}` String(this.args.height) + (this.args.heightType || "px"); return;} } if (this.args.width) { // 经过正则过滤后,得到干净的实体类/关联类的类名 let targetClass = glob[1] result["width"] = String(this.args.width) + (this.args.widthType || "px"); let query = { } query:if (this.args.filterQuery,back_color) { pageSize: 4, //设计的时候可以根据需要设置翻页和大小result["backgroundColor"] = this.args.back_color; startIndex: 0, } }; if (this.args.txt_fontColor) { result["color"] = this.handleQueryData({.args.txt_fontColor; targetClass: targetClass, } query: query,return result; }, fresh: true,// 需要用到实体类属性列表时使用 }).then((resfilter_attributes() => { 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, }; })return []; }, }, methods: { // use `handleQueryData` to fetch data. ...mapActions("DWF_form", ["handleQueryData"]), // 默认不用变化,返回编辑框供建模工具绘制,当控件拖入画布区以后被点击的时候触发 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; }, }, }; |
4.3 设置动态样式
在前面章节里提到了前景色和背景色的设置,在即将开发的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() { // 外层卡片的样式 // 表单保存的时候将控件的设置合并到表单自身的JSON中 getArgs() { return this.args; }, // 返回控件绑定的目标属性,如果没有绑定返回undefined或者null getFormName() { return this.args.name; }, 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 (this.args.heightresult === null) { result["height"] =console.error( String(this.args.height) + (this.args.heightType || "px"`handleRefresh: invalid targetClass value -> ${bindTargetClass}` ); } return; if (this.args.width) {} let targetClass = result["width"] = 1]; let query = { String(query: this.args.width) + (this.args.widthType || "px");filterQuery, pageSize: 4, startIndex: 0, }; iflet objs = (this.args.back_color) handleQueryData({ result["backgroundColor"] = this.args.back_color;targetClass: targetClass, query: query, } fresh: true, if (this.args.txt_fontColor)}).then((res) => { result["color"]let objs = this.args.txt_fontColorres; }this.cellData = { return result; title: this.args.label, }, }, 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 数据与标签的绑定
在表单定制工具中的标签
在表单建模工具中,args对应的配置界面可以直接利用dwf提供的EditBox组件自动实现,而个性化属性attrbuteForTile和attributeForLabel需要在EditBox标签内增加slot实现,具体的代码如下:
代码块 | ||
---|---|---|
| ||
<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> |
在上述代码里:
- 预览区中会根据控件的配置数据从cellData里将内容显示出来。
- 选项设置区中利用EditBox将args的数据显示出来,其中使用了一个组件AttributeSelector用于帮助用户设置属性,详细内容 DWF提供的内置选项设置组件中间描述。
- 最后在图表区里,显示了控件的图标
在表单引擎中的标签
在app端的表单引擎里由于没有设置选项的界面,因此,标签可以大幅度简化,如下所示:
icon: "ios-bookmarks-outline",
list: res,
};
});
},
},
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,
};
});
},
},
}; |
4.3 设置动态样式
在前面章节里提到了前景色和背景色的设置,在即将开发的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 数据与标签的绑定
在表单定制工具中的标签
在表单建模工具中,args对应的配置界面可以直接利用dwf提供的EditBox组件自动实现,而个性化属性attrbuteForTile和attributeForLabel需要在EditBox标签内增加slot实现,具体的代码如下:
代码块 | ||
---|---|---|
| ||
<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" @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> |
表单引擎中的事件响应
在这里的<CellGroup>标签里,增加了@on-click的实现,对应控件onClick()函数,需要激活操作,具体的调用和 第七章 表单控件开发-进阶 提到的方法类似,在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
);
}
}, |
5 增加供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("单击");
},
} |
6 结果展示
上述代码汇总在一起之后,通过装配,可以在建模工具内看到新增的多对象控件“简单列表”,选择类、标题属性、内容属性,然后点击刷新数据,即可看到数据显示的结果。
图-简单列表多对象控件
在控件中可以添加单击事件,例如:绑定一个简单的消息弹框的事件之后,显示的内容如下:
7 最终代码
7.1 建模工具
建模工具的代码如下
代码块 | ||||||
---|---|---|---|---|---|---|
| ||||||
<template> <!-- 建模时的预览前端,即插件的实际显示样式 :addinName="name"和ref="main"一般情况不可去除 --> <section v-if="t_preview" :addinName="name" ref="main"> <!-- 在画布区的显示内容 --> <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> <!-- see https://www.iviewui.com/components/cell#JCYF 在控件拖放区图标的名字 --> <Card :style="cardStyle" <div class="cellform-groupaddin-wrappername">>简单列表</div> <p slot="title"> </span> </section> </template> |
在上述代码里:
- 预览区中会根据控件的配置数据从cellData里将内容显示出来。
- 选项设置区中利用EditBox将args的数据显示出来,其中使用了一个组件AttributeSelector用于帮助用户设置属性,详细内容 DWF提供的内置选项设置组件中间描述。
- 最后在图表区里,显示了控件的图标
在表单引擎中的标签
在app端的表单引擎里由于没有设置选项的界面,因此,标签可以大幅度简化,如下所示:
代码块 |
---|
<template> <!-- change to slot syntax to avoid a bug建模时的预览前端,即插件的实际显示样式 where :title may not respond to change as a prop. :addinName="name"和ref="main"一般情况不可去除 --> <Icon<section :typestyle="cellData.icon"></Icon>{ {{ cellData.title }}width: args.width + args.widthType, </p>}" :addinName="name" ref="main"> <CellGroup v-if="cellData.list && cellData.list.length"<!-- 在画布区的显示内容 --> <span <Cell:style="{ v-for="obj in cellData.list"height: args.height+ args.heightType, :key="obj.oid"width: args.width + args.widthType, }" :titlestyle="String(obj[args.attributeForTitle])" font-size: 50px; :label="String(obj[args.attributeForLabel])"display: flex; > justify-content: center; align-items: center;" <Avatar :src@click="String(obj[args.attributeForIcon])onClick" slot @dblclick="icononDoubleClick" /> >{{args.label}}</Cell>span> </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 section> </template> |
表单引擎中的事件响应
在这里的<CellGroup>标签里,增加了@on-click的实现,对应控件onClick()函数,需要激活操作,具体的调用和 第七章 表单控件开发-进阶 提到的方法类似,在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
);
}
}, |
5 增加供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("单击");
},
} |
6 结果展示
上述代码汇总在一起之后,通过装配,可以在建模工具内看到新增的多对象控件“简单列表”,选择类、标题属性、内容属性,然后点击刷新数据,即可看到数据显示的结果。
图-简单列表多对象控件
在控件中可以添加单击事件,例如:绑定一个简单的消息弹框的事件之后,显示的内容如下:
7 最终代码
7.1 建模工具
建模工具的代码如下
代码块 | ||||||
---|---|---|---|---|---|---|
| ||||||
<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-if="actEdit" :addinName="name" :widgetAnnotation="widgetAnnotation" :editExtendInfo="editExtendInfo" ref="editbox" v-model="args" :attributes="filter_attributes" :router="router" :route="route" :store="store" :root="root" :itemValue="itemValue" :query_oprs="query_oprs" :dataTypes="dataTypes" :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.attributeForTitleattributeForLabel" ></AttributeSelector> <p>详情属性(Label)</p> <AttributeSelector ></AttributeSelector> <p>图标属性(Icon)</p> <AttributeSelector :target-class="args.bindTargetClass" v-model="args.attributeForLabel" ></AttributeSelector> <p>图标属性(Icon)</p> <AttributeSelector :target-class="args.bindTargetClass" v-model="args.attributeForIcon" ></AttributeSelector> 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> ></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: [ "addin", "basicArgs", "argsProps", "activeUUID", "store", "itemValue", "attributes", "relation", "editExtendInfo", "widgetAnnotation", "itemValuecheckResult", "attributesquery_oprs", "route", "router", "root", "Message", "query_oprsecharts", ], components: { EditBox, AttributeSelector, }, // Vue数据绑定的时候要求返回的结果 data() { return { // 插件的名字 name: "simpleCellGroupSimpleCellGroup", // 表示是否已经进入画布区 t_preview: false: true, // 是否显示控件的属性编辑区 t_edit: false, // 属性配置项,按需设置 addIcon: true, actEdit: true, actIndex: -1, setModal: false, // 控件支持设置类型 dataTypes: ["String"], // 是否显示控件的属性编辑区属性配置项,按需设置 t_editargs: false,{ // 属性配置项,按需设置在选项区显示的名称 args title: {"简单列表", // 直接复用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: {}, }; }, created() { // 将表单引擎传递下来的store赋值给当前控件的store,以便handleQueryData可以用到这个全局属性 this.$store = this.store; }, mounted() { // 生命周期函数,实现数据加载 this.freshData(); console.log(this.itemValue.data.targetClass); }, 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; }, // 需要用到实体类属性列表时使用 filter_attributes() { 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表单保存的时候将控件的设置合并到表单自身的JSON中 getAllowgetArgs() { return nullthis.args; }, // 返回控件绑定的目标属性,如果没有绑定返回undefined或者null getFormName() { return nullthis.args.name; }, 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))$/; const result = regexForTargetClass.exec(bindTargetClass); if (result === null) { console.error( `handleRefresh: invalid targetClass value -> ${bindTargetClass}` ); return; } let targetClass = result[1]; iflet (resultquery === null) { console.error( query: this.args.filterQuery, pageSize: 4, `handleRefreshstartIndex: invalid targetClass value -> ${bindTargetClass}` 0, }; let objs = this.handleQueryData({ targetClass: targetClass, );query: query, return;fresh: true, }).then((res) => { let targetClassobjs = result[1]res; let querythis.cellData = { query title: this.args.filterQuerylabel, pageSize icon: 4"ios-bookmarks-outline", startIndex list: 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>res, }; }); }, }, }; </script> |
对应的配置文件如下:
代码块 | ||||
---|---|---|---|---|
| ||||
config:
ignore:
info:
part-web:
name: modeler
cname: 建模端
forms:
...
SimpleCellGroup.vue:
icon: "md-apps"
cname: 高级控件
type: form/multi
operations:
...
mobileForms:
...
mobileOperations:
...
dependencies:
...
|
7.2 应用前端
代码块 | ||||||
---|---|---|---|---|---|---|
| ||||||
<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 { props:// [插件的名字 name: "widgetAnnotationsimpleCellGroup", "itemValue" // 表示是否已经进入画布区 t_preview: false, "attributes", "route" // 是否显示控件的属性编辑区 t_edit: false, "router", "root", "store", "query_oprs", ], // 属性配置项,按需设置 args: { // 直接复用DWF默认的配置项,从而无需自行开发配置界面 bindTargetClass: "", // Vue数据绑定的时候要求返回的结果标签作为列表标题 data() { return { label: "", // 插件的名字建模设置的过滤条件 name filterQuery: "simpleCellGroup", // 表示是否已经进入画布区 高度取值和单位 height: "", t_previewheightType: false"px", // 是否显示控件的属性编辑区 宽度取值和单位 width: "", t_edit widthType: false"px", // 属性配置项,按需设置背景色设置 argsback_color: {"", // 直接复用DWF默认的配置项,从而无需自行开发配置界面文字颜色设置 bindTargetClasstxt_fontColor: "", // 标签作为列表标题* --------------------- * 下面是新增的自定义属性 label: "", * -------------------- */ // 建模设置的过滤条件单元格显示标题 filterQueryattributeForTitle: "", // 高度取值和单位单元格显示内容 heightattributeForLabel: "", heightType: "px", // 宽度取值和单位 width: "", widthType: "px", // 背景色设置 back_color: "",// 已被用户设置的事件列表,元素格式为 { opr_type: '', opr_path: '', name: '事件中文名' } // 文字颜色设置events: [], txt_fontColor: ""}, //* --------------------- 需要显示数据的集合,包括标题,图标,单元标题,单元内容,约定格式如下 * 下面是新增的自定义属性cellData: {}, // * -------------------- */ 记录被点击选择的对象 // 单元格显示标题cellSelected: {}, }; attributeForTitle: "", }, mounted() { // 单元格显示内容生命周期函数,实现数据加载 this.freshData(); }, attributeForLabelcomputed: "",{ cardStyle() { // 已被用户设置的事件列表,元素格式为 { opr_type: '', opr_path: '', name: '事件中文名' } events: [], 外层卡片的样式 const result = {}; if (this.args.height) { result["height"] = String(this.args.height) + (this.args.heightType || "px"); }, // 需要显示数据的集合,包括标题,图标,单元标题,单元内容,约定格式如下 cellData: {},if (this.args.width) { // 记录被点击选择的对象 result["width"] = cellSelected: {}, }; }, mounted() { // 生命周期函数,实现数据加载 this.freshData(); }, computed: { cardStyle(String(this.args.width) + (this.args.widthType || "px"); } if (this.args.back_color) { // 外层卡片的样式 result["backgroundColor"] = this.args.back_color; const result = {}; if (this.args.heighttxt_fontColor) { result["heightcolor"] = this.args.txt_fontColor; } return result; }, 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}, methods: { // use `handleQueryData` to fetch data. ...mapActions("DWF_form", ["handleQueryData"]), /* 在建模的时候传入,提示控件是在画布区还是在控件列表中 0:表示在画布区,已经被拖入 1:表示在控件区,准备被拖入 2: 表示在拖动中,还未放下 */ setDisplayType(type) { if (type == 0) this.t_preview = true; return this; }, // 当插件无法直接通过style设置高度时,使用setHeight方法设置高度 setHeight() { result["color"] = if (!this.args.txt_fontColor$refs.main) return; }, return result;// 现有表单加载在画布区加载控件的时候,会将之前的配置传入 }, }, methods: { // use `handleQueryData` to fetch data. ...mapActions("DWF_form", ["handleQueryData"]), /* 在建模的时候传入,提示控件是在画布区还是在控件列表中 0:表示在画布区,已经被拖入 1:表示在控件区,准备被拖入 2: 表示在拖动中,还未放下 */ setDisplayType(typesetArgs(args) { for (var i in args) { this.args[i] = args[i]; } return this; }, // 表单保存的时候将控件的设置合并到表单自身的JSON中 getArgs() { if (type == 0) this.t_preview = true;return this.args; }, // 返回控件绑定的目标属性,如果没有绑定返回undefined或者null getFormName() { return thisnull; }, // 默认不用变化,返回编辑框供建模工具绘制,当控件拖入画布区以后被点击的时候触发 getEditBoxgetSelected() { return this.t_edit = true;cellSelected; }, getAll() { return this.$refscellData.editlist; }, // 当插件无法直接通过style设置高度时,使用setHeight方法设置高度单击事件 setHeightonClick(idx) { if (!this.$refs.main) returnthis.cellSelected = this.cellData.list[idx]; this.triggerEvent("单击"); }, // 现有表单加载在画布区加载控件的时候,会将之前的配置传入根据名字触发表单事件上配置的操作 setArgstriggerEvent(argseventName) { for (var i in args) { this.args[i]let eventConfig = args[i]null; }// 提取事件对应的操作参数 returnif (this; }, // 表单保存的时候将控件的设置合并到表单自身的JSON中 getArgs(.args.events && this.args.events.length > 0) { returneventConfig = this.args;.events.find((val) => { }, // 是否允许其他子控件拖入,如果不允许则返回null getAllow() { return nullreturn val.name === eventName; }); }, // 返回控件绑定的目标属性,如果没有绑定返回undefined或者null触发实际操作 getFormNameif (eventConfig) { return null; }, this.invokeOperation( getSelected() { return this.cellSelected; }eventConfig.opr_type, getAll() { return this.cellData.list; }, // 单击事件 onClick(idx) { eventConfig.opr_path, this.itemValue, this.cellSelected = this.cellData.list[idx]store ); this.triggerEvent("单击");} }, // 根据名字触发表单事件上配置的操作刷新控件内部数据 triggerEventfreshData(eventName) { letconst eventConfigbindTargetClass = null; // 提取事件对应的操作参数 if (this.args.eventsbindTargetClass; && this.args.events.length > 0) { eventConfig = this.args.events.find((val) => if (!bindTargetClass) { return val.name === eventNameconsole.error("no bindTargetClass"); })return; } const regexForTargetClass = /^([0-9a-zA-Z_]+)&([er])$/; 触发实际操作 if (eventConfig) { this.invokeOperation( eventConfig.opr_type, eventConfig.opr_path,const glob = regexForTargetClass.exec(bindTargetClass); if (glob === null) { this.itemValue,console.error( this.store`handleRefresh: invalid targetClass value -> ${bindTargetClass}` ); }return; }, //const 刷新控件内部数据 freshData() {targetClass = glob[1]; const bindTargetClassquery = this.args.bindTargetClassfilterQuery; if (!bindTargetClass) console.log(this.dwf_ctx); this.handleQueryData({ console.error("no bindTargetClass");targetClass: targetClass, query: query, return;fresh: true, }).then((res) => { const regexForTargetClassattributeForTitle = /^([0-9a-zA-Z_]+)&([er])$/this.args.attributeForTitle; const globattributeForLabel = regexForTargetClass.exec(bindTargetClass)this.args.attributeForLabel; if let (globobjs === null) { console.error( res; this.cellData = { title: this.args.label, icon: "ios-bookmarks-outline", `handleRefreshlist: invalid targetClass value -> ${bindTargetClass}` res, )}; 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>}; </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> |
对应的装配文件格式如下:
代码块 | ||||
---|---|---|---|---|
| ||||
config:
ignore:
info:
part-web:
name: app
cname: 应用端
forms:
SimpleCellGroup.vue: form/multi
operations:
...
dependencies: {}
|
8 小结
本章主要结合vue列表控件介绍了如何在dwf中实现一个多对象控件。在这个过程中重点涉及了:
...