vue2封装一个表单
在某些项目中,我们可能会遇到一些结构类似的表单,为了不重复写样式结构,所以进行单独的封装。如果没有大量重复的表单结构,也就不需要封装了,直接写多好的。
大概的思路,为了相对灵活,所以将数据和描述项单独抽离,数据只是为了提交给后端,如果连同描述项一起绑定在一起,那么提交给后端的数据就包含了一些不必要的东西。所以单独使用描述项去描述每个字段,描述项包括:这个字段的 label,这个字段的校验规则,这个字段的组件类型(可能是输入框,可能是选择框等),这个字段如果为选择框,那么还需要包含对应的选择项。
Demo 用的 UI 库是 iview。
编码
封装表单组件
- src/views/FormDemo/BaseForm/components/TestForm.vue
<template>
<div class="form-container">
<!-- 可以写表单统一的样式结构,这里就不写样式了 -->
<!-- 一旦封装了样式结构,那么对应的描述数据项就相对要复杂一些了,所以要有所取舍 -->
<Form :model="form" :rules="rules" ref="form">
<Row :gutter="16">
<Col v-for="item in formKeys" :key="item.key" :xl="4" :md="12" :xs="24">
<FormItem
:label="item.label || ''"
:prop="item.key"
>
<component :is="item.inputType" v-model="form[item.key]">
<template v-if="item.inputType === 'Select'">
<Option v-for="o in item.options" :value="o.value" :key="o.value">{{ o.label }}</Option>
</template>
<template v-if="item.inputType === 'CheckboxGroup'">
<Checkbox v-for="o in item.options" :label="o" :key="o"></Checkbox>
</template>
</component>
</FormItem>
</Col>
</Row>
</Form>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
name: 'BaseForm',
model: {
prop: 'value',
event: 'change'
},
props: {
value: {
type: Object,
required: true
},
descFields: {
type: Object,
required: true
}
},
data() {
return {
form: {},
formKeys: [],
rules: {}
}
},
methods: {
// 表单验证
formValidate() {
let flag = false
this.$refs.form.validate(valid => {
if (valid) {
// 验证通过,将表单数据改变
this.$emit('change', this.form)
flag = true
} else {
flag = false
}
})
return flag
}
},
created() {
this.form = JSON.parse(JSON.stringify(this.value))
const keys = Object.keys(this.form)
this.formKeys = keys.map(key => {
if (!this.rules[key]) {
this.rules[key] = this.descFields[key].rule
}
return {
key,
label: this.descFields[key].label,
inputType: this.descFields[key].inputType,
options: this.descFields[key].options,
}
})
// console.log(this.form, keys)
// console.log(this.formKeys)
}
})
</script>
<style lang="scss" scoped>
.form-container {
& ::v-deep .ivu-form-item {
display: flex;
flex-direction: column;
}
& ::v-deep .ivu-form-item-label {
text-align: left;
}
& ::v-deep .ivu-checkbox {
margin-right: 5px;
}
}
</style>
引入组件使用
- src/views/FormDemo/BaseForm/index.vue
<template>
<Card>
<Form :model="form">
<FormItem label="测试外层表单字段1">
<Input v-model="form.test1"></Input>
</FormItem>
<TestForm v-model="form.form1" :descFields="descFields1" ref="testForm1" />
<FormItem label="测试外层表单字段2">
<Input v-model="form.test2"></Input>
</FormItem>
<TestForm v-model="form.form2" :descFields="descFields2" ref="testForm2" />
<FormItem label="测试外层表单字段3">
<Input v-model="form.test3"></Input>
</FormItem>
<FormItem label="测试外层表单字段4">
<Input v-model="form.test4"></Input>
</FormItem>
<TestForm v-model="form.form3" :descFields="descFields3" ref="testForm3" />
</Form>
<Button @click="handleSave">提交</Button>
</Card>
</template>
<script lang="ts">
import Vue from 'vue';
import TestForm from './components/TestForm.vue'
export default Vue.extend({
name: 'BaseForm',
components: {
TestForm
},
data() {
return {
form: {
// 一般是具有一定关联关系的字段放在同一个对象下面,
// 用于描述一个特定的对象
form1: {
field1: '',
field2: '',
field3: '',
field4: '',
},
form2: {
field1: '',
field2: '',
field3: '',
field4: '',
field5: '',
},
form3: {
field1: '',
field2: '',
field3: [],
},
test1: '',
test2: '',
test3: '',
test4: '',
},
descFields1: {
field1: {
label: '字段1',
inputType: 'Input',
rule: [{ required: true, message: '该项为必填', trigger: 'blur' }],
},
field2: {
label: '字段2',
inputType: 'Input',
rule: [{ required: true, message: '该项为必填', trigger: 'blur' }],
},
field3: {
label: '字段3',
inputType: 'Input',
rule: [{ required: true, message: '该项为必填', trigger: 'blur' }],
},
field4: {
label: '字段4',
inputType: 'Input',
rule: [{ required: true, message: '该项为必填', trigger: 'blur' }],
},
},
descFields2: {
field1: {
label: '测试字段1',
inputType: 'Input',
},
field2: {
label: '测试字段2',
inputType: 'Select',
options: [
{ label: '选项1', value: '1' },
{ label: '选项2', value: '2' },
],
rule: [{ required: true, message: '该项为必选', trigger: 'change' }],
},
field3: {
label: '测试字段3',
inputType: 'Input',
rule: [{ required: true, message: '该项为必填', trigger: 'blur' }],
},
field4: {
label: '测试字段4',
inputType: 'Input',
rule: [{ required: true, message: '该项为必填', trigger: 'blur' }],
},
field5: {
label: '测试字段5',
inputType: 'Input'
},
},
descFields3: {
field1: {
label: '测试字段1',
inputType: 'Input',
},
field2: {
label: '测试字段2',
inputType: 'Select',
options: [
{ label: '选项1', value: '1' },
{ label: '选项2', value: '2' },
],
rule: [{ required: true, message: '该项为必选', trigger: 'change' }],
},
field3: {
label: '测试字段3',
inputType: 'CheckboxGroup',
rule: [{ required: true, message: '该项为必填', trigger: 'blur' }],
options: ['多选1', '多选2', '多选3']
},
}
}
},
methods: {
// 保存数据
handleSave() {
const refs = ['testForm1', 'testForm2', 'testForm3']
const validateArr = refs.map(ref => {
return this.$refs[ref].formValidate()
})
if (validateArr.every(flag => flag)) {
console.log(this.form)
}
},
}
})
</script>
<style lang="scss" scoped>
</style>