Vue 表单 N 选一逻辑实现及其优化点
项目中我们经常会遇到表单的联合校验的功能。
比如表单中有电话号码和手机号码两个选项,两个必须选填一个。表单提交的时候需要校验两者的合并方式。
如果这种必填的选项有 N 个呢?
我们是不是可以封装一个 N 选 1 的抽象逻辑出来呢?
于是就有了这样一版初稿。
效果是 name,resouce,desc 三个字段必填一项,如果三个都不填,则必须选择一项。
首先是寻找必填的选项,我的判定方式是,按照传递的数组的顺序找到第一个存在的选项。
找到对应的表单字段下规则数组中带有 required 的选项的规则,将其 required 改为对应的状态,其余内容不变。
当然这样做在 watch 中监听每次都会触发这个方法,可以加个条件,仅当传入的字段数组中的值发生改变的时候,才进行处理。
还可以通过节防抖的方法,减少表单输入的时候的触发次数。
具体的 demo 代码就在下面啦。👇
当然我们也可以封装一个自定义指令的方式,把这个方法封装成 v-arrRequired 的自定义指令,实现这个功能,以便最小化的影响原来的代码逻辑。
<template>
<div class="home">
home 页面
<el-form
:model="ruleForm"
:rules="rules"
ref="ruleForm"
label-width="100px"
class="demo-ruleForm"
>
<el-form-item label="活动名称" prop="name">
<el-input v-model="ruleForm.name"></el-input>
</el-form-item>
<el-form-item label="活动区域" prop="region">
<el-select v-model="ruleForm.region" placeholder="请选择活动区域">
<el-option label="区域一" value="shanghai"></el-option>
<el-option label="区域二" value="beijing"></el-option>
</el-select>
</el-form-item>
<el-form-item label="活动时间" required>
<el-col :span="11">
<el-form-item prop="date1">
<el-date-picker
type="date"
placeholder="选择日期"
v-model="ruleForm.date1"
style="width: 100%;"
></el-date-picker>
</el-form-item>
</el-col>
<el-col class="line" :span="2">-</el-col>
<el-col :span="11">
<el-form-item prop="date2">
<el-time-picker
placeholder="选择时间"
v-model="ruleForm.date2"
style="width: 100%;"
></el-time-picker>
</el-form-item>
</el-col>
</el-form-item>
<el-form-item label="即时配送" prop="delivery">
<el-switch v-model="ruleForm.delivery"></el-switch>
</el-form-item>
<el-form-item label="活动性质" prop="type">
<el-checkbox-group v-model="ruleForm.type">
<el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox>
<el-checkbox label="地推活动" name="type"></el-checkbox>
<el-checkbox label="线下主题活动" name="type"></el-checkbox>
<el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="特殊资源" prop="resource">
<el-radio-group v-model="ruleForm.resource">
<el-radio label="线上品牌商赞助"></el-radio>
<el-radio label="线下场地免费"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="活动形式" prop="desc">
<el-input type="textarea" v-model="ruleForm.desc"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')"
>立即创建</el-button
>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
name: "Home",
data() {
return {
ruleForm: {
name: "",
region: "",
date1: "",
date2: "",
delivery: false,
type: [],
resource: "",
desc: "",
},
rules: {
name: [
{ required: true, message: "请输入活动名称", trigger: "blur" },
{ min: 3, max: 5, message: "长度在 3 到 5 个字符", trigger: "blur" },
],
region: [
{ required: true, message: "请选择活动区域", trigger: "change" },
],
date1: [
{
type: "date",
required: true,
message: "请选择日期",
trigger: "change",
},
],
date2: [
{
type: "date",
required: true,
message: "请选择时间",
trigger: "change",
},
],
type: [
{
type: "array",
required: true,
message: "请至少选择一个活动性质",
trigger: "change",
},
],
resource: [
{ required: true, message: "请选择活动资源", trigger: "change" },
],
desc: [{ required: false, message: "请填写活动形式", trigger: "blur" }],
},
};
},
methods: {
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
alert("submit!");
} else {
console.log("error submit!!");
return false;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
},
singleChoose(rules, chosenRules, isMultiple) {
// 是否支持多选 是否支持单选 抽象封装一个表单校验规则组件。
// 如何自动指定必填的选项
let currentRequireName = chosenRules[0];
let hasInitFlag = false;
// 表单控件 二选一必填
for (let key in rules) {
chosenRules.forEach((item) => {
if (item === key) {
if (!hasInitFlag) {
hasInitFlag = true;
currentRequireName = key;
}
rules[key].find((item) => item.required);
}
});
}
},
getCurrentRuleName({ rules = {}, chosonRuleNames = [], oldForm = {} }) {
let flag = false;
let currentRuleName = chosonRuleNames[0];
for (let chosenRuleIndex in chosonRuleNames) {
if (flag) {
break;
}
if (oldForm[chosonRuleNames[chosenRuleIndex]] && !flag) {
currentRuleName = chosonRuleNames[chosenRuleIndex];
flag = true;
}
}
console.log("currentRuleName", currentRuleName);
// 找到对应的表单字段下规则数组中带有required的选项的规则,将其required改为对应的状态,其余内容不变
chosonRuleNames.forEach((item, index) => {
if (item === currentRuleName) {
let findItem = rules[currentRuleName].find((oldRuleItem) =>
oldRuleItem.hasOwnProperty("required")
);
findItem.required = true;
chosonRuleNames.forEach((filterItem, filterIndex) => {
if (filterIndex !== index) {
let findFilterItem = rules[filterItem].find((filterRuleItem) =>
filterRuleItem.hasOwnProperty("required")
);
findFilterItem.required = false;
}
});
}
});
},
},
mounted() {
this.singleChoose(this.rules, ["resource", "desc"], false);
},
watch: {
ruleForm: {
handler(oldForm, curForm) {
console.log(oldForm, curForm, oldForm.name === curForm.name);
this.getCurrentRuleName({
rules: this.rules,
chosonRuleNames: ["name", "resource", "desc"],
oldForm,
});
},
deep: true,
immediate: true,
},
},
};
</script>