# 开发widget

一个完整的widget由三部分组成:SchemaViewSetting(仅渲染时可没有)

  • widget 文件结构
checkbox
├── Schema.js
├── Setting.vue
├── View.vue # 或View.jsx
└── index.js
1
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'
1
2
3

# Schema开发

Schema 需要遵守一定的schema规范。widget特有字段请放在option中。

schema是用于标注和验证Epage widget属性元信息数据的 JSON 格式文档。 Schema是实例化schema的基础类。 开发widget时可以继承Shema,也可自己重写一个Schema

  • schema与Schema区别

    • schema实际是json形式的数据对象,Schema是js中的类,可以继承或被继承
    • schema可以保存到服务端或从服务端获取,Schemawidget的一部分,一般跟随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)
1
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的可选值。

  1. 可能的值类型:字符串数组 表示,如:"number"["number"]均合法且等同
  2. 值全集:
    1. 单值:"string""number""boolean""array"
    2. 数组单值:"array<string>""array<number>""array<boolean>"
  3. 可选值示例:["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" }]
  }
}
1
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>
1
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

  1. 添加扩展表单字段修改。如系统控制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>
1
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>
1
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去运行用户自定义的转换脚本
更新: 4/6/2021, 11:10:45 PM