先看代码
这是Element Plus
官网上面的一个带验证的表单示例
js
<template
<el-form
:model="ruleForm"
:rules="rules"
>
//输入框
<el-form-item label="Activity name" prop="name">
<el-input v-model="ruleForm.name" />
</el-form-item>
// 选择器
<el-form-item label="Activity zone" prop="region">
<el-select v-model="ruleForm.region" placeholder="Activity zone">
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
//多选框
<el-form-item label="Activity type" prop="type">
<el-checkbox-group v-model="ruleForm.type">
<el-checkbox label="Online activities" name="type" />
<el-checkbox label="Promotion activities" name="type" />
<el-checkbox label="Offline activities" name="type" />
<el-checkbox label="Simple brand exposure" name="type" />
</el-checkbox-group>
</el-form-item>
</template>
可以看到除了最外层的 <el-form>
以外,有大量的重复el-form-item
以及里面各类的表单组件,既然有重复那能不能通过循环数组,然后根据数组里面的数据动态生成呢? 答案是肯定的。
利用vue提供的内置组件<component>
就能够根据运行时的数据动态地选择和渲染不同的组件。通过defineProps
接收父组件传递进来的数据,v-bind="$attrs"
接收非props
数据,就能够实现表单的配置化。
先实现一个最简单的带有验证的输入框跟密码框
首先看最外层的el-form
它需要的核心属性就2个model , rules
,el-form-item
需要的属性就label , prop
js
<template>
<el-form :model="model" :rules="rules">
<template v-for="(item, index) in options" :key="index">
<el-form-item :label="item.label" :prop="item.prop">
<component
:is="`el-${item.type}`"
v-bind="item.attrs"
v-model="model[item.prop]"
>
</component>
</el-form-item>
</template>
</el-form>
</template>
<script setup>
import { ref} from 'vue'
const props = defineProps({
options: {
type: Array,
required: true
}
})
const model = ref(null)
const rules = ref(null)
</style>
给el-form
定义2个响应式数据,el-form-item
重复的就靠props
接收父组件传进来数据循环生成, <component>
负责动态渲染组件, v-bind="item.attrs"
接收属性。剩下的就是配置数据了。
父组件里使用封装的组件,根据使用的表单组件,查看需要的属性名一一配置,表单就生成好了。
js
<template>
<div class="form">
<m-from :options="options" label-width="100px"></m-from>
</div>
</template>
<script setup>
import { ref } from 'vue'
import mFrom from '@/components/content/mFrom/mFrom.vue'
const options = ref([
// 输入框
{
type: 'input',
label: '用户名',
value: '',
prop: 'username',
rules: [
{
required: true,
message: '请输入用户名',
trigger: 'blur'
},
{
min: 3,
max: 5,
message: '用户名长度在3到5个字符',
trigger: 'blur'
}
],
attrs: {
type: '',
clearable: true,
size: 'large',
style: {
width: '30%'
}
}
},
// 密码框
{
type: 'input',
label: '密码',
value: '',
prop: 'password',
rules: [
{
required: true,
message: '请输入密码',
trigger: 'blur'
},
{
min: 6,
max: 10,
message: '用户名长度在6到10个字符',
trigger: 'blur'
}
],
attrs: {
type: 'password',
showPassword: true,
clearable: true,
size: 'large',
style: {
width: '30%'
}
}
}
])
</script>
完成这些后会有个问题,我定义的model,rules数据模型
是个空里面是没有key
的,所以表单中的输入元素的值与 <el-form :model="model" :rules="rules">
建立不了双向绑定关系。
通过循环可以解决这个问题,因为这是一个响应式对象引用类型所以需要深拷贝,这里用到lodash
库中的cloneDeep
js
import cloneDeep from 'lodash/cloneDeep'
const model = ref(null)
const rules = ref(null)
onMounted(() => {
if (props.options && props.options.length) {
let m = {}
let r = {}
props.options.forEach((item) => {
m[item.prop] = item.value
r[item.prop] = item.rules
})
model.value = cloneDeep(m)
rules.value = cloneDeep(r)
}
})
通过循环就可将数组里prop
字段的值添加到对象里面了。给在最外层 <el-form>
添加 v-if="model"
只有当model
有值的时候才渲染整个组件,这样就完成了一个带有输入框跟密码框表单。
但是又会出现一个问题,这是一个选择器组件的示例,能看到里面除了<el-form-item>
和<el-select>
还有一个子组件,我并没有对里面这个子组件做处理,所以是渲染不出来的。下面对它进行特殊处理。
js
<el-form-item label="Activity zone">
<el-select v-model="form.region" placeholder="please select your zone">
<!-- 子组件 -->
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
首先数据增加children
字段对子组件进行配置
js
//下拉列表
{
type: 'select',
label: '部门',
value: '',
prop: 'salary',
rules: [
{
required: true,
message: '--请选择部门--',
trigger: 'blur'
}
],
attrs: {
size: 'large',
style: {
width: '30%'
}
},
children: [ // 子组件的配置
{
type: 'option',
label: '开发部',
value: '1'
},
{
type: 'option',
label: '人事部',
value: '2'
},
{
type: 'option',
label: '运营部',
value: '3'
}
]
},
然后添加判断,有children
字段的就渲染,没有的就不渲染。
js
<template>
<el-form
v-if="model"
v-bind="$attrs"
:model="model"
:rules="rules"
:validate-on-rule-change="false"
>
<template v-for="(item, index) in options" :key="index">
<!-- 没有children才显示的 -->
<el-form-item
v-if="!item.children || !item.children.length"
:label="item.label"
:prop="item.prop"
>
<component
:is="`el-${item.type}`"
v-bind="item.attrs"
v-model="model[item.prop]"
>
</component>
</el-form-item>
<!-- 有children才显示的 -->
<el-form-item
v-if="item.children && item.children.length"
:label="item.label"
:prop="item.prop"
>
<component
:is="`el-${item.type}`"
v-bind="item.attrs"
v-model="model[item.prop]"
>
<component
v-for="(child, i) in item.children"
:key="i"
:label="child.label"
:value="child.value"
:is="`el-${child.type}`"
>
</component>
</component>
</el-form-item>
</template>
</el-form>
</template>
最终效果