Bootstrap

在项目中遇到的关于form表单的问题

前言

以下内容都是基于element Plus 和 vue3

一个form-item校验两个下拉框

有时候不可避免会遇到需要一个form-item校验两个下拉框的情况,比如:
在这里插入图片描述
这种情况下传统的校验已经无法实现,需要通过form表单提供的自定义校验来实现。以上面的必填为例

<el-form-item label="工作时报" prop="workStartTime">
        <el-select
            v-model="states.formData.workStartTime"
            filterable
            style="width: 120px"
            placeholder="请选择"
            clearable
        >
            <el-option
                v-for="item in states.workStartTimeList"
                :key="item.completeTime"
                :label="item.label"
                :value="item.completeTime"
            />
        </el-select>
        <span style="padding: 0px 5px"> ~ </span>
        <el-select
            v-model="states.formData.workEndTime"
            filterable
            style="width: 120px"
            placeholder="请选择"
            clearable
            ref="workEndTimeRef"
        >
            <el-option
                v-for="item in states.workEndTimeList"
                :key="item.completeTime"
                :label="item.label"
                :value="item.completeTime"
            />
        </el-select>
</el-form-item>
const validatePass = (rule: any, value: any, callback: any) => {
    let workEndTime = workEndTimeRef.value.selected.value
    if (value && workEndTime) {
        // 校验成功,这里也可以一些其他逻辑
        callback()
    } else {
        callback(new Error('工作时报不能为空'))
    }
}
const rules = reactive({
    workStartTime: [
        {
            required: true,
            validator: validatePass,
            trigger: 'change',
        },
    ],
})

注意点:
1、定义的校验方法要放在校验规则上面,不然会提示找不到方法
2、这点最重要,首先form-item上绑定的属性是workStartTime,所以你校验方法中只能拿到该属性对应的值。另一个下拉的值要先获取到,你必须在下拉上绑定一个ref
在这里插入图片描述
你可以通过打印这个ref来找到另一个下拉的值。当第一个下拉改变后就会触发校验,这时只需要判断两个下拉是否都已经有值,如果没有值,返回提示信息就好。

补充
最好是在自定义校验外面添加一个延时函数。因为在业务修改回显时,当有一个值赋值后立马就会触发校验,但这时还没有执行到另一条数据的赋值。就会出现,两条数据赋值完后,提示校验失败
在这里插入图片描述
这种方案失败了,因为输入开始时间就会触发校验;又因为结束时间没填,会出现警告提示。然后测试人员和产品经理说丑,让人不理解。哎,心累

最新沟通的解决方案是拆成两个校验
在这里插入图片描述
结束时间上也是存在必填星号和label的,通过修改样式让其看不见(最好外面套一个类,避免影响其他的样式)

.work-endtime {
    :deep(.el-form-item__label) {
        opacity: 0;
    }
}

下拉框加一个二次确认,确认值是否改变

以下图为例,当计划类型值的改变时需要清空其他值时,这时最好给与一个二次确认。确认是否改变,而不是直接改变,导致其他数据被清空,这样会很奇怪。
在这里插入图片描述
在这里插入图片描述
这里的问题是,当取消后应该显示原来显示的值。最初想的方向有点错了,想通过监听来实现,当取消时直接将旧值赋值给绑定的属性,但实际结果是监听一直被触发,确认弹窗无法被关闭。

其实实现起来挺简单,最怕的就是方向想错了。可以定义一个变量来保存旧值,然后将二次确认弹窗的部分加到下拉框change事件里

// 计划类型改变事件
const planTypeChange = val => {
    if (states.lastPlanType) {
        E_Msg.confirm('提示', '计划类型改变将清空所有资源对应的编码,是否确认修改?')
            .then(() => {
                emits('planTypeChange', val, true)
            })
            .catch(() => {
                states.formData.planType = states.lastPlanType
            })
    } else {
        states.lastPlanType = val
    }
}

当第一次下拉选择时,不应该弹出二次确认,这时将当前选择的值记录,当下次下拉改变后,就会进行提示。
这里不用判断记录的值是否和当前选择的值相同,只有当下拉值改变后,才会触发change事件。change事件触发了,一定说明值改变了。

获取form校验的返回结果

有时候因为业务逻辑比较复杂,就会进行组件拆分。这时遇到的问题时,如何在父组件里获取到子组件校验后的返回值。查了一下确实可以实现

// 子组件

//获取表单数据
const getFormData = () => {
    return baseFormRef.value.validate().then(res => {
        console.log('校验结果:', res)
        if (res) {
           // 校验成功,将表单数据返回
            return states.formData
        }
    })
     .catch(err => {
                console.log('错误:', err)
                return false
      })
}

 // 父组件,子组件的返回值是一个promise函数,这里要进行处理
 // 这里比较特殊,父组件也需要进行校验,正常情况添加上 async和await就好
formRef.value.validate(async valid => {
        if (valid) {
            // 基本信息
            const baseData = await baseRef.value.getFormData()
            // 资源信息
            const resourceData = resourceRef.value.getFormData()
            console.log('保存的信息:', baseData, resourceData)
            if (baseData && resourceData) {
                
            }
        }
    })

注:
最近发现了一个问题,你的表单校验不通过时的逻辑要写在catch里

// 正确
return baseFormRef.value.validate().then(res => {
        console.log('校验结果:', res)
        if (res) {
           // 校验成功,将表单数据返回
            return states.formData
        }
    })
     .catch(err => {
           console.log('错误:', err)
           return false
      })

// 错误,当有未填写的表单时,会执行catch,用于不会走else
return baseFormRef.value.validate().then(res => {
        console.log('校验结果:', res)
        if (res) {
           // 校验成功,将表单数据返回
            return states.formData
        }else{ 

          }
    })

在表格中嵌套表单进行校验

之前写了一篇如何实现的文章:表格嵌套表单的校验
在这里插入图片描述

效果图如上,是在表单项下面显示的校验信息。但是可能是产品经理绝对不好看,统一改成下面这种格式。表格里只显示红框,统一在表格上面显示提示信息
在这里插入图片描述
其实表单的校验信息还在,只是修改了一下margin-bottom给挡住了

:deep(.table .el-form-item) {
    margin-bottom: 0;
}

一开始把只是校验必填,在保存的时候循环一下就好了。后来又有了手机号、邮箱等校验,就想着循环判断值不好。查了下官方文档,刚好form组件上有一个validate 事件

const formvalidate = (prop, isValid, message) => {
    console.log('校验了', prop, isValid, message)
}

当你调用表单检验的方法时,该事件会执行。prop 是表单上绑定的属性,isValid 是该字段是否校验通过,message 是校验不通过时显示的信息。

基于该事件可以实现想要的功能

const formvalidate = (prop, isValid, message) => {
    console.log('校验了', prop, isValid, message)
    if (isValid) {
        if (states.checkKeys.has(prop)) {
            states.checkKeys.delete(prop)
        }
    } else {
        states.checkKeys.add(prop)
    }
    if (states.checkKeys.size == 0) {
        states.warningTextMsg = ''
        states.warningTextShow = false
    } else {
        if (message == '手机号格式不正确' || message == '邮箱格式不正确') {
            states.warningTextMsg = '以下信息格式不正确'
        } else {
            states.warningTextMsg = '以下必填信息不能为空'
        }
        states.warningTextShow = true
    }
}

states.checkKeys 是一个Set对象,在一开始的时候就初试话为一个空的SetSet 比数组更加有优势,方便执行一些操作。
上面大体意思是如果字段没有校验通过则存储prop属性,如果校验通过则删除该属性。具体逻辑就不说了,没啥东西,最后实现的效果如下图:
在这里插入图片描述

动态添加校验项

在这里插入图片描述
如上图,当是否理化为是的时候,理化方法必填,否则理化方法不必填。这就需要动态修改必填项,可以如下

<el-table-column prop="name" label="理化方法" width="250">
 <template v-slot="{ row, $index }">
     <el-form-item
         :prop="`tableData[${$index}].phyCheMethodList`"
         :rules="
             {
                 required: row.isPhysicalChemicalInspection,
                 message: '理化必填',
                 trigger: 'blur',
             }
         "
     >
         <el-select
             v-model="row.phyCheMethodList"
             clearable
             placeholder="请选择"
             filterable
             multiple
             collapse-tags
             collapse-tags-tooltip
         >
             <el-option
                 v-for="item in states.dictRationalMethodList"
                 :key="item.id"
                 :value="item.value"
                 :label="item.name"
             ></el-option>
         </el-select>
 </el-form-item>

效果图
在这里插入图片描述

;