vue3 + element form 封装自定义 Hooks 以解决切换语言后国际化校验信息无法更新问题

242 阅读2分钟

代码仓库

江海寄余生/vue3 demo - Gitee IDE

主要思路:

    一触发校验就判断校验是否通过,校验失败就将错误的字段保存,监听到语言切换时重新校验错误字段;校验成功时将错误字段删除。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>