背景
根据后端返回的不同类型,直接动态渲染不同表单
数据格式
treeData:[{
type: 'HInput',
title: '患者姓名',
value: ''
},{
type: 'HRadio',
title: '性别',
label: [{
title: '男',
checked: false // 选中标识
},{
title: '女',
contactKey: 'aaa', // 联动标识
checked: false
}]
},{
type: 'HDatePicker',
title: '出生日期',
value: ''
},{
type: 'HInput',
title: '地区',
value: ''
},{
type: 'HInput',
title: '身高',
unit: '厘米',
value: ''
},{
type: 'HRadio',
title: '是否孕期',
contactVal: {// 响应上方联动标识,false不展示,true展示,这儿为对象,可能受多个项目影响
aaa: 'aaa-false',
}
label: [{
title: '是',
checked: false,
children: [{ // 联动块
type: 'HDatePicker',
title: '预产期',
value: ''
}]
},{
title: '否',
checked: false
}]
}]
实现思路
想法1:数据——虚拟dom——视图
1、根据表单数据,映射出一套虚拟dom
2、通过createElement方法实现视图
3、数据修改,重新映射虚拟dom(diff处理)
难点:
1、虚拟dom规范设计
2、思路尚不成熟
想法2:复合组件方式
下面着重介绍下思路2的实现方式。
思路2实现(组件划分)
基本组件
1、element组件的包装,用于处理数据,传值
2、后端返回的表单type类型为基本组件名称
<template>
<div>
{{value.title}}
<el-radio-group v-model="radio" @change="changeHandler">
<el-radio v-for="(item, index) in value.label" :key="index" :label="item.title">{{item.title}}</el-radio>
</el-radio-group>
</div>
</template>
<script>
export default {
props:{
value:{
default: () => {},
type: Object
}
},
data(){
return {
radio: ''
}
},
methods:{
// 数据变动校验,存在变动,发布事件通知页面组件,并回传当前数据以便于页面组件判断是否存在联动模块
changeHandler(val){
for(let i = 0; i < this.value.label.length; i++){
this.value.label[i].checked = false;
if(this.value.label[i].title === val){
this.value.label[i].checked = true;
}
}
this.$emit('event1', this.value)
}
}
}
</script>
复合递归组件
1、使用component动态组件,透传直接渲染基本组件,v-model绑定数据,实现数据的双向绑定;
2、使用插槽根据判读渲染label或children模块;
3、根据contactVal属性是否含有true判断联动部分是否展示
4、通过require.context去阅读基本组件目录,动态引入基本组件
<template>
<div>
<component class="itemContent" v-if="!itemData.contactVal||itemData.contactVal.indexOf('true')>=0" v-bind:is="itemData.type" @event1="change($event)" v-model="itemData"></component>
<slot v-if="itemData.label||(itemData.children&&itemData.checked)"></slot>
</div>
</template>
<script>
import moduleComponents from '../util/getBasicArr.js';
export default {
name: 'HComponent',
props:{
itemData:{
default: () => {},
type: Object
}
},
methods: {
change(data) {
// 通知页面组件进行数据处理,接口调用
this.$emit('event1', data)
}
},
components: moduleComponents
}
</script>
// 动态引入子组件
var context = require.context('../components/BasicComponents',false,/\.vue$/);
var moduleComponents = {};
context.keys().forEach((key)=>{
const fileName = key.split(".")[1].split("/")[1];
const fileModule = context(key).default;
moduleComponents[fileName] = {
...fileModule,
namespaced: true
}
})
export default moduleComponents;
页面组件
1、遍历渲染数据;
2、根据回传内容进行数据处理(修改或清空对应联动模块数据)
3、调用暂存接口
<template>
<div class="call_status">
<HdfComponent v-for="(item,index) in treeData" :key="index" :itemData="item" @event1="change($event)">
<HdfComponent v-for="(subItem, subIndex) in item.label" :key="subIndex" :itemData="subItem" @event1="change($event)">
<HdfComponent v-for="(sunItem, sunIndex) in subItem.children" :key="sunIndex" :itemData="sunItem" @event1="change($event)"></HdfComponent>
</HdfComponent>
</HdfComponent>
</div>
</template>
<script>
import HdfComponent from './components/HdfComponent.vue'
export default {
data(){
return {
treeData:[]
}
},
methods: {
change(data) {
this.hasContact(data)
},
// 判断当前处理的选项是否有联动
hasContact(obj){
if(Object.prototype.hasOwnProperty.call(obj, 'contactKey')){
// 存在联动并且有值,去更新联动数据
if(obj.checked || obj.value){
this.handleData(obj.contactKey, 'update')
}else{
// 存在联动没有值,去清空联动数据
this.handleData(obj.contactKey, 'reset')
}
}
for(const key in obj){
if(Object.prototype.toString.call(obj[key]) === '[object Array]'){
for(const item of obj[key]){
this.hasContact(item)
}
} else if(Object.prototype.toString.call(obj[key]) === '[object Object]'){
this.hasContact(obj[key])
}
}
},
// 修改所有联动部分
handleData(contactKey, handleType){
const treeData = this.treeData;
for(const item of treeData){
this.handleObj(item, contactKey, handleType)
}
this.treeData = treeData;
},
// 深层次遍历去修改联动数据
handleObj(obj, contactKey, handleType){
if(Object.prototype.hasOwnProperty.call(obj, 'contactVal') && obj.contactVal.indexOf(contactKey) >= 0){
if(handleType === 'update'){
obj.contactVal = obj.contactVal.replace('false', 'true')
}
if(handleType === 'reset'){
this.resetData(obj)
}
}
for(const key in obj){
if(Object.prototype.toString.call(obj[key]) === '[object Array]'){
for(const item of obj[key]){
this.handleObj(item, contactKey, handleType)
}
} else if(Object.prototype.toString.call(obj[key]) === '[object Object]'){
this.handleObj(obj[key], contactKey, handleType)
}
}
},
// 重置联动数据
resetData(obj){
for(const key in obj){
if(key === 'value'){
obj[key] = ''
}
if(key === 'checked'){
obj[key] = false
}
if(key === 'contactVal'){
obj[key] = obj[key].replace('true', 'false')
}
if(Object.prototype.toString.call(obj[key]) === '[object Array]'){
for(const item of obj[key]){
this.resetData(item)
}
}
if(Object.prototype.toString.call(obj[key]) === '[object Object]'){
this.resetData(obj[key])
}
}
}
},
components: {
HdfComponent
}
}
</script>
难点
1、联动较为复杂的,使得结构变乱;
2、因为为树结构,需要伴随深层次的遍历,若表单数据较多,可能存在性能问题
思路尚不成熟,各位大佬要有更好的方法即时留言。