Lynn.Dou 发表于 2024-4-2 12:22:02

SpreadJS大数据量复制粘贴思路方案

本帖最后由 Lynn.Dou 于 2024-4-30 11:46 编辑

背景:
SpreadJS提供了复制粘贴能力,可以满足用户基础的业务需求。但有用户反馈,对于几十万行甚至百万行的数据,如果直接选择整列进行复制粘贴,会出现卡顿问题,影响使用体验。
针对此类问题,建议大家先尝试升级至新版本进行测试,看是否可以满足当前业务需求。因为SpreadJS在版本迭代的过程中,性能也是在不断优化的。
但是如果数据量特别大,其实从前端入手也没有很好的优化空间了,因为复制的内容需要缓存,复制的内容越大,需要缓存的空间就越多,并且SpreadJS的复制粘贴不仅仅只是单纯的复制数据,还支持样式,格式等其他内容。粘贴的时候也需要针对每一个属性去按个分析,如果复制内容多的话,这里会损失比较多的性能。
所以,可以换个思路,借助后端GcExcel来解决。

思路:
针对单元格区域的复制粘贴行为,可以使用SpreadJS原生的复制粘贴菜单。
对于整列的数据,可以新增一个菜单项,将当前选中的列信息传递给后端GcExcel去同步处理复制粘贴逻辑,并将处理后的结果返回给SpreadJS进行显示,以此解决此类问题。

实现方式:
SpreadJS新增菜单项实现思路(以复制项为例,粘贴项同理):

// config中新增菜单项
      var config = JSON.parse(JSON.stringify(GC.Spread.Sheets.Designer.DefaultConfig));
      config.commandMap = {
            myCopyCmd: {
                text: "列复制",
                commandName: "myCopyCmd",
                iconClass: "gc-spread-copy",
                visibleContext: "ClickColHeader",
                execute: async (context, propertyName, fontItalicChecked) => {
                  var spread = context.getWorkbook();
                  spread.commandManager().execute({ cmd: "contextMenuCopyCol" })
                }
            },
            myPasteCmd: {
                text: "列粘贴",
                commandName: "myPasteCmd",
                iconClass: "gc-spread-paste",
                visibleContext: "ClickColHeader",
                execute: async (context, propertyName, fontItalicChecked) => {
                  var spread = context.getWorkbook();
                  spread.commandManager().execute({ cmd: "contextMenuPasteCol" })
                }
            },
      }
      config.contextMenu.splice(0, 0, "myCopyCmd");
      config.contextMenu.splice(0, 0, "myPasteCmd");
      // 工作簿,初始化且显示
      var designer = new GC.Spread.Sheets.Designer.Designer("ss", config);
      var spread = designer.getWorkbook();

      // 注册自定义命令 - 列复制
      spread.commandManager().register("contextMenuCopyCol",
            {
                canUndo: true,
                execute: function (context, options, isUndo) {
                  var Commands = GC.Spread.Sheets.Commands;
                  // 在此加cmd
                  options.cmd = "contextMenuCopyCol";
                  if (isUndo) {
                        Commands.undoTransaction(context, options);
                        return true;
                  } else {
                        var sheet = spread.getActiveSheet();
                        // 注意:需要在options中定义sheetName,否则撤销行为可能无效。
                        options.sheetName = sheet.name();
                        Commands.startTransaction(context, options);
                        // 判断如果是选择的整列,则调用后端接口,将sel相关信息传递给后端GcExcel
                        var sels = sheet.getSelections();
                        console.log(sels);
                        if (sels && sels.length > 0) {
                            for (var i = 0; i < sels.length; i++) {
                              var sel = sels;
                              var row = sel.row;
                              var col = sel.col;
                              if (row == -1) {
                                    console.log();
                                    alert("整列复制");
                                    // 根据实际业务需求,调用后端接口,将sel相关信息传递给后端GcExcel
                                    var xhr = new XMLHttpRequest();
                                    xhr.open("POST", "/sjs/copy", true);

                                 var formData = new FormData();
                                 formData.append('col', col);

                                 xhr.onreadystatechange = function () {
                                       if (xhr.readyState === XMLHttpRequest.DONE) {
                                           if (xhr.status === 200) {
                                             console.log(xhr.responseText);
                                             var result = JSON.parse(xhr.responseText);
                                             var code = result.code;

                                             if (code === 200) {
                                                   console.log("复制列成功")
                                             }
                                           } else {
                                             console.log(xhr.status);
                                             console.log(xhr.responseText);
                                           }
                                       }
                                 };
                                 xhr.send(formData);

                              }
                            }
                        }
                        Commands.endTransaction(context, options);
                        return true;
                  }
                }
            });

      // 注册自定义命令 - 列粘贴
      spread.commandManager().register("contextMenuPasteCol",
      {
            canUndo: true,
            execute: function (context, options, isUndo) {
                var Commands = GC.Spread.Sheets.Commands;
                // 在此加cmd
                options.cmd = "contextMenuPasteCol";
                if (isUndo) {
                  Commands.undoTransaction(context, options);
                  return true;
                } else {
                  var sheet = spread.getActiveSheet();
                  // 注意:需要在options中定义sheetName,否则撤销行为可能无效。
                  options.sheetName = sheet.name();
                  Commands.startTransaction(context, options);
                  // 判断如果是选择的整列,则调用后端接口,将sel相关信息传递给后端GcExcel
                  var sels = sheet.getSelections();
                  console.log(sels);
                  if (sels && sels.length > 0) {
                        for (var i = 0; i < sels.length; i++) {
                            var sel = sels;
                            var row = sel.row;
                            var col = sel.col;
                            if (row == -1) {
                              console.log();
                              alert("整列粘贴");
                              // 根据实际业务需求,调用后端接口,将sel相关信息传递给后端GcExcel
                              spread.save(function (blob) {
                                 var xhr = new XMLHttpRequest();
                                 xhr.open("POST", "/sjs/paste", true);
                                 // 设置响应类型为blob
                                 xhr.responseType = 'blob';
                                 xhr.onload = function(e) {
                                     if (this.status == 200) {
                                       // 处理文件流
                                       var file = new File(,'test.sjs')
                                       spread.open(file);
                                       console.log("加载结束")
                                     }
                                 };

                                 var formData = new FormData();
                                 formData.append('sjs', blob);
                                 formData.append('col', col);
                                 xhr.send(formData);
                              }, function (e) {
                                 console.log(e);
                              });
                            }
                        }
                  }
                  Commands.endTransaction(context, options);
                  return true;
                }
            }
      });来看下后端GcExcel的处理逻辑:
对于复制列行为,GcExcel主要是获取并存储复制的列索引信息。

@PostMapping("/copy")
    public R copyColumn(@RequestParam("col") int copyCol) throws IOException {
      System.out.println("success");
      col = copyCol;
      // 调用后端接口,复制col所在列,进行粘贴
      System.out.println("复制成功");
      // 一切正常
      return R.success(CodeEnum.SUCCESS, Constants.OK);
    }在粘贴时,则将SpreadJS导出的sjs文件流进行加载,并通过copy方法进行复制粘贴,最后再将修改后的sjs文件流返回给前端SpreadJS加载。
@PostMapping("/paste")
    public void pasteColumn(HttpServletResponse response, @RequestParam("sjs") MultipartFile sjsFile, @RequestParam("col") int pasetCol) throws IOException {
      System.out.println("success");

      // 设置响应内容类型为二进制流
      response.setContentType("application/octet-stream");
      response.setHeader("Content-Disposition", "attachment; filename=test.sjs");

      // 创建一个新的文件流来保存文件。
      InputStream inputStream = null;
      try {
            inputStream = sjsFile.getInputStream();
      } catch (FileNotFoundException e) {
            // 处理文件未找到异常。
            e.printStackTrace();
      } catch (IOException e) {
            throw new RuntimeException(e);
      }
      // 加载sjs文件。
      workbook.open(inputStream, OpenFileFormat.Sjs);
      // 调用后端接口,复制col所在列,粘贴至pasetCol
      IWorksheet worksheet = workbook.getWorksheets().get("Sheet1");
      worksheet.getRange("A1").setValue("test");
      worksheet.getRange(-1, col, -1, 1).copy(worksheet.getRange(-1, pasetCol, -1, 1));
      System.out.println("粘贴成功");
      // 返回sjs文件
      OutputStream outputStream = response.getOutputStream();
      workbook.save(outputStream, SaveFileFormat.Sjs);
      //workbook.save("data/test1.sjs");
      outputStream.close();
    }

来简单测试下效果(注:正式授权下不会多出试用提醒sheet):


完整代码请参考附件demo。



页: [1]
查看完整版本: SpreadJS大数据量复制粘贴思路方案