Svelte框架实现表格协同文档

首先,从框架搭建上,本篇示例采用当下流行的前后端分离的开发方式,前端使用npm作为脚手架搭建Svelte框架。 后端使用Java的SpringBoot作为后端框架。
首先,介绍下在前端Svelte框架下搭建在线表格编辑器。
1、在pageage.json文件中引入相关资源

   "@grapecity/spread-excelio": "15.2.5",
    "@grapecity/spread-sheets": "15.2.5",
    "@grapecity/spread-sheets-barcode": "15.2.5",
    "@grapecity/spread-sheets-charts": "15.2.5",
    "@grapecity/spread-sheets-designer": "15.2.5",
    "@grapecity/spread-sheets-designer-resources-cn": "15.2.5",
    "@grapecity/spread-sheets-languagepackages": "15.2.5",
    "@grapecity/spread-sheets-pdf": "15.2.5",
    "@grapecity/spread-sheets-pivot-addon": "15.2.5",
    "@grapecity/spread-sheets-pivots": "^14.0.0",
    "@grapecity/spread-sheets-print": "15.2.5",
    "@grapecity/spread-sheets-resources-zh": "15.2.5",
    "@grapecity/spread-sheets-shapes": "15.2.5",
    "@grapecity/spread-sheets-tablesheet": "15.2.5",

2、然后,集成在线表格编辑器Svelte组件版。在上一篇文章中,我们介绍了如何在Svelte框架中实现在线表格编辑器。
我们按照此思路新建一个SpreadSheet.svelte文件,写入基础在线表格编辑器。

<script>
import {onMount} from 'svelte';
import '@grapecity/spread-sheets-print';
import "@grapecity/spread-sheets-charts";
import '@grapecity/spread-sheets-shapes';
import '@grapecity/spread-sheets-pivot-addon';
import '@grapecity/spread-sheets-tablesheet';
import '@grapecity/spread-sheets-designer-resources-cn';
import '@grapecity/spread-sheets-designer';
import * as GC from '@grapecity/spread-sheets';
import * as GCDesigner from '@grapecity/spread-sheets-designer';

let designer = null;
onMount(async () => {
designer = new GCDesigner.Spread.Sheets.Designer.Designer(document.getElementById("designerHost"));
let spread = designer.getWorkbook();
});

</script>
<div id="designerHost" class="designer-host"></div>

<style scoped>
@import "@grapecity/spread-sheets-designer/styles/gc.spread.sheets.designer.min.css";
@import '@grapecity/spread-sheets/styles/gc.spread.sheets.excel2013white.css';

.designer-host {
width: 100%;
height: 100vh;
}

</style>

3、协同文档可能不止一个,我们需要在页面上创建一个文档列表,来允许用户选择编辑哪个文档,所以我们需要创建一个文档列表页面OnlineSheets.svelte。在此页面中,我们要实现路由跳转,和加载文档数据。
这里我们用了svelte-spa-router进行路由跳转 与isomorphic-fetch进行前后端数据传输。

<script>
    import {onMount} from 'svelte';
    import { link } from "svelte-spa-router";
    import {Utility} from "../utility.js";

    let docList = [];
    onMount(async () => {
        Utility.getDocList().then(result => {
            docList  = result.map((item,index)=>{
                return {
                    path:'/Spreadsheet/' + item.substring(0, item.lastIndexOf('.')),
                    index,
                    fileName:item
                }
            })
        });
    });
</script>
<main class="main">
    <table className='table' aria-labelledby="tabelLabel">
        <thead>
        <tr>
            <th>Document</th>
            <th></th>
        </tr>
        </thead>
        <tbody>
        {#each docList as docItem}
            <tr>
                <td>{docItem.index}</td>
                <td>{docItem.fileName}</td>
                <td className='row'>
                    <a use:link={docItem.path}> Open</a>
                </td>
            </tr>
        {/each}
        </tbody>
    </table>
</main>

以上代码实现了文档列表查看与文档跳转,使用 Open将跳转至前面设计好的在线表格编辑器中。
至此,前端的相关内容就准备好了,接下来搭建下后端工作。
后端的准备工作,首先安装gradle作为包管理器。当然,这里也可以用其他工具来代替,例如maven,或者源生引入jar包的方式将需要用到的jar包引入进来。之后创建springboot工程配合搭建gradle引用GCExcel以及后面协同需要用到的websocket。

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.4.3</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>2.4.3</version>
</dependency>

<dependency>
<groupId>com.grapecity.documents</groupId>
<artifactId>gcexcel</artifactId>
<version>4.0.3</version>
</dependency>

<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>

<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>10.0.2</version>
</dependency>

<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.5.0</version>
</dependency>
</dependencies>

这样子,我们做了框架的基本环境搭建,接下来我们介绍下如何搭建webSocket。
在SpreadSheet.svelte文件中写入如下代码建立webSocket链接:

    function connectDocument(docName) {
        if (webSocket != null) {
            return;
        }
        var ws = new WebSocket(Utility.webSocketUrl);  //'ws://localhost:8090/spreadjs'
        ws.onopen = function () {
            var data = {
                cmd: "connect",
                docID: docName
            }
            ws.send(JSON.stringify(data));
        }
        ws.onmessage = onmessage;
        webSocket = ws;
    }

接下来我们访问下文档列表页,从文档列表页跳转进入文档,进行编辑。

接下来我们需要监听前端发出的操作。这里因为在线表格编辑器本身将所有用户可能做的操作全部做了封装,所以省下了很多的功夫。

   onMount(async () => {
        //初始化Designer
        designer = new GCDesigner.Spread.Sheets.Designer.Designer(document.getElementById("designerHost"));
        let spread = designer.getWorkbook();
        //fromJSON
        openDocument(docName);
        //建立webSocket
        connectDocument(docName);
        var cm = spread.commandManager();
        cm.addListener('myListener', onCommandExecute)
    });

根据cmd去判断并且对命令再做一些简单封装,之后将封装过的命令发到服务端,之后通过websocket发同步指令:

  function onCommandExecute(args) {
        console.log(args.command);
        var command = args.command;
        var ServerCommand = null;

        switch (command.cmd) {
            case Utility.ServerCommands.EditCell:
                ServerCommand = {
                    sheetName: command.sheetName,
                    row: command.row,
                    column: command.col,
                    newValue: command.newValue
                }
                break;
            case Utility.ServerCommands.ResizeRow:
                ServerCommand = {
                    sheetName: command.sheetName,
                    rows: command.rows,
                    size: command.size
                };
                break;
            case Utility.ServerCommands.ResizeColumn:
                ServerCommand = {
                    sheetName: command.sheetName,
                    columns: command.columns,
                    size: command.size
                };
                break;
            case 'Designer.' + Utility.ServerCommands.SetFontFamily:
            case 'Designer.' + Utility.ServerCommands.SetFontSize:
            case 'Designer.' + Utility.ServerCommands.SetBackColor:
            case 'Designer.' + Utility.ServerCommands.SetForeColor:
            case 'Designer.' + Utility.ServerCommands.SetFontWeight:
            case 'Designer.' + Utility.ServerCommands.SetFontStyle:
            case 'Designer.' + Utility.ServerCommands.SetUnderline:
            case 'Designer.' + Utility.ServerCommands.SetDoubleUnderline:
                if (command.value && command.value.indexOf('undefined') === -1) {
                    ServerCommand = {
                        sheetName: command.sheetName,
                        selections: command.selections,
                        value: command.value
                    }
                }
                break;
            case Utility.ServerCommands.MoveFloatingObjects:
                ServerCommand = {
                    sheetName: command.sheetName,
                    floatingObjects: command.floatingObjects,
                    offsetX: command.offsetX,
                    offsetY: command.offsetY
                };
                break;
            case Utility.ServerCommands.ResizeFloatingObjects:
                ServerCommand = {
                    sheetName: command.sheetName,
                    floatingObjects: command.floatingObjects,
                    offsetX: command.offsetX,
                    offsetY: command.offsetY,
                    offsetWidth: command.offsetWidth,
                    offsetHeight: command.offsetHeight
                };
                break;
            case Utility.ServerCommands.InsertColumns:
            case Utility.ServerCommands.InsertRows:
                ServerCommand = {
                    sheetName: command.sheetName,
                    selections: command.selections
                };
                break;
            default:
        }

        if (ServerCommand != null) {

            var cmd = command.cmd;
            var dotIndex = cmd.lastIndexOf('.');
            if (dotIndex !== -1) {
                cmd = cmd.substring(dotIndex + 1);
            }
            ServerCommand.cmd = cmd;
            ServerCommand.docID = params.fileName;

            Utility.ExecuteCommandAtServer(ServerCommand);

            command.docID = ServerCommand.docID;
            webSocket.send(JSON.stringify(command))
        }
    }

当协同端通过websocket接收到请求的时候,使用onmessage方法做同步命令。这里在协同端执行command之前需要先撤销之前的监听,避免再发送websocket导致死循环。在执行之后,再次添加监听。

  function onmessage(message) {
        var command = JSON.parse(message.data);
        command._styles = null;
        let spread = designer.getWorkbook()
        var cm = spread.commandManager();
        cm.removeListener('myListener');

        spread.commandManager().execute(command);

        cm.addListener('myListener', onCommandExecute);
    }

至此,协同基础内容搭建结束,我们来看看编辑单元格内容后,发生了什么吧。
如下图所示,修改E4单元格内容,同时打开控制台网络tab。
将E4单元格数值2500改为2000,此时触发了EditCell事件,同时发出了交互指令:

此时新建一个窗口,复制链接,查看文档内容已经变为了2000。
如下动图所示:

拓展阅读

React + Springboot + Quartz,从0实现Excel报表自动化

电子表格也能做购物车?简单三步就能实现

使用纯前端类Excel表格控件SpreadJS构建企业现金流量表


葡萄城技术团队
葡萄城技术团队,分享技术文章。

葡萄城创建于1980年,是全球领先的软件开发技术和低代码平台提供商。以“赋能开发者”为使命,葡萄城致力...

2.5k 声望
20.4k 粉丝
0 条评论
推荐阅读
【文末领取】精算与金融建模行业解决方案白皮书,不要错过!
精算学是对人类社会所面临的各种风险及其他客观事务进行量化分析和处理的一门科学。在保险、金融、投资和各类风险管理等许多领域得到广泛应用,尤其在保险和社会保障领域,已成为不可或缺的科学和技术。以保险公...

葡萄城技术团队阅读 111

封面图
手把手教你写一份优质的前端技术简历
不知不觉一年一度的秋招又来了,你收获了哪些大厂的面试邀约,又拿了多少offer呢?你身边是不是有挺多人技术比你差,但是却拿到了很多大厂的offer呢?其实,要想面试拿offer,首先要过得了简历那一关。如果一份简...

tonychen153阅读 17.9k评论 5

封面图
正则表达式实例
收集在业务中经常使用的正则表达式实例,方便以后进行查找,减少工作量。常用正则表达式实例1. 校验基本日期格式 {代码...} {代码...} 2. 校验密码强度密码的强度必须是包含大小写字母和数字的组合,不能使用特殊...

寒青57阅读 8.6k评论 11

JavaScript有用的代码片段和trick
平时工作过程中可以用到的实用代码集棉。判断对象否为空 {代码...} 浮点数取整 {代码...} 注意:前三种方法只适用于32个位整数,对于负数的处理上和Math.floor是不同的。 {代码...} 生成6位数字验证码 {代码...} ...

jenemy49阅读 7.3k评论 12

再也不学AJAX了!(二)使用AJAX ① XMLHttpRequest
「再也不学 AJAX 了」是一个以 AJAX 为主题的系列文章,希望读者通过阅读本系列文章,能够对 AJAX 技术有更加深入的认识和理解,从此能够再也不用专门学习 AJAX。本篇文章为该系列的第二篇,最近更新于 2023 年 1...

libinfs42阅读 7k评论 12

封面图
CSS 绘制一只思否猫
欢迎关注我的公众号:前端侦探练习 CSS 有一个比较有趣的方式,就是发挥想象,绘制各式各样的图案,比如来绘制一只思否猫?思否猫,SegmentFault 思否的吉祥物,是一只独一无二、特立独行、热爱自由的(&gt;^ω^&lt...

XboxYan48阅读 3.3k评论 14

封面图
「多图预警」完美实现一个@功能
一天产品大大向 boss 汇报完研发成果和产品业绩产出,若有所思的走出来,劲直向我走过来,嘴角微微上扬。产品大大:boss 对我们的研发成果挺满意的,balabala...(内心 OS:不听,讲重点)产品大大:咱们的客服 I...

wuwhs32阅读 3.5k评论 5

封面图

葡萄城创建于1980年,是全球领先的软件开发技术和低代码平台提供商。以“赋能开发者”为使命,葡萄城致力...

2.5k 声望
20.4k 粉丝
宣传栏