前提
需求: 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) {
// 提交逻辑
}
})