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属性配置项:


  
    
    
    
    
    
    
  

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

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

增加后的配置项如下:


  
    
      中等
    
    
      较小
    
    
      迷你
    
  

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


  
    
      选择
    
  

此处使用了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布局对应的目标实际代码如下 :

    
      
        
      
    

在这个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 (
      
        
          
        
      
    )
  },
...
}
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"
  }

对应的目标代码如下:

    
      
      
    

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

  rowFormItem(h, scheme) {
    let child = renderChildren.apply(this, arguments)
    if (scheme.type === 'flex') {
      child = 
              {child}
            
    }
    return (
      
        
          {child}
        
      
    )
  }

值得注意的是,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}`
},
...

attrBuilder会生成常用的属性,这里与el-button匹配的是tag, disabled;其余属性都是和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代码预览器

后续再补充。

你可能感兴趣的:(前端,javascript,vue.js,学习)