vue + element 动态渲染、移除表单并添加验证

博客地址:https://ainyi.com/66

又接到新需求了吧~~

背景

在一个大表单里,有可能会出现这种需求,用户可以自己操作动态添加、移除表单,更加个性化的效果。
常见于填写个人信息、附加内容的表单

例如:
“工作经历”可以用户自己点击继续添加按钮,在原有的表单后面 append 多一个表单,不需要就点击右上方 X 按钮移除

问题

在实现之前,提出几个问题

  1. vue 怎么动态渲染或移除表单上去
  2. v-model 怎么绑定动态添加表单的 value 值
  3. 动态新增的表单如何验证
  4. 动态表单怎么填写对应的 prop

...
好吧,我当时也思考了一会,最后选择数组方式,动态渲染

代码实现讲解

利用数组,v-for 循环方式,可以完美实现动态渲染和移除,因为操作的只有对象数组而已

请格外注意动态添加表单的 rule 和 prop

每个动态添加的表单都要加上 rule
prop 需要根据对象数组下标绑定设置对应的 value(:prop="'azList[' + index + '].azName'")

<div class="section-form" v-for="(item, index) in form.azList" :key="index"> 
  <span v-if="isShowCloseBtn" class="close" @click="deleteItem(index)">
    <i class="el-icon-close"></i>
  </span> 
  <el-form-item label="可用区名称:"
    :rules="[{ required: true, message: '可用区名称不能为空' }]"
    :prop="'azList[' + index + '].azName'"
    label-width="150px"> 
    <el-input placeholder="请输入可用区名称" v-model="item.azName" :maxlength="30"></el-input> 
  </el-form-item> 
  <el-form-item label="逻辑可用区编码:"
    :rules="[{ required: true, message: '逻辑可用区编码不能为空' }]"
    label-width="150px"
    :prop="'azList[' + index + '].logicCode'"> 
    <el-input placeholder="请输入唯一ID" v-model="item.logicCode" :maxlength="30"></el-input> 
  </el-form-item> 
  <el-form-item label="物理可用区编码:"
    :rules="[{ required: true, message: '物理可用区编码不能为空' }]"
    label-width="150px"
    :prop="'azList[' + index + '].physicCode'"> 
    <el-input placeholder="请输入唯一ID" v-model="item.physicCode" :maxlength="30"></el-input> 
  </el-form-item>
</div>

那么对应的 js 代码为

export default {
  name: 'vouchersDetail',
  data() {
    return {
      form: {
        regionName: '',
        regionCode: '',
	// 动态添加的对象数组
        azList: [
          {
            azName: '',
            logicCode: '',
            physicCode: ''
          }
        ]
      }
    }
  },
  computed: {
    // 至少保留一个动态表单的开关
    isShowCloseBtn() {
      return this.form['azList'].length > 1
    }
  },
  methods: {
    addItem() {
      // 点击添加表单的按钮,只需要将表单绑定的 value 作为对象 push 到对象数组
      this.form['azList'].push({
        azName: '',
        logicCode: '',
        physicCode: '',
        weight: ''
      })
    },
    deleteItem(index) {
      // 点击移除表单的按钮,根据点击的当前 index 移除对象数组的元素
      this.form['azList'].splice(index, 1)
    },
    goBack() {
      window.history.back(-1)
    }
  }
}


更新

19号更新,分离组件方法,写法更简便,易维护,还可以将校验规则剥离出去

根据上面的方法 利用数组,v-for 循环方式
此次更新,关键在于,是父组件引用子组件的 template 循环
v-for 循环数组的 item 对象传入子组件 template
每个子组件的 form 的 :model = 传入的 item,也就不需要用到数组下标 index,每个子组件是独立的一个 form,也就是说,每个动态添加字段的校验规则可以剥离出去了

父组件

template 循环

<create-region
  class="section-form"
  ref="refCreateAz"
  :infoData="item"
  :indexNum="index"
  :isShowCloseBtn="isShowCloseBtn"
  v-for="(item, index) in form.azList"
  :key="index"
  @deleteItem="deleteItem">
</create-region>

js 与原来无差,只是多了引入子组件的 component

components: {
  CreateRegion: () => import('@/views/region/models/CreateRegion')
}

子组件

<template>
  <el-form :model="infoData" :rules="rulesAz" label-width="150px" ref="formAz">
    <span v-if="isShowCloseBtn" class="close" @click="deleteItem">
      <i class="el-icon-close"></i>
    </span>
    <el-form-item label="可用区名称:" prop="azName" label-width="150px">
      <el-input placeholder="请输入可用区名称" v-model="infoData.azName" :maxlength="30"></el-input>
    </el-form-item>
    <el-form-item label="逻辑可用区编码:" label-width="150px" prop="logicCode">
      <el-input placeholder="请输入唯一ID" v-model="infoData.logicCode" :maxlength="30"></el-input>
    </el-form-item>
    <el-form-item label="物理可用区编码:" label-width="150px" prop="physicCode">
      <el-input placeholder="请输入唯一ID" v-model="infoData.physicCode" :maxlength="30"></el-input>
    </el-form-item>
    <el-form-item label="权重设置:" label-width="150px">
      <el-input placeholder="请设置权重" v-model="infoData.weight"></el-input>
    </el-form-item>
  </el-form>
</template>

<script>
import { ORGION_AZLIST_RULES } from '@/views/service/rules'
export default {
  props: {
    infoData: {
      require: true
    },
    indexNum: {
      type: Number
    },
    isShowCloseBtn: {
      type: Boolean
    }
  },
  data() {
    return {
      form: this.infoData,
      rulesAz: ORGION_AZLIST_RULES.call(this)
    }
  },
  computed: {},
  methods: {
    deleteItem() {
      this.$emit('deleteItem', this.indexNum)
    },
    validates() {
      return new Promise((resolve, reject) => {
        this.$refs['formAz'].validate(async valid => {
          if (valid) {
            // 验证通过
            resolve(true)
          } else {
            reject(false)
          }
        })
      })
    }
  }
}
</script>

校验

export const ORGION_AZLIST_RULES = function() {
  return {
    logicCode: [
      {
        required: true,
        message: '逻辑可用区编码不能为空',
        trigger: 'blur'
      },
      {
        validator: CHECK_AZEXITS_CODE.bind(this),
        trigger: 'blur'
      }
    ],
    physicCode: [
      {
        required: true,
        message: '物理可用区编码不能为空',
        trigger: 'blur'
      },
      {
        validator: CHECK_AZEXITS_CODE.bind(this),
        trigger: 'blur'
      }
    ],
    azName: [
      {
        required: true,
        message: '可用区名称不能为空',
        trigger: 'blur'
      }
    ]
  }
}

自定义校验

export const CHECK_AZEXITS_CODE = async function(rule, value, callback) {
  let paramName = rule.field
  let reqData = {}
  reqData[paramName] = value
  let { result } = await getAzExist(reqData)
  if (result.result) {
    if (paramName === 'logicCode') {
      callback(new Error('逻辑可用区编码已存在,请重新输入'))
    } else {
      callback(new Error('物理可用区编码已存在,请重新输入'))
    }
  } else {
    callback()
  }
}

写在后面

如果大家有啥更好的方法实现,欢迎在评论区相互探讨~

写完下班、

博客地址:https://ainyi.com/66

posted @ 2019-01-15 21:31  筱月  阅读(8981)  评论(0编辑  收藏  举报