antdv 表格嵌套表单并添加校验

99 阅读1分钟

前提

需求: 1. 实现一个动态的表格内嵌表单组件 2. 表格支持可选 3. 点击提交时进行校验 4. 点击新增,新增一行 5. 点击批量删除, 将选中项批量删除

技术栈: vue2.7 + antdv template:

   <a-form
        ref="tableFormRef"
        :form="form"
        :model="tableData"
        :label-col="{ style: { width: '100px' } }"
        :wrapper-col="{ span: 24 }"
        autocomplete="off"
      >
        <Table
          :columns="currentColumns"
          :data-source="tableData.dataSource"
          :row-selection="rowSelection"
          :pagination="false"
          rowKey="id"
          :scroll="{ x: scrollWidth, y: 500 }"
        >
          <!-- 自定义表头 -->
          <div v-for="item in currentColumns" :key="item.key" :slot="item.slots.title">
            {{ item.scopedSlots.label }}<span style="color: red">*</span>
          </div>
          <template slot="user_name" slot-scope="text, record, index">
            <a-form-item>
              <a-input
                v-decorator="[
                  `dataSource[${index}].user_name`,
                  { rules: [{ required: true, message: '请输入姓名' }] },
                ]"
                placeholder="请输入姓名"
              />
            </a-form-item>
          </template>

          <template slot="py_name" slot-scope="text, record, index">
            <a-form-item>
              <a-input
                v-decorator="[
                  `dataSource[${index}].py_name`,
                  { rules: [{ required: true, message: '请输入拼音' }] },
                ]"
              />
            </a-form-item>
          </template>

          <template slot="sex" slot-scope="text, record, index">
            <a-form-item>
              <a-select
                v-decorator="[
                  `dataSource[${index}].sex`,
                  { rules: [{ required: true, message: '请选择性别' }] },
                ]"
                :options="[
                  { label: '男', value: 1 },
                  { label: '女', value: 2 },
                ]"
              />
            </a-form-item>
          </template>
          <template
            v-for="(slotName, ind) in [
              'id_card_front',
              'id_card_back',
            ]"
            :slot="slotName"
            slot-scope="text, record, index"
          >
            <a-form-item>
              <input
                type="hidden"
                v-decorator="[`dataSource[${index}].${slotName}`,
                { rules: imgRules(slotName, index) }]"
                :value="record[slotName]"
              />
              <a-space :key="ind">
                <a-button type="link" @click="() => handleUpload(slotName, record, index)">
                  上传
                </a-button>
                <a-button
                  v-if="isNotImg(slotName)"
                  type="link"
                  :disabled="!record[slotName]"
                  @click="() => handleDownload(slotName, record)"
                >
                  下载
                </a-button>
                <a-button
                  v-else
                  type="link"
                  :disabled="!record[slotName]"
                  @click="() => handlePreview(slotName, record)"
                >
                  预览
                </a-button>
              </a-space>
            </a-form-item>
          </template>
        </Table>
        <div class="operate-bottom">
          <a-space>
            <a-button @click="onCancel"> 取消 </a-button>
            <a-button @click="onSubmit" type="primary"> 提交 </a-button>
          </a-space>
        </div>
      </a-form>

响应式绑定数据


// 表单基于antdv中的$form来维护
const form = ref(null);
form.value = proxy.$form.createForm(proxy, { name: 'batch_create_form' });
// 表单绑定值
const tableData = ref({
  dataSource: [{
    id: Math.random().toString(36)
      .slice(2, 11),
  }],
  warnName: '',
  warnProbability: ['', ''],
});

// 选中项
const selectedRows = ref([]);
const selectedRowKeys = ref([]);
const isDisabledInput = record => false;
const rowSelection = computed(() => ({
  onChange: (selectedKeys, Rows) => {
    selectedRows.value = Rows;
    selectedRowKeys.value = selectedKeys;
  },
  getCheckboxProps: record => ({
    props: {
      disabled: isDisabledInput(record),
      name: `${record.id}`,
    },
  }),
  selectedRowKeys: selectedRowKeys.value,
}));


const clearSelected = () => {
  selectedRows.value = [];
  selectedRowKeys.value = [];
};

// 表格列配置
cosnt columns = {
    key: 'id_card_front',
    // title: '身份证正面',
    dataIndex: 'id_card_front',
    align: 'center',
    width: '120px',
    slots: { title: 'id_card_front_title' },
    scopedSlots: {
      customRender: 'id_card_front',
      title: 'id_card_front_title',
      label: '身份证国徽面',
    },
  },
  {
    key: 'id_card_back',
    // title: '身份证反面',
    dataIndex: 'id_card_back',
    align: 'center',
    width: '120px',
    slots: { title: 'id_card_back_title' },
    scopedSlots: {
      customRender: 'id_card_back',
      label: '身份证人像面',
    },
  },
  {
    key: 'id_card_handheld',
    // title: '手持身份证',
    dataIndex: 'id_card_handheld',
    align: 'center',
    width: '120px',
    slots: { title: 'id_card_handheld_title' },
    scopedSlots: {
      customRender: 'id_card_handheld',
      label: '手持身份证',
    },
  },
  {
    key: 'student_info',
    // title: '学信网认证截图',
    dataIndex: 'student_info',
    align: 'center',
    width: '120px',
    slots: { title: 'student_info_title' },
    scopedSlots: {
      customRender: 'student_info',
      label: '学信网认证截图',
    },
  },
  ...
  ]

当表格内嵌的表单触发值的更新时,需要手动更新表单项


// 更新表格数据源的值并且触发表单的更新
const updateFormFields = (index, values) => {
  Object.keys(values).forEach((key) => {
    if (values[key] !== undefined) {
      tableData.value.dataSource[index][key] = values[key];
    }
  });
  const fieldUpdates = {};
  Object.keys(values).forEach((key) => {
    const fieldName = `dataSource[${index}].${key}`;
    if (!form.value.getFieldInstance(fieldName)) {
      form.value.getFieldDecorator(fieldName, { initialValue: values[key] });
    }
    fieldUpdates[fieldName] = values[key];
  });
  form.value.setFieldsValue(fieldUpdates);
  proxy.$forceUpdate();
};


const handleInputChange = (event, record, index, field) => {
  const {
    target: { value },
  } = event;
  updateFormFields(index, {
    [field]: value,
  });
};

const onSelectChange = (value, record, index, field) => {
  updateFormFields(index, {
    [field]: value,
  });
};

const onDateChange = (value, record, index) => {
  updateFormFields(index, {
    graduate_time: value.format('YYYY-MM-DD'),
  });
};

新增一行

// 增加一行
const addRow = () => {
  tableData.value.dataSource.push({
    // `substr` 方法已弃用,使用 `slice` 方法替代
    id: Math.random().toString(36)
      .slice(2, 11),
  });
  console.log('新增',  tableData.value.dataSource);
};

批量删除 由于表单和表格分别维护一个数据源,当表格删除选中项时,数组元素索引改变,导致表单展示异常,因为表单绑定的值是根据表格数据源索引来绑定的,所以在删除选中项后,我需要给表单重新赋值

const deleteRow = () => {
  if (selectedRows.value.length > 0) {
    // 1. 保存当前表单数据
    const formData = form.value.getFieldsValue();

    // 2. 获取所有要删除的id
    const idsToDelete = selectedRows.value.map(item => item.id);

    // 3. 记录要保留的行索引
    const keepIndices = [];
    const newDataSource = tableData.value.dataSource.filter((item, index) => {
      const keep = item && !idsToDelete.includes(item.id);
      if (keep) keepIndices.push(index);
      return keep;
    });

    // 4. 更新表格数据源
    tableData.value.dataSource = newDataSource;

    // 5. 使用nextTick确保DOM更新完成后再更新表单
    nextTick(() => {
      // 6. 构建新的表单数据
      const newFormData = { dataSource: [] };

      // 7. 填充新的表单数据 - 通过索引位置关联
      newDataSource.forEach((item, newIndex) => {
        // 创建新行数据
        const rowData = { id: item.id };

        // 获取原始表单数据中对应的索引位置
        const originalIndex = keepIndices[newIndex];

        // 复制原表单中除了id以外的所有字段
        if (originalIndex !== undefined && formData.dataSource && formData.dataSource.length > originalIndex) {
          const originalRow = formData.dataSource[originalIndex];
          if (originalRow) {
            Object.keys(originalRow).forEach(field => {
              if (field !== 'id') {
                rowData[field] = originalRow[field];
              }
            });
          }
        }

        newFormData.dataSource[newIndex] = rowData;
      });

      // 8. 重置表单并设置新数据
      form.value.resetFields();
      form.value.setFieldsValue(newFormData);
    });
  }
  clearSelected();
};

表单提交时校验

 form.value.validateFields((err, values) => {
    if (!values.dataSource?.length) {
      return proxy.$message.error('请添加人员信息');
    }
    if (!err) {
        // 提交逻辑
    }
  })