Jeecg-boot 开发挖坑日记09 AntD 表单校验

之前开发的时间比较赶,所以在某些还没弄明白的地方我直接使用了 v-model 来绑定数据,没并没有使用 v-decorator,所以表单验证需要自己手动去写,异常提示也是需要单独的去做,

现在空下来了,重新整理了一下业务代码,总的来说大部分的表单元素都有demo可以对照着看,复杂的部分也只是上传组件或者一些联动的部分。

表单验证

使用 this.$form.createForm() 创建的收集器,可以通过 validateFieldsvalidateFieldsAndScroll 这两个api对收集器进行数据校验,当然需要提前在 form-item 中配置校验规则

配置校验规则
<a-form-item>
  <a-input
    v-decorator="['email',{ 
      rules: [
          { required: true, message: '请输入您的电子邮箱!' },
          { type: 'email', message: '请输入正确的电子邮箱号!' },
      ],
    }]"
    placeholder="您的电子邮箱
  >
    <a-icon slot="prefix" type="mail" />
  </a-input>
</a-form-item>
表单校验
methods:{
  // 保存
  handleSubmit() {
    // 触发表单验证
    this.form.validateFields(['email', 'password'],(err, values) => {
      // ['email', 'password'] 是需要校验的字段名
      ...
    })
  },
  // 保存
  handleSubmit() {
    // 触发表单验证
    this.form.validateFields(,(err, values) => {
      // 也可以直接省略 [fieldNames] 校验全部数据
      ...
    })
  },
}

这样每次在提交表单时就会触发数据校验,并且在 err 中会返回错误信息,values中返回校验过的数据,搭配 if(!err) 就可以完成业务操作了。

头像上传组件

使用 Upload 组件的 picture-card 模式来作为头像上传组件,在@change事件中对 form 进行 setFieldsValue 来赋值给 a-input:hidden 这样就可以进行校验和返回值了。

<template>
  <a-form :form="form" @submit.prevent="handleSubmit">
    <a-form-item>
      <a-upload
        listType="picture-card"
        class="avatar-uploader"
        :headers="headers"
        :showUploadList="false"
        :action="uploadURL"
        :beforeUpload="beforeUpload"
        @change="handleChange"
      >
        <img v-if="avatar" :src="avatar" alt="avatar" style="width:100%;" />
        <div v-else>
          <a-icon :type="loading ? 'loading' : 'plus'" />
          <div class="ant-upload-text">Upload</div>
        </div>
      </a-upload>
      <a-input type="hidden" v-decorator="['avatar', validatorRules.avatar]" />
    </a-form-item>
    <a-form-item>
      <a-button type="primary" html-type="submit" block>保存</a-button>
    </a-form-item>
  </a-form>
</template>
<script>
export default {
  name: 'UserInfoEdit',
  data() {
    return {
      avatar: '',
      loading: false,
      validatorRules: {
        avatar: {
          rules: [{ required: true, message: '请上传头像!' }]
        },
      },
      form: this.$form.createForm(this),
    }
  },
  methods:{
    // 头像上传前检查
    beforeUpload(file) {
      const isLt2M = file.size / 1024 / 1024 < 2
      if (!isLt2M) {
        this.$message.error('图片最大限制为2MB!')
      }
      return isLt2M
    },
    // 头像修改
    handleChange(info) {
      if (info.file.status === 'uploading') {
        this.loading = true
        return
      }
      if (info.file.status === 'done') {
        this.form.setFieldsValue({ avatar: info.file.response.url }) // 短链接提交给后端的数据
        this.avatar = info.file.response.thumbUrl // 赋值给展示链接展示链接
        this.loading = false
      }
    },
    // 保存
    handleSubmit() {
      // 触发表单验证
      this.form.validateFields((err, values) => {
        if (!err) {
          // 具体业务代码
        }
      })
    },
  }
}
</script>

当然也可以直接绑定在 Upload 组件上,我只是偷个懒不想再在验证之后去修改数据了。

动态校验规则

比如说,可以修改上传数量限制。这个时候直接修改规则并不会直接重新验证,需要重新修改数据之后才会再次校验。

  • 如果动态校验,需要校验的时候在 option 中加上 { force: true }
// 伪代码片段
methods:{
  // 保存
  handleSubmit() {
    // 触发表单验证
    this.form.validateFields({ force: true },(err, values) => {
      ...
    })
  },
}

一个FormItem中多个被装饰过的表单元素

多个子元素
有时候会遇到这样的的情况,在一个FormItem中有多个Input或者Select联动。

如果只是使用 v-decorator 来装饰会有警告提示:

Warning: [antdv: Form.Item] Cannot generate `validateStatus` and `help` automatically, while there are more than one `getFieldDecorator` in it.`

反正就是 一个 Form.Item 建议只放一个被 getFieldDecorator 或 v-decorator 装饰过的 child
如果存在多个就需要自己配置 help, required, validateStatus,这点可以在 API文档 中看到。

如何配置呢?附一个伪代码例子

<template>
  <a-form :form="form">
    <a-form-item
      :labelCol="labelCol"
      :wrapperCol="wrapperCol"
      label="地址"
      :help="help"
      :validateStatus="validateStatus"
    >
      <a-input-group compact>
        <a-select
          style="width:33%"
          placeholder=" - "
          v-decorator="['provinceId', validatorRules.provinceId]"
          @change="handleCityChange"
        >
          <a-spin v-if="regionList.province.fetch" slot="notFoundContent" size="small" />
          <a-select-option
            v-for="i in regionList.province.list"
            :key="i.id"
            :title="i.regionName"
          >{{ i.regionName }}</a-select-option>
        </a-select>
        <a-select
          style="width:33%"
          placeholder=" - "
          v-decorator="['cityId',validatorRules.cityId]"
          @change="handleCityChange"
        >
          <a-spin v-if="regionList.city.fetch" slot="notFoundContent" size="small" />
          <a-select-option
            v-for="i in regionList.city.list"
            :key="i.id"
            :title="i.regionName"
          >{{ i.regionName }}</a-select-option>
        </a-select>
        <a-select
          style="width:33%"
          placeholder=" - "
          v-decorator="['areaId', validatorRules.areaId]"
        >
          <a-spin v-if="regionList.area.fetch" slot="notFoundContent" size="small" />
          <a-select-option
            v-for="i in regionList.area.list"
            :key="i.id"
            :title="i.regionName"
          >{{ i.regionName }}</a-select-option>
        </a-select>
      </a-input-group>
    </a-form-item>
    <a-form-item :wrapperCol="offsetWrapperCol">
      <a-button type='primary' @click="handleSubmit">提交</a-button>
    </a-form-item>
  </a-form>
</template>

<script>
export default {
  name: 'address-modal',
  data() {
    return {
      // 自定义校验信息
      validateStatus: '',
      help: '',
      form: this.$form.createForm(this),
      confirmLoading:false,
      // 校验规则
      validatorRules: {
        provinceId: { rules: [{ required: true, message: '请选择省份' }] },
        cityId: { rules: [{ required: true, message: '请选择城市' }] },
        areaId: { rules: [{ required: true, message: '请选择地区' }] },
      },
      // 城市列表数据
      regionList: {
        province: {
          list: [],
          fetch: false
        },
        city: {
          list: [],
          fetch: false
        },
        area: {
          list: [],
          fetch: false
        }
      },
      // 表单栅格
      labelCol: {
        xs: { span: 24 },
        sm: { span: 5 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      },
      offsetWrapperCol: {
        xs: { span: 24 },
        sm: { span: 16, offset: 5 }
      },
    }
  },
  methods: {
    handleSubmit() {
      // 监听表单提交事件,触发表单验证
      this.form.validateFields((err, values) => {
        if (!err) {
          // 这里是验证通过之后的操作
        } else {
          // 校验失败,自定义校验信息
          // 这边三个值依次取值,返回第一个有错误信息的属性
          const onErr = err.provinceId || err.cityId || err.areaId 
          if (onErr && onErr.errors) {
            // 如果有错误信息,设置错误状态
            this.validateStatus = 'error'
            // 一个错误信息内部是校验内容是以数组的形式返回的,可以在data.validatorRules中的rules中看到顺序
            this.help = onErr.errors[0].message
          } else {
            // 如果没有报错的情况
            this.validateStatus = 'success'
            this.help = ''
          }
        }
      })
    },
    handleCityChange(value, option) {
      // 这里是监听改变的业务逻辑,比如获取下级地址列表
    },
  }
}
</script>

两种提交方式(存为草稿和保存发布)

保存数据分委两种,一种是存为草稿,另一种是保存并发布;

  • 存为草稿则只需要验证一部分必填字段,其它字段如果填写就校验,并且收集所有已经填写的字段。
  • 保存校验所有字段,并且需要匹配所有规则。

暂时还没有想好,现阶段分开两个函数,存为草稿是在 validateFields 中的规则内填写需要校验的字段,并且在if(!err) 中,使用 getFieldsValue 获取整个表单数据

附:

  • AntD Vue 1.5.0+ 增加了新组件FormModel 表单 可以通过 v-model 绑定收集数据,并且校验数据了