业务场景:有个页面需要用到表格的样式效果来进行采集用户输入内容进行表单提交,并且提交的时候需要对表格中的表单项做必填校验=> 注意这里有个关键点(校验的时候需要只根据勾选的表格行数据进行校验)。这里的难点在于怎么对表格中勾选到的行做必填校验。后来看到有网友跟我碰到了一样的问题就在这里跟大家分享下希望对有需要的人有帮助。
代码是使用 Vue + element-ui 实现的

这里我写了个demo大家先看下效果
在这里插入图片描述
这里点击保存的时候不仅校验表格中的输入框,还有上方的两个表单项。首先大家要明白这里的html结构是一个大表单 el-form里边嵌套的el-table,而el-table中又嵌套了表单项 el-form-item支持编辑。

看结构:

<el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
      <el-row>
        <el-col :span="6">
          <el-form-item label="姓名" prop="name">
            <el-input v-model="formData.name"></el-input>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row>
        <el-col :span="6">
          <el-form-item label="年龄" prop="age">
            <el-input v-model="formData.age"></el-input>
          </el-form-item>
        </el-col>
      </el-row>
      <el-table
        border
        ref="tableBox"
        :data="formData.tableData"
        @selection-change="handleSelectionChange"
        :row-style="{ height:'70px' }"
        :cell-style="{ padding:'0px' }">
        <el-table-column type="selection" min-width="60" align="center">
        </el-table-column>
        <el-table-column prop="province" label="省份" min-width="60" align="center">
        </el-table-column>
        <el-table-column prop="city" label="城市" :render-header="addRedStar" min-width="70" align="center">
          <template slot-scope="scope">
            <el-form-item :prop="'tableData.' + scope.$index + '.city'" :rules="rules.city">
              <el-input
                v-model="scope.row.city"
                maxlength="200"
                oninput="if(value.length > 200) value=value.slice(0, 200)"
                placeholder="请输入">
              </el-input>
            </el-form-item>
          </template>
        </el-table-column>
        <el-table-column prop="scenicArea" label="景区" :render-header="addRedStar" min-width="70" align="center">
          <template slot-scope="scope">
            <el-form-item :prop="'tableData.' + scope.$index + '.scenicArea'" :rules="rules.scenicArea">
              <el-input
                v-model="scope.row.scenicArea"
                maxlength="200"
                oninput="if(value.length > 200) value=value.slice(0, 200)"
                placeholder="请输入">
              </el-input>
            </el-form-item>
          </template>
        </el-table-column>
      </el-table>
    </el-form>
    <el-row style="margin-top: 50px">
      <el-col :span="24">
        <el-button type="primary" @click="submit">保存</el-button>
      </el-col>
    </el-row>

这里要注意数据结构,因为表格是嵌套在el-form表单中的,所以 表格el-table的数据应该是 :data=“formData.tableData” 表单对象下的表格数组,别忘了还要给表单定义rules校验规则,表格中表单校验需要绑定prop字段而这个字段是根据表格索引动态绑定的:

<el-form-item :prop="'tableData.' + scope.$index + '.city'" :rules="rules.city">

上边代码指的是通过表格索引将要校验的字段在表格数据中做对应。
我这里只写了输入框类型的表单做演示,大家可以根据自己需要灵活的使用其他表单项和添加自定义校验规则。

定义数据部分:

formData: {
        name: '',
        age: '',
        tableData: [
          { index: 0, province: '河南', city: '', scenicArea: '' },
          { index: 1, province: '北京', city: '', scenicArea: '' },
          { index: 2, province: '广州', city: '', scenicArea: '' }
        ]
      },
      rules: {
      name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
        age: [{ required: true, message: '请输入年龄', trigger: 'blur' }],
        city: [{ required: true, message: '请输入城市', trigger: 'blur' }],
        scenicArea: [{ required: true, message: '请输入景区', trigger: 'blur' }]
      },
      multipleSelection: [], //复选框勾选的表格数据
      verify: [] // 表格中勾选的要校验的字段

到这里表格中编辑的样式结构及定义的数据结构已经准备完毕,下边需要考虑怎么进行表单提交校验了。
通常我们使用表单校验都是用的el-form中的 validate()方法进行表单校验,因为这里我们给整个表格中的编辑字段都绑定了必填校验字段但是有复选框的存在我们需要对勾选行的部分字段做必填校验,这样的话validate()校验全部就不能用了。
后来翻饿了么文档发现了个我们很少用的方法 validateField()
在这里插入图片描述
通过这个方法我们可以实现对部分表单字段进行校验,需要往这个方法中传入我们需要校验的字段即可。但这里又会遇到一个问题我们如果只是校验表单对象形式的数据的话,校验字段rules还是一个一个的我们能够拿到,但el-table数组中每个格子绑定的校验字段怎么获取呢?上边给表格中的表单项绑定校验字段是根据表格索引动态指定的,还有勾选限制?大家此时脑海里要有这个表单表格的校验数据结构。
后来发现可以通过表单引用对象中的fields属性获取到所有要校验的字段。
然后根据勾选的表格数据做遍历筛选出表格勾选数据中需要校验的字段,没勾选到不需要校验的字段则去除校验。
我们在复选框勾选事件中进行遍历筛选出保存时需要校验的字段

声明的verify 就是表格中勾选的需要校验的字段,

// 复选框勾选事件
    handleSelectionChange (val) {
      this.multipleSelection = val
      // 获取表单所有要校验的字段
      const prop = this.$refs.formRef.fields.map(item => item.prop)
      if (val.length <= 0) return this.$refs.formRef.clearValidate(prop)
      this.verify = []
      // 将选中的数据和全部表格中校验的字段遍历 取出勾选对应的校验字段、并将没勾选的校验去除
      this.$refs.formRef.fields.forEach(item => {
        val.forEach(element => {
          if (item.prop.split('.')[1] === element.index.toString()) {
            this.verify.push(item.prop)
          } else {
            this.$refs.formRef.clearValidate(item.prop)
          }
        })
      })
      console.log(this.verify)
      // scope.$index当前行数据在数组中的索引,勾选哪一行校验哪一行的必填项
    },

然后 我们在保存的事件中 将表格中勾选的需要校验的字段verify 和 表格外
多余的其他表单项的必填校验字段 进行合并则是最终我们保存时需要校验的所有表单字段。
我这里还多做了个校验,如果没有勾选表格数据的话,需要勾选表格才能提交,这个是用Promise 加的,这个根据大家情况,可要可不要。
使用validateField() 表单校验的时候还需要注意啊这个方法有些特殊跟validate不一样,不是回调一下就能拿到校验结果的,而是你传入了多少个需要校验的字段他就会产生多少次回调,所以对于必填校验来说我们需要等她所有回调结束然后遍历所有的回调结果保证每次都校验通过才算是真正校验结束。

submit () {
      const f1 = new Promise((resolve, reject) => {
        if (this.multipleSelection.length > 0) {
          resolve('save!')
        } else {
          reject(this.$message.error('请勾选数据'))
        }
      })
      const ary = ['name', 'age']
      // 合并表格中勾选的和上方表单中要校验的字段
      const params = [].concat(this.verify, ary)
      Promise.all([f1]).then(() => {
        const validateFieldList = []
        this.$refs.formRef.validateField(params, isOk => {
          // 部分表单校验时,有几个校验字段就回调几次,isOk空的时候为通过,
          if (!isOk) {
            // 收集每次回调校验的结果等全部回调结束后再判断最终是否全部校验通过
            validateFieldList.push(isOk)
            const flag = validateFieldList.every((item) => item === '')
            // 判断校验的字段数量是否全部校验完且每个结果都是通过则证明最终校验通过
            if (validateFieldList.length === params.length && flag) {
              this.$message.success('保存成功')
            } else {
              return
            }
          }
        })
      })
    },

从开始写到实现效果遇到的问题还是比较多的,大家多注意我说的几个关键点捋清思路,然后我把完整demo代码放到下面可以直接粘贴看效果,这里只写了保存校验这个场景的也是最重要部分,另外编辑返显重置的情况代码有点多就不发了,大家明白了这个思路很多交互场景就都能实现了。

<template>
  <div class=''>
    <el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
      <el-row>
        <el-col :span="6">
          <el-form-item label="姓名" prop="name">
            <el-input v-model="formData.name"></el-input>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row>
        <el-col :span="6">
          <el-form-item label="年龄" prop="age">
            <el-input v-model="formData.age"></el-input>
          </el-form-item>
        </el-col>
      </el-row>
      <el-table
        border
        ref="tableBox"
        :data="formData.tableData"
        @selection-change="handleSelectionChange"
        :row-style="{ height:'70px' }"
        :cell-style="{ padding:'0px' }">
        <el-table-column type="selection" min-width="60" align="center">
        </el-table-column>
        <el-table-column prop="province" label="省份" min-width="60" align="center">
        </el-table-column>
        <el-table-column prop="city" label="城市" :render-header="addRedStar" min-width="70" align="center">
          <template slot-scope="scope">
            <el-form-item :prop="'tableData.' + scope.$index + '.city'" :rules="rules.city">
              <el-input
                v-model="scope.row.city"
                maxlength="200"
                oninput="if(value.length > 200) value=value.slice(0, 200)"
                placeholder="请输入">
              </el-input>
            </el-form-item>
          </template>
        </el-table-column>
        <el-table-column prop="scenicArea" label="景区" :render-header="addRedStar" min-width="70" align="center">
          <template slot-scope="scope">
            <el-form-item :prop="'tableData.' + scope.$index + '.scenicArea'" :rules="rules.scenicArea">
              <el-input
                v-model="scope.row.scenicArea"
                maxlength="200"
                oninput="if(value.length > 200) value=value.slice(0, 200)"
                placeholder="请输入">
              </el-input>
            </el-form-item>
          </template>
        </el-table-column>
      </el-table>
    </el-form>
    <el-row style="margin-top: 50px">
      <el-col :span="24">
        <el-button type="primary" @click="submit">保存</el-button>
      </el-col>
    </el-row>
  </div>
</template>
<script>
export default {
  name: '',
  props: {},
  components: {},
  data () {
    return {
      formData: {
        name: '',
        age: '',
        tableData: [
          { index: 0, province: '河南', city: '', scenicArea: '' },
          { index: 1, province: '北京', city: '', scenicArea: '' },
          { index: 2, province: '广州', city: '', scenicArea: '' }
        ]
      },
      rules: {
        name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
        age: [{ required: true, message: '请输入年龄', trigger: 'blur' }],
        province: [{ required: true, message: '请输入省份', trigger: 'blur' }],
        city: [{ required: true, message: '请输入城市', trigger: 'blur' }],
        scenicArea: [{ required: true, message: '请输入景区', trigger: 'blur' }]
      },
      multipleSelection: [],
      verify: []
    }
  },
  computed: {},
  watch: {},
  created () {},
  mounted () {},
  methods: {
    // 保存
    submit () {
      const f1 = new Promise((resolve, reject) => {
        if (this.multipleSelection.length > 0) {
          resolve('save!')
        } else {
          reject(this.$message.error('请勾选数据'))
        }
      })
      const ary = ['name', 'age']
      // 合并表格中勾选的和上方表单中要校验的字段
      const params = [].concat(this.verify, ary)
      Promise.all([f1]).then(() => {
        const validateFieldList = []
        this.$refs.formRef.validateField(params, isOk => {
          // 部分表单校验时,有几个校验字段就回调几次,isOk空的时候为通过,
          if (!isOk) {
            // 收集每次回调校验的结果等全部回调结束后再判断最终是否全部校验通过
            validateFieldList.push(isOk)
            const flag = validateFieldList.every((item) => item === '')
            // 判断校验的字段数量是否全部校验完且每个结果都是通过则证明最终校验通过
            if (validateFieldList.length === params.length && flag) {
              this.$message.success('保存成功')
            } else {
              return
            }
          }
        })
      })
    },
    // 复选框勾选事件
    handleSelectionChange (val) {
      this.multipleSelection = val
      // 获取表单所有要校验的字段
      const prop = this.$refs.formRef.fields.map(item => item.prop)
      if (val.length <= 0) return this.$refs.formRef.clearValidate(prop)
      this.verify = []
      // 将选中的数据和全部表格中校验的字段遍历 取出勾选对应的校验字段、并将没勾选的校验去除
      this.$refs.formRef.fields.forEach(item => {
        val.forEach(element => {
          if (item.prop.split('.')[1] === element.index.toString()) {
            this.verify.push(item.prop)
          } else {
            this.$refs.formRef.clearValidate(item.prop)
          }
        })
      })
      console.log(this.verify)
      // scope.$index当前行数据在数组中的索引,勾选哪一行校验哪一行的必填项
    },
    // 表头生成必填校验红星样式
    addRedStar (h, { column }) {
      return [
        h('span', { style: 'color: red' }, '*'),
        h('span', ' ' + column.label)
      ]
    }
  }
}
</script>
<style lang='scss' scoped>
</style>

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐