💻 背景介绍
在今天评审中,产品提出了一个输入“阶梯规则”的功能;用户可以动态新增或删除多个阶梯,每个阶梯包含两个字段:
- 条件值(
conditionValue):必填,且要求下一行的值小于上一行; - 比例值(
ration):必填,且不能大于100%;
当点击提交按钮时,我们需要对每一个输入项做一下校验:
- 所有输入项必须填写,不能为空;
- 阶梯条件值不能重复;
- 条件值必须按从大到小排序;
设计效果大致如下图所示:
🎯 实现思路
我们使用Element Plus的<el-form>表单组件来实现校验逻辑,同时需要注意一下几点:
- 多行表单结构:通过
v-for渲染stepData数组,动态生成每一行阶梯s项; - 嵌套校验规则:每一行都嵌套一个
el-form-item,设置自定义校验逻辑; - 清空标签
label:由于多行为结构化展示,我们将主表单项的label置空; - 表单规则配置:包含是否必填、是否重复、是否递减校验等;
🧩 实现代码
<template>
<el-form :model="stepData" ref="formRef" class="config-form">
<el-form-item
v-for="(item, index) in stepData"
:key="index"
class="step-item"
label=""
>
<div class="rule-row">
<div class="input-group">
<span class="label-text">当条件值大于</span>
<el-form-item
:prop="`[${index}].conditionValue`"
:rules="[
{
required: true,
message: '请输入',
trigger: ['blur', 'change'],
},
{
validator: validateDuplicate,
trigger: ['blur', 'change']
},
{
validator: validateDescending,
trigger: ['blur', 'change']
}
]"
class="inline-form-item"
>
<el-input
v-model="stepData[index].conditionValue"
placeholder="请输入"
class="custom-input"
@input="
stepData[index].conditionValue = stepData[index].conditionValue
.replace(/[^-0-9]/g, '')
.replace(/-+/g, '-')
.replace(/^(-?)0+(\d+)$/, '$1$2')
"
/>
</el-form-item>
<span class="label-text">时,对应比例为</span>
<el-form-item
:prop="`[${index}].ratio`"
:rules="[
{
required: true,
message: '请输入',
trigger: ['blur', 'change'],
}
]"
class="inline-form-item"
>
<el-input
v-model="stepData[index].ratio"
placeholder="请输入"
class="custom-input"
@input="
stepData[index].ratio = stepData[index].ratio
.replace(/[^0-9]/g, '')
.replace(/^0+(\d+)/, '$1')
.replace(/^(\d{1,3}).*$/, (match, num) =>
Number(num) > 100 ? '' : num
)
"
/>
</el-form-item>
<span class="label-text">%</span>
</div>
<el-button
v-if="index !== 0"
type="danger"
link
@click="handleDelete(index)"
class="delete-btn"
>
删除
</el-button>
</div>
</el-form-item>
<div class="action-bar">
<el-button
type="primary"
@click="handleAdd"
:disabled="stepData.length >= 5"
class="action-btn"
>
新增阶梯
</el-button>
<el-button
type="primary"
@click="handleSubmit"
class="action-btn submit-btn"
>
提交
</el-button>
</div>
</el-form>
</template>
<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
const formRef = ref(null)
const stepData = ref([
{ conditionValue: "", ratio: "" }
])
// 验证是否有重复的条件值
const validateDuplicate = (rule, value, callback) => {
if (!value) {
callback()
return
}
const currentIndex = Number(rule.field.match(/\[(\d+)\]/)[1])
const duplicateIndex = stepData.value.findIndex((item, index) =>
index !== currentIndex && item.conditionValue === value
)
if (duplicateIndex !== -1) {
callback(new Error('条件值不能重复'))
} else {
callback()
}
}
// 验证是否按从大到小排列
const validateDescending = (rule, value, callback) => {
if (!value) {
callback()
return
}
const currentIndex = Number(rule.field.match(/\[(\d+)\]/)[1])
const currentValue = Number(value)
// 检查与前一个值的关系(如果不是第一个)
if (currentIndex > 0) {
const prevValue = Number(stepData.value[currentIndex - 1].conditionValue)
if (!isNaN(prevValue) && currentValue >= prevValue) {
callback(new Error('条件值必须小于上一行的值'))
return
}
}
// 检查与后一个值的关系(如果不是最后一个)
if (currentIndex < stepData.value.length - 1) {
const nextValue = Number(stepData.value[currentIndex + 1].conditionValue)
if (!isNaN(nextValue) && currentValue <= nextValue) {
callback(new Error('条件值必须大于下一行的值'))
return
}
}
callback()
}
// 新增行
const handleAdd = () => {
if (stepData.value.length >= 5) {
ElMessage.warning('最多只能添加5个阶梯')
return
}
stepData.value.push({ conditionValue: "", ratio: "" })
}
// 删除行
const handleDelete = (index) => {
if (index === 0) {
ElMessage.warning('第一行不能删除')
return
}
stepData.value.splice(index, 1)
}
// 提交表单
const handleSubmit = async () => {
try {
await formRef.value.validate()
// 验证通过,可以在这里处理提交逻辑
ElMessage.success('验证通过,可以提交数据')
console.log('提交的数据:', stepData.value)
} catch (error) {
ElMessage.error('请检查表单是否填写正确')
}
}
</script>