Ant Design of Vue +TS 表单动态增加数据验证卧坑姿势

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>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据提供的引用内容,ant-design-vue可以通过内置的Form组件和Table组件实现表格内部字段验证功能。具体步骤如下: 1. 在表格中添加需要验证的字段,例如下面的代码中的name和age字段: ```html <a-form :form="form"> <a-form-item label="Name" :rules="[{ required: true, message: 'Please input name' }]"> <a-input v-decorator="['name']" /> </a-form-item> <a-form-item label="Age" :rules="[{ required: true, message: 'Please input age' }]"> <a-input-number v-decorator="['age']" /> </a-form-item> </a-form> <a-table :columns="columns" :dataSource="dataSource" :pagination="false" /> ``` 2. 在表格中添加操作列,例如下面的代码中的操作列包含了编辑和删除按钮: ```html <a-table :columns="columns" :dataSource="dataSource" :pagination="false"> <template #action="text, record"> <a-button @click="edit(record)">Edit</a-button> <a-button @click="delete(record)">Delete</a-button> </template> </a-table> ``` 3. 在编辑操作中打开表单,并将当前行的数据绑定到表单中: ```javascript edit(record) { this.form.setFieldsValue(record); this.editingKey = record.key; } ``` 4. 在表单中添加保存按钮,并在点击保存按钮时进行表单验证数据更新: ```html <a-form :form="form"> <a-form-item label="Name" :rules="[{ required: true, message: 'Please input name' }]"> <a-input v-decorator="['name']" /> </a-form-item> <a-form-item label="Age" :rules="[{ required: true, message: 'Please input age' }]"> <a-input-number v-decorator="['age']" /> </a-form-item> <a-form-item> <a-button type="primary" @click="save">Save</a-button> </a-form-item> </a-form> ``` ```javascript save() { this.form.validateFields((err, values) => { if (!err) { const newData = [...this.dataSource]; const index = newData.findIndex((item) => this.editingKey === item.key); if (index > -1) { const item = newData[index]; newData.splice(index, 1, { ...item, ...values }); this.dataSource = newData; this.editingKey = ''; } else { newData.push(values); this.dataSource = newData; this.editingKey = ''; } } }); } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值