带你用nodejs和websocket实现最简单的表格协同编辑

2,442 阅读3分钟

背景

在工作中, 钉钉表格、飞书表格的在线协同编辑想必大家都用过, 那这种是怎么实现的? 小伙伴们一定有所好奇, 今天我实现一个最简单的表格协同编辑,带大家探探路。

在线体验

地址 dc-query-9ggxcidy3d7a2fa3-1257124629.tcloudbaseapp.com/index.html

Kapture 2023-05-05 at 11.33.029.gif

技术选型

前端 vue3

服务端 midwayjs(阿里开源的基于nodejs的开发框架)

实现分析

前端页面

听取了同事的意见,用原生的table里 tr td里加 input当表格的前端页面。

服务端呢

1、 搭建一个websocket服务

2、响应前端的ws连接, 推送最新表格数据

3、如果已经连了多个用户, 此时其中一人编辑了之后,给服务端推最新表格数据, 服务端保存最新数据后,广播给其他所有人

4、前端呢,得提前订阅广播的事件, 接收到服务端广播的最新数据后, 渲染页面

至此 最简流程完成。

逻辑图

image.png

代码实现

前端

html部分

<template>
 <div>
  <table cellspacing="0" width="100%" style="margin-bottom: 1em">
   <tr v-for="(row, rowIndex) in list">
    <td v-for="(item, index) in row"><input @blur="(val) => onBlur_value(val, rowIndex, index)" :value="item.value" type="text"></td>
   </tr>
  </table>
 </div>
</template>

表格数据

格式 就是一个二维数据, 第一层代表tr, 第二层就是td了

const list = ref([
 [
  {
   value: '1'
  },
  {
   value: '1'
  }
 ],
 [
  {
   value: '1'
  },
  {
   value: '1'
  }
 ],
 [
  {
   value: '1'
  },
  {
   value: '1'
  }
 ]
])

就是这样 image.png

连接websocket服务

这边前端后都用的socket.io

import {ref, onBeforeMount, onBeforeUnmount} from 'vue'
import io from "socket.io-client";

const list = ref([])

let socket = null;

onBeforeMount(() => {
 socket = io('http://10.255.8.9:7001/table', {
  // reconnection: false, //关闭自动重连
 });
 console.log(socket.connected); // socket是否与服务器连接
 console.log(socket.disconnected); // socket是否与服务器断开连接

// 当服务器收到数据的时候时触发
 socket.on("data", (data) => {
  console.log(data)
  if(data.cmd === 'tableData') {
   list.value = data.data;
  }
 });
})

修改表格 推送数据

/**
 * input失焦事件
 * @param val
 * @param rowIndex
 * @param index
 */
const onBlur_value = (val, rowIndex, index) => {
 console.log(val.target.value)
 list.value[rowIndex][index].value = val.target.value
 socket.emit("data", {
  cmd: 'editTable', // cmd === 'editTable' 是服务端约定的修改表格数据的标识, 就跟后端接口路由一样
  data: list.value // 最新全量数据
 });
}

服务端

初始化一个midayjs应用

www.midwayjs.org/docs/quicks…

npm init midway

安装socket.io相关组件

www.midwayjs.org/docs/extens…

npm i @midwayjs/socketio@3 --save

安装完以后照着上面链接里的说明配置好一切

创建socket专属目录

src创建专门放socket服务的目录

├── package.json  
├── src  
│ ├── configuration.ts ## 入口配置文件  
│ ├── interface.ts  
│ └── socket ## socket.io 服务的文件  

创建table.ts

命名空间为 /table

import {
    WSController,
    OnWSConnection,
    Inject,
    OnWSMessage,
    WSEmit,
    Init,
    App
} from '@midwayjs/decorator';
import {Context, Application} from '@midwayjs/socketio';
import {OnWSDisConnection} from "@midwayjs/core";

/**
 * 客户端连接websocket
 */
@WSController('/table')
export class ClientController {

}   

table数据

let tableData = [
    [
        {
            value: '1'
        },
        {
            value: '1'
        }
    ],
    [
        {
            value: '1'
        },
        {
            value: '1'
        }
    ],
    [
        {
            value: '1'
        },
        {
            value: '1'
        }
    ]
]

响应连接

import {
    WSController,
    OnWSConnection,
    Inject,
    OnWSMessage,
    WSEmit,
    Init,
    App
} from '@midwayjs/decorator';
import {Context, Application} from '@midwayjs/socketio';
import {OnWSDisConnection} from "@midwayjs/core";


/**
 * 客户端连接websocket
 */
@WSController('/table')
export class ClientController {
    @Inject()
    ctx: Context;

    @App('socketIO')
    app: Application;

    // 客户端连接
    @OnWSConnection()
    async onConnectionMethod() {
        console.log('on client connect', this.ctx.id);
        // 发送链接成功提示
        this.ctx.emit('data', {
            cmd: 'connected',
            data: 'connected success'
        });
        // 推送最新table数据
        this.ctx.emit("data", {
            cmd: 'tableData',
            data: tableData
        });
    }
}   

接收表格修改的数据推送

/**
 * 响应客户端发送的数据
 * @param data
 */
@OnWSMessage('data')
@WSEmit('data')
async gotMessage(data = {cmd: '', data: {}}) {
    switch (data.cmd) {
        case "editTable":
            this.editTable(data)
            break;
        default:
            break;
    }
}

保存最新数据,广播给其他人

/**
 * 保存最新数据,广播给其他人
 * @param data
 */
editTable(data) {
    tableData = data.data
    console.log(data.data)

    this.ctx.broadcast.emit("data", {
        cmd: 'tableData',
        data: tableData
    });
}

最后

至此, 一个最简单的表格协同编辑就完成了。

后面继续解决如下问题

  • 显示在线编辑的用户
  • 实时显示每个人聚焦的单元格
  • 单元格争抢问题
  • 修改记录 等等等等

正儿八经商用的表格协同编辑还要考虑和解决诸多问题, 我们下回接着完善, 欢迎持续关注。

散会。