# el-form动态表单校验

动态表单校验示例如下

# 示例

判断项
附件配置
操作新增选项
删除

动态表单校验

<template>
  <div class="config-form">
    <el-form ref="configForm" :model="configForm" :rules="configFormRules">
      <el-form-item label-width="128px" required label="资质类型">
        <el-radio-group :disabled="configForm.category_condition_has_use == '1'" v-model="configForm.type">
          <el-radio :label="1">单选配置</el-radio>
          <el-radio :label="2">多选配置</el-radio>
          <el-radio :label="3">单项配置</el-radio>
        </el-radio-group>
      </el-form-item>
      <el-form-item label-width="128px" label="资质要求名称" prop="condition_name">
        <el-input v-model="configForm.condition_name" clearable placeholder="请输入资质要求名称"></el-input>
      </el-form-item>
      <el-form-item label-width="128px" label="判断类型" prop="judge">
        <div class="judge-box">
          <div class="row header">
            <div class="name filed" v-if="configForm.type !== 3">判断项</div>
            <div class="config filed">附件配置</div>
            <div class="operation filed">
              <span>操作</span><span class="add-project" @click="addProjectItem" v-show="configForm.type !== 3">新增选项</span>
            </div>
          </div>
          <div class="row" v-if="configForm.value_dto_list.length === 0" style="text-align: center;display: block;">暂无配置项,请添加配置项</div>
          <template v-for="(item, index) in configForm.value_dto_list">
            <div class="row" v-if="!(configForm.type === 3 && index > 0)" :key="index">
              <div class="name reset-padding filed" v-if="configForm.type !== 3">
                <el-form-item :prop="'value_dto_list.' + index + '.name'" :rules="[{required: true, message: `请输入选项${index + 1}名称`, trigger: 'blur'},  { validator: checkSelectName, trigger: 'blur' }]" :label="`选项${index + 1}名称`">
                  <el-input :disabled="item.value_has_use == '1'" v-model="item.name" :placeholder="`请输入选项${index + 1}名称`" clearable></el-input>
                </el-form-item>
              </div>
              <div class="config no-padding filed">
                <div :class="['row-box radio-box flex-c-s', item.is_need_file == 1 ? 'border' : '' ]">
                  <el-form-item required class="flex-grow" label="是否上传相关资质">
                    <el-radio-group :disabled="item.value_has_use == '1'" v-model="item.is_need_file">
                      <el-radio label="1"></el-radio>
                      <el-radio label="0"></el-radio>
                    </el-radio-group>
                  </el-form-item>
                  <el-form-item :required="item.is_need_file == 1" class="right-radio flex-grow" v-show="item.is_need_file == '1'" label="是否必填至少一项">
                    <el-radio-group :disabled="item.value_has_use == '1'" v-model="item.at_least_one_value">
                      <el-radio label="1"></el-radio>
                      <el-radio label="0"></el-radio>
                    </el-radio-group>
                  </el-form-item>
                </div>
                <template v-if="item.is_need_file == '1'">
                  <div class="row-box" v-for="(materialItem, childIndex) in item.value_material_dto_list" :key="childIndex">
                    <el-form-item :prop="`value_dto_list[${index}].value_material_dto_list[${childIndex}].material_name`" :rules="[{required: true, message: `请输入上传资质附件名称`, trigger: 'change'},{ validator: checkMaterialName, trigger: 'change' }]" class="" label="上传资质附件名称">
                      <el-input v-model="materialItem.material_name" :disabled="materialItem.value_material_has_use == '1'" clearable placeholder="请输入上传资质附件名称"></el-input>
                    </el-form-item>
                    <el-form-item :prop="`value_dto_list[${index}].value_material_dto_list[${childIndex}].is_need_file`" :rules="[{required: true, validator: checkMaterialNeedFile, trigger: 'change' }]" class="" label="是否必填">
                      <el-radio-group :disabled="materialItem.value_material_has_use == '1'" v-model="materialItem.is_need_file">
                        <el-radio label="1"></el-radio>
                        <el-radio label="2"></el-radio>
                      </el-radio-group>
                    </el-form-item>
                    <div :class="['delete', materialItem.value_material_has_use == '1' ? 'disabled' : '']" @click="deleteMaterialItem(materialItem.value_material_has_use, index, childIndex)">删除</div>
                  </div>
                </template>
                <div class="row-box flex-c-s" v-show="item.is_need_file == '1'">
                  <div class="add-name-btn" @click="addMaterialItem(index)">新增资质附件名称</div>
                </div>
              </div>
              <div class="operation filed">
                <span :class="['delete', item.value_has_use == '1' ? 'disabled' : '']" @click="deleteProject(item.value_has_use, index)">删除</span>
              </div>
            </div>
          </template>
        </div>
      </el-form-item>
      <el-form-item label-width="128px" label="">
        <div class="btn-box">
          <el-button @click="dialogShow = false">取消</el-button>
          <el-button type="primary" @click="saveConfig">保存配置</el-button>
        </div>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
export default {
  name: "el-form-nesting",
  data() {
    //校验选项明重复
    const checkSelectName = (rule, value, callback) => {
      const arr = this.configForm.value_dto_list.filter(item => item.name === value)
      if (arr && arr.length > 1) {
        callback("当前选项名已存在,请重新输入!")
      } else {
        callback()
      }
    }

    //校验资质名称
    const checkMaterialName = (rule, value, callback) => {
      const targetIndex = rule.field.split(".")[0].replace(/[^0-9]/g, "")
      const arr = this.configForm.value_dto_list[targetIndex].value_material_dto_list.filter(item => item.material_name === value)
      if (arr && arr.length > 1) {
        callback("当前附件资质名称已存在,请重新选择或输入!")
      } else {
        callback()
      }
    }

    //校验必须有一个必填的时候 至少得一项设置必填
    const checkMaterialNeedFile = (rule, value, callback) => {
      const targetIndex = rule.field.split(".")[0].replace(/[^0-9]/g, "")
      //如果 选了 必填至少一项 则不能全部为 非必填
      if (this.configForm.value_dto_list[targetIndex].at_least_one_value == '1') {
        const requireList = this.configForm.value_dto_list[targetIndex].value_material_dto_list.filter(item => item.is_need_file == "1")
        if (requireList && requireList.length === 0) {
          callback("资质附件列表至少一项为必填!")
        } else {
          callback()
        }
      } else {
        callback()
      }
    }

    //校验单项时 列表数据太多
    const checkJudge = (rule, value, callback) => {
      callback()
    }

    return {
      checkSelectName, //校验选项名
      checkMaterialName, //校验资质名称重复
      checkMaterialNeedFile,  //校验必须有一个必填的时候 至少得一项设置必填
      configForm: {
        category_condition_id: null, //类目条件id
        category_id: "", //类目id
        category_name: "",
        is_shop_condition: "1", //是否为店铺资质配置条件:1:是,0:否
        condition_type: "1", //条件类型,1:供应商店铺行业资质配置,2:经销商店铺行业资质配置,3:合伙人店铺行业资质配置,101:供应商商品资质配置,201:经销商商品资质配置
        type: 1, //条件选项类型:1:单选 2:复选 3:单项选择
        condition_name: "", //条件名
        value_dto_list: [
          {
            name: "", //条件值名称
            is_need_file: "0", //是否上传相关资质,0:无附件,1:有附件
            at_least_one_value: "0", //是否必填至少一项,0:否,1:是
            value_material_dto_list: [
              {
                material_name: "", //材料名称
                is_need_file: "1", //是否需要附件,0:无附件,1:附件必填,2:选填
              }
            ], //条件材料列表
            del_value_material_id_list: [] //删除的条件材料_id列表
          }
        ],
        del_value_id_list: [] //删除的选项id_list
      },
      configFormRules: {
        condition_name: [{ required: true, message: '请输入资质要求名称', trigger: 'change' }],
        judge: [{ validator: checkJudge, trigger: 'change' }]
      },
    }
  },
  methods: {
    /**
    * @description: 添加配置项
    * @param {*}
    * @return {*}
    * @author: syx
    */
    addProjectItem() {
      this.$set(this.configForm.value_dto_list, this.configForm.value_dto_list.length, {
        name: "", //条件值名称
        is_need_file: "0", //是否上传相关资质,0:无附件,1:有附件
        at_least_one_value: "0", //是否必填至少一项,0:否,1:是
        category_condition_id: this.configForm.category_condition_id || null,
        value_material_dto_list: [
          {
            material_name: "", //材料名称
            is_need_file: "1", //是否需要附件,0:无附件,1:附件必填,2:选填
          }
        ], //条件材料列表
        del_value_material_id_list: [] //删除的条件材料_id列表
      })
    },
    /**
     * @description: 删除配置项
     * @param {*}
     * @return {*}
     * @author: syx
     */
    deleteProject(useStatus, index) {
      if (useStatus == '1') {
        return
      }
      if (this.configForm.value_dto_list.length === 1) {
        return this.$message.warning("至少得保留一项判断选项")
      }
      const id = this.configForm.value_dto_list[index].id || ""
      if (!id) {
        this.configForm.value_dto_list.splice(index, 1)
        return
      }
    },
    /**
     * @description: 添加配置项
     * @param {*}
     * @return {*}
     * @author: syx
     */
    addMaterialItem(index) {
      this.$set(this.configForm.value_dto_list[index].value_material_dto_list, this.configForm.value_dto_list[index].value_material_dto_list.length, {
        value_id: this.configForm.value_dto_list[index].id || null,
        material_name: "", //材料名称
        is_need_file: "1", //是否需要附件,0:无附件,1:附件必填,2:选填
      })
    },
    /**
     * @description: 删除附件列表项
     * @param {*} index
     * @param {*} childIndex
     * @return {*}
     * @author: syx
     */
    deleteMaterialItem(useStatus, index, childIndex) {
      if (useStatus == '1') {
        return
      }
      if (this.configForm.value_dto_list[index].value_material_dto_list.length === 1) {
        return this.$message.warning("至少得保留一项资质附件名称")
      }

      const id = this.configForm.value_dto_list[index].value_material_dto_list[childIndex].id || ""
      if (!id) {
        this.configForm.value_dto_list[index].value_material_dto_list.splice(childIndex, 1)
        return
      }
    },
    /**
    * @description: 保存配置
    * @param {*}
    * @return {*}
    * @author: syx
    */
    saveConfig() {
      this.$refs.configForm.validate((valid) => {
        if (valid) {
          let arr = []
          for (let i = 0; i < this.configForm.value_dto_list.length; i++) {
            let item = this.configForm.value_dto_list[i]
            if (item.is_need_file === "0") {
              //过滤掉  没有 id的
              const list = item.value_material_dto_list.filter(item => {
                return item.id
              })
              let delList = []
              for (let i = 0; i < list.length; i++) {
                const item = list[i]
                if (item.material_name !== "无附件") {
                  delList.push(item.id)
                }
              }
              // 编辑情况 存在有 id 且 不用删除  则 为无 附件的 情况
              if (this.isEdit) {
                if (delList.length === 0) {
                  item.value_material_dto_list = item.value_material_dto_list
                } else {
                  item.del_value_material_id_list = delList
                  item.value_material_dto_list = []
                }
              } else {
                item.value_material_dto_list = []
              }
            }

            //如果是单项配置,设置选项名为 资质条件名  去除多余被隐藏的判断项
            if (this.configForm.type === 3) {
              item.name = this.configForm.condition_name
              arr.push(JSON.parse(JSON.stringify(item)))
              break
            }
            arr.push(JSON.parse(JSON.stringify(item)))
          }
          this.configForm.value_dto_list = arr

          qualificationAllocationApi.saveCategoryCondition(this.configForm).then(data => {
            const msg = this.isEdit ? "编辑资质配置项成功" : "新增资质配置项成功"
            this.dialogShow = false
            this.$message.success(msg)
            if (!this.isEdit) {
              //新增成功  刷新侧边列表数据
              this.getMenuList()
            }
            this.getList()
            this.getCatetoryMaterials()
          })
        }
      })
    },
  }
}
</script>

<style lang="less" scoped>
.flex-c-s {
  display: flex;
  justify-content: flex-start;
  align-items: center;
}
.flex-grow {
  flex-grow: 1;
}
.config-form {
  margin-top: 0;
  .el-form {
    .el-form-item {
      .inline-input {
        width: 400px;
      }
    }
  }
  .judge-box {
    width: 100%;
    border: 1px solid #ccc;
    .row {
      display: flex;
      line-height: 40px;
      border-bottom: 1px solid #ccc;
      &.header {
        background: #eaeaeb;
        height: 40px;
      }
      &:last-child {
        border: none;
      }
      .name {
        width: 200px;
        &.reset-padding {
          padding: 10px 12px !important;
        }
      }
      .config {
        flex-grow: 1;
        &.no-padding {
          padding: 0 !important;
        }
        .row-box {
          border-top: 1px solid #ccc;
          padding: 0 12px;
          padding-top: 10px;
          position: relative;
          &.radio-box {
            &.border::after {
              position: absolute;
              content: "";
              top: 0;
              left: 49%;
              width: 1px;
              height: 100%;
              background: #ccc;
            }
          }
          .delete {
            position: absolute;
            bottom: 0;
            right: 24px;
            color: red;
            cursor: pointer;
            &.disabled {
              cursor: not-allowed;
              color: #909399;
            }
          }
          .el-form-item {
            .inline-input {
              width: 400px;
            }
          }

          &:first-child {
            border: none;
          }
          .right-radio {
            padding-left: 12px;
          }

          .add-name-btn {
            width: 100%;
            height: 40px;
            border: 1px dashed #409eff;
            color: #409eff;
            text-align: center;
            line-height: 40px;
            margin-bottom: 12px;
            cursor: pointer;
          }
        }
      }
      .operation {
        width: 150px;
        text-align: center;
        position: relative;
        .add-project {
          margin-left: 12px;
          margin-top: 4px;
          padding: 0 12px;
          display: inline-block;
          height: 32px;
          border: 1px dashed #409eff;
          color: #409eff;
          text-align: center;
          line-height: 32px;
          cursor: pointer;
        }
        .delete {
          position: absolute;
          top: 50%;
          left: 50%;
          transform: translate(-50%, -50%);
          color: red;
          cursor: pointer;
          &.disabled {
            cursor: not-allowed;
            color: #909399;
          }
        }
      }
      .filed {
        border-left: 1px solid #ccc;
        padding: 0 12px;
        &:first-child {
          border: none;
        }
      }
    }
  }
}
</style>
显示代码

# 主要关键代码

:prop="'value_dto_list.' + index + '.name'"

prop一定要注意

可以从rule中拿到 prop值,然后拆字段拿到想要的数据

const targetIndex = rule.field.split(".")[0].replace(/[^0-9]/g, "")

WARNING

变动性太大,仅供参考