# 开发widget
一个完整的widget由三部分组成:Schema
、View
、Setting
(仅渲染时可没有)
- widget 文件结构
checkbox
├── Schema.js
├── Setting.vue
├── View.vue # 或View.jsx
└── index.js
2
3
4
5
- widget 入口文件,例:
src/widgets/checkbox/index.js
export { default as Schema } from './schema'
export { default as Setting } from './setting'
export { default as View } from './view'
2
3
# Schema开发
Schema 需要遵守一定的schema规范。widget特有字段请放在option中。
schema
是用于标注和验证Epage widget属性元信息数据的 JSON 格式文档。Schema
是实例化schema
的基础类。 开发widget时可以继承Shema
,也可自己重写一个Schema
。
schema与Schema区别
schema
实际是json形式的数据对象,Schema
是js中的类,可以继承或被继承schema
可以保存到服务端或从服务端获取,Schema
是widget
的一部分,一般跟随widget
一起打包
schema与Schema关系
schema
可以描述视图(View
)的json数据,而Schema
是基础类- 通过
new Schema({ schema, widgets, clone })
可以将指定的schema
初始化
# Schema.js
类定义
import { schema } from 'epage-core'
// 如果基于 epage-iview 进行扩展开发,可以通过以下方式导入
// import Epage from 'epage'
// 复选框为表单组件,这里继承 FormSchema
export default class CheckboxSchema extends schema.FormSchema {
// schema 为实例化当前widget的json对象
// widgets 为已经注册的widgets
// descriptor 为对象。descriptor.clone 表示实例化时schema是否做深克隆
constructor (props) {
super()
// 申明表单值类型为字符串数组
this.type = 'array<string>'
this.label = '多选框'
// 定义组件的校验规则,规定第一条校验规则为是否必填规则
this.rules = [{ required: false, message: '必填', type: 'array', trigger: 'change' }]
// widget所有自定义字段需要放到option中
this.option = {
// checkbox可选项是手动维护还是接口获取,可选 `static` || 'dynamic'
// static表示手动维护,选项将跟随schema一起保存起来,保存位置: schema.option.data
// dynamic: 表示可选项通过接口获取,如果为dynamic,就需要配置url及adapter字段
type: 'static',
// 控制可选项横排还是竖排
direction: 'horizontal',
// 当 type 为dynamic时,需要填写这个url地址,将从远端获取数据作为可选项
url: '',
// adapter 为处理通过url获取的可选项的转换器,以适配当前widget可选项可用的字段
// 转换后 return 出来的结果将赋值给 dynamicData
// adapter 将在web worker中执行,执行完成进行数据同步
// 示例: return data.map(function(item){ reutrn {key: item.id, value: item.name}})
adapter: '',
// 接受 adapter return 出来的结果
dynamicData: [],
// type为static时,手动配置的可选项数据
data: [{ key: 'A', value: '选项A' }]
}
// 如果为容器widget(可以有子孙widget,即可以嵌套),会递归创建schama示例
// 同时动态生成 schema.key值,组件的唯一标识
this.create(props)
const rule = {
trigger: 'change',
validator: getRuleValidator(this.rules[0], this.type) // 值是否必填、类型是否符合的校验
}
// 更新widget是否必填校验规则
this.updateRequiredRule(rule)
}
}
const staticProps = {
title: '多选框',
widget: 'checkbox',
icon: 'android-checkbox-outline',
type: ['array<string>', 'array<number>'],
validators: [],
logic: {
value: ['<>', '><'],
event: []
}
}
Object.assign(CheckboxSchema, staticProps)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
TIP
logic字段的value及event详解请参考logics
# Schema.js类静态属性
关于上例中staticProps
属性定义,有以下约定:
# Schema.title
用于设计器工具面板widget说明
# Schema.widget
区别于其他widget类型的唯一名称
# Schema.icon
用于设计器工具面板widget图标,根据iview@2.x内置图标定义。也可以可自定义css选择器名,如 .icon-checkbox-custom
WARNING
自定义css选择器只能是class选择器,不能是id
、标签
等选择器
# Schema.type
表单类型widget值返回类型,将作为schema.type
的可选值。
- 可能的值类型:字符串 或 数组 表示,如:
"number"
及["number"]
均合法且等同 - 值全集:
- 单值:
"string"
、"number"
、"boolean"
、"array"
- 数组单值:
"array<string>"
、"array<number>"
、"array<boolean>"
- 单值:
- 可选值示例:
["string", "number"]
、["array<string>", "array<number>"]
、
# Schema.type
初始值表
Schema.type 值示例 | 初始值 | 说明 |
---|---|---|
"string" | "" | |
"number" | 0 | |
"boolean" | false | |
"array" | [] | |
"array<?>" | [] | array<?> 初始值都为[] |
["string"] | "" | |
["number", "string"] | 0 | |
[array<"number">, "array<string>"] | [] | [any] 初始值取数组第一个默认类型的默认值[] |
["number", "array<string>"] | 0 |
# Schema.type
示例表
Schema.type 值 | 赋form data 值 | 校验结果 |
---|---|---|
"string" | "1" | ✅ |
"string" | 1 | ❌ |
"string" | ["1"] | ❌ |
"number" | 1 | ✅ |
"boolean" | true | ✅ |
"boolean" | "true" | ❌ |
"array" | [] | ✅ |
"array" | [1] | ✅ |
"array" | ["1"] | ✅ |
"array<string>" | [] | ✅ |
"array<string>" | [1] | ❌ |
"array<string>" | ["1"] | ✅ |
["string"] | ["1"] | ✅ |
["string", "number"] | "1" | ✅ |
["string", "number"] | 1 | ✅ |
["array<string>", "array<number>"] | [1] | ✅ |
["array<string>", "array<number>"] | ["1"] | ✅ |
# Schema.validators
表单校验规则类型列表,通常针对表单widget有效。可选值为已注册的规则类型,如['string', 'email']
,关于规则类型请访问Epage.Rule,规则不够用?可以试试 自定义规则
# Schema.logic
配置当前widget可以参与的逻辑关系。当在设计器逻辑视图配置了关系,当前widget发生此处定义的关系时,触发其他widget属性发生的变化。
更多关于逻辑关系请参考 logics
# schema.json
示例
{
"key": "kiL9VszcV",
"name": "city",
"widget": "checkbox",
"type": "string",
"label": "城市",
"description": "",
"help": "",
"hidden": false,
"disabled": false,
"size": "default",
"rules": [{
"required": true,
"message": "城市必填",
"trigger": "change"
}],
"option": {
"type": "static",
"direction": "horizontal",
"url": "",
"adapter": "",
"dynamicData": [],
"data": [{ "key": "A", "value": "A" }]
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Setting开发
Setting 是修改schema
字段的vue (opens new window)表单组件,此表单组件由iview@2.x (opens new window)提供UI支持。先看下示例:
Setting.vue
文件
<template>
<setting-form :store='store' :setting='setting'>
<data-source :store='store' @success='onSuccess' />
<FormItem label='排列方式'>
<RadioGroup
v-model='selectedSchema.option.direction'
size='small'
@on-change='onDirectionChange'>
<Radio label='vertical'>垂直排列</Radio>
<Radio label='horizontal'>水平排列</Radio>
</RadioGroup>
</FormItem>
</setting-form>
</template>
<script>
import { setting } from 'epage-iview'
const { SettingForm, settingExtend, components } = setting
const { DataSource } = components
export default {
components: {
SettingForm,
DataSource
},
extends: settingExtend,
methods: {
onSuccess (dynamicData) {
this.store.updateWidgetOption(this.selectedSchema.key, { dynamicData })
},
onDirectionChange (direction) {
this.store.updateWidgetOption(this.selectedSchema.key, { direction })
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
settingExtend
extends的props可以参考settingExtend
# SettingForm
SettingForm
用于修改schema属性字段的vue表单组件,可以通过slot方式替换内置的表单项,关于vue slot请参考components-slots (opens new window)
默认slot包括:
slot name | 对应schema字段 | 备注 |
---|---|---|
key | schema.key | 默认不可修改,key作为单个widget实例唯一标识,是获取表单输入、与其他widget逻辑关联、事件监听等必须的关联字段,直接更改将导致一些功能不生效,或无法正常渲染,强烈不建议修改 |
name | schema.name | form name字段,语义化的业务字段名称,常用于提交给后端表单name值 |
label | schema.label | 注:form中用于label使用 |
placeholder | schema.placeholder | |
description | schema.description | |
help | schema.help | |
disabled | schema.disabled | |
rule | schema.rule |
除默认slot,完全可以根据当前widget特定扩展修改schema的字段,也可以替换默认的某些slot
- 添加扩展表单字段修改。如系统控制checkbox可选项是水平排列还是垂直排列
<template>
<setting-form :store='store'>
<data-source :store='store' @success='onSuccess'/>
<FormItem label='排列方式'>
<RadioGroup
v-model='selectedSchema.option.direction'
size='small'
@on-change='onDirectionChange'>
<Radio label='vertical'>垂直排列</Radio>
<Radio label='horizontal'>水平排列</Radio>
</RadioGroup>
</FormItem>
</setting-form>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
# dataSource
dataSource组件用于数据备选项(如select组件的下拉选项)的手动配置或通过接口获取,以及数据类型string、number的选择。适用于checkbox、radio、select、cascader等widget
默认slot包括:
slot name | 备注 |
---|---|
default | 默认slot,内容添加到dataSource组件ui的最下面 |
dynamic | 增加动态数据相关的配置项,如增加cascader 组件label、value、children字段的配置项 |
tree | 用于cascader 组件树状数据的展示配置 |
# View开发
View 是最终渲染到页面的组件。对于vue框架的组件库对应View.vue
,对于react框架的组件库对应View.jsx
。以下针对iview
组件库的说明View
源码示例
<template>
<div class='ep-widget'>
<template v-if='mode === "display"'>
<span>{{getDisplayValue()}}</span>
</template>
<template v-else>
<CheckboxGroup
v-if='schema.key'
:disabled='schema.disabled'
v-model='model[schema.key]'
:size='schema.size || rootSchema.size'
:class='cls'
@on-change="event('on-change', ...arguments)"
>
<Checkbox
v-for='(item, k) in getOptions() || []'
:key='k'
:label='item.key'
>{{item.value}}</Checkbox>
</CheckboxGroup>
</template>
</div>
<script>
import { Worker as EpageWorker } from 'epage-core'
import { helper } from 'epage'
import { viewExtend } from 'epage-iview'
const { ajax } = helper
export default {
extends: viewExtend,
data () {
return {
worker: null // 组件的worker实例
}
},
computed: {
cls () {
return {
'ep-widget-checkbox-group': this.schema.option.direction === 'vertical'
}
}
},
mounted () {
const { type } = this.schema.option
if (type !== 'static') {
// 组件实例化时,new出一个新的worker实例,避免单例模式
this.worker = new EpageWorker()
this.listenerMessage()
this.getDynamicData()
}
},
methods: {
getDisplayValue () {
const { data, dynamicData, type } = this.schema.option
const value = this.model[this.schema.key]
const options = type === 'static' ? data : dynamicData
let result = []
result = !value ? value.map(item => {
const option = options.find(option => option.key === item)
return !!option && option.value
}) : []
return result + ''
},
getOptions () {
const { type, data, dynamicData } = this.schema.option // data 静态选项 dynamicData 动态选项
let result = []
if (type === 'static') result = data
if (type === 'dynamic') result = dynamicData
return result
},
listenerMessage () {
worker.onmessage = e => {
// 存放临时数据
const { message, success, data } = e.data
if (success) {
// 更新option中的值
this.store.updateWidgetOption(this.schema.key, { dynamicData: data })
this.$emit('success', data)
} else {
this.$emit('error', message)
}
}
},
// 获取下拉组件动态选项
getDynamicData () {
const { url, adapter } = this.schema.option
if (!url) return
ajax(url).then(res => {
worker.postMessage({
action: 'fetch',
data: res,
fn: adapter
})
}).catch(err => {
this.$emit('error', { success: false, message: err })
})
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
TIP
- 模板中
mode
变量用于控制编辑态还是现实态,非表单组件不用作此判断 - 因需要对接口返回的数据做转换,这里用实例化的
worker
去运行用户自定义的转换脚本