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]