“持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情”
前言
这篇文章将主要介绍如何使用 luckysheet 进行实战应用。包括如何在 Vue 中进行简单的页面设计,如何在 mysql 数据库中进行数据存储,打通这个联调的流程,本文将记录这一过程遇到的坑。
一、前端页面编写
由上文中我们学习了 luckysheet 如何进行本地引用。并根据官网的例子搭建了一个简单的页面。而本文将对其进行简单应用于实战项目。
1.1 报表列表
真正的报表并不会直接展示 excel 的内容,不可能一个报表一个页面吧。所以我们需要一个列表进行维护,然后点击每条报表记录,进入每个luckysheet 页面进行详情编辑。
页面展示包含:报表名称、描述等几个字段。展示如下:
1.2 报表页面编写
1.2.1 数据查询
loadUrl 是工作表 options 的一个配置项,为 字符串的形式。配置完该对象后,能够加载所有工作表的配置,并包含当前页单元格数据。
源码的请求写法是:
$.post(loadurl, {"gridKey" : server.gridKey}, function (d) {})
所以,这是个 post 请求,并且只有个字符串,不能设置请求头和请求方式。如果要请求数据,就在后面url中拼接上所需的参数。例如下面这种。
loadUrl: "http://localhost:8081/getWorkBook?gridKey=1",
而 gridkey 可以理解为整个工作簿的唯一标识。也就是跟刚才跳转过来的页面相关。
因此,我把 gridkey 设置为 列表中报表的id,通过 route 请求传送过来。
关于 route 的请求发送和接收,并且一定得保证跳转过后的页面刷新后 gridkey 仍然存在,则可与使用这种方式:
- router.js 中配置
{
// 若gridkey后面?标识这个参数是可选的,不传 gridkey后面也不会导致页面无法访问
path:'/report/custom/luckysheet/:gridkey',
name:'reportCustom',
component: () => import('../views/report/customReport/luckysheet/Index.vue')
},
- 跳转传参
info(row){
var gridkey = row.id
this.$router.push(
{path:`/report/custom/luckysheet/${gridkey}`})
}
- 参数接收
this.gridkey=this.$route.params.gridkey
1.2.2 数据存储
常用的数据存储是可以利用 hook 函数来完成。luckysheet 本身提供了 hook 函数的写法。
hook: {
updated: function(e) {
//监听更新,并在3s后自动保存
if(autoSave)
clearTimeout(autoSave)
$(luckysheet_info_detail_save).text("已修改")
autoSave = setTimeout(function() {
var excel = luckysheet.getAllSheets();
//去除临时数据,减小体积
for(var i in excel)
excel[i].data = undefined
$.post("http://127.0.0.1:8081/setWorkBook", {
jsonExcel: JSON.stringify(excel)
}, function() {
$(luckysheet_info_detail_save).text("已保存")
})
}, 1 * 300)
return true;
}
但是我在集成进 Vue 项目中,无法识别到普通方法,这可能是 Vue 的生命周期与它的 hook 生命周期不同。因此,用 Jquery 原生的请求方式调用即可。
我这块就不使用 hook 的监听保存功能了。
直接定义一个按钮,点击保存即可。
// 数据存储
setWorkBook(){
var excel = luckysheet.getAllSheets();
//去除临时数据,减小体积
for(var i in excel){
excel[i].data = undefined
}
this.$api.req("/report/custom/setWorkBook",{
json: JSON.stringify(excel),
gridKey:this.gridkey},(res)=>{
this.$message.success("保存成功")
},(res) =>{
this.$message.error("保存失败")
})
},
测试页面如下:
二、后端数据存储
2.1 存储设计流程
创建存储表
CREATE TABLE `web_sub_report_work_book_sheet` (
`id` bigint NOT NULL AUTO_INCREMENT,
`grid_key` bigint NOT NULL COMMENT '唯一标识',
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '表名称',
`block_data_json` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '压缩数据',
`block_index` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '工作表唯一标识',
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`delete_flag` tinyint DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
- gridkey 用来区分是哪个报表
- blok_index 则用来区分是哪个 sheet
- 最终所有的数据打包成 json数据经过压缩后存入 block_data_json 中。
存储流程如下:
前端发送请求体:
{
gridkey: 1,
json:{}
}
保存代码
/**
* 工作簿数据存储
* @param requestData
* @return
*/
@Override
@Transactional
public ResponseData setWorkBook(RequestData<SetWorkBookDTO, SubUserVO> requestData) {
SetWorkBookDTO data = requestData.getData();
if(data == null || ObjectUtil.isNull(data.getGridKey()) || StringUtils.isEmpty(data.getJson())){
return ResponseData.failureResponse(ReportCode.REPORT_CODE_50102);
}
// 记录本次gridkey相关的表格
List<String> okSheet = new ArrayList<>();
List<String> objects = JSONArray.parseArray(StringEscapeUtils.unescapeJava(data.getJson()), String.class);
objects.stream().forEach(e->{
String id = "";
String name = "";
String index = "";
Map<String, Object> map = JSON.parseObject((String)e);
for (String key : map.keySet()) {
if ("id".equals(key)) {
id = map.get(key).toString();
}
if ("name".equals(key)) {
name = map.get(key).toString();
}
if ("index".equals(key)) {
index = map.get(key).toString();
}
}
WorkBookSheet wb = new WorkBookSheet();
wb.setGridKey(data.getGridKey());
wb.setName(name);
wb.setBlockIndex(index);
if(StringUtils.isEmpty(id)){
// 解压数据,需要把 值赋予到json中给前端,不然id每次都是空的
if(workBookSheetMapper.insert(wb)>0){
map.put("id",wb.getId());
map.put("index",wb.getBlockIndex());
String dataStr = JSON.toJSONString(map);
wb.setBlockDataJson(CompressUtils.compress(dataStr));
workBookSheetMapper.updateById(wb);
}
}else{
String dataStr = JSON.toJSONString(map);
String ysStr = CompressUtils.compress(dataStr);
wb.setId(id);
wb.setBlockDataJson(ysStr);
workBookSheetMapper.updateById(wb);
}
okSheet.add(wb.getBlockIndex());
});
// 删除不在 idList 里面的数据,因为不在里面说明前端删除了该工作簿
LambdaQueryWrapper<WorkBookSheet> delWrapper = Wrappers.lambdaQuery();
delWrapper.eq(WorkBookSheet::getGridKey,data.getGridKey()).notIn(WorkBookSheet::getBlockIndex, okSheet);
workBookSheetMapper.delete(delWrapper);
return ResponseData.successResponse();
}
2.2 数据压缩工具类
如果直接存储,那这个数据量是非常可怕的,如果要存入数据库中,最好要做个压缩操作
/**
* 字符串解压工具类
* @author xiaolei
* @version 1.0
* @date 2022-05-30 17:22
*/
public class CompressUtils {
/**
* @param str:正常的字符串
* @return 压缩字符串
*/
public static String compress(String str) {
try {
if (str == null || str.length() == 0) {
return str;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPOutputStream gzip = new GZIPOutputStream(out);
gzip.write(str.getBytes());
gzip.close();
return out.toString(String.valueOf(StandardCharsets.ISO_8859_1));
} catch (Exception ex) {
return null;
}
}
/**
* @param str:压缩后字符串
* @return 解压字符串 生成正常字符串。
*/
public static String uncompress(String str) {
try {
if (str == null || str.length() == 0) {
return str;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(str.getBytes(StandardCharsets.ISO_8859_1));
GZIPInputStream gunzip = new GZIPInputStream(in);
byte[] buffer = new byte[256];
int n;
while ((n = gunzip.read(buffer)) >= 0) {
out.write(buffer, 0, n);
}
// toString()使用平台默认编码,也可以显式的指定如toString("GBK")
return out.toString();
} catch (Exception ex) {
return ex.getMessage();
}
}
}
2.3 数据查询
// 默认的表格数据
public static String initData ="[{"name":"Cell","celldata":null}]";
@Override
public String getWorkBookById(String gridKey) {
if(StringUtils.isEmpty(gridKey)){
return null;
}
LambdaQueryWrapper<WorkBookSheet> wrapper = new LambdaQueryWrapper<WorkBookSheet>().eq(WorkBookSheet::getGridKey,gridKey);
List<WorkBookSheet> workBookSheets = workBookSheetMapper.selectList(wrapper);
// 如果 mysql 中没有找到,就返回虚数据
if(workBookSheets.size()<=0){
return new String(initData.getBytes(StandardCharsets.UTF_8));
}
List<Object> collect = workBookSheets.stream().map(e -> {
return JSONObject.parse(CompressUtils.uncompress(e.getBlockDataJson()));
}).collect(Collectors.toList());
return JSON.toJSONString(collect);
}
最终生成的数据如下
这里是一个简单的例子,在 Vue 中集成使用 Luckysheet,并对其进行数据保存入库,前后端流程打通,后面再根据自己的业务进行自定义需求开发。