EasyForm的组件库分为两类,第一类是容器组件,主要负责表单元素的布局排列,第二类为字段组件,主要负责数据收集或内容展示。容器组件可以嵌套其他容器或字段组件,字段组件可以放置于容器之内或者表单上(即整个表单也相当于一个大容器)。

一、容器组件开发

本节通过开发一个简单的card卡片容器讲解如何实现容器组件的扩展开发。

  1. 首先拉取EasyForm源码:

Gitlab: http://loan.git.com.cn/gitlab/sadp/easy-low-code/easy-form-pro

  1. VS Code导入源码项目,并执行 npm install 安装依赖包,安装依赖完成之后执行 npm run serve 进入开发模式;

  2. 打开 src/extension/sample/extension-schema.js,可看到card容器定义的JSON Schema如下:

  1. export const cardSchema = {
  2. type: 'card',
  3. category: 'container',
  4. icon: 'card',
  5. widgetList: [],
  6. options: {
  7. name: '',
  8. label: 'card',
  9. hidden: false,
  10. cardWidth: '100%', //卡片宽度,百分比或像素宽度
  11. shadow: 'never', //是否显示阴影
  12. customClass: '', //自定义css样式
  13. }
  14. }

JSON Schema解释说明:

  • type:容器组件的类型名称,必须唯一,不能跟已有组件重复;
  • icon:容器图标名称,可以去iconfont.cn下载所需的svg文件,放入src/icons/svg目录即可(自动加载);
  • category:容器组件必须设置为’container’;
  • widgetList:容器内存放的组件列表,初始值必须为[];
  • options:组件属性对象,每一个属性值对应一个属性编辑器。
  1. 编写card容器的SFC组件文件,容器组件对应两个状态:设计期状态可以接收组件拖拽,运行期状态负责组件渲染,所以需要编写两个SFC文件,命名规则需严格遵守:容器名称-widget(设计期)、容器名称-item(运行期);

4.1 card-widget.vue代码如下:

  1. <template>
  2. <container-wrapper :designer="designer" :widget="widget" :parent-widget="parentWidget" :parent-list="parentList"
  3. :index-of-parent-list="indexOfParentList">
  4. <el-card :key="widget.id" class="card-container" @click.native.stop="selectWidget(widget)"
  5. :shadow="widget.options.shadow" :style="{width: widget.options.cardWidth + '!important' || ''}"
  6. :class="[selected ? 'selected' : '', customClass]">
  7. <div slot="header"><span>{{widget.options.label}}</span></div>
  8. <draggable :list="widget.widgetList" v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 200}"
  9. handle=".drag-handler"
  10. @add="(evt) => onContainerDragAdd(evt, widget.widgetList)"
  11. @update="onContainerDragUpdate" :move="checkContainerMove">
  12. <transition-group name="fade" tag="div" class="form-widget-list">
  13. <template v-for="(subWidget, swIdx) in widget.widgetList">
  14. <template v-if="'container' === subWidget.category">
  15. <component :is="subWidget.type + '-widget'" :widget="subWidget" :designer="designer" :key="subWidget.id" :parent-list="widget.widgetList"
  16. :index-of-parent-list="swIdx" :parent-widget="widget"></component>
  17. </template>
  18. <template v-else>
  19. <component :is="subWidget.type + '-widget'" :field="subWidget" :designer="designer" :key="subWidget.id" :parent-list="widget.widgetList"
  20. :index-of-parent-list="swIdx" :parent-widget="widget" :design-state="true"></component>
  21. </template>
  22. </template>
  23. </transition-group>
  24. </draggable>
  25. </el-card>
  26. </container-wrapper>
  27. </template>

封装了Element UI的el-card组件,重点看4~7行代码,如何跟JSON Schema的属性进行结合,其他部分代码复制粘贴即可。

4.2 card-item.vue代码如下:

  1. <template>
  2. <container-item-wrapper>
  3. <el-card :key="widget.id" class="card-container" :class="[customClass]"
  4. :shadow="widget.options.shadow" :style="{width: widget.options.cardWidth + '!important' || ''}"
  5. :ref="widget.id" v-show="!widget.options.hidden">
  6. <div slot="header"><span>{{widget.options.label}}</span></div>
  7. <template v-if="!!widget.widgetList && (widget.widgetList.length > 0)">
  8. <template v-for="(subWidget, swIdx) in widget.widgetList">
  9. <template v-if="'container' === subWidget.category">
  10. <component :is="subWidget.type + '-item'" :widget="subWidget" :key="swIdx" :parent-list="widget.widgetList"
  11. :index-of-parent-list="swIdx" :parent-widget="widget"></component>
  12. </template>
  13. <template v-else>
  14. <component :is="subWidget.type + '-widget'" :field="subWidget" :designer="null" :key="swIdx" :parent-list="widget.widgetList"
  15. :index-of-parent-list="swIdx" :parent-widget="widget"></component>
  16. </template>
  17. </template>
  18. </template>
  19. </el-card>
  20. </container-item-wrapper>
  21. </template>

重点看3-6行代码,其他部分代码复制粘贴即可。

  1. 加载options对应的属性编辑器,对应表单设计器右侧的SettingPanel面板,具体代码参见extension-loader.js:
  1. PERegister.registerCPEditor('cardWidth', 'card-cardWidth-editor',
  2. PEFactory.createInputTextEditor('cardWidth', 'extension.setting.cardWidth'))
  3. let shadowOptions = [
  4. {label: 'never', value: 'never'},
  5. {label: 'hover', value: 'hover'},
  6. {label: 'always', value: 'always'},
  7. ]
  8. PERegister.registerCPEditor('shadow', 'card-shadow-editor',
  9. PEFactory.createSelectEditor('shadow', 'extension.setting.cardShadow',
  10. {optionItems: shadowOptions}))

代码解释: 此处只加载了cardWidth、shadow两个属性的编辑器组件,因为其他属性编辑器在VForm框架内已经预先加载了。以上使用到的字符串资源分别位于src\lang\zh-CN_extension.js、src\lang\en-US_extension.js两个文件中。

二、字段组件开发

本节通过开发一个简单的alert组件讲解如何实现字段组件、静态内容组件的扩展开发。

步骤1、2同上。

  1. 打开 src/extension/sample/extension-schema.js,可看到alert组件定义的JSON Schema如下:
  1. export const alertSchema = {
  2. type: 'alert',
  3. icon: 'alert',
  4. formItemFlag: false, //是否需嵌套于el-form-item
  5. options: {
  6. name: '',
  7. title: 'Good things are coming...',
  8. type: 'info',
  9. description: '',
  10. closable: true,
  11. closeText: '',
  12. center: true,
  13. showIcon: false,
  14. effect: 'light',
  15. hidden: false,
  16. onClose: '',
  17. customClass: '',
  18. }
  19. }

JSON Schema解释说明:

  • type:字段组件的类型名称,必须唯一,不能跟已有组件重复;
  • icon:容器图标名称,可以去iconfont.cn下载所需的svg文件,放入src/icons/svg目录即可(自动加载);
  • formItemFlag:是否嵌套于el-form-item组件内,因el-alert并不需要显示字段标签,故此处设置为false;
  • options:组件属性对象,每一个属性值对应一个属性编辑器。
  1. 编写字段组件的SFC文件,字段组件在设计期和运行期共用,故只需要编写一个SFC文件,命名规则需严格遵守:组件名称-widget。

    4.1 alert-widget.vue代码如下:

  1. <template>
  2. <static-content-wrapper :designer="designer" :field="field" :design-state="designState"
  3. :parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
  4. :sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
  5. <el-alert ref="fieldEditor" :title="field.options.title" :type="field.options.type"
  6. :description="field.options.description" :closable="field.options.closable"
  7. :center="field.options.center" :close-text="field.options.closeText"
  8. :show-icon="field.options.showIcon" :effect="field.options.effect" @close="handelCloseCustomEvent"></el-alert>
  9. </static-content-wrapper>
  10. </template>
  11. <script>
  12. //...
  13. methods: {
  14. handelCloseCustomEvent() {
  15. if (!!this.field.options.onClose) {
  16. let closeFn = new Function(this.field.options.onClose)
  17. closeFn.call(this)
  18. }
  19. }
  20. }
  21. //...
  22. </script>

因为formItemFlag设置为false,故此处必须使用static-content-wrapper包裹组件(如formItemFlag设置为true,则应使用form-item-wrapper包裹组件),5~8行代码对应options属性值的动态绑定,handelCloseCustomEvent方法处理了自定义事件onClose交互逻辑。

  1. 加载options对应的属性编辑器,对应表单设计器右侧的SettingPanel面板,具体代码参见extension-loader.js:
  1. PERegister.registerCPEditor('title', 'alert-title-editor',
  2. PEFactory.createInputTextEditor('title', 'extension.setting.alertTitle'))
  3. let typeOptions = [
  4. {label: 'success', value: 'success'},
  5. {label: 'warning', value: 'warning'},
  6. {label: 'info', value: 'info'},
  7. {label: 'error', value: 'error'},
  8. ]
  9. PERegister.registerCPEditor('type', 'alert-type-editor',
  10. PEFactory.createSelectEditor('type', 'extension.setting.alertType',
  11. {optionItems: typeOptions}))
  12. PERegister.registerCPEditor('description', 'alert-description-editor',
  13. PEFactory.createInputTextEditor('description', 'extension.setting.description'))
  14. PERegister.registerCPEditor('closable', 'alert-closable-editor',
  15. PEFactory.createBooleanEditor('closable', 'extension.setting.closable'))
  16. PERegister.registerCPEditor('closeText', 'alert-closeText-editor',
  17. PEFactory.createInputTextEditor('closeText', 'extension.setting.closeText'))
  18. PERegister.registerCPEditor('center', 'alert-center-editor',
  19. PEFactory.createBooleanEditor('center', 'extension.setting.center'))
  20. PERegister.registerCPEditor('showIcon', 'alert-showIcon-editor',
  21. PEFactory.createBooleanEditor('showIcon', 'extension.setting.showIcon'))
  22. let effectOptions = [
  23. {label: 'light', value: 'light'},
  24. {label: 'dark', value: 'dark'},
  25. ]
  26. PERegister.registerCPEditor('effect', 'alert-effect-editor',
  27. PEFactory.createRadioButtonGroupEditor('effect', 'extension.setting.effect',
  28. {optionItems: effectOptions}))
  29. PERegister.registerEPEditor('onClose', 'alert-onClose-editor',
  30. PEFactory.createEventHandlerEditor('onClose', []))

上述代码了共加载了9个属性编辑器,其中包含onClose事件编辑器,其他属性使用预先加载的默认属性编辑器。以上使用到的字符串资源分别位于src\lang\zh-CN_extension.js、src\lang\en-US_extension.js两个文件中。

  1. 字段组件开发完毕,扩展组件的加载由extension-loader.js的loadExtension()方法执行,该方法在main.js中被调用。

三、SFC代码生成

如果你最终使用VFormRender渲染表单JSON,则此部分内容完全可以跳过,无须实现该功能。

  1. 编写容器组件SFC生成器方法

实现思路很简单,根据容器的JSON配置生成对应的Element UI组件模板代码,打开src\extension\samples\extension-sfc-generator.js,可看到card容器的SFC生成器方法如下:

  1. export const cardTemplateGenerator = function (cw, formConfig) {
  2. const wop = cw.options
  3. const headerAttr = `header="${wop.label}"`
  4. const classAttr = buildClassAttr(cw)
  5. const styleAttr = !!wop.cardWidth ? `style="{width: ${wop.cardWidth} !important}"` : ''
  6. const shadowAttr = `shadow="${wop.shadow}"`
  7. const vShowAttr = !!wop.hidden ? `v-show="false"` : ''
  8. const cardTemplate =
  9. `<div class="card-container">
  10. <el-card ${headerAttr} ${classAttr} ${styleAttr} ${shadowAttr} ${vShowAttr}>
  11. ${
  12. cw.widgetList.map(wItem => {
  13. if (wItem.category === 'container') {
  14. return buildContainerWidget(wItem, formConfig)
  15. } else {
  16. return buildFieldWidget(wItem, formConfig)
  17. }
  18. }).join('')
  19. }
  20. </el-card>
  21. </div>`
  22. return cardTemplate
  23. }

SFC代码生成大量使用了ES6的模板字符串功能,极大地简化了代码生成逻辑。

  1. 编写字段组件SFC生成器方法

打开src\extension\samples\extension-sfc-generator.js,可看到alert组件的SFC生成器方法如下:

  1. export const alertTemplateGenerator = function(fw, formConfig) {
  2. const wop = fw.options
  3. const titleAttr = `title="${wop.title}"`
  4. const typeAttr = `type=${wop.type}`
  5. const descriptionAttr = !!wop.description ? `description="${wop.description}"` : ''
  6. const closableAttr = `:closable="${wop.closable}"`
  7. const closeTextAttr = !!wop.closeText ? `close-text="${wop.closeText}"` : ''
  8. const centerAttr = `:center="${wop.center}"`
  9. const showIconAttr = `:show-icon="${wop.showIcon}"`
  10. const effectAttr = `effect="${wop.effect}"`
  11. const alertTemplate =
  12. `<el-alert ${titleAttr} ${typeAttr} ${descriptionAttr} ${closableAttr} ${closeTextAttr} ${centerAttr}
  13. ${showIconAttr} ${effectAttr}>
  14. </el-alert>`
  15. return alertTemplate
  16. }

实现思路同上,根据组件的JSON配置生成相应的Element UI组件模板代码。

  1. 注册上述两种代码生成器

在extension-loader.js中注册SFC代码生成器,每个代码生成器都需注册。

  1. // ...
  2. registerCWGenerator('card', cardTemplateGenerator) //注册card容器组件的代码生成器
  3. // ...
  4. // ...
  5. registerFWGenerator('alert', alertTemplateGenerator) //注册alert组件的代码生成器
  6. // ...

以上内容已完结。