Vue+ElementUI实现表单动态渲染、可视化配置的方法


Posted in Javascript onMarch 07, 2018

动态渲染就是有一个异步的数据,大概长这样:

{
 "inline": true,
 "labelPosition": "right",
 "labelWidth": "",
 "size": "small",
 "statusIcon": true,
 "formItemList": [
 {
 "type": "input",
 "label": "姓名",
 "disable": false,
 "readonly": false,
 "value": "",
 "placeholder": "请输入姓名",
 "rules": [],
 "key": "name",
 "subtype": "text"
 },
 {
 "type": "radio",
 "label": "性别",
 "value": "",
 "button": false,
 "border": true,
 "rules": [],
 "key": "gender",
 "options": [
 {
  "value": "1",
  "label": "男",
  "disabled": false
 },
 {
  "value": "0",
  "label": "女",
  "disabled": false
 }
 ]
 }
 ]
}

然后你需要把这个json渲染成这样:

Vue+ElementUI实现表单动态渲染、可视化配置的方法 

最后提交表单的数据长这样:

{
 "name": "Genji",
 "gender": "1"
}

然后我们目标就是封装这样一个组件:

<dynamic-form :config="someConfig" v-model="someData" />

实现

开始之前,你需要知道 v-model 的工作原理 :

<input v-model="something">

这不过是以下示例的语法糖:

<input
 :value="something"
 @input="something = $event.target.value">

了解这些后,我们再来一步一步实现这个组件。

首先,把配置转发到 el-form :

<template>
 <el-form 
 class="dynamic-form" 
 :inline="formConfig.inline" 
 :model="value" 
 :label-position="formConfig.labelPosition" 
 :label-width="formConfig.labelWidth" 
 :size='formConfig.size' 
 :status-icon="formConfig.statusIcon">
 <slot/>
 </el-form>
</template>
<script>
export default {
 props: {
 formConfig: {
 type: Object,
 required: true
 },
 value: {
 type: Object,
 required: true
 }
 },
}
</script>

第二步,设置默认值。

因为在每个 form-item 都会需要一个 v-model ,所以在渲染之前,保证每个字段都有值。这里需要注意一点,组件内不要直接修改父组件传入的 prop ,所以我们在这里用 {...this.value} 快速拷贝一份,最后别忘了通知父组件。代码如下:

export default {
 props: {
 formConfig: {...},
 value: {...},
 },
 methods: {
 setDefaultValue() {
 const formData = { ...this.value }
 // 设置默认值
 this.formConfig.formItemList.forEach(({ key, value }) => {
 if (formData[key] === undefined || formData[key] === null) {
  formData[key] = value
 }
 })
 this.$emit('input', formData)
 }
 },
 mounted() {
 this.setDefaultValue()
 },
}

第三步,渲染 form-item 。

如何把下面的数据渲染为我们熟悉的 el-form-item ?

{
 "type": "input",
 "label": "姓名",
 "disable": false,
 "readonly": false,
 "value": "",
 "placeholder": "请输入姓名",
 "rules": [],
 "key": "name",
 "subtype": "text"
}

第一种,利用 vue 内置的 component 组件,写起来可能像这样:

<el-form-item>
 <component :is="`el-${item.type}`" />
</el-form-item>

第二种,使用 v-if 逐个判断:

<el-form-item>
 <el-input v-if="item.type === 'input'" />
 <span v-else>未知控件类型</span>
</el-form-item>

考虑到每种表单控件的处理逻辑千差万别,楼主采用了第二种方式。

根据这个思路,我们来封装一个 dynamic-form-item ,接收一个 item ,渲染一个 el-form-item :

<template>
 <el-form-item :rules="item.Rules" :label="item.label" :prop="item.key">
 <el-input 
 v-if="item.type==='input'" 
 v-bind="$attrs" v-on="$listeners" 
 :type="item.subtype" 
 :placeholder="item.placeholder" 
 :disabled="item.disable" 
 :readonly="item.readonly" 
 :autosize="item.autosize"></el-input>
 <el-select 
 v-else-if="item.type==='select'" 
 v-bind="$attrs" v-on="$listeners"
 :multiple="item.multiple" 
 :disabled="item.disabled" 
 :multiple-limit="item.multipleLimit">
  <el-option 
  v-for="o in item.options" 
  :key="o.value" 
  :label="o.label" 
  :value="o.value" 
  :disabled="o.disabled">
  </el-option>
 </el-select>
 <!--突然有点想念JSX-->
 ...
 <span v-else>未知控件类型</span>
 </el-form-item>
</template>
<script>
export default {
 props: {
 item: {
 type: Object,
 required: true
 }
 }
}
</script>

tips: 使用 v-bind="$attrs" v-on="$listeners" 可以方便地转发父组件的 v-model 指令,详见vue高阶组件。

最后,我们就可以循环输出一个完整的表单了:

<dynamic-form-item
 v-for="item in formConfig.formItemList"
 :key="item.key"
 v-if="value[item.key]!==undefined"
 :item="item"
 :value="value[item.key]"
 @input="handleInput($event, item.key)" />

这里不能用 v-model="value[item.key]" ,上文说了,组件内不能直接修改props,所以这里我们还是转发一下。

methods: {
 handleInput(val, key) {
 // 这里element-ui没有上报event,直接就是value了
 this.$emit('input', { ...this.value, [key]: val })
 },
 setDefaultValue() {...}
},

完整代码地址:  src/components/dynamic-form/form.vue

扩展功能

1.数字显示单位,限制小数位数

element-ui 没有做这个功能,不过我觉得还是挺常见的,所以使用 el-input 手动封装了一个 input-number :

Vue+ElementUI实现表单动态渲染、可视化配置的方法 

<!--普通使用-->
<input-number 
 v-model="someNumber"
 :min="1" 
 :max="99" 
 :decimal1="2" 
 append="元"></input-number>
<!--在dynamic-form-item中的应用-->
<input-number 
 v-else-if="item.type==='number'" 
 v-bind="$attrs" v-on="$listeners" 
 :min="item.min" 
 :max="item.max" 
 :decimal1="item.decimal1" 
 :append="item.append" 
 :prepend="item.prepend" 
 :disabled="item.disabled"></input-number>

完整代码: src/components/dynamic-form/input-number.vue

2.异步验证

得益于 async-validator ,我们可以很方便地自定义验证规则。

Vue+ElementUI实现表单动态渲染、可视化配置的方法 

在配置中

{
 "type": "input",
 ...
 "rules":[
  {
   "sql": "SELECT {key} FROM balabala",
   "message": "xx已被占用",
   "trigger": "blur"
  }
 ]
}

dynamic-form-item 组件中, 遍历 item.rules , 将sql验证转化为自定义 validator 函数:

<template>
 <el-form-item :rules="Rules" >
  ...
 </el-form-item>
</template>
<script>
import request from '@/utils/request'
export default {
 props: {
 item: {...}
 },
 computed: {
 Rules() {
  const rules = this.item.rules
  if (rules === undefined) return undefined
  const R = []
  rules.forEach(rule => {
  if (rule.sql) {
   const validator = (rule2, value, callback) => {
   // 根据项目自行修改
   request('/api/validate', 'POST', {
    key: rule2.field,
    value,
    sql: rule.sql.replace(/{key}/ig, rule2.field)
   })
    .then(res => {
    callback(!res || undefined)
    })
    .catch(err => {
    this.$message.error(err.message)
    callback(false)
    })
   }
   R.push({ validator, message: rule.message, trigger: 'blur' })
  } else {
   R.push(rule)
  }
  })
  return R
 }
 },
}
</script>

3.省市区快捷配置

感谢 element-china-area-data 的作者。

在配置中:

{
 "type": "cascader",
 ...
 "areaShortcut": "provinceAndCityData"
}

在 dynamic-form-item 组件中:

<template>
 <el-form-item>
  ...
  <el-cascader 
   :options="item.options || require('element-china-area-data')[item.areaShortcut]"
   ></el-cascader>
 </el-form-item>
</template>

4.从远程加载选项

包括但不限于 radio 、 checkbox 、 cascader 、 select

在配置中:

{
 "type": "checkbox",
 ...
 "optionsUrl": "/api/some/options"
}

在 dynamic-form-item 组件中:

<template>
 <el-form-item>
  ...
  <el-select>
   <el-option 
    v-for="o in item.options || ajaxOptions"
    ></el-option>
  </el-select>
 </el-form-item>
</template>
<script>
import request from '@/utils/request'
export default {
 props: {
 item: {...}
 },
 computed: {...},
 data() {
 return {
  ajaxOptions: []
 }
 },
 created() {
 const { optionsUrl, key, type } = this.item
 if (optionsUrl) {
  // 根据项目自行修改
  request(`${optionsUrl}?key=${key}`, 'GET')
  .then(res => {
   this.ajaxOptions = res
  })
  .catch(err => { this.$message.error(err.message) })
 }
 }
}
</script>

总结

以上所述是小编给大家介绍的Vue+ElementUI实现表单动态渲染、可视化配置的方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
jquery 入门教程 [翻译] 推荐
Aug 17 Javascript
在服务端(Page.Write)调用自定义的JS方法详解
Aug 09 Javascript
iframe子父页面调用js函数示例
Nov 07 Javascript
jquery插件bxslider用法实例分析
Apr 16 Javascript
jquery插件jquery.beforeafter.js实现左右拖拽分隔条对比图片的方法
Aug 07 Javascript
Angular表单验证实例详解
Oct 20 Javascript
基于jquery实现的银行卡号每隔4位自动插入空格的实现代码
Nov 22 Javascript
JavaScript队列的应用实例详解【经典数据结构】
Apr 12 Javascript
jquery replace方法去空格
May 08 jQuery
Vue.js递归组件构建树形菜单
Dec 24 Javascript
jQuery使用each遍历循环的方法
Sep 19 jQuery
vue页面引入three.js实现3d动画场景操作
Aug 10 Javascript
浅谈webpack打包之后的文件过大的解决方法
Mar 07 #Javascript
解决iview打包时UglifyJs报错的问题
Mar 07 #Javascript
React Native使用fetch实现图片上传的示例代码
Mar 07 #Javascript
使用vue制作探探滑动堆叠组件的实例代码
Mar 07 #Javascript
基于vue-cli 打包时抽离项目相关配置文件详解
Mar 07 #Javascript
关于Vue的路由权限管理的示例代码
Mar 06 #Javascript
vue.js在标签属性中插入变量参数的方法
Mar 06 #Javascript
You might like
PHP 网页过期时间的控制代码
2009/06/29 PHP
php 调试利器debug_print_backtrace()
2012/07/23 PHP
使用CodeIgniter的类库做图片上传
2014/06/12 PHP
Laravel下生成验证码的类
2017/11/15 PHP
baidu博客的编辑友情链接的新的层窗口!经典~支持【FF】
2007/02/09 Javascript
使用jQuery向asp.net Mvc传递复杂json数据-ModelBinder篇
2010/05/07 Javascript
JQuery实现简单验证码提示解决方案
2012/12/20 Javascript
jquery遍历数组与筛选数组的方法
2013/11/05 Javascript
js给onclick赋值传参数的两种方法
2013/11/25 Javascript
js获取json元素数量的方法
2015/01/27 Javascript
JavaScript为事件句柄绑定监听函数实例详解
2015/12/15 Javascript
layer实现弹窗提交信息
2016/12/12 Javascript
javascript循环链表之约瑟夫环的实现方法
2017/01/16 Javascript
JS双向链表实现与使用方法示例(增加一个previous属性实现)
2019/01/31 Javascript
Vue源码解析之数据响应系统的使用
2019/04/24 Javascript
详解Python中的循环语句的用法
2015/04/09 Python
Python浅拷贝与深拷贝用法实例
2015/05/09 Python
Python使用内置json模块解析json格式数据的方法
2017/07/20 Python
Python及PyCharm下载与安装教程
2017/11/18 Python
Python设计模式之状态模式原理与用法详解
2019/01/15 Python
Python3 sys.argv[ ]用法详解
2019/10/24 Python
pytorch实现从本地加载 .pth 格式模型
2020/02/14 Python
解决Python数据可视化中文部分显示方块问题
2020/05/16 Python
Django静态文件加载失败解决方案
2020/08/26 Python
怎么处理XML的中文问题
2015/03/26 面试题
我有一个char * 型指针正巧指向一些int 型变量, 我想跳过它们。 为什么如下的代码((int *)p)++; 不行?
2013/05/09 面试题
经典c++面试题三
2015/07/08 面试题
妇产医师自荐信
2014/01/29 职场文书
学习保证书范文
2014/04/30 职场文书
青年文明号口号
2014/06/17 职场文书
2014迎国庆标语大全
2014/09/19 职场文书
大学学生个人总结
2015/02/15 职场文书
2015年小学体育工作总结
2015/05/22 职场文书
golang interface判断为空nil的实现代码
2021/04/24 Golang
python自动计算图像数据集的RGB均值
2021/06/18 Python
Windows Server 2019 安装DHCP服务及相关配置
2022/04/28 Servers