自己动手封装一个Vue表单组件

仿element-ui封装一个通用的表单组件

需求分析

  • 实现JForm

    • 指定数据,校验规则
  • JFormItem

    • 执行校验
    • 显示错误信息
  • JInput

    • 维护数据

最终理想效果:element Form 表单

JInput

创建components/form/JInput.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
<div>
<!-- 自定义组件双向绑定::value @input -->
<!-- v-bind="$attrs"展开$attrs 每一个项都可以单独设置上去 -->
<input :type="type" :value="value" @input="onInput" v-bind="$attrs">
</div>
</template>
<script>
export default {
inheritAttrs: false, // 设置为false避免attrs设置到根元素上
props: {
value: {
type: String,
default: ''
},
type: {
type: String,
default: 'text'
}
},
methods: {
onInput(e) {
// 派发一个input事件即可
this.$emit('input', e.target.value)
}
},
}
</script>

使用JInput

创建components/form/index.vue,添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div>
<h3>JForm表单</h3><hr>
<j-input v-model="model.username"></j-input>
<j-input type="password" v-model="model.password"></j-input>
</div>
</template>
<script>
import JInput from "./JInput";
export default {
components: {
JInput
},
data() {
return {
model: { username: "Joker", password: "" },
};
}
};
</script>

实现JFormItem

创建components/form/JFormItem.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div>
<label v-if="label">{{label}}</label>
<solt></solt>
<p v-if="error">{{error}}</p>
</div>
</template>
<script>
export default {
props:{
label:{//输入项标签
type:String,
default:''
}
},
data(){
return{
error:''//校验错误信息
}
}
}
</script>

使用JFormItem

在components/form/index.vue中添加基础代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<div>
<h3>JForm表单</h3><hr>
<j-form-item label="用户名">
<j-input v-model="model.username"></j-input>
</j-form-item>
<j-form-item label="密码">
<j-input type="password" v-model="model.password"></j-input>
</j-form-item>
</div>
</template>
<script>
import JInput from "./JInput";
import JFormItem from "./JFormItem";
export default {
components: {
JInput,JFormItem
},
data() {
return {
model: { username: "Joker", password: "" },
};
}
};
</script>

实现JForm

创建components/form/JForm.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<form>
<solt></solt>
<form>
</template>
<script>
export default {
provide() {
return {
form: this // 将组件实例作为提供者,子代组件可方便获取
};
},
props: {
model: {
type: Object,
required: true
},
rules: {
type: Object
}
}
}
</script>

使用JForm

在components/form/index.vue中添加基础代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<template>
<div>
<!-- JForm -->
<JForm :model="userInfo" :rules="rules" ref="loginForm">
<!-- 用户名 -->
<JFormItem label="用户名" prop="username">
<JInput v-model="userInfo.username" placeholder="请输入用户名"></JInput>
</JFormItem>
<!-- 密码 -->
<JFormItem label="密码" prop="password">
<JInput type="password" v-model="userInfo.password" placeholder="请输入用户名"></JInput>
</JFormItem>
<!-- 提交按钮 -->
<JFormItem>
<button @click="login">登录</button>
</JFormItem>
</JForm>
</div>
</template>
<script>
import JInput from "./JInput";
import JFormItem from "./JFormItem";
import JForm from "./JForm";

export default {
components: {
JInput,JFormItem,JForm
},
data() {
return {
rules: {
username: [{ required: true, message: "请输入用户名" }],
password: [{ required: true, message: "请输入密码" }]
},
model: { username: "Joker", password: "" },
};
},
methods:{
login(){
this.$refs['loginForm'].validate(valid => {
if(valid){
alert("请求登录")
}else{
alert("请求失败")
}
})
}
}
};
</script>

数据校验

JInput中通知校验,修改components/form/JInput.vue中代码

1
2
3
4
5
6
7
8
9
10
<!-- dom -->
<input :type="type" :value="value" @input="onInput" v-bind="$attrs">

<!-- methods -->
onInput(e) {
// 派发一个input事件即可
this.$emit('input', e.target.value)
// 通知父级执行校验
this.$parent.$emit('validate')
}

JFormItem监听校验通知,获取规则并执行校验,修改components/form/JFormItem.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<template>
<div>
<label v-if="label">{{label}}</label>
<solt></solt>
<p v-if="error">{{error}}</p>
</div>
</template>
<script>
export default {
inject:['form'],//注入
mounted(){//监听校验事件
this.$on('validate',()=>{this.validate()})
},
methods:{
validate(){
//获取对应JFormItem的校验规则
console.log(this.form.rules[this.prop]);
//获取对应JFormItem的值
console.log(this.form.model[this.prop]);
}
},
props:{
label:{//输入项标签
type:String,
default:''
},
prop: {
type: String
}
},
data(){
return{
error:''//校验错误信息
}
}
}
</script>

安装校验库async-validator: npm i async-validator -S

在components/form/JFormItem.vue中引入,并添加校验代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Schema from "async-validator";
...
validate(){
//获取对应校验规则
const rules = this.form.rules[this.prop];
//获取校验值
const value = this.form.model[this.prop];
//获取描述对象
const desc = {[this.prop]:rules};
//创建Schema实例
const schema = new Schema(desc)
return schema.validate({[this.prop]:value},errors =>{
if(errors){
this.error=errors[0].message
}else{
//校验通过
this.error=''
}
})
}

表单全局校验

为JForm提供validate方法,修改components/form/JForm.vue

1
2
3
4
5
6
7
8
9
10
11
validate(cb){
// 获取所有孩子KFormItem
// [resultPromise]
const task = this.$children
.filter(item => item.prop) // 过滤掉没有prop属性的Item
.map(item => item.validate())
//统一处理所有的Promise结果
Promise.all(tasks)
.then(()=> cb(true))
.catch(()=> cb(false))
}
文章作者: Joker
文章链接: https://qytayh.github.io/2020/06/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E5%B0%81%E8%A3%85%E4%B8%80%E4%B8%AAVue%E8%A1%A8%E5%8D%95%E7%BB%84%E4%BB%B6/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Joker's Blog