Mr_HJ / form-generator项目文档学习与记录

本文涉及的产品
云解析DNS,个人版 1个月
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: Mr_HJ / form-generator项目文档学习与记录

form-generator: Element UI表单设计及代码生成器

form-generator经典vue的表单设计器一些设计原理记录

1、JSON表单参数对照表

对项目中的json表单配置做一些参数说明。

内置布局及其属性

目前有的布局方式:

  • colFormItem:生成el-col包裹的组件布局
  • rowFormItem:生成一个空的el-row
colFormItem
属性 可选性 说明 默认值
__config__.layout 可选 组件使用的布局方式 colFormItem
__vModel__ 必选 表单字段的属性名,可自定义 系统自增
__config__.defaultValue 可选 默认值;与__vModel__对应使用,可指定表单字段的默认值;可用于表单数据回填
__config__.tag 必选 组件名称
__config__.changeTag 必选 是否允许显示切换组件面板
__config__.tagIcon 必选 组件svg图标名称
__config__.label 必选 表单标题
__config__.showLabel 必选 是否显示表单标题
__config__.labelWidth 必选 表单标题区域宽度(px)
__config__.required 必选 是否要求表单校验
__config__.regList 可选 表单正则校验;赋值为数组时,显示配置项
__config__.span 必选 24栅格系统,表示组件的栅格数
__config__.children 可选 子组件,目前仅保留字段,实际并没有做解析
__config__.document 可选 组件说明文档地址
__slot__ 可选 对应,需在工程文件夹src\components\render\slots中添加与__config__.tag同名的.js文件解析该配置。
其余属性 可选 根据不同组件的属性灵活配置。属于本组件的属性写在一级(与__config__同级);若需自定义属性以达到控制右侧面板或其他目的的,可在__config__中自定义属性(如:__config__.showLabel
rowFormItem
属性 可选性 说明 默认值
__config__.layout 可选 组件使用的布局方式 colFormItem
__config__.componentName 必选 组件名,无需操作 系统自增
__config__.tagIcon 必选 组件svg图标名称
__config__.layoutTree 可选 是否显示布局树
__config__.children 必选 子组件,组件嵌套的关键 []
__config__.document 可选 组件说明文档地址
其余属性 可选 可参照 el-row属性表按需配置

2、表单设计器

项目使用vue-cli4生成。用到了jsx,所以要对vue render比较熟悉!!! 如果对render和jsx还不熟悉,一定要反复阅读并理解渲染函数 & JSX。二开对于初学者,有一定的难度。

项目由四部分组成:表单设计器,.vue代码生成器,.vue代码预览器,表单json解析器

接下来通过添加一个《按钮 el-button》来带大家感受下这四部分。

前置准备:将项目下载到本地,然后安装依赖。如有需要可参阅运行

一、在添加一个新组件前,首先要思考的是,项目中有没有引入该组件?

对于el-button,它是随element UI全局注册的组件,所以不需要再引入。如果是一个没有引入的组件,需要引入,引入方法参阅vue官方文档组件注册

二、将组件添加到表单设计器

确保el-button组件可用后,将其添加到表单设计器。

2.1 在文件src\components\generator\config.js中添加一个布局型组件

...
export const layoutComponents = [
  ...,
  {
    __config__: {
      label: '按钮',
      showLabel: true,
      changeTag: true,
      labelWidth: null,
      tag: 'el-button',
      tagIcon: 'button',
      defaultValue: undefined,
      span: 24,
      layout: 'colFormItem',
      document: 'https://element.eleme.cn/#/zh-CN/component/button'
    },
    __slot__: {
      default: '主要按钮'
    },
    type: 'primary',
    icon: 'el-icon-search',
    round: false,
    size: 'medium',
    plain: false,
    circle: false,
    disabled: false
  }
]

其中__config__和__slot__是本项目自定义的属性,自定义属性的格式均为__XXX__;

其余属性与el-button组件属性对应;

config.tagIcon中使用的是svg图标。图标来自iconfont,下载后放在src\icons\svg文件夹中。

此时,左侧备选组件会出现【按钮】组件,但是,按钮不能显示文字。

2.2 新建与__config__.tag的值同名的__slot__解析文件el-button.js

src\components\render\slots\el-button.js,代码如下:

export default {
  default(h, conf, key) {
    return conf.__slot__[key]
  }
}

default函数解析将json配置中的default属性:

__slot__: {
    default: '主要按钮'
 }

解析为按钮上的文字。

此时,中间设计器中,按钮上的文字已经可以显示出来了。但是,右侧面板中,可配置属性还比较少,需要添加属性配置。

__slot__解析文件是支持jsx语法的,本例中表现的不够具体,更多的使用方式可以翻阅源码中slots文件夹;其中el-input.js代表性强,建议理解。

__slot__的解析流程设计得比较绕,主要的出发点是为了:保证表单的配置是纯json格式的,方便数据库存储和用户配置。这里的【用户】指的是:没有编程基础的普通用户。

2.3 接下来我们让设计器支持type,icon等组件属性的可视化修改。

在src\views\index\RightPanel.vue中添加相应的编辑表单项。

2.3.1 type属性配置项:

<el-form-item
   v-if="activeData.type !== undefined && activeData.__config__.tag === 'el-button'"
  label="按钮类型"
>
  <el-select v-model="activeData.type" :style="{ width: '100%' }">
    <el-option label="primary" value="primary" />
    <el-option label="success" value="success" />
    <el-option label="warning" value="warning" />
    <el-option label="danger" value="danger" />
    <el-option label="info" value="info" />
    <el-option label="text" value="text" />
  </el-select>
</el-form-item>

2.3.2 size属性配置项:经过检查el-color-picker已经有size属性的配置项了,所以重用原有的就行了。

增加

activeData.__config__.tag === 'el-button'

增加后的配置项如下:

<el-form-item
  v-if="activeData.size !== undefined &&
    (activeData.__config__.optionType === 'button' ||
      activeData.__config__.border ||
      activeData.__config__.tag === 'el-color-picker' ||
      activeData.__config__.tag === 'el-button')"
  label="选项尺寸"
>
  <el-radio-group v-model="activeData.size">
    <el-radio-button label="medium">
      中等
    </el-radio-button>
    <el-radio-button label="small">
      较小
    </el-radio-button>
    <el-radio-button label="mini">
      迷你
    </el-radio-button>
  </el-radio-group>
</el-form-item>

2.3.3 icon属性配置项:复制el-input的前图标配置项,修改为:

<el-form-item
  v-if="activeData['icon']!==undefined && activeData.__config__.tag === 'el-button'"
  label="按钮图标"
>
  <el-input v-model="activeData['icon']" placeholder="请输入按钮图标名称">
    <el-button slot="append" icon="el-icon-thumb" @click="openIconsDialog('icon')">
      选择
    </el-button>
  </el-input>
</el-form-item>

此处使用了openIconsDialog调用封装好的图标选择器,方便快速选取图标。

组件属性的可视化配置是一项需要耐心的操作,以上列举了3个属性的配置,更多的属性也都是配置在RightPanel.vue中。当然,现有的配置方式存在一定的问题,这是需要在以后项目中逐步优化的。

总结

表单设计器的开发流程基本就是上边这三步。config.js配置备选图标;在有使用__slot__时需要编写解析文件;在RightPanel.vue可视化配置组件属性。

接下来,当点击运行按钮的时候,发现新加的组件并不能显示。这是因为没有编写相应的.vue代码生成器生成规则。

3、表单解析器

本文描述的解析器,是一个能将form-generator导出的json表单,解析为一个真实表单的程序。

接下来的行文中使用【json表单】表示form-generator导出的json表单。

剧透:本文其实就是带大家阅读parser.vue源码,哈哈。

布局

json表单目前支持两种布局:

colFormItem和rowFormItem

1.1 colFormItem布局

colFormItem布局(以el-input为例)对应的json形式如下:

{
    "__config__": {
      "label": "单行文本",
      "labelWidth": null,
      "showLabel": true,
      "changeTag": true,
      "tag": "el-input",
      "tagIcon": "input",
      "required": true,
      "layout": "colFormItem",
      "span": 12,
      "document": "https://element.eleme.cn/#/zh-CN/component/input",
      "regList": [{
        "pattern": "/^1(3|4|5|7|8|9)\\d{9}$/",
        "message": "手机号格式错误"
      }]
    },
    "__slot__": {
      "prepend": "",
      "append": ""
    },
    "__vModel__": "mobile",
    "placeholder": "请输入手机号",
    "style": {
      "width": "100%"
    },
    "clearable": true,
    "prefix-icon": "el-icon-mobile",
    "suffix-icon": "",
    "maxlength": 11,
    "show-word-limit": true,
    "readonly": false,
    "disabled": false
  }

colFormItem布局对应的目标实际代码如下 :

<el-col :span="12">
      <el-form-item label="单行文本" prop="mobile">
        <el-input v-model="formData.mobile" placeholder="请输入手机号" :maxlength="11" show-word-limit clearable
          prefix-icon='el-icon-mobile' :style="{width: '100%'}"></el-input>
      </el-form-item>
    </el-col>

在这个json到xml的解析过程中,form-generator的parser使用jsx来完成

...
const layouts = {
  colFormItem(h, scheme) {
    const config = scheme.__config__
    const listeners = buildListeners.call(this, scheme)
    let labelWidth = config.labelWidth ? `${config.labelWidth}px` : null
    if (config.showLabel === false) labelWidth = '0'
    return (
      <el-col span={config.span}>
        <el-form-item label-width={labelWidth} prop={scheme.__vModel__}
          label={config.showLabel ? config.label : ''}>
          <render conf={scheme} {...{ on: listeners }} />
        </el-form-item>
      </el-col>
    )
  },
...
}
1.2 rowFormItem布局

rowFormItem布局对应的json形式如下:

{
    "__config__": {
      "layout": "rowFormItem",
      "tagIcon": "row",
      "layoutTree": true,
      "document": "https://element.eleme.cn/#/zh-CN/component/layout#row-attributes",
      "span": 12,
      "formId": 104,
      "renderKey": 1594570310282,
      "componentName": "row104",
      "children": []
    },
    "type": "default",
    "justify": "start",
    "align": "top"
  }

对应的目标代码如下:

<el-col :span="12">
      <el-row>
      </el-row>
    </el-col>

同样使用jsx来完成布局解析:

rowFormItem(h, scheme) {
    let child = renderChildren.apply(this, arguments)
    if (scheme.type === 'flex') {
      child = <el-row type={scheme.type} justify={scheme.justify} align={scheme.align}>
              {child}
            </el-row>
    }
    return (
      <el-col span={scheme.span}>
        <el-row gutter={scheme.gutter}>
          {child}
        </el-row>
      </el-col>
    )
  }

值得注意的是,json表单支持嵌套; 通过__config__.children记录嵌套关系。使用renderChildren递归解析。(目前仅对rowFormItem布局的children做解析)

function renderChildren(h, scheme) {
  const config = scheme.__config__
  if (!Array.isArray(config.children)) return null
  return renderFormItem.call(this, h, config.children)
}

完整的代码,请阅读parse源码,此链接中的版本并不算复杂。

数据和逻辑

传统的vue格式表单,我们可能需要写如下格式的js,完成element UI表单的数据和逻辑。

export default {
  data() {
    return {
      formData: {
        mobile: undefined,
        field103: undefined,
      },
      rules: {
        mobile: [{
          required: true,
          message: '请输入手机号',
          trigger: 'blur'
        }, {
          pattern: /^1(3|4|5|7|8|9)\d{9}$/,
          message: '手机号格式错误',
          trigger: 'blur'
        }],
        field103: [{
          required: true,
          message: '请输入密码',
          trigger: 'blur'
        }],
      },
    }
  },
  methods: {
    submitForm() {
      this.$refs['elForm'].validate(valid => {
        if (!valid) return
        // TODO 提交表单
      })
    },
    resetForm() {
      this.$refs['elForm'].resetFields()
    },
  }
}

对于解析器来说,这是一个抽象的过程:

  • 数据部分:
    构建表单数据实现如下:
data() {
    const data = {
      formConfCopy: deepClone(this.formConf),
      [this.formConf.formModel]: {},
      [this.formConf.formRules]: {}
    }
    this.initFormData(data.formConfCopy.fields, data[this.formConf.formModel])
    this.buildRules(data.formConfCopy.fields, data[this.formConf.formRules])
    return data
  },
  • 逻辑部分:
    请阅读,源码 methods 部分。这块和你日常vue编程差不多,只不过属性都是抽象的。

JSON表单结构说明

上边的一系列操作,都是建立在理解json表单都有哪些内容的基础上的。详细请参阅JSON参数对照表

form-generator中的render.js

render.js就是对vue的render函数的简单定制封装。如果你还不理解vue的render函数,请移步至:渲染函数 & JSX

render.js实现的功能是将json表单中的__config__.tag解析为具体的vue组件; 其工作过程可以理解为以下3个部分:

render(h) {
    const dataObject = makeDataObject()
    const confClone = deepClone(this.conf)
    const children = []
    // 1 如果slots文件夹存在与当前tag同名的文件,则执行文件中的代码
    mountSlotFlies.call(this, h, confClone, children)
    // 2 将字符串类型的事件,发送为消息
    emitEvents.call(this, confClone)
    // 3 将json表单配置转化为vue render可以识别的 “数据对象(dataObject)”
    buildDataObject.call(this, confClone, dataObject)
    return h(this.conf.__config__.tag, dataObject, children)
  }

4、vue代码生成器

《表单设计器 · 开发教程》el-button已经可以可视化配置属性了。如果你仅仅想使用json格式的表单配置,可以跳过本文,直接阅读《表单解析器 · 开发教程》

本文将继续完成vue代码生成器部分的教程。

点击【导出vue文件】按钮的时候,需要选择一个【生成类型】。说明目前支持生成,文件和弹框两种类型的代码。其实文件类型的代码用el-dialog包裹下就是弹框类型的代码了。

而生成代码的本质就是简单的字符串拼接。分别拼接出html、js、css三种类型的代码,最后组装成vue代码。

代码生成器中大量使用了:es6 模板字符串

一、生成html代码

在文件src\components\generator\html.js中添加el-button的html代码生成规则:

1.1 在tags对象中添加el-button属性,生成html

...
'el-button': el => {
  const {
    tag, disabled
  } = attrBuilder(el)
  const type = el.type ? `type="${el.type}"` : ''
  const icon = el.icon ? `icon="${el.icon}"` : ''
  const round = el.round ? 'round' : ''
  const size = el.size ? `size="${el.size}"` : ''
  const plain = el.plain ? 'plain' : ''
  const circle = el.circle ? 'circle' : ''
  let child = buildElButtonChild(el)
  if (child) child = `\n${child}\n` // 换行
  return `<${tag} ${type} ${icon} ${round} ${size} ${plain} ${disabled} ${circle}>${child}</${tag}>`
},
...

attrBuilder会生成常用的属性,这里与el-button匹配的是tag, disabled;其余属性都是和el-button组件属性对应的,目标是生成字符串:

`<el-button type="success" icon="el-icon-warning" size="medium"> 主要按钮 </el-button>`

1.2 由于按钮内的文字是配置在__slot__中的

__slot__: {
    default: '主要按钮'
 }

所以相应的应该去读取__slot__.default。为了保持和其他组件的统一,定义了函数buildElButtonChild读取__slot__.default。

在文件src\components\generator\html.js中添加buildElButtonChild函数:

// el-buttin 子级
function buildElButtonChild(scheme) {
  const children = []
  const slot = scheme.__slot__ || {}
  if (slot.default) {
    children.push(slot.default)
  }
  return children.join('\n')
}

写好了tags['el-button']和buildElButtonChild函数后,当再次点击运行按钮预览时,发现el-button组件已经可以预览了。

html.js中的代码都是字符串拼接处理并不高深,如需进一步的处理可以从入口函数

makeUpHtml

顺着结构阅读源码。

二、生成js脚本代码

在文件src\components\generator\js.js中,依然是通过字符串拼接的方式,生成脚本代码。

由于el-button无需js脚本,所以本文用el-input组件做讲解:

假设我们有如下的json配置:

{
  __config__: {
    tag: 'el-input',
    required: true,
    regList: [{
      pattern: '/^1(3|4|5|7|8|9)\\d{9}$/',
      message: '手机号格式错误'
    }]
  },
  __vModel__: 'mobile',
  placeholder: '请输入手机号',
}

目标是生成element UI表单校验js代码:

mobile: [{
  required: true,
  message: '请输入手机号',
  trigger: 'blur'
}, {
  pattern: /^1(3|4|5|7|8|9)\d{9}$/,
  message: '手机号格式错误',
  trigger: 'blur'
}]

json配置中有两个校验规则:required和regList,我们要做的代码生成,无非就是将json配置中的key和value,转化成js代码字符串。源码中的转化实现如下:

// 构建校验规则
function buildRules(scheme, ruleList) {
  const config = scheme.__config__
  if (scheme.__vModel__ === undefined) return
  const rules = []
  if (ruleTrigger[config.tag]) {
    if (config.required) {
      const type = isArray(config.defaultValue) ? 'type: \'array\',' : ''
      let message = isArray(config.defaultValue) ? `请至少选择一个${config.label}` : scheme.placeholder
      if (message === undefined) message = `${config.label}不能为空`
      rules.push(`{ required: true, ${type} message: '${message}', trigger: '${ruleTrigger[config.tag]}' }`)
    }
    if (config.regList && isArray(config.regList)) {
      config.regList.forEach(item => {
        if (item.pattern) {
          rules.push(
            `{ pattern: ${eval(item.pattern)}, message: '${item.message}', trigger: '${ruleTrigger[config.tag]}' }`
          )
        }
      })
    }
    ruleList.push(`${scheme.__vModel__}: [${rules.join(',')}],`)
  }
}

上边的函数就是一个json配置key和value的搬运工,很朴实的一段代码,所以js.js中其他生成脚本的代码也不神秘,如有需要放开去看源码就行了,入口函数:

makeUpJs

三、生成css

css部分请直接看源码。文件:src\components\generator\css.js

重点看:

const styles = {
  'el-rate': '.el-rate{display: inline-block; vertical-align: text-top;}',
  'el-upload': '.el-upload__tip{line-height: 1.2;}'
}

此文件只做了一件简单事情:遍历待生成代码的json表单配置。如果配置中使用了el-rate或el-upload,将他们的css样式生成出去。这就是全部。入口函数:

makeUpCss

如果你要改写某个组件的默认样式,比如el-button,将你需要的css加进styles对象中即可。

5、vue代码预览器

后续再补充

相关文章
|
11天前
|
机器学习/深度学习 算法 数据挖掘
【博士每天一篇文论文-算法】A small-world topology enhances the echo state property and signal propagationlun
本文研究了小世界拓扑结构在回声状态网络(ESN)中的作用,发现具有层级和模块化组织的神经网络展现出高聚类系数和小世界特性,这有助于提高学习性能和促进信号传播,为理解神经信息处理和构建高效循环神经网络提供了新的视角。
17 0
【博士每天一篇文论文-算法】A small-world topology enhances the echo state property and signal propagationlun
|
3月前
|
移动开发 JavaScript 前端开发
Mr_HJ / form-generator项目文档学习与记录(续1)
Mr_HJ / form-generator项目文档学习与记录(续1)
26 2
|
3月前
|
移动开发 前端开发
Mr_HJ / form-generator项目学习-增加自定义的超融组件(二)
Mr_HJ / form-generator项目学习-增加自定义的超融组件(二)
43 3
|
3月前
|
JSON 移动开发 前端开发
Mr_HJ / form-generator项目学习-增加自定义的超融组件(一)
Mr_HJ / form-generator项目学习-增加自定义的超融组件(一)
32 3
|
3月前
|
移动开发 前端开发
Mr_HJ / form-generator项目文档学习与记录(续2)
Mr_HJ / form-generator项目文档学习与记录(续2)
19 0
|
3月前
|
JSON JavaScript 数据格式
Mr_HJ / form-generator项目文档学习与记录(续)
Mr_HJ / form-generator项目文档学习与记录(续)
23 0
|
3月前
|
XML Java API
poi-tl——Word模板生成器
poi-tl——Word模板生成器
|
3月前
|
存储 分布式计算 Apache
Spark编程范例:Word Count示例解析
Spark编程范例:Word Count示例解析
|
10月前
|
前端开发 JavaScript Android开发
02HUI - 部署及文档结构
02HUI - 部署及文档结构
42 0
|
机器学习/深度学习 算法 PyTorch
【菜菜的CV进阶之路-Pytorch基础-model.eval】同一个模型测试:shuffle=False和shuffle=True 结果差异很大
【菜菜的CV进阶之路-Pytorch基础-model.eval】同一个模型测试:shuffle=False和shuffle=True 结果差异很大
241 0
【菜菜的CV进阶之路-Pytorch基础-model.eval】同一个模型测试:shuffle=False和shuffle=True 结果差异很大