useAttrValidate
Hook在Vue.js项目中,表单验证是一个常见的需求,尤其在处理复杂表格编辑场景时。本文将详细介绍一个基于Vue3的自定义Hook——useAttrValidate
,它提供了一种便捷的方式来处理表格组件中的字段校验。
效果如下:
Javascript
import { reactive, nextTick } from "vue";
首先引入Vue3的核心API——reactive
(响应式对象)和nextTick
(异步更新队列)。useAttrValidate
Hook依赖这些API来实现状态管理和异步验证逻辑。
useAttrValidate
Hook const useAttrValidate = () => {
// 校验的每个输入框的集合
const inputRef = reactive({});
const FAIL_CLASS_NAME = "el-input_validatefail";
/**
* @param submitData 需要校验的数据
* @param keyName 校验的具体字段
* @param model 配置table或者的模板,具有中文名称
* @param requiredList 必填项数组 类似 ['attrRemark', 'attrDefaultValue'];
* @description 校验自定义数据
*/
const attrValidate = (
submitData,
model,
keyName = "tblAttrList",
requiredList?: Array<string>
): Promise<string> => {
// 需要校验的字段,可手动配置,也可以从model的required获取
const tmpRequiredList = requiredList?.length
? requiredList
: Object.keys(model)
.map((key) => {
if (model[key].required) return key;
})
.filter((item) => !!item);
return new Promise(async (resolve, reject) => {
const rulesArray: Array<Promise<string>> = [];
if (submitData[keyName].length) {
submitData[keyName].forEach(async (config) => {
// 校验必选
tmpRequiredList.forEach((requireProp) => {
if (
(config[requireProp!] === undefined ||
config[requireProp!] === "" ||
config[requireProp!] === null) &&
tmpRequiredList.includes(requireProp!)
) {
inputRef[requireProp + config.id]!.setAttribute(
"data-after",
"请输入"
);
setInputWarnning(inputRef[requireProp + config.id], "请输入");
reject(`请输入${model[requireProp!].label}`);
}
});
// 检验自定义rules
for (let key in model) {
// 拿到model内每个值
const modelValue = model[key];
// 遍历对应字段的rules规则
if (modelValue?.rules && modelValue.rules.length) {
const rules = modelValue.rules;
rules.forEach((rule) => {
rulesArray.push(
new Promise((res, rej) => {
if (typeof rule !== "object") reject(`rules书写有误`);
// 取出单个rule的validate方法
if ("validator" in rule) {
const {validator} = rule;
validator(key, config[key], (text) => {
if (text) {
inputRef[key + config.id]!.setAttribute(
"data-after",
text
);
setInputWarnning(inputRef[key + config.id], text);
rej(text);
} else {
res("");
}
});
}
})
);
});
}
}
});
// 对所有rules进行检测
rulesArray.length &&
(await Promise.all(rulesArray).catch((error) => {
reject(error);
}));
resolve("成功");
} else {
reject("无法提交,至少需含1个属性");
}
});
};
const setInputWarnning = (ref, text, isAccess = false) => {
const node = ref;
const inputWrapper = node.querySelector(".el-input__wrapper");
console.dir(inputWrapper);
console.log(document.styleSheets);
if (!inputWrapper) return;
if (!isAccess) {
console.log(document.styleSheets[0]);
for (let cssRule of document.styleSheets[0].cssRules) {
if (cssRule.selectorText === `.${FAIL_CLASS_NAME}::after`) {
cssRule.style.content = `'${text}'`;
}
}
node.classList.add("el-input_validatefail");
inputWrapper.style.boxShadow = "0 0 0 1px #f56c6c inset";
inputWrapper.style.transition = "all .3s";
} else {
for (let cssRule of document.styleSheets[0].cssRules) {
if (cssRule.selectorText === `.${FAIL_CLASS_NAME}::after`) {
cssRule.style.content = `''`;
}
}
inputWrapper.style.boxShadow = "0 0 0 1px #dcdfe6 inset";
inputWrapper.style.transition = "all .3s";
}
};
/**
*
* @param key 字段名
* @param config 字段对应的model配置
* @param row 当前行的数据
* @description 当前对于特殊字段的特定校验方式,可以实时的校验rules规则
*/
const singleFieldValidate = (key, config, row) => {
if (config?.rules && config.rules.length) {
const rules = config.rules;
rules.forEach((rule) => {
if (typeof rule !== "object") return `rules书写有误`;
// 取出单个rule的validate方法
if ("validator" in rule) {
const {validator} = rule;
validator(key, row[key], (text) => {
nextTick(() => {
if (text) {
console.dir(inputRef[key + row.id]);
inputRef[key + row.id]!.setAttribute("data-after", text);
setInputWarnning(inputRef[key + row.id], text, false);
return text;
} else {
setInputWarnning(inputRef[key + row.id], "", true);
return "";
}
});
});
}
});
}
inputRef[key + row.id]!.setAttribute("data-after", "");
setInputWarnning(inputRef[key + row.id], "", true);
};
/**
*
* @param key 字段名
* @param row 当前行的数据
*/
const clearValidate = (key, row) => {
console.log(key, row, inputRef);
inputRef[key + row.id]!.setAttribute("data-after", "");
setInputWarnning(inputRef[key + row.id], "", true);
};
/**
*
* @param key 字段名
* @param el 当前节点的htmlRef 等同于document.getElementById()返回的结果
* @param row当前行的数据
* @returns 当前节点的htmlRef
* @description 获取当前节点的htmlRef,并储存于inputRef这个集合里面。用于后续定位并添加data-after伪元素
*/
const getRef = (key, el: HTMLElement, row?) => {
let id = new Date().getTime().toString();
if(row && row?.id){
id= row.id
}
// el.id = id;
inputRef[key + id] = el;
return el;
};
return {
inputRef,
getRef,
attrValidate,
singleFieldValidate,
clearValidate,
};
};
这是一个自定义Hook,通过调用它可以获得一组用于表单验证的方法和状态管理器。
inputRef
和 FAIL_CLASS_NAME
Javascript
const inputRef = reactive({});
const FAIL_CLASS_NAME = "el-input_validatefail";
inputRef
是一个响应式对象,用于存储需要进行验证的输入框引用。FAIL_CLASS_NAME
是一个失败提示样式的类名,在验证失败时为输入框添加此样式。
attrValidate
方法Javascript
/**
* @param submitData 需要校验的数据
* @param keyName 校验的具体字段
* @param model 配置table或者的模板,具有中文名称
* @param requiredList 必填项数组 类似 ['attrRemark', 'attrDefaultValue'];
* @description 校验自定义数据
*/
const attrValidate = (
submitData,
model,
keyName = "tblAttrList",
requiredList?: Array<string>
): Promise<string> => {
// 需要校验的字段,可手动配置,也可以从model的required获取
const tmpRequiredList = requiredList?.length
? requiredList
: Object.keys(model)
.map((key) => {
if (model[key].required) return key;
})
.filter((item) => !!item);
return new Promise(async (resolve, reject) => {
const rulesArray: Array<Promise<string>> = [];
if (submitData[keyName].length) {
submitData[keyName].forEach(async (config) => {
// 校验必选
tmpRequiredList.forEach((requireProp) => {
if (
(config[requireProp!] === undefined ||
config[requireProp!] === "" ||
config[requireProp!] === null) &&
tmpRequiredList.includes(requireProp!)
) {
inputRef[requireProp + config.id]!.setAttribute(
"data-after",
"请输入"
);
setInputWarnning(inputRef[requireProp + config.id], "请输入");
reject(`请输入${model[requireProp!].label}`);
}
});
// 检验自定义rules
for (let key in model) {
// 拿到model内每个值
const modelValue = model[key];
// 遍历对应字段的rules规则
if (modelValue?.rules && modelValue.rules.length) {
const rules = modelValue.rules;
rules.forEach((rule) => {
rulesArray.push(
new Promise((res, rej) => {
if (typeof rule !== "object") reject(`rules书写有误`);
// 取出单个rule的validate方法
if ("validator" in rule) {
const {validator} = rule;
validator(key, config[key], (text) => {
if (text) {
inputRef[key + config.id]!.setAttribute(
"data-after",
text
);
setInputWarnning(inputRef[key + config.id], text);
rej(text);
} else {
res("");
}
});
}
})
);
});
}
}
});
// 对所有rules进行检测
rulesArray.length &&
(await Promise.all(rulesArray).catch((error) => {
reject(error);
}));
resolve("成功");
} else {
reject("无法提交,至少需含1个属性");
}
});
};
attrValidate
函数是核心验证逻辑,接收四个参数:
submitData
:待提交的数据对象。model
:包含各字段配置的对象,如是否必填、验证规则等。keyName
:提交数据中需要校验的子属性键名,默认为"tblAttrList"。requiredList
:必填项数组,若不为空则优先使用该数组。此方法会遍历每一项数据,对必填项进行检查,并根据model
中的rules
执行自定义验证逻辑。如果全部验证通过,则返回"成功";否则返回错误信息。
setInputWarning
用于设置输入框的警告信息以及样式更改。
const setInputWarnning = (ref, text, isAccess = false) => {
const node = ref;
const inputWrapper = node.querySelector(".el-input__wrapper");
console.dir(inputWrapper);
console.log(document.styleSheets);
if (!inputWrapper) return;
if (!isAccess) {
console.log(document.styleSheets[0]);
for (let cssRule of document.styleSheets[0].cssRules) {
if (cssRule.selectorText === `.${FAIL_CLASS_NAME}::after`) {
cssRule.style.content = `'${text}'`;
}
}
node.classList.add("el-input_validatefail");
inputWrapper.style.boxShadow = "0 0 0 1px #f56c6c inset";
inputWrapper.style.transition = "all .3s";
} else {
for (let cssRule of document.styleSheets[0].cssRules) {
if (cssRule.selectorText === `.${FAIL_CLASS_NAME}::after`) {
cssRule.style.content = `''`;
}
}
inputWrapper.style.boxShadow = "0 0 0 1px #dcdfe6 inset";
inputWrapper.style.transition = "all .3s";
}
};
singleFieldValidate
针对单个字段及其规则进行实时验证。
/**
*
* @param key 字段名
* @param config 字段对应的model配置
* @param row 当前行的数据
* @description 当前对于特殊字段的特定校验方式,可以实时的校验rules规则
*/
const singleFieldValidate = (key, config, row) => {
if (config?.rules && config.rules.length) {
const rules = config.rules;
rules.forEach((rule) => {
if (typeof rule !== "object") return `rules书写有误`;
// 取出单个rule的validate方法
if ("validator" in rule) {
const {validator} = rule;
validator(key, row[key], (text) => {
nextTick(() => {
if (text) {
console.dir(inputRef[key + row.id]);
inputRef[key + row.id]!.setAttribute("data-after", text);
setInputWarnning(inputRef[key + row.id], text, false);
return text;
} else {
setInputWarnning(inputRef[key + row.id], "", true);
return "";
}
});
});
}
});
}
inputRef[key + row.id]!.setAttribute("data-after", "");
setInputWarnning(inputRef[key + row.id], "", true);
};
clearValidate
清除指定字段的验证结果。
/**
*
* @param key 字段名
* @param row 当前行的数据
*/
const clearValidate = (key, row) => {
console.log(key, row, inputRef);
inputRef[key + row.id]!.setAttribute("data-after", "");
setInputWarnning(inputRef[key + row.id], "", true);
};
getRef
获取并存储输入框的HTML引用,便于后续操作。
/**
*
* @param key 字段名
* @param el 当前节点的htmlRef 等同于document.getElementById()返回的结果
* @param row当前行的数据
* @returns 当前节点的htmlRef
* @description 获取当前节点的htmlRef,并储存于inputRef这个集合里面。用于后续定位并添加data-after伪元素
*/
const getRef = (key, el: HTMLElement, row?) => {
let id = new Date().getTime().toString();
if(row && row?.id){
id= row.id
}
// el.id = id;
inputRef[key + id] = el;
return el;
};
在实际项目中,你可以这样使用useAttrValidate
:
Javascript
import useAttrValidate from './useValidate';
setup() {
const { inputRef, getRef, attrValidate, clearValidate } = useAttrValidate();
const handleValidate = async () => {
try {
await attrValidate(submitData, tableModel);
// 提交表单...
} catch (error) {
console.error('验证失败:', error);
}
};
// 在渲染输入框时绑定引用
<input ref="(el) => getRef(fieldName, el, rowData)" />
// 清除验证结果
clearValidate(fieldName, rowData);
// 调用验证方法
handleValidate();
// ...
//model示例:
const propertyConfigModel = {
fieldName: {
label: "xxx",
required: true,
rules: [
{
validator(field, value, cb) {
const regExp = /^[a-zA-Z0-9_]+$/;
const res = regExp.test(value);
if (res) cb();
else cb('字段名只支持字母、数字和"_"');
},
},
],
}
fieldType: {
label: "xxxx",
required: true,
column: {
slot: true,
header: true,
},
rules:[]
},
fieldLength: {
label: "xxxx",
rules: [
{
validator(field, value, cb) {
if (value && isNaN(Number(value))) {
cb("长度必须为数字类型");
} else {
cb();
}
},
},
],
},]
}
总之,`useAttrValidate` Hook通过结合Vue3的响应式系统和自定义验证逻辑,为复杂表格表单提供了统一且灵活的验证方案。开发者可以根据具体业务需求调整模型配置,轻松实现各种复杂的表单验证场景。