代码仓库
主要思路:
一触发校验就判断校验是否通过,校验失败就将错误的字段保存,监听到语言切换时重新校验错误字段;校验成功时将错误字段删除。rules 使用 computed 定义,并将 validate-on-rule-change 设置为 false,防止 rules 更新是触发校验。
将上述逻辑封装为自定义 hook 以实现代码复用 封装 hook 的代码如下所示:
import { __DEV__ } from '@/app.config';
import layoutStore from '@/store/index'
import { FormInstance, FormItemProp } from 'element-plus';
import { ref, watch } from 'vue'
import { useI18n } from 'vue-i18n';
/*
* @param params useValidForm 传参,可以通过解构获取对应参数
* @returns 可以返回一些公共的校验方法
*/
export const useValidForm = (params: any = {}) => {
const layout = layoutStore()
// 用于收集校验错误的字段
const errroFieldList = ref<any>(new Map())
const formRef = ref<FormInstance>()
const i18n = useI18n()
const getType = (data: any) => Object.prototype.toString.call(data)
watch(() => layout.getLanguage, (n, o) => {
if (getType(formRef.value) === "[object Array]") {
if (errroFieldList.value) {
for (const [key, value] of errroFieldList.value) {
if (value) {
const propsList = Array.from(value)
// 先清空再重新触发校验是因为第一个 el-form-item 重新触发校验时 message 的数据是对的但是页面上的 error message 不会重新更新,其它项不会这样,但是暂时找不到原因
formRef.value?.[key].clearValidate(propsList)
requestAnimationFrame(() => {
formRef.value?.[key].validateField(propsList)
})
}
}
}
} else {
const propsList: any = Array.from(errroFieldList.value.get(-1) ?? new Set())
if (propsList?.length > 0) {
formRef.value?.clearValidate(propsList)
requestAnimationFrame(() => {
formRef.value?.validateField(propsList)
})
}
}
})
/**
* 收集校验错误的字段
*
* @param prop 校验项的名称
* @param isValid 是否校验通过
* @param message 校验信息
* @param formRefIndex 用于判断当前校验的是第几个表单项,如果没有对 el-form 进行循环(没有传值),则默认为 -1
*/
const triggerValidate = (prop: FormItemProp, isValid: boolean, message: string, formRefIndex: number = -1) => {
// formRefIndex 值大于 -1 针对的是 el-form 绑定的值为动态数组的情况
const _errroList = errroFieldList.value.get(formRefIndex) ?? new Set()
// 如果校验不通过,则将其存入 errroFieldList 中, 否则将其从 errroFieldList 中删除
if (!isValid) {
_errroList.add(prop)
} else {
_errroList.delete(prop)
}
if (_errroList.size <= 0) {
errroFieldList.value.delete(formRefIndex)
} else {
errroFieldList.value.set(formRefIndex, _errroList)
}
}
return {
errroFieldList,
formRef,
i18n,
triggerValidate,
}
}
在组件中使用示例:
<template>
<div class="customer-el-form">
<el-form
ref="formRef"
:model="formData"
:rules="formDataRules"
:validate-on-rule-change="false"
@validate="triggerValidate">
<el-form-item
label="组名"
prop="group">
<el-input v-model="formData.group" />
</el-form-item>
<div
v-for="(person, personIndex) in formData.list"
:key="personIndex">
<el-form-item
label="用户名"
:prop="`list.${personIndex}.name`"
:rules="formDataRules.name">
<el-input v-model="person.name" />
</el-form-item>
<div v-if="personIndex !== formData.list.length - 1">-------------------------------</div>
</div>
</el-form>
<button @click="addPerson">增加</button>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useValidForm } from './hooks/useValidForm';
const { formRef, triggerValidate, i18n } = useValidForm();
const formData = ref({
group: '',
list: [
{
name: '',
age: null,
},
],
});
const formDataRules = computed(() => {
return {
group: {
required: true,
trigger: ['blur', 'change'],
message: i18n.t('user.groupEmpty'),
},
name: {
required: true,
trigger: ['blur', 'change'],
message: i18n.t('user.nameEmpty'),
},
age: {
required: true,
trigger: ['blur', 'change'],
message: i18n.t('user.ageEmpty'),
},
};
});
// 动态添加数据
const addPerson = () => {
formData.value.list.push({
name: '',
age: null,
});
};
</script>
<style lang="less" scoped>
.customer-el-form {
width: 20rem;
}
</style>