>[success] # 动态表单组件 ~~~ 1.公司有个项目需求是用户可以拖拽,自己定义表单内容然后根据 自己的需求做自己的表单验证和表单的打印 2.整套代码我虽然不是动态表单组件的模块的开发人员,也大概看了 这部分代码,我们公司的思路是通过'element-ui' 提供的组件基础上 我们吧需要的对应字段和'form-item' 放到一起变成一个自定义的组件 ,相比下面我通过'思否'学到的这种相比,我们的好处这是在定制化 程度上可能略高,实际体验和代码感觉略差,后续有机会,也会简单 整理一下我们公司这套分享出来 3.下面会主要对'思否' 这套进行说明 ~~~ >[info] ## 创建动态组件准备 -- 数据渲染表单字段篇 ~~~ 1.需要知道'vue' 提供的动态组件'component' 和'is' 相关知识 2.需要 了解'element-ui' 或者'iview-ui' , 接下来的内容是通过'iview-ui' 做的讲解说明 3.要知道表单组件整体分类,在这里我个人给表单组件分成两种, '普通组件' 和 '组合组件','普通组件'指的就是那些不需要配合直接使用 的,例如'input','组合组件' 指的是那些其实是两个部分或者多个部分整合 一起才能构成一个表单项,例如'select' 给和'option'一起才能使用 4.动态组件需要和后台规定好各自所需要的字段,下面的案例规定方式 不是唯一方式,可以根据实际需求来自行规定 5.这里将动态组件分成两个版本,第一个版本就是普通用来渲染,第二个是 加上了验证,拆成两个版本主要是为了方便理解 ~~~ >[danger] ##### 在views 文件下创建视图组件 -- FormTest.vue ~~~ 1.在vue的视图组件文件下创建一个任意的vue文件 ~~~ ~~~ 1.创建一个动态表单组件'form-group' 2.用formList 模拟后台返回数据 2.1 name -- 用来做验证的key 2.2 type -- 组件类型为了动态组件渲染时候使用 2.3 value -- 显示返回的内容值,例如input 需要绑定的v-model 就是这个value 2.4 label -- 在form-item 用来展示字段文字 2.5 children -- 组合组件 3.这里使用的是iview的ui,因此type 这里对应好相应的iview组件 4.在整个formList 模型中注意他们type对应的组件在去iview 文档中查询自己需要的属性即可 5.简单组件例如input 和 range一类他们只需要单个配合即可渲染,复杂的例如select 需要配合 option等多个组合,因此数据结构也会略微需要调整,可以对这种组合形式组件增加自己以用来识别 定义的字段'children',或者根据需求自定义自己想要的 6.例如radio 和 checkbox 无论是在iview 还是 在element 他们看似是一个组件构成 但实际他们用起来也可以说是组合组件的存在,他们都有一个 group 组的存在 ~~~ ~~~ <template> <div> <form-group :list="formList" :url="url"></form-group> </div> </template> <script> import FormGroup from '@/components/form-group' export default { name: 'FormTest', components: { FormGroup, }, data(){ return { url: '/data/formData', formList:[{ name:'name', type:'i-input', value:'', label:'姓名', rule:[ { required: true, message: 'The name cannot be empty', trigger: 'blur' } ], },{ name:'range', type:'slider', value:[10,40], range:true, label:'范围' },{ name:'select', type:'i-select', value:'', label:'性别', children:{ type:'i-option', list:[ {value:'man',title:'男'}, {value:'woman',title:'女'}, ] } },{ name:'education', type:'radio-group', value:1, label:'学历', children:{ type:'radio', list:[ {label:'man',title:'男'}, {label:'woman',title:'女'}, ] } },{ name:'skill', type:'checkbox-group', value:[], label:'技能', children:{ type:'checkbox', list:[ {label:'man',title:'大学'}, {label:'woman',title:'高中'}, ] } }] } } } </script> ~~~ >[danger] ##### 在components 文件下动态渲染表单组件 -- form-group.vue(无验证版) ~~~ 1.动态组件 -- component 是vue自带的 2.虽然使用了vue自带的component,但这里要注意因为不同类型的表单所需要的 渲染属性也是略有区别的,因此需要合理设计,例如这里的range 就是针对iview的 范围组件range才加上的属性 3.真对简单类型的组件例如:'input' 和 'range' 等只需要,数据匹配正确利用'componet' 即可动态生成: <component :is="item.type" :range="item.range" v-model="item.value"></component> 4.针对复杂的例如select 等 这里就涉及到需要使用多个'component' 组件进行渲染 5.小技巧利用_uid 和 index 搭配生成唯一的 key ~~~ ~~~ <template> <Form :lable-width="lableWidth"> <FormItem v-for="(item,index) in list" :label="item.label" label-position="left" :key = "`${_uid}_${index}`" > <component :is="item.type" :range="item.range" v-model="item.value"> <!--做判断是否有children 属性,有的话就做组合组件效果 label 是给radio用的 用来单选--> <template v-if="item.children"> <component v-for="(child,i) in item.children.list" :key="`${_uid}_${index}_${i}`" :value="child.value" :label="child.label" :is="item.children.type" > {{child.title}} </component> </template> </component> </FormItem> </Form> </template> <script> /** * 1.list 接受从父组件传递过来的定义渲染接口的内容值 * 2.利用动态根据传递过来的list 中的type 进行组件渲染 * 3.小技巧利用_uid 和 index 搭配生成唯一的 key **/ export default { name: "FormGroup", props:{ list:{ type:Array, default:()=>{} }, lableWidth:{ type:Number, default:100 } } } </script> <style scoped> </style> ~~~ >[danger] ##### 在components 文件下动态渲染表单组件 -- form-group.vue(有验证版) ~~~ 1.做表单验证前需要思考,整个项目结构逻辑,需要一个'重置按钮', ,因为数据还需要和'Form'组件进行匹配,'Form'组件需要'rules', 'model',因此还需要将数据重洗 2.根据上面分析因此在'data' 中定义了几个参数分别是: 2.1 initValueObj -- 重置时候需要的数据保存参数 2.2 rules -- 验证规则的保存位置 2.3 valueObj -- 给Form 组件model 数据存储 2.4 errorStore -- 后台返回的错误信息展示用的 3.因为这里用的是'valueObj' 存储最后提交表单的值,也因此在动态组件 渲染的时候也要变成从这个对象去拿对应值即可 4. 表单提交的时候,变化的是接口url,因此实际我们可以,在使用动态组件 页,传递一个动态的url,让整个动态组件做一个内置提交功能 ~~~ ~~~ <template> <Form :lable-width="lableWidth" v-if="Object.keys(valueObj).length" :rules="rules" :model="valueObj"> <FormItem v-for="(item,index) in list" :label="item.label" :prop="item.name" label-position="left" :error="errorStore[item.name]" @click.native="handleFocus(item.name)" :key = "`${_uid}_${index}`" > <component :is="item.type" :range="item.range" v-model="valueObj[item.name]"> <!--做判断是否有children 属性,有的话就做组合组件效果 label 是给radio用的 用来单选--> <template v-if="item.children"> <component v-for="(child,i) in item.children.list" :key="`${_uid}_${index}_${i}`" :value="child.value" :label="child.label" :is="item.children.type" > {{child.title}} </component> </template> </component> </FormItem> <FormItem> <!--<Button @click="handleSubmit" type="primary">提交</Button>--> <Button @click="handleReset">重置</Button> </FormItem> </Form> </template> <script> import clonedeep from 'clonedeep' // import { sentFormData } from '@/api/data' export default { name: "FormGroup", props:{ list:{ type:Array, default:()=>{} }, lableWidth:{ type:Number, default:100 }, url: { type: String, required: true } }, data(){ return{ initValueObj:{}, rules:{}, valueObj:{},// 给model errorStore: {} } }, watch: { list () { this.setInitValue() } }, methods:{ setInitValue(){ let initValueObj={} let rules={} let valueObj={} let errorStore = {} this.list.forEach(item => { rules[item.name] = item.rule initValueObj[item.name] = item.value valueObj[item.name] = item.value }) this.rules = rules this.valueObj = valueObj this.initValueObj = initValueObj this.errorStore = errorStore }, handleReset () { this.valueObj = clonedeep(this.initValueObj) }, handleFocus (name) { this.errorStore[name] = '' }, handleSubmit () { this.$refs.form.validate(valid => { if (valid) { sentFormData({ url: this.url, data: this.valueList }).then(res => { this.$emit('on-submit-success', res) }).catch(err => { this.$emit('on-submit-error', err) for (let key in err) { this.errorStore[key] = err[key] } }) } }) }, }, mounted () { this.setInitValue() } } </script> <style scoped> </style> ~~~ * 上面使用的api写法 ~~~ export const sentFormData = ({ url, data }) => { return axios.request({ url, data, method: 'post' }) } ~~~