本帖最后由 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[i];
- 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[i];
- 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([this.response],'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。
|
|