在做项目的时候,刚开始并没有想到要做表单校验,项目又有超级多的表单要生成,所以最开始想到高度封装一个表单组件,通过参数来生成表单,并进行传值等操作
下面展示了部分代码(远程搜索感觉还挺有意思的,所以保留下来了)
<减少下代码长度>
<减少下代码长度>
<减少下代码长度>
<减少下代码长度>
import { ref, computed, nextTick } from 'vue'
const props = defineProps({
data: {
type: Array,
required: true,
},
Formvalue: {
type: Object,
required: true,
},
})
这样就完成了一个最简单的封装表单(不包含表单校验),可以通过传递下面的一个数组,生成一个表单
[
[
{
type: 'inputSelect',
label: '采购计划',
key: 'purchaseId',
width: 12,
remoteFunc: searchFunc('system/purchase-collection/list', 'id'),
loading: false,
options: [],
},
{
type: 'input',
label: '订单金额',
key: 'amount',
width: 12,
},
],
[
{
type: 'input',
label: '单重',
key: 'singleWeight',
width: 8,
},
{
type: 'input',
label: '采购重量',
key: 'purchaseWeight',
width: 8,
},
{
type: 'number',
label: '采购数量',
key: 'purchaseQuantity',
width: 8,
},
],
[
{
type: 'textarea',
label: '备注',
key: 'remarks',
width: 24,
},
],
[
{
type: 'fileList',
label: '发票文件',
key: 'invoice',
width: 24,
},
],
]
感觉这个比较有意思,所以想单独拿出来说,首先searchFunc这个函数有一点点的闭包和柯里化的思想(httpInstance是封装的一个axios),这个函数返回一个函数,利用这个函数作为一个远程搜索的函数,其次再调用返回的函数时,传的参数分别是上面传递的要用于渲染这个form-item的对象(也就是注释中写到的,也就涉及到了第二个点,深拷贝与浅拷贝,也就是我这里的传参,是一个引用传参,所以在这个函数中修改的内容,会同步修改在上文中传递的那个数组,这样就实现了一个远程的联想选择.
const searchFunc = (url, key) => {
let tempUrl = `${url}?${key}=`
//ele={
//type: 'inputSelect',
//label: '采购计划',
//key: 'purchaseId',
//width: 12,
//rules: [requiredRule],
//remoteFunc: searchFunc('system/purchase-collection/list', 'id'),
//loading: false,
//options: [],
//}
return (ele, content) => {
if (content) {
ele.loading = true
console.log(`${tempUrl}${content}`)
httpInstance({
url: `${tempUrl}${content}`,
method: 'get',
headers: headers,
}).then((res) => {
console.log(res)
ele.options = res.rows.map((item) => {
return { label: item[key], value: item[key] }
})
})
ele.loading = false
} else {
ele.options = []
}
}
}
实现跨组件的表单校验,其实原理比较简单,只是逻辑上有点复杂,如果表单只有一个的话,最好的方法是通过pinia来进行状态管理,这里用到的就是父子传参,在适当地使用回调函数callback
HTML不用改很多
//子组件 CreateForm
<不变的代码>
//父组件
JS部分
//父组件
const createFormRef = ref()
//校验,在父组件中传递回调函数,接受子组件中传递出来的valid
createFormRef.value.validateForm((valid) => {
if (valid) {
//处理逻辑
}
})
//清除校验的红色提示
createFormRef.value.clearValidate()
//子组件
import type { FormInstance } from 'element-plus'
const formRef = ref(null)
// 从 data 中提取所有规则
const formRules = computed(() => {
const rules: Record = {}
props.data.flat().forEach((ele) => {
if (ele.rules) {
rules[ele.key] = ele.rules
}
})
return rules
})
//暴露两个方法给父组件
// 表单校验
const validateForm = (callback: (valid: boolean) => void) => {
nextTick(() => {
if (formRef.value) {
formRef.value.validate((valid: boolean, fields: Record[]) => {
callback(valid)
})
} else {
callback(false, '表单实例获取失败')
}
})
}
//清除校验
const clearValidate = () => {
if (formRef.value) {
formRef.value.clearValidate()
}
}
defineExpose({
validateForm,
clearValidate,
})
传递的参数示例
const requiredRule = { required: true, message: '该字段为必填项', trigger: 'blur' }
const positiveNumberRule = {
validator: (rule, value, callback) => {
if (isNaN(Number(value)) || Number(value) <= 0) {
callback(new Error('请输入大于 0 的数字'))
} else {
callback()
}
},
trigger: 'blur',
}
[
[
{
type: 'inputSelect',
label: '采购计划',
key: 'purchaseId',
width: 12,
rules: [requiredRule],
remoteFunc: searchFunc('system/purchase-collection/list', 'id'),
loading: false,
options: [],
},
{
type: 'input',
label: '订单金额',
key: 'amount',
width: 12,
rules: [requiredRule,positiveNumberRule],
},
]
]
这样就完成了一个封装程度比较高的表单组件,能够比较简单的使用表单校验