题记
简单记录下,最近有这么一个小功能:
以下是一个录入的动态表单,通过ProFormList
实现。
现在业务人员嫌这个录入太慢了,希望通过 excel
达到快速录入的效果。很多时候大家都是下载模板,上传表格来实现的。但是这个表单,除了这个动态表还有很多额外的其他字段,而且不希望固定成一个录入模板。
所以我想着直接从 excel
复制数据,然后自动录入,自动新增行不就得了。
实现效果
思考
通过从 excel
复制数据,可以得到这样的数据。'1\t2\t3\n4\t5\t6\n7\t\t\n8\t\t'
,其中,每一列通过\t
换行符分隔,每一行通过\n
换行符分隔。
数据拿到了。接下来我们只需要获取到复制的数据,然后拆分每一个单元格数据到对应的格子上就好了。 所以我们要解决下面几个问题:
- 监听复制粘贴事件,拿到对应的复制数据,获取当前聚焦的输入框
- 获取表单数据,具体是复制到第几行第几列
- 自动填充到对应的单元格,自动新增行
实现
1、监听粘贴事件,拿到复制数据
我们通过 window
的paste
事件事件就可以拿到了。
其中:ev.clipboardData?.getData("text/plain")
是拿到粘贴板的数据。
PS:记得阻止默认的复制粘贴行为: ev.preventDefault();
ts
useEffect(() => {
const onPaste = (ev: ClipboardEvent) => {
///粘贴事件ev
//复制数据
ev.clipboardData?.getData("text/plain")
};
window.addEventListener("paste", onPaste);
return () => {
window.removeEventListener("paste", onPaste);
};
}, []);
2、获取表单数据,具体是复制到第几行第几列
通过 antd 生成的表单元素可以发现,元素的 id 构成为 ${namePath}_${第几行}_${字段名}
构成。 而粘贴事件的 ev.target
就是当前的聚焦输入框。
然后我们只需要通过传入 form
和监听的表单的 NamePath
计算下就出来了。
ts
//剪切事件的 ev.target 就是当前的聚焦输入框
const id = `${ev.target?.["id"]}`;
//获取动态表单对象
const fieldValue = form.getFieldValue(filedNamePath);
const idPrefix = filedNamePath.join("_");
//获取属性名和行数
const idSubstring = id.slice(idPrefix.length + 1);
const ids = idSubstring.split("_");
//拿到行号
const lineNum = +ids[0];
//拿到当前聚焦的属性名
const propsName = ids.slice(1).join("_");
3.解析粘贴数据,设置到对应的单元格
ts
//粘贴数据
const data = ev.clipboardData?.getData("text/plain");
//所有的属性:按照输入框顺序和对应排列
const allProps = ['name','age'....等等其他表单字段名];
const indexName = allProps.indexOf(propsName);
//列数据
const columnData = data?.split("\n");
columnData?.forEach((column, i) => {
//行数据
const lineDatas = column.split("\t");
lineDatas.forEach((line, indexLine) => {
const n = allProps[indexName + indexLine];
if (n) {
//如果不存在行,则新增行
fieldValue[lineNum + i] ??= {};
//设置行数据到对应的单元格,这里其实还可以加数据校验等等逻辑
fieldValue[lineNum + i][n] = line;
}
});
});
//更新表单数据
form.setFieldValue(filedNamePath, fieldValue);
以上为基本思路。
完整例子
- 将以上逻辑抽离成公共函数,方便其他地方使用。
以下为完整逻辑。
ts
/**
* 录入表格新增复制粘贴效果
* 需注意:props 中不要带下划线,会影响判断
*
* @param form antd 的 form 表单
* @param filedNamePath 属性名称
* @param props 属性内容,按表单顺序排序
*/
export function onClipboardFormData(
ev: ClipboardEvent,
form: FormInstance,
filedNamePath: string[],
props: string[]
) {
const id = `${ev.target?.["id"]}`;
const idPrefix = filedNamePath.join("_");
if (id.startsWith(idPrefix)) {
ev.preventDefault();
const fieldValue = form.getFieldValue(filedNamePath);
//获取属性名和行数
const idSubstring = id.slice(idPrefix.length + 1);
const ids = idSubstring.split("_");
const lineNum = +ids[0];
const propsName = ids.slice(1).join("_");
const data = ev.clipboardData?.getData("text/plain");
//所有的属性:按照输入框顺序和对应排列
const allProps = props;
const indexName = allProps.indexOf(propsName);
if (indexName != -1) {
//列数据
const columnData = data?.split("\n");
columnData?.forEach((column, i) => {
//行数据
const lineDatas = column.split("\t");
lineDatas.forEach((line, indexLine) => {
const n = allProps[indexName + indexLine];
if (n) {
fieldValue[lineNum + i] ??= {};
fieldValue[lineNum + i][n] = line;
}
});
});
form.setFieldValue(filedNamePath, fieldValue);
}
}
}
- 在需要用到的动态表单处使用
ts
const [form] = Form.useForm();
useEffect(() => {
const onPaste = (ev: ClipboardEvent) => {
onClipboardFormData(
ev,
//Antd表单实例化对象
form,
// 表单属性路径
["default", "materials"],
//动态属性名,按照显示排序
["name", "spec", "material_quality", "process", "color", "quantity"]
);
};
window.addEventListener("paste", onPaste);
return () => {
window.removeEventListener("paste", onPaste);
};
}, []);
- 最终效果