概览
对于大数据量批量导入,渲染到表格的场景中,可能会造成浏览器崩溃,此时前端分页可以很好地解决这个问题,但是组件库自带的表单验证通常只能进行当前页的验证,如何实现对全量数据的表单验证呢?此篇文章来梳理解决
一. 完整代码示例
<template>
<el-form :model="dataForm" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<table border>
<thead>
<tr>
<th>#</th>
<th><span class="requiredLabel">姓名</span></th>
<th><span class="requiredLabel">年龄</span></th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in currentPageData" :key="index">
<td>{{ index + (currentPage - 1) * pageSize + 1 }}</td>
<td>
<el-form-item
:prop="'details.' + (index + (currentPage - 1) * pageSize) + '.name'"
:rules="rules.name"
:key="'details.' + (index + (currentPage - 1) * pageSize) + '.name'"
>
<el-input
v-model="item.name"
placeholder="请输入姓名"
clearable
></el-input>
</el-form-item>
</td>
<td>
<el-form-item
:prop="'details.' + (index + (currentPage - 1) * pageSize) + '.age'"
:rules="rules.age"
:key="'details.' + (index + (currentPage - 1) * pageSize) + '.age'"
>
<el-input
v-model="item.age"
placeholder="请输入年龄"
clearable
type="number"
></el-input>
</el-form-item>
</td>
</tr>
</tbody>
</table>
<div>
<el-button @click="prevPage" :disabled="currentPage === 1">上一页</el-button>
<el-button @click="nextPage" :disabled="currentPage === Math.ceil(totalCount / pageSize)"
>下一页</el-button
>
</div>
<el-button @click="handleCreate">立即创建</el-button>
</el-form>
</template>
<script setup>
import { reactive, ref, computed,nextTick } from 'vue';
import AsyncValidator from 'async-validator';
const dataForm = reactive({
details: Array.from({ length: 20 }, (_, i) => ({ name: '', age: null })),
});
const rules = {
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
age: [{ required: true, message: '请输入年龄', trigger: 'blur'}],
};
const currentPage = ref(1);
const pageSize = ref(5);
const totalCount = ref(dataForm.details.length);
const ruleForm = ref(null);
const currentPageData = computed(() => {
const start = (currentPage.value - 1) * pageSize.value;
const end = start + pageSize.value;
return dataForm.details.slice(start, end);
});
const schemaValidator = (item) => {
const schema = new AsyncValidator(rules);
return schema
.validate(item, { firstFields: true })
.then((res) => {
console.log(res, 'then res');
return true;
})
.catch(({ errors }) => {
return false;
});
};
const handleCreate = async () => {
const fields = {
name: {
rules,
validate: schemaValidator,
},
};
for (let i = 1; i < dataForm.details.length; i++) {
const item = dataForm.details[i];
let allValid = true; // 假设当前 item 的所有字段都有效
for (const key in item) {
if (fields[key]) {
try {
if (!(await fields[key].validate(item))) {
currentPage.value = Math.floor((i+1)/pageSize.value) + 1;
allValid = false;
break; // 跳出内层循环
}
} catch (error) {
allValid = false;
break; // 跳出内层循环,并在发生错误时也视为验证失败
}
}
}
if (!allValid) {
break; // 跳出外层循环
}
}
await nextTick();
ruleForm.value.validate((valid) => {
console.log(valid,'valid123456')
if (valid) {
console.log('表单验证通过');
// 处理表单提交逻辑
} else {
console.log('表单验证失败');
return false;
}
});
};
const prevPage = () => {
if (currentPage.value > 1) currentPage.value--;
};
const nextPage = () => {
if (currentPage.value < Math.ceil(totalCount.value / pageSize.value)) currentPage.value++;
};
</script>
<style lang="scss" scoped>
/* 添加您的样式 */
</style>
可以直观运行感受下
二. 解决思路梳理
整体实现思路:
- 校验时,获取第一个不符合校验项的下标;
- 根据下标,找到对应不符合项的页码;
- 切换当前页码至不符合项的页码;
- 进行表单验证
此时的难点在于如何获取不符合项的页码
需要借助async-validator库的使用,这里着重讲下,相关代码如下
import AsyncValidator from 'async-validator';
const rules = {
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
age: [{ required: true, message: '请输入年龄', trigger: 'blur'}],
};
const schemaValidator = (item) => {
const schema = new AsyncValidator(rules);
return schema
.validate(item, { firstFields: true })
.then((res) => {
console.log(res, 'then res');
return true;
})
.catch(({ errors }) => {
return false;
});
};
const handleCreate = async () => {
const fields = {
name: {
rules,
validate: schemaValidator,
},
};
for (let i = 1; i < dataForm.details.length; i++) {
const item = dataForm.details[i];
let allValid = true; // 假设当前 item 的所有字段都有效
for (const key in item) {
if (fields[key]) {
try {
if (!(await fields[key].validate(item))) {
currentPage.value = Math.floor((i+1)/pageSize.value) + 1;
allValid = false;
break; // 跳出内层循环
}
} catch (error) {
allValid = false;
break; // 跳出内层循环,并在发生错误时也视为验证失败
}
}
}
if (!allValid) {
break; // 跳出外层循环
}
}
await nextTick();
ruleForm.value.validate((valid) => {
if (valid) {
console.log('表单验证通过');
// 处理表单提交逻辑
} else {
console.log('表单验证失败');
return false;
}
});
};
需要进行两层的循环遍历,第一层是全部的数据,第二层是校验规则,在全部的数据里面根据校验规则进行筛选,主要如下:
- 遍历全部数据,根据校验规则,获取到全部数据中子项中有校验的字段;
- 调用validate进行校验,获取全部数据中子项中第一个不符合校验的下标值;
async-validator的作用主要是什么?
可以根据校验规则,来进行是否符合校验判断,并返回布尔值。
三. 循环中退出
有两种写法:
第一种:增加变量,break退出
const getWrapperInnerData = async () => {
for (let i = 0; i < propsDetails.length; i++) {
const item = propsDetails[i];
let valid = true;
for (const key in item) {
if (fields[key]) {
if (!(await validateName(item.index).catch(() => false))) {
console.log(item.index);
valid = false;
break;
}
}
}
if (!valid) {
break;
}
}
};
第二种:return 退出
const getWrapperInnerData = async () => {
for (let i = 0; i < propsDetails.length; i++) {
const item = propsDetails[i];
for (const key in item) {
if (fields[key]) {
if (!(await validateName(item.index).catch(() => false))) {
console.log(item.index);
return false;
}
}
}
}
};
第一种写法
在第一种写法中,通过引入一个valid变量来跟踪当前项的校验状态,并在发现不符合项时通过break语句退出内层循环。然后,在外层循环中检查valid变量的值,如果为false,则通过另一个break语句退出外层循环。这种方式需要额外的变量和嵌套的控制流语句来管理循环的退出。
第二种写法
第二种写法则更为直接。在内层循环中,一旦发现不符合校验项,就立即通过return false语句退出整个函数。这种方式省去了额外的变量和复杂的控制流结构,使得代码更加简洁明了。同时,由于return语句会立即终止函数的执行,因此也确保了函数在找到第一个不符合项后不会继续处理剩余的项。
此外,第二种写法还隐含了一个优势:它清晰地表达了函数的意图——即校验过程中一旦发现不符合项就立即终止,并返回一个表示失败的值(在这里是false)。这种明确的返回值使得函数的调用者能够更容易地理解函数的执行结果。
因此,从简洁性和明确性的角度来看,第二种写法是更优的选择。它不仅能够实现所需的功能,而且代码更加清晰易懂。