找回密码
 立即注册

QQ登录

只需一步,快速开始

Lynn.Dou 讲师达人认证 悬赏达人认证 SpreadJS 开发认证
超级版主   /  发表于:2024-4-2 12:22  /   查看:1320  /  回复:0
本帖最后由 Lynn.Dou 于 2024-4-30 11:46 编辑

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

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

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

  1. // config中新增菜单项
  2.         var config = JSON.parse(JSON.stringify(GC.Spread.Sheets.Designer.DefaultConfig));
  3.         config.commandMap = {
  4.             myCopyCmd: {
  5.                 text: "列复制",
  6.                 commandName: "myCopyCmd",
  7.                 iconClass: "gc-spread-copy",
  8.                 visibleContext: "ClickColHeader",
  9.                 execute: async (context, propertyName, fontItalicChecked) => {
  10.                     var spread = context.getWorkbook();
  11.                     spread.commandManager().execute({ cmd: "contextMenuCopyCol" })
  12.                 }
  13.             },
  14.             myPasteCmd: {
  15.                 text: "列粘贴",
  16.                 commandName: "myPasteCmd",
  17.                 iconClass: "gc-spread-paste",
  18.                 visibleContext: "ClickColHeader",
  19.                 execute: async (context, propertyName, fontItalicChecked) => {
  20.                     var spread = context.getWorkbook();
  21.                     spread.commandManager().execute({ cmd: "contextMenuPasteCol" })
  22.                 }
  23.             },
  24.         }
  25.         config.contextMenu.splice(0, 0, "myCopyCmd");
  26.         config.contextMenu.splice(0, 0, "myPasteCmd");
  27.         // 工作簿,初始化且显示
  28.         var designer = new GC.Spread.Sheets.Designer.Designer("ss", config);
  29.         var spread = designer.getWorkbook();

  30.         // 注册自定义命令 - 列复制
  31.         spread.commandManager().register("contextMenuCopyCol",
  32.             {
  33.                 canUndo: true,
  34.                 execute: function (context, options, isUndo) {
  35.                     var Commands = GC.Spread.Sheets.Commands;
  36.                     // 在此加cmd
  37.                     options.cmd = "contextMenuCopyCol";
  38.                     if (isUndo) {
  39.                         Commands.undoTransaction(context, options);
  40.                         return true;
  41.                     } else {
  42.                         var sheet = spread.getActiveSheet();
  43.                         // 注意:需要在options中定义sheetName,否则撤销行为可能无效。
  44.                         options.sheetName = sheet.name();
  45.                         Commands.startTransaction(context, options);
  46.                         // 判断如果是选择的整列,则调用后端接口,将sel相关信息传递给后端GcExcel
  47.                         var sels = sheet.getSelections();
  48.                         console.log(sels);
  49.                         if (sels && sels.length > 0) {
  50.                             for (var i = 0; i < sels.length; i++) {
  51.                                 var sel = sels[i];
  52.                                 var row = sel.row;
  53.                                 var col = sel.col;
  54.                                 if (row == -1) {
  55.                                     console.log();
  56.                                     alert("整列复制");
  57.                                     // 根据实际业务需求,调用后端接口,将sel相关信息传递给后端GcExcel
  58.                                     var xhr = new XMLHttpRequest();
  59.                                     xhr.open("POST", "/sjs/copy", true);

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

  62.                                    xhr.onreadystatechange = function () {
  63.                                        if (xhr.readyState === XMLHttpRequest.DONE) {
  64.                                            if (xhr.status === 200) {
  65.                                                console.log(xhr.responseText);
  66.                                                var result = JSON.parse(xhr.responseText);
  67.                                                var code = result.code;

  68.                                                if (code === 200) {
  69.                                                    console.log("复制列成功")
  70.                                                }
  71.                                            } else {
  72.                                                console.log(xhr.status);
  73.                                                console.log(xhr.responseText);
  74.                                            }
  75.                                        }
  76.                                    };
  77.                                    xhr.send(formData);

  78.                                 }
  79.                             }
  80.                         }
  81.                         Commands.endTransaction(context, options);
  82.                         return true;
  83.                     }
  84.                 }
  85.             });

  86.         // 注册自定义命令 - 列粘贴
  87.         spread.commandManager().register("contextMenuPasteCol",
  88.         {
  89.             canUndo: true,
  90.             execute: function (context, options, isUndo) {
  91.                 var Commands = GC.Spread.Sheets.Commands;
  92.                 // 在此加cmd
  93.                 options.cmd = "contextMenuPasteCol";
  94.                 if (isUndo) {
  95.                     Commands.undoTransaction(context, options);
  96.                     return true;
  97.                 } else {
  98.                     var sheet = spread.getActiveSheet();
  99.                     // 注意:需要在options中定义sheetName,否则撤销行为可能无效。
  100.                     options.sheetName = sheet.name();
  101.                     Commands.startTransaction(context, options);
  102.                     // 判断如果是选择的整列,则调用后端接口,将sel相关信息传递给后端GcExcel
  103.                     var sels = sheet.getSelections();
  104.                     console.log(sels);
  105.                     if (sels && sels.length > 0) {
  106.                         for (var i = 0; i < sels.length; i++) {
  107.                             var sel = sels[i];
  108.                             var row = sel.row;
  109.                             var col = sel.col;
  110.                             if (row == -1) {
  111.                                 console.log();
  112.                                 alert("整列粘贴");
  113.                                 // 根据实际业务需求,调用后端接口,将sel相关信息传递给后端GcExcel
  114.                                 spread.save(function (blob) {
  115.                                    var xhr = new XMLHttpRequest();
  116.                                    xhr.open("POST", "/sjs/paste", true);
  117.                                    // 设置响应类型为blob
  118.                                    xhr.responseType = 'blob';
  119.                                    xhr.onload = function(e) {
  120.                                      if (this.status == 200) {
  121.                                        // 处理文件流
  122.                                        var file = new File([this.response],'test.sjs')
  123.                                        spread.open(file);
  124.                                        console.log("加载结束")
  125.                                      }
  126.                                    };

  127.                                    var formData = new FormData();
  128.                                    formData.append('sjs', blob);
  129.                                    formData.append('col', col);
  130.                                    xhr.send(formData);
  131.                                 }, function (e) {
  132.                                    console.log(e);
  133.                                 });
  134.                             }
  135.                         }
  136.                     }
  137.                     Commands.endTransaction(context, options);
  138.                     return true;
  139.                 }
  140.             }
  141.         });
复制代码
来看下后端GcExcel的处理逻辑:
对于复制列行为,GcExcel主要是获取并存储复制的列索引信息。

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

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

  7.         // 创建一个新的文件流来保存文件。
  8.         InputStream inputStream = null;
  9.         try {
  10.             inputStream = sjsFile.getInputStream();
  11.         } catch (FileNotFoundException e) {
  12.             // 处理文件未找到异常。
  13.             e.printStackTrace();
  14.         } catch (IOException e) {
  15.             throw new RuntimeException(e);
  16.         }
  17.         // 加载sjs文件。
  18.         workbook.open(inputStream, OpenFileFormat.Sjs);
  19.         // 调用后端接口,复制col所在列,粘贴至pasetCol
  20.         IWorksheet worksheet = workbook.getWorksheets().get("Sheet1");
  21.         worksheet.getRange("A1").setValue("test");
  22.         worksheet.getRange(-1, col, -1, 1).copy(worksheet.getRange(-1, pasetCol, -1, 1));
  23.         System.out.println("粘贴成功");
  24.         // 返回sjs文件
  25.         OutputStream outputStream = response.getOutputStream();
  26.         workbook.save(outputStream, SaveFileFormat.Sjs);
  27.         //workbook.save("data/test1.sjs");
  28.         outputStream.close();
  29.     }
复制代码


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

copyPaste.gif481101989.png
完整代码请参考附件demo。



image.png745792846.png

前后端交流Demo.zip

1.01 MB, 下载次数: 394

0 个回复

您需要登录后才可以回帖 登录 | 立即注册
返回顶部