element 在表格中使用表單校驗

先上個圖

image.png

上圖說明

  1. 看起來是表格包含了很多個表單,其實是表單包含了表格;
  2. 表頭是通過插槽自定義的,也可以直接使用表格的 label 屬性;
  3. 外層是一個大表格,里面是一個小表格,然后小表格的每一列是根據(jù)大表格的主表列里的選項不同,數(shù)據(jù)不同自動循環(huán)出來的,有的是必填,有的是非必填,截圖上都是必填;
  4. 小表格可以進行動態(tài)添加數(shù)據(jù),小表格跟大表格是兩個表單,并且分開校驗;

element 表單校驗理解

  1. el-form 組件中的 model 屬性,即綁定校驗的外層對象,我們都知道對象里第一層數(shù)據(jù)的校驗,而這個對象里面可能還有對象、數(shù)組什么的,怎么去校驗被層層包裹住的數(shù)據(jù)呢,這就是表格校驗要過的第一個關(guān)卡;
  2. el-form 組件中的 rules 屬性,對數(shù)據(jù)的校驗規(guī)則對象,它通過 el-form-item 的 prop 屬性去找該字段匹配 rules 屬性綁定對象的哪一個字段,嵌套層級的話,可以通過 prop 屬性提現(xiàn)出來,但是如果嵌套比較煩,我們對 rules 對象的書寫有比較麻煩,el-form-item 組件中 也有一個 rules 屬性,可以直接傳遞該字段的校驗數(shù)組,清晰明了;
  3. 此處即用到了表格單行校驗,也用到了全表格校驗,全表格校驗和正常的表單校驗一樣,但是單行校驗就相對復(fù)雜了;
  4. 此處還有一個表格的展開行的坑,就是當(dāng)我們要默認(rèn)展開行或者要根據(jù)需求去做一些特殊的表格展開的時候,el-table 組件的 row-key 屬性一定要在數(shù)據(jù)中是字符串類型的,不然不會出效果;

上代碼

標(biāo)簽部分
    <!-- iq 是自定義的,放到對象里時,一定要轉(zhuǎn)成字符串 -->
    <el-button
      @click="iq=iq+1,tableForm.tableData.unshift({iq:iq.toString(),manageCom:'',gradeCode:[],isEdit: true,list:[],add:1})"
      :loading="isLoading" type="primary">新增配置
    </el-button>
    <el-form :model="tableForm" ref="tableForm" size="small">
      <el-table :data="tableForm.tableData" ref="tableData" v-loading="isLoading" @expand-change="expandChange"
        :expand-row-keys="expandRowKeys" row-key="iq">
        <el-table-column type="expand">
          <template slot-scope="props">
            <!-- 給每一個小表格動態(tài)賦值 ref 屬性 -->
            <el-form v-if="props.row.asalaryCode" :model="tableForm.tableData[props.$index]"
              :ref="`tableForm${props.$index}`" size="mini">
              <el-table :data="props.row.list" border default-expand-all>
                <div v-for="(col, index) in salaryArr" :key="index">
                  <!-- 因為是根據(jù) asalaryCode 字段來生成小表格的,所以得先判斷 -->
                  <div v-if="col.value == props.row.asalaryCode">
                    <!-- 循環(huán)生成小表格表頭  :fixed="indexF == 0" 不知為什么在渲染的時候 順序不對,就加了這段 -->
                    <el-table-column v-for="(fileByte, indexF) in col.fileBytes" :key="indexF" align="center"
                      :fixed="indexF == 0">
                      <!-- 自定義表頭部 -->
                      <template slot="header">
                        <span>
                          <span v-if="!fileByte.notRequired" style="color: red; font-size: 16px">*</span>子表列{{indexF++}}
                        </span>
                      </template>

                      <template slot-scope="scope">
                        <!-- prop 屬性是組件用來找到指定校驗數(shù)據(jù)的關(guān)鍵,此處比較模糊,放在下面解釋 -->
                        <el-form-item :prop="`list.${scope.$index}.${fileByte.model}`"
                          :rules="rules[fileByte.rule || fileByte.model]" style="margin-top:18px">

                          <el-select v-if="fileByte.type == 'select'" v-model="scope.row[fileByte.model]"
                            :disabled="!props.row.isEdit" placeholder="請選擇" filterable class="item-width">
                            <!-- 動態(tài)獲取枚舉 -->
                            <el-option v-for="item in getNacosOne('parameterName' + props.row.asalaryCode)"
                              :key="item.value" :label="item.value" :value="item.value">
                            </el-option>
                          </el-select>

                          <el-input v-else-if="fileByte.type == 'input'" v-model.trim="scope.row[fileByte.model]"
                            placeholder="請輸入" clearable class="item-width" />
                        </el-form-item>
                      </template>
                    </el-table-column>

                    <el-table-column v-if="props.row.isEdit && tableForm.tableData[props.$index].asalaryCode"
                      width="100" label="操作" align="center">
                      <template slot-scope="scope">
                        <el-popconfirm title="該行將被刪除,請確認(rèn)" @confirm="deleteOne(props.row.list,scope.$index,1)">
                          <el-button slot="reference" type="text">刪除</el-button>
                        </el-popconfirm>
                      </template>
                    </el-table-column>
                  </div>
                </div>
              </el-table>
              <div @click="childrenAdd(props.row.list" class="addBtn"
                style="border: 1px solid rgba(24, 144, 255, 1);color: rgba(24, 144, 255, 1);">
                <i class="el-icon-plus"></i>添加
              </div>
            </el-form>
          </template>
        </el-table-column>

        <el-table-column align="center">
          <template slot="header">
            <span>
              <span style="color: red; font-size: 16px">*</span>主表列1
            </span>
          </template>
          <template slot-scope="scope">
            <!-- prop 屬性是組件用來找到指定校驗數(shù)據(jù)的關(guān)鍵,用此處的 prop 來舉個栗子:tableForm['tableData'][scope.$index]['manageCom'],
            這就是該字段在這個表格中的定位,即通過對 prop 值的拆解,再跟 form 表單的 model 屬性結(jié)合,就能找到指定數(shù)據(jù),再加上 rules,就能進行數(shù)據(jù)校驗 -->
            <!-- 此處 el-form-item 組件的 rules 屬性也是為了定位相應(yīng)的校驗規(guī)則,直接傳,才能對動態(tài)表格進行操作 -->
            <el-form-item :prop="`tableData.${scope.$index}.manageCom`" :rules="rules.manageCom"
              style="margin-top:18px">
              <el-cascader v-model="scope.row.manageCom" @change="edit(scope.row,'isEdit')" :options="manageComArr"
                :props="{label:'label',value:'manageComCode',emitPath:false,checkStrictly: true}"
                :show-all-levels="false" placeholder="請選擇" filterable class="item-width" />
            </el-form-item>
          </template>
        </el-table-column>

        <el-table-column align="center">
          <template slot="header">
            <span>
              <span style="color: red; font-size: 16px">*</span>主表列2
            </span>
          </template>
          <template slot-scope="scope">
            <el-form-item :prop="`tableData.${scope.$index}.gradeCode`" :rules="rules.gradeCode"
              style="margin-top:18px">
              <el-select v-model="scope.row.gradeCode" placeholder="請選擇" collapse-tags multiple clearable filterable
                class="item-width">
                <el-option v-for="item in getNacosOne('agentRank')" :key="item.value"
                  :label="item.value + '-' + item.label" disabled :value="item.value">
                </el-option>
              </el-select>
            </el-form-item>
          </template>
        </el-table-column>
        <el-table-column align="center">
          <template slot="header">
            <span>
              <span style="color: red; font-size: 16px">*</span>主表列3
            </span>
          </template>
          <template slot-scope="scope">
            <el-form-item :prop="`tableData.${scope.$index}.asalaryCode`" :rules="rules.asalaryCode"
              style="margin-top:18px">
              <el-select v-model="scope.row.asalaryCode" @change="salaryChange(scope)" clearable placeholder="請選擇"
                filterable class="item-width">
                <el-option v-for="item in salaryArrDis" :key="item.value" :label="item.value" :value="item.value">
                </el-option>
              </el-select>
            </el-form-item>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="200" align="center">
          <template slot-scope="scope">
            <el-button v-if="(scope.row.isEdit || scope.row.isConf) && scope.row.list.length > 0" @click="save(scope)"
              :disabled="scope.row.isLoading" type="text">保存
            </el-button>
            <el-button v-else-if="scope.row.list.length > 0"
              @click="expandChange(scope.row,[1,2]),edit(scope.row,'isEdit')" type="text">配置
            </el-button>
            <!-- 刪除大項 -->
            <el-popconfirm title="該薪資項系數(shù)配置信息將被刪除,請確認(rèn)" @confirm="deleteOne(tableForm.tableData,scope.$index)"
              :disabled="scope.row.isLoading" style="margin: 0 10px;">
              <el-button slot="reference" type="text">刪除</el-button>
            </el-popconfirm>
          </template>
        </el-table-column>
      </el-table>
    </el-form>
js部分
export default {
  data() {
    return {
      manageComArr: [],
      iq: 0,
      // el-form 組件需要用一層對象包裹起來
      tableForm: {
        tableData: [],
      },
      expandRowKeys: [], // 用于展開默認(rèn)項
      // 校驗規(guī)則對象
      rules: {
        manageCom: [{ required: true, message: "請選擇", trigger: "change" }],
        gradeCode: [
          {
            type: "array",
            required: true,
            message: "請選擇",
            trigger: "change",
          },
        ],
        asalaryCode: [{ required: true, message: "請選擇", trigger: "change" }],

        stdKey: [{ required: true, message: "請選擇", trigger: "change" }],
        minStd: [
          {
            required: true,
            message: "請輸入",
            trigger: "blur",
          },
          { validator: checkStd, trigger: "blur" },
        ],
        maxStd: [
          { required: true, message: "請輸入", trigger: "blur" },
          { validator: checkStd, trigger: "blur" },
        ],

        stdRatio: [
          { required: true, message: "請輸入", trigger: "blur" },
          {
            validator: (rule, value, callback) =>
              value >= 0 ? callback() : callback(new Error("范圍大于等于0!")),
            trigger: "blur",
          },
        ],
        secondRatio: [
          { required: true, message: "請輸入", trigger: "blur" },
          {
            validator: (rule, value, callback) =>
              value >= 0 ? callback() : callback(new Error("范圍大于等于0!")),
            trigger: "blur",
          },
        ],
        stdValue: [
          { required: true, message: "請輸入", trigger: "blur" },
          {
            validator: (rule, value, callback) =>
              value >= 0 ? callback() : callback(new Error("請輸入整數(shù)!")),
            trigger: "blur",
          },
        ],
      },
      // 用于生成小表格的數(shù)據(jù)
      salaryArr: [
        {
          value: "03",
          fileBytes: [
            { model: "stdKey", type: "select" },
            { model: "minStd", type: "input" },
            { model: "maxStd", type: "input" },
            { model: "stdRatio", type: "input" },
          ],
        },
        {
          value: "04",
          fileBytes: [
            { model: "stdKey", type: "select" },
            { model: "minStd", type: "input" },
            { model: "maxStd", type: "input" },
            { model: "stdRatio", type: "input" },
          ],
        },
        {
          value: "05",
          fileBytes: [
            { model: "stdKey", type: "select" },
            { model: "minStd", type: "input" },
            { model: "maxStd", type: "input" },
            { model: "stdRatio", type: "input" },
          ],
        },
        {
          value: "06",
          fileBytes: [
            { model: "stdKey", type: "select" },
            {
              model: "stdRatio",
              type: "input",
            },
            { model: "stdValue", type: "input" },
          ],
        },
        {
          value: "07",
          fileBytes: [
            { model: "stdKey", type: "select" },
            { model: "minStd", type: "input" },
            { model: "maxStd", type: "input" },
            { model: "stdRatio", type: "input" },
          ],
        },
        {
          value: "08",
          fileBytes: [
            { model: "stdKey", type: "select" },
            {
              model: "grownDate",
              type: "text",
              notRequired: true,
            },
            { model: "stdRatio", type: "input" },
            {
              model: "secondRatio",
              type: "input",
            },
          ],
        },
        {
          value: "09",
          fileBytes: [
            { model: "stdKey", type: "select" },
            { model: "minStd", type: "input" },
            { model: "maxStd", type: "input" },
            { model: "stdRatio", type: "input" },
          ],
        },
        {
          value: "10",
          fileBytes: [
            { model: "stdKey", type: "select" },
            { model: "minStd", type: "input" },
            { model: "maxStd", type: "input" },
            { model: "stdRatio", type: "input" },
          ],
        },
      ],
    };
  },
  methods: {
    // 結(jié)構(gòu)為題,我們要保存的是主表中的一行(三個字段)和子表整個表格,所以主表的那三個單獨字段的校驗就需要做特殊處理
    save(scope) {
      let that = this;
      let validateFieldList = [];
      // 主表格單條數(shù)據(jù)提交校驗 三個字段
      this.$refs.tableForm.validateField(
        [
          `tableData.${scope.$index}.manageCom`,
          `tableData.${scope.$index}.gradeCode`,
          `tableData.${scope.$index}.asalaryCode`,
        ],
        async (valid) => {
          validateFieldList.push(valid);
          if (validateFieldList.length == 3) {
            // 校驗子表
            that.$refs[`tableForm${scope.$index}`].validate((validI) => {
              // 都成功才成功
              if (validateFieldList.every((item) => item === "") && validI) {
                that.saveReq();
              } else that.$message.error(VALID_ERR_TABLE);
            });
          }
        }
      );
    },
    // 展開行觸發(fā)事件,主動展開的是哪一行,就把哪一行的 row-key 放到 expandRowKeys 數(shù)組中,只要 expandRowKeys 只有一項,那就只展開一項,別的項就會收起
    expandChange(row, expandedRows) {
      this.$set(
        this,
        "expandRowKeys",
        expandedRows.length == 0 ? [] : [row.iq]
      );
    },
    // 子表添加一行
    childrenAdd(tableData) {
      let obj = { isEdit: true, add: 1, stdKey: "" };
      tableData.push(obj);
    },
    // 獲取數(shù)據(jù)后將 iq 放到每一項數(shù)據(jù)中
    tableThen(res) {
      this.iq = 0;
      res.data.list.forEach((item) => {
        this.iq++;
        item.iq = this.iq.toString(); // row-key="iq" 這個必須是字符串,蛋疼
        if (item.list) item.list.forEach((itemC) => (itemC.iq = this.iq++));
      });
      this.$set(this.tableForm, "tableData", res.data.list);
    },
  },
};

總結(jié)

  1. 表格單行保存、編輯呢,一般可以使用 el-table-column 組件插槽中的 $index 就可以判斷出要編輯的是哪一行了,不需要在獲取到數(shù)據(jù)之后再手動給每一條數(shù)據(jù)添加 key 去判斷,此處是因為有別的用處;
  2. 表格的單個數(shù)據(jù)的編輯也可以使用 el-table-column 組件插槽中的 $index 加上字段名稱就可定位到修改哪一行的哪個數(shù)據(jù);
  3. 此功能的難點在表格單行數(shù)據(jù)的保存時的數(shù)據(jù)校驗,在校驗對象中,也可以加自定義校驗規(guī)則,不沖突,特別要注意的就是 prop 屬性,只要 prop 屬性是對的,組件能找到該數(shù)據(jù),就能進行規(guī)則校驗;

仙人指路

  1. moment.js 獲取 昨天、今天、上周、本周、上月、本月、上季度、本季度、去年 時間段,并集成到 vue Ant Design a-range-picker 日期選擇器中
  2. vue Ant Design Select 選擇框輸入搜索已有數(shù)據(jù) mixin
  3. vue + Ant Design 表格多選 mixin
  4. element-ui Upload 自定義上傳 loading
  5. vue 中父子組件通信 js 引用的作用
  6. Vue組件通信—provide/inject
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容