文章目录
- Update 20210601
- Update 20210525
- 原文:
- 1.a-form的model
- 2.获取数据
- 源码
Update 20210601
今天做别的页面的时候发现又取不到数据了
测试发现应该是name需要和数据项里面的对象同名
比如下面这个例子,我的数据是relate.related_name
那name就需要设置成related_name,如果设置成别的name就取不到数据
// 这个行!
<a-form-item label="姓名" name="related_name">
<a-input v-model:value="relate.related_name"/>
</a-form-item>
// 这个不行!
<a-form-item label="姓名" name="name">
<a-input v-model:value="relate.related_name"/>
</a-form-item>
Update 20210525
事情太多,终于有时间继续写代码,用了以后发现我之前的formRef只能验证一个最后一个Form,原理其实和我那个错误一样,我用了同一个ref()所以每次v-for之后都会被最后一个Form的ref覆盖,导致每次只能验证最后一个Form,所以重新对代码进行更新。将formref也修改成一个数组,为了方便把数据Json化,没有选择把数据放在数据数组里面,而是弄了一个数据数组一起动态增加的REF数组,当然这种方法很容易出问题,后续代码优化过程中需要将增加数据的操作在抽象一下。完成代码请大家拉到最后的代码中去看,已经更新了。又要去干杂活了,代码没啥时间整理,直接整个贴,更新主要是怕误导大家,大家将就着看一下。
<a-form
v-for="(filial, index) in filialValidateForm.filials"
:key='index'
:model="filialValidateForm.filials[index]"
:ref="filialValidateForm.formrefs[index]"
:rules="rules"
:label-col="labelCol"
:wrapper-col="wrapperCol"
>
interface FormData {
filials: FilialInfo[],
formrefs: Ref[],
}
const filialValidateForm: UnwrapRef<FormData> = reactive({
filials: [],
formrefs: [],
})
原文:
大家好,我是橘子,今天继续来搞搞前端
这次是用了Ant Design of Vue这个库,下文简称AD。说实话这个库到底叫什么,为什么起这种带空格的名字我是没理解。
https://2x.antdv.com/components/form-cn#components-form-demo-dynamic-form-item
日常吐槽:我真的是不理解前端的生态,我觉得挺奇怪的,说AD算是使用范围比较广的框架了吧,但是百度上问题基本上找不到答案。。不知道是因为名字太长还是版本变动太快。还是百度垃圾。
好了,言归正传,说说这两天遇到的问题。
先描述下需求,我需要用户填一个子女信息的表单,表单里面有姓名、性别等好几个item需要用户填写。然后这个表单可以动态增加的。看了DEMO(DEMO链接:链接)里面的实现。
哦吼不难嘛,就是用v-for搭配控制表单的数组来动态完成就可以了。那就上手试试。
页面样式的部分问题不大,很快就能增加表单。问题出现在数据校验这块。
一开始我用了同一个form然后再里面用一个div进行v-for
我发现我定义的验证函数取到的value值一直是空的。
经过我的不泄努力最后发现新手们有几个地方需要注意:
1.a-form的model
这里绑定的数据不能是你的页面数组,所以,就不能和我一样把v-for放在子元素div里面循环,而要放在form里面循环,这样才能拿到具体的数据对象。
比如我根据DEMO把数据类型设定成这样
const filialValidateForm: UnwrapRef<{ filials: FilialInfo[] }> = reactive({
filials: [],
})
如果按照我之前的做法:v-for部分放在div里面,你会发现他没有办法正常工作,无论你填写什么他都显示你没填写。测试下来,你修改提示关键字什么的他都能识别,证明他是可以识别到rules的,说明问题不在这。测试自定义规则的时候会发现value一直是undefined。但是我们去取值,会发现数据是有好好绑定到数据对象上面的,我们通过代码是可以获取填写的值的。
<a-form
:rules="rules"
:label-col="labelCol"
:wrapper-col="wrapperCol"
>
<div
v-for="(filial, index) in filialValidateForm.filials"
:key='index'
:model="filial"
ref="formRef">
‘XXX’ is required
那猜测问题是出现在这个model上面,我猜测是由于这个遍历的数据对象(filial)无法在Form组件里面获取,而我们的数据验证是依托于这个form的ref这个引用对象来做的。所以你在子元素遍历这个数据数组的时候无法将它绑定到ref上面,导致整个表单的数据无法验证。
那解决办法是啥嘞?两个思路,第一个是在子元素的div里面获取ref,但是这个对我这个菜鸡来说太复杂了,索性就以第二个思路来做,把这个v-for塞到a-form里面。我本能的觉得这个行为很奇怪,但是又说不出来哪里怪,如果这么做有什么风险请各位大佬帮我指出。
改成这样之后我猜是因为在这层ref可以获取到我这里遍历的filial这个对象了,所以就可以绑定上了。
<a-form
v-for="(filial, index) in filialValidateForm.filials"
:key='index'
:model="filial"
ref="formRef"
:rules="rules"
:label-col="labelCol"
:wrapper-col="wrapperCol"
>
然后把按钮放到表单外,当然现在这个按钮都已经没有那个提交的事件了,所以放哪里也都一样。
2.获取数据
接下来是获取数据,获取数据之前肯定得先看一下我们输入验证的情况嘛,根据文档里面大概是这样的。
formRef.value
.validate()
.then(() => {
console.log('success');
//验证成功打开确认提交遮罩层
formState.modalShow = true
})
.catch((error: ValidateErrorEntity<FormState>) => {
console.log('value error', error);
});
这里又遇到一个问题,如果你在这里去输出看一下formRef.value的值你可能会三观尽毁,怎么会这样反复横跳?
一会你可以调用到validate函数,一会又调用不到,到底是怎么回事呢?
你可以和我一样打印一下formRef.value的值,找一下里面有没有一个FormContext的对象,这个对象我不知道什么时候会出现,我发现的情况是:
1.当你只有通过代码插入form的时候,无论插入几个,当通过代码填写的数据未被手工修改的情况下,可以直接使用formRef.value.validate()。
2.当你用代码添加form且form里面的数据被手动修改过的情况下,需要用formRef.value.FormContext.validate()。
通过文档你可以看出,可以调用到validate这方法的时候,我们这个应该就是formRef应该是指向一个Form对象
我有看了一下源码,源码里面的FormContext应该是Form给FormItem父子通讯的一个类。
也就是说,出现了FormContext应该是因为我的ref被污染了,我取到了子对象的Form.Item的ref,那原因的很明显了,是我在解决前面无法获取value问题的瞎碰的时候乱抄作业导致的,后面的FormItem污染了我的ref,所以在修改这几个Input后导致ref被替换了。把其他的formRef删除之后就不会出现反复横跳的问题了。
// 注意这里的formRef
<a-form-item ref="formRef" label="工作单位" name="filial_work_place">
<a-input v-model:value="filial.filial_work_place"/>
</a-form-item>
<a-form-item ref="formRef" label="职位" name="filial_title">
<a-input v-model:value="filial.filial_title"/>
</a-form-item>
最后附上我整个逻辑的vue文件上来,整体逻辑还没做完,但测试的功能点已经差不多了,大家就不要吐槽我的代码风格了,我都是个人开发自己看看嘛,懒。
我是llsxily,你可以叫我菜鸡。(:з」∠)
源码
<template>
<div v-if="formState.showPage">
<a-row :span="20" type="flex" justify="center">
<a-col :span="24">
<a-modal
:title="formState.modalTitle"
v-model:visible="formState.modalShow"
:confirm-loading="formState.modalConfirmLoading"
@ok="handleOk"
:maskClosable="false"
:cancel-button-props="{ disabled: formState.modalCancelBtnDisabled}"
:cancelText="formState.modalCancelText"
:okText="formState.modalOkText"
>
<p>{{ formState.modalText }}</p>
</a-modal>
<a-form
v-for="(filial, index) in filialValidateForm.filials"
:key='index'
:model="filialValidateForm.filials[index]"
:ref="filialValidateForm.formrefs[index]"
:rules="rules"
:label-col="labelCol"
:wrapper-col="wrapperCol"
>
<div>
<a-card :title="filial.filial_name">
<template #extra>
<a-popconfirm
:title="`是否删除 ${ filial.filial_name } 的信息`"
ok-text="确认"
cancel-text="取消"
@confirm="deleteItem(index)"
>
<a href="#">删除该项</a>
</a-popconfirm>
</template>
<a-form-item label="个人姓名" name="filial_name">
<a-input v-model:value="filial.filial_name"/>
</a-form-item>
<a-form-item label="性别" name="filial_sex">
<a-radio-group v-model:value="filial.filial_sex">
<a-radio value="男">男</a-radio>
<a-radio value="女">女</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="身份证号" name="filial_id">
<a-input v-model:value="filial.filial_id"/>
</a-form-item>
<a-form-item label="工作单位" name="filial_work_place">
<a-input v-model:value="filial.filial_work_place"/>
</a-form-item>
<a-form-item label="职位" name="filial_title">
<a-input v-model:value="filial.filial_title"/>
</a-form-item>
</a-card>
</div>
</a-form>
<a-form-item :wrapper-col="{ span: 14, offset: 4 }" style="margin-top: 10px">
<a-button type="primary" @click="onAddEmptyFilial">增加子女</a-button>
<a-button style="margin-left: 10px" type="primary" @click="onSubmit">提交信息</a-button>
</a-form-item>
</a-col>
</a-row>
</div>
</template>
<script lang="ts">
import {inject, reactive, ref, UnwrapRef, Ref} from "vue";
import axios from "axios";
import Store from "@/store/store";
import {RuleObject, ValidateErrorEntity} from "ant-design-vue/es/form/interface";
import {notification} from "ant-design-vue";
interface FilialInfo {
filial_name: string
filial_id: string
filial_work_place: string
filial_sex: string
filial_title: string
}
interface FormState {
showPage: boolean
nameType: boolean
modalTitle: string
modalShow: boolean
modalText: string
modalCancelBtnDisabled: boolean
modalConfirmLoading: boolean
modalCancelText: string
modalOkText: string
}
interface FormData {
filials: FilialInfo[],
formrefs: Ref[],
}
export default {
name: "FilialContent",
setup() {
// 表单状态控制类
const formState: UnwrapRef<FormState> = reactive({
showPage: false,
nameType: false,
// modal状态控制
modalTitle: '警告',
modalText: '是否确认提交子女信息数据',
modalShow: false,
modalCancelBtnDisabled: false,
modalConfirmLoading: false,
modalCancelText: "取消",
modalOkText: "确认",
})
// 整体数据对象
const filialValidateForm: UnwrapRef<FormData> = reactive({
filials: [],
formrefs: [],
})
// 校验规则
const rules = {
filial_name: [{required: true, message: '请填写姓名', trigger: 'blur'}],
filial_sex: [{required: true, message: '请填写性别', trigger: 'blur'}],
filial_id: [{required: true, message: '请填写身份证号码', trigger: 'blur'}],
filial_work_place: [{required: true, message: '请填写工作单位', trigger: 'blur'}],
filial_title: [{required: true, message: '请填写职位', trigger: 'blur'}],
};
// 业务流
// 从父组件获取token
const token = inject(Store.token)
// 数据初始化
let params = new URLSearchParams()
params.append('zzybjj_token', String(token))
params.append('type', '2')
axios.post('/get_other_info/', params)
.then((response) => {
console.log(response.data)
if (response.data.filial_num == 0) {
onAddEmptyFilial()
} else {
console.log(response.data.filial_data.length)
let dataArr = response.data.filial_data
for (let key in dataArr) {
addInfo(dataArr[key])
}
}
formState.showPage = true
})
// 按钮回调函数
// 增加空卡片
let is_addFilial = false
const onAddEmptyFilial = () => {
is_addFilial = true
let m_filial: UnwrapRef<FilialInfo> = {
filial_name: '',
filial_id: '',
filial_work_place: '',
filial_sex: '',
filial_title: '',
}
filialValidateForm.filials.push(m_filial)
filialValidateForm.formrefs.push(ref())
}
// 添加数据卡片
const addInfo = (data: any) => {
let m_filial: UnwrapRef<FilialInfo> = {
filial_name: data.name,
filial_id: data.id,
filial_work_place: data.work_place,
filial_sex: data.sex,
filial_title: data.title,
}
filialValidateForm.filials.push(m_filial)
filialValidateForm.formrefs.push(ref())
}
// 删除项目
const deleteItem = (key: string) => {
console.log(key)
// 从数组中删除对应数据
filialValidateForm.filials = filialValidateForm.filials.filter(obj => obj !== filialValidateForm.filials[Number(key)])
filialValidateForm.formrefs = filialValidateForm.formrefs.filter(obj => obj !== filialValidateForm.formrefs[Number(key)])
}
// 提示窗打开函数
const openNotificationWithIcon = (type: string, title: string, description: string) => {
notification[type]({
message: title,
description: description,
});
};
// 取数工具函数
const getFormData = () => {
let params = new URLSearchParams();
console.log(filialValidateForm)
console.log(JSON.stringify(filialValidateForm.filials))
params.append('filials_data', JSON.stringify(filialValidateForm.filials))
params.append('zzybjj_token', String(token))
return params
}
// Modal状态重置工具函数
const resetModal = () => {
formState.modalText = '是否确认提交子女信息数据'
formState.modalShow = false
formState.modalCancelBtnDisabled = false
formState.modalConfirmLoading = false
}
// 提交按钮回调函数
const onSubmit = () => {
if (filialValidateForm.filials.length == 0) {
// 无数据确认框
formState.modalText = '当前无数据,如果继续提交会删除之前的数据,确认提交吗?'
formState.modalShow = true
return
} else {
console.log(filialValidateForm.filials)
console.log(filialValidateForm.formrefs)
for(let key in filialValidateForm.filials){
console.log(key)
filialValidateForm.formrefs[key].value.validate()
.then(()=>{
onCheckdate(true)
}).catch((error: ValidateErrorEntity<FormState>) => {
console.log('value error', error);
onCheckdate(false)
})
}
}
}
let checkCount = 0
let checkRet = true
const onCheckdate = (ret: boolean) =>{
checkCount += 1
checkRet = checkRet && ret
if(checkCount == filialValidateForm.formrefs.length){
formState.modalShow = checkRet
checkRet = true
checkCount = 0
}
}
// 确认提交函数
const handleOk = () => {
formState.modalCancelBtnDisabled = true
formState.modalText = '返回结果前请勿关闭浏览器'
formState.modalConfirmLoading = true
let params = getFormData()
axios.post('/save_filial_info/', params)
.then((response) => {
resetModal()
if (response.data.code === 200) {
openNotificationWithIcon('success', '成功', '子女信息已保存')
}
console.log(response.data)
})
}
return {
labelCol: {span: 5},
wrapperCol: {span: 13},
other: '',
rules,
formState,
filialValidateForm,
deleteItem,
onAddEmptyFilial,
onSubmit,
handleOk,
};
}
}
</script>
<style scoped>
</style>