【WEB】Antd-Pro 表单通过 excel 复制粘贴快速录入小功能

题记

简单记录下,最近有这么一个小功能:

以下是一个录入的动态表单,通过ProFormList实现。

现在业务人员嫌这个录入太慢了,希望通过 excel达到快速录入的效果。很多时候大家都是下载模板,上传表格来实现的。但是这个表单,除了这个动态表还有很多额外的其他字段,而且不希望固定成一个录入模板。

所以我想着直接从 excel复制数据,然后自动录入,自动新增行不就得了。

实现效果

思考

通过从 excel 复制数据,可以得到这样的数据。'1\t2\t3\n4\t5\t6\n7\t\t\n8\t\t',其中,每一列通过\t换行符分隔,每一行通过\n换行符分隔。

数据拿到了。接下来我们只需要获取到复制的数据,然后拆分每一个单元格数据到对应的格子上就好了。 所以我们要解决下面几个问题:

  1. 监听复制粘贴事件,拿到对应的复制数据,获取当前聚焦的输入框
  2. 获取表单数据,具体是复制到第几行第几列
  3. 自动填充到对应的单元格,自动新增行

实现

1、监听粘贴事件,拿到复制数据

我们通过 windowpaste事件事件就可以拿到了。

其中: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);

以上为基本思路。

完整例子

  1. 将以上逻辑抽离成公共函数,方便其他地方使用。

以下为完整逻辑。

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);
    }
  }
}
  1. 在需要用到的动态表单处使用
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);
    };
  }, []);
  1. 最终效果

Bye~

相关推荐
l1x1n012 分钟前
No.3 笔记 | Web安全基础:Web1.0 - 3.0 发展史
前端·http·html
Q_w774217 分钟前
一个真实可用的登录界面!
javascript·mysql·php·html5·网站登录
昨天;明天。今天。28 分钟前
案例-任务清单
前端·javascript·css
一丝晨光1 小时前
C++、Ruby和JavaScript
java·开发语言·javascript·c++·python·c·ruby
Front思1 小时前
vue使用高德地图
javascript·vue.js·ecmascript
zqx_72 小时前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己2 小时前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称2 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色3 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2343 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js