前些天看到Luckysheet支持协同编辑Excel,正符合我们协同项目的一部分,故而想进一步完善协同文章,但是遇到了一下困难,特此做声明哈,若侵权,请联系我删除文章!

若侵犯版权、个人隐私,请联系删除哈!!!(我可不想踩缝纫机)
Luckysheet ,一款纯前端类似excel的在线表格,功能强大、配置简单、完全开源。当然,也原生支持协同,下面,我们针对协同部分做详细讲解。官网使用的是Java,也有协同的Demo,我就不说了,下面用 Node 实现协同,完整的样例如下,我们开始吧

- <link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/css/pluginsCss.css' />
- <link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/plugins.css' />
- <link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/luckysheet/dist/css/luckysheet.css' />
- <link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/luckysheet/dist/assets/iconfont/iconfont.css' />
- <script src="https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/js/plugin.js">script>
- <script src="https://cdn.jsdelivr.net/npm/luckysheet/dist/luckysheet.umd.js">script>
Luckysheet: 🚀Luckysheet ,一款纯前端类似excel的在线表格,功能强大、配置简单、完全开源。
https://gitee.com/mengshukeji/Luckysheet 官网建议我们在上网址下载完整的包,这样,我们得到的是luckysheet的源码,可以进行二次开发。很重要哈,最后我们也会这样做。

npm i --s // 执行 npm 命令,进行依赖包的下载
npm run build // 执行打包命令(二次开发是需要修改源码的)
把dist包放到自己的项目中,我已经更名了哈:

然后,index.html 直接引入这个地址的文件就行了(二开一定是引这个地址哈)。
-
- <link rel='stylesheet' href='./luckysheet/dist/plugins/css/pluginsCss.css' />
- <link rel='stylesheet' href='./luckysheet/dist/plugins/plugins.css' />
- <link rel='stylesheet' href='./luckysheet/dist/css/luckysheet.css' />
- <link rel='stylesheet' href='./luckysheet/dist/assets/iconfont/iconfont.css' />
-
- <script src="./luckysheet/dist/plugins/js/plugin.js">script>
- <script src="./luckysheet/dist/luckysheet.umd.js">script>
这个方式建议大家都试试,二次开发一定是这个方式哈!
如果大家觉得不用二开,就是用原生的功能 ,那直接使用 npm 下载就行了。
npm i luckysheet
- <link rel='stylesheet' href='./node_modules/luckysheet/dist/plugins/css/pluginsCss.css' />
- <link rel='stylesheet' href='./node_modules/luckysheet/dist/plugins/plugins.css' />
- <link rel='stylesheet' href='./node_modules/luckysheet/dist/css/luckysheet.css' />
- <link rel='stylesheet' href='./node_modules/luckysheet/dist/assets/iconfont/iconfont.css' />
- <script src="./node_modules/luckysheet/dist/plugins/js/plugin.js">script>
- <script src="./node_modules/luckysheet/dist/luckysheet.umd.js">script>
<div id="luckysheet" style="margin:0px;padding:0px;position:absolute;width:100%;height:100%;left: 0px;top: 0px;">div>
- onMounted(() => {
- // 初始化表格
- var options = {
- container: "luckysheet", //luckysheet为容器id
- };
- luckysheet.create(options);
- });

这样就已经是一个完善的表格编辑器了,支持函数、图表、填充等多项功能。

因此,我们分别配置这几个参数:
配置loadUrl接口地址,加载所有工作表的配置,并包含当前页单元格数据,与loadSheetUrl配合使用。参数为gridKey(表格主键)
$.post(loadurl, {"gridKey" : server.gridKey}, function (d) {})
源码写法如上,因此,我们需要创建一个 post请求的地址:

app.use("/excel", excelRouter); // 添加公共前缀
配置 loadUrl,加了 baseURL是做了请求代理哈
- allowUpdate: true,
- loadUrl: "/baseURL/excel",
接口要求返回以下数据,我们直接复制,然后返回:
- "[
- //status为1的sheet页,重点是需要提供初始化的数据celldata
- {
- "name": "Cell",
- "index": "sheet_01",
- "order": 0,
- "status": 1,
- "celldata": [{"r":0,"c":0,"v":{"v":1,"m":"1","ct":{"fa":"General","t":"n"}}}]
- },
- //其他status为0的sheet页,无需提供celldata,只需要配置项即可
- {
- "name": "Data",
- "index": "sheet_02",
- "order": 1,
- "status": 0
- },
- {
- "name": "Picture",
- "index": "sheet_03",
- "order": 2,
- "status": 0
- }
- ]"
本例中,只返回一个sheet表,初始化 0 0 单元格内容为 ‘默认数据’
- router.post("/", (req, res, next) => {
- // console.log("lucySheet");
- let sheetData = [
- //status为1的sheet页,重点是需要提供初始化的数据celldata
- {
- name: "Cell",
- index: "sheet_01",
- order: 0,
- status: 1,
- celldata: [
- {
- r: 0,
- c: 0,
- v: { v: "默认数据", m: "111", ct: { fa: "General", t: "n" } },
- },
- ],
- },
- ];
- res.json(JSON.stringify(sheetData));
- });


操作表格后,实时保存数据的websocket地址,此接口也是共享编辑的接口地址。注意,发送给后端的数据默认是经过pako压缩过后的。后台拿到数据需要先解压。通过共享编辑功能,可以实现Luckysheet实时保存数据和多人同步数据,每一次操作都会发送不同的参数到后台
因此,我们需要初始化一个 ws 连接:
- module.exports = () => {
- console.log("等待初始化 WS 服务...");
- // 搭建ws服务器
- const { WebSocketServer } = require("ws");
-
- const wss = new WebSocketServer({ port: 9000 });
-
- console.log(" WS 服务初始化成功,连接地址:ws://localhost:9000");
-
- wss.on("connection", (ws, req) => {
- console.log("用户连接");
- });
- };
打开控制台,可以看到连接成功的提示,我们可以一下源码是怎么处理的:

除了看到输出语句外,我们更应该关注一个 send 事件,因为 websocket 是通过send 发送数据的,还有的是pako.gzip()压缩。因此,服务端监听 message 获取数据:

至此,我们可以获取一些基础信息:
也就是这样,我也不知道如何进行下去了,就加了官方的微信,就发生了篇头的那张截图。但是革命还在继续。加了官网微信群,特此感谢【小李飞刀刀】的指导。

- const pako = require("pako");
-
- /**
- * @DESC 导出解压方法
- * @param { string } str
- * @returns
- */
- exports.unzip = (str) => {
- let chartData = str
- .toString()
- .split("")
- .map((i) => i.charCodeAt(0));
-
- let binData = new Uint8Array(chartData);
-
- let data = pako.inflate(binData);
-
- return decodeURIComponent(
- String.fromCharCode.apply(null, new Uint16Array(data))
- );
- };

得到上图,就知道该怎么办了吧,映射的是用户的所有操作哈。需要添加用户标记
- let id = Math.random().toString().split(".")[1].slice(0, 3);
-
- // 需要添加自定义属性
- ws.wid = id;
-
- ws.wname = "user_" + id;
我们一定要看源码是如何处理的哈,官网文档并没有那么详细:

因此,同步光标的时候,我们应该发送type =3 的数据,我们封装ws的事件响应中心:
- // wss.clients 所有的客户端
- wss.clients.forEach((conn) => {
- // 不发送给自己
- if (conn.wid === ws.wid) return;
- // 使得 this 指向当前连接对象
- wshandle.call(conn, unzip(data));
- });

我们还没做数据同步哈,因此数据没有显示,不影响,先显示用户光标。
- /**
- * ws 事件响应中心
- * 根据不同的事件,返回不同的数据
- * type 1 成功/失败
- * type 2 更新数据
- * type 3 用户光标
- * type 4 批量处理数据
- */
- function wshandle(data) {
- // 表示用户移动鼠标 实际是需要根据指令实现不同的响应的,但是这里统一做 更新数据
- this.send(callbackdata.call(this, data, JSON.parse(data).t === "mv" ? 3 : 2));
- }

至此,协同好像已经实现了,但是还没完。
源码中需要返回 {message ,id} 两个数据,因此直接封装 退出函数:
- /**
- * 用户退出
- */
- function exit() {
- this.send(JSON.stringify({ message: "用户退出", id: this.wid }));
- }
监听ws close 事件:
- ws.on("close", (ws) => {
- try {
- // 实现用户退出
- wss.clients.forEach((conn) => {
- if (conn.wid === ws.wid) return;
- // 使得 this 指向当前连接对象
- exit.call(conn);
- });
- } catch (error) {
- console.log(error);
- }
- });


不知道大家发现没有,当多人协作时,我们的用户id 是错的,原因是我们move时,传的参数不对:

- // 使得 this 指向当前连接对象 ,并且保证,操作对象始终是当前用户
- wshandle.call(conn, { id: ws.wid, name: ws.wname }, unzip(data));
-
- // 表示用户移动鼠标 实际是需要根据指令实现不同的响应的,但是这里统一做 更新数据
- // 手动传输 user
- this.send(callbackdata(user, data, JSON.parse(data).t === "mv" ? 3 : 2));
-
- // function callback:
-
- return JSON.stringify({
- createTime: dayjs().format("YYYYMMHH mm:hh:ss"),
- data,
- id: user.id,
- returnMessage: "success",
- status: 0,
- type,
- username: user.name,
- });
表格操作完成后,使用luckysheet.getAllSheets()方法获取到全部的工作表数据,全部发送到后台存储。

协同存储就是用户的每次操作,都会触发 websocket,因此,我们直接在websocket中调用控制层,实现数据的更新,举例说明:
- ## Excel 协同数据存储 文件基础信息表 workbooks
-
- - gridKey 唯一key 【主键】
- - lang 语言
- - column 空表格默认的列数量
- - row 空表格默认的行数据量
- - fileid 关联的文件 ==> 通过关联获取 luckysheet title 字段
- - ... 更多字段,根据项目实际情况添加
-
- ## sheet 信息表 worksheets
-
- - index 工作表索引 【表主键】
- - gridKey 【外键】
- - name 工作表名称
- - status 激活状态
- - order 工作表的下标
- - hide 是否隐藏
- - row 行数
- - column 列数
- - defaultRowHeight 自定义行高
- - defaultColWidth 自定义列宽
-
- ## celldata 数据表 celldatas
-
- - cdid 表主键
- - index 【表外键 => 实现关联 worksheets】
- - r 行
- - c 列
- - ctfa 格式名称为自动格式 (要构建成对象)
- - ctt 格式类型为数字类型 (要构建成对象)
- - v 内容的原始值为
- - m 内容的显示值为
- - bg 背景为
- - ff 字体为
- - fc 字体颜色为
- - bl 字体加粗
- - it 字体斜体
- - fs 字体大小为
- - cl 启用删除线
- - ht 水平居中
- - vt 垂直居中
- - tr 文字旋转
- - tb 文本自动换行
- - f 单元格是一个求和公式
- - lasteditor 最后编辑者 可以实现单元格的编辑历史追踪
有了表的基础数据后,我们初始化的时候就可以查询数据库实现表格渲染:
- /** 关键函数: fileid 获取初始化 luckysheet方法 */
- exports.initLuckysheet = async (req, res, next) => {
- let { fileid } = req.query;
-
- if (!fileid) return httpCode(res); // 参数缺失,直接返回
-
- /**
- * 根据id查找数据库,组装数据返回
- * 刚建的文件,有可能没有,因此需要默认返回数据,并且将这么默认数据作为一条记录
- */
-
- // 1. 通过 fileid 查询 gridKey
- let bookInfo = await univerImpl.getWorkBookImpl(fileid);
- if (!bookInfo.length) return httpCode(res);
- let { gridKey } = bookInfo[0];
-
- // 2. 通过 gidKey 查询 sheet 数据 这里有可能有空值
- let sheetInfo = await univerImpl.getSheetDataByGridKeyImpl(gridKey);
- if (!sheetInfo.length) {
- // 如果没有,则创建,并返回空表格 【注意:此处只返回一个 sheet 页,不然浪费空间】
- // 创建记录供用户保存
- let index = await getNanoid();
- await univerImpl.createWorkSheetImpl({
- gridKey,
- name: "sheet 1",
- index, // 工作表索引 【唯一ID】
- status: 0, // 激活状态
- order: 0, // 工作表的下标
- });
-
- return res.json(JSON.stringify([{ name: "sheet 1", celldata: [] }]));
- }
-
- // 3. 通过 index 查询 celldata 数据
- let { index } = sheetInfo[0];
- let cellInfo = await univerImpl.getCellDataByIndexImpl(index);
- /** 数据可能是空,也可能是多个,但是注意 ctfa 、ctt 是需要构建成对象返回的,数据库做了扁平化存储 */
- if (!cellInfo || !cellInfo.length)
- return res.json(JSON.stringify([{ ...sheetInfo[0], celldata: [] }]));
-
- // 4. 组装数据
- let celldata = [];
-
- // 注意luckysheet 需要的数据格式
- cellInfo.forEach((i) =>
- celldata.push({ r: i.r, c: i.c, v: { ...i, ct: { fa: i.ctfa, t: i.ctt } } })
- );
-
- // 5. 返回数据
- return res.json(JSON.stringify([{ ...sheetInfo[0], celldata }]));
- };
用户每次更新luckysheet,都会触发 ws 事件,实现数据协同存储:
- // * v 单个单元格刷新
- async function v() {
- // 获取cdid
- let cdid = await getNanoid();
- // 1. 先获取行列信息 当前sheet的index值
- let findRes = await univerImpl.findCellDataByRCImpl(this);
- // 2. 判断当前行是否已经有数据
- findRes.length
- ? await univerImpl.updateCellDataImpl(this)
- : // 3. 有则更新、没有则新增
- await univerImpl.insertCellDataImpl(this, cdid);
- }
以上只是列举思路,完成的代码可以到 git上查看哈,整体的逻辑还是挺多的。
两种方式实现哈,先隐藏默认,然后自定定位实现添加按钮,或者根据配置项实现配置
- /deep/.luckysheet_info_detail_save,
- /deep/.luckysheet_info_detail_update {
- display: none;
- }

npm i luckyexcel
绑定了一个 input ref='importFileRef'
- const importFileHandle = (e) => {
- let { files } = e.target;
- LuckyExcel.transformExcelToLucky(files[0], (exportJson, luckysheetfile) => {
- luckysheet.create({
- container: "luckysheet", // luckysheet is the container id
- data: exportJson.sheets,
- title: exportJson.info.name,
- userInfo: exportJson.info.name.creator,
- });
- // 清空
- importFileRef.value.value = "";
- });
- };

但是这样会丢失协同性:
- // 文件导入
- const importFileHandle = (e) => {
- let { files } = e.target;
- LuckyExcel.transformExcelToLucky(files[0], (exportJson, luckysheetfile) => {
- // 【会丢失协同性】
- // luckysheet.create({
- // container: "luckysheet", // luckysheet is the container id
- // data: exportJson.sheets,
- // title: exportJson.info.name,
- // userInfo: exportJson.info.name.creator,
- // });
-
- let { info, sheets } = exportJson;
-
- luckysheet.setWorkbookName(info.name);
-
- sheets.forEach((sheet) => {
- // sheet 便是每一个 sheet 页,需要根据实际的数量动态创建
- luckysheet.setSheetAdd({
- sheetObject: sheet,
- });
- });
- // 清空
- importFileRef.value.value = "";
- });
- };

npm i exceljs file-saver
- import Excel from "exceljs";
-
- import FileSaver from "file-saver";
-
- import { ElMessage } from "element-plus";
-
- export const exportExcel = async (name, luckysheet) => {
- // 获取 buffer
- let buffer = await getBuffer(luckysheet);
- download(name, buffer);
- };
-
- /**
- * 使用 fileSaver 进行文件保存操作
- * @param {Buffer} buffer
- */
- function download(name, buffer) {
- try {
- const blob = new Blob([buffer], {
- type: "application/vnd.ms-excel;charset=utf-8",
- });
- FileSaver.saveAs(blob, `${name}.xlsx`);
- ElMessage.success("文件导出成功");
- } catch (error) {
- ElMessage.error("文件导出失败");
- }
- }
-
- /**
- *
- * @param { Array as luckysheet.getluckysheetfile() } luckysheet
- * @returns
- */
- async function getBuffer(luckysheet) {
- // 参数为luckysheet.getluckysheetfile()获取的对象
- // 1.创建工作簿,可以为工作簿添加属性
- const workbook = new Excel.Workbook();
- // 2.创建表格,第二个参数可以配置创建什么样的工作表
- luckysheet.every(function (table) {
- if (table.data.length === 0) return true;
- const worksheet = workbook.addWorksheet(table.name);
- // 3.设置单元格合并,设置单元格边框,设置单元格样式,设置值
- setStyleAndValue(table.data, worksheet);
- setMerge(table.config.merge, worksheet);
- setBorder(table.config.borderInfo, worksheet);
- return true;
- });
- // 4.写入 buffer
- const buffer = await workbook.xlsx.writeBuffer();
- return buffer;
- }
-
- var setMerge = function (luckyMerge = {}, worksheet) {
- const mergearr = Object.values(luckyMerge);
- mergearr.forEach(function (elem) {
- // elem格式:{r: 0, c: 0, rs: 1, cs: 2}
- // 按开始行,开始列,结束行,结束列合并(相当于 K10:M12)
- worksheet.mergeCells(
- elem.r + 1,
- elem.c + 1,
- elem.r + elem.rs,
- elem.c + elem.cs
- );
- });
- };
-
- var setBorder = function (luckyBorderInfo, worksheet) {
- if (!Array.isArray(luckyBorderInfo)) {
- return;
- }
- // console.log('luckyBorderInfo', luckyBorderInfo)
- luckyBorderInfo.forEach(function (elem) {
- // 现在只兼容到borderType 为range的情况
- // console.log('ele', elem)
- if (elem.rangeType === "range") {
- let border = borderConvert(elem.borderType, elem.style, elem.color);
- let rang = elem.range[0];
- // console.log('range', rang)
- let row = rang.row;
- let column = rang.column;
- for (let i = row[0] + 1; i < row[1] + 2; i++) {
- for (let y = column[0] + 1; y < column[1] + 2; y++) {
- worksheet.getCell(i, y).border = border;
- }
- }
- }
- if (elem.rangeType === "cell") {
- // col_index: 2
- // row_index: 1
- // b: {
- // color: '#d0d4e3'
- // style: 1
- // }
- const { col_index, row_index } = elem.value;
- const borderData = Object.assign({}, elem.value);
- delete borderData.col_index;
- delete borderData.row_index;
- let border = addborderToCell(borderData, row_index, col_index);
- // console.log('bordre', border, borderData)
- worksheet.getCell(row_index + 1, col_index + 1).border = border;
- }
- // console.log(rang.column_focus + 1, rang.row_focus + 1)
- // worksheet.getCell(rang.row_focus + 1, rang.column_focus + 1).border = border
- });
- };
- var setStyleAndValue = function (cellArr, worksheet) {
- if (!Array.isArray(cellArr)) {
- return;
- }
- cellArr.forEach(function (row, rowid) {
- // const dbrow = worksheet.getRow(rowid+1);
- // //设置单元格行高,默认乘以1.2倍
- // dbrow.height=luckysheet.getRowHeight([rowid])[rowid]*1.2;
- row.every(function (cell, columnid) {
- if (rowid == 0) {
- const dobCol = worksheet.getColumn(columnid + 1);
- //设置单元格列宽除以8
- dobCol.width = luckysheet.getColumnWidth([columnid])[columnid] / 8;
- }
- if (!cell) {
- return true;
- }
- //设置背景色
- let bg = cell.bg || "#FFFFFF"; //默认white
- bg = bg === "yellow" ? "FFFF00" : bg.replace("#", "");
- let fill = {
- type: "pattern",
- pattern: "solid",
- fgColor: { argb: bg },
- };
- let font = fontConvert(
- cell.ff,
- cell.fc,
- cell.bl,
- cell.it,
- cell.fs,
- cell.cl,
- cell.ul
- );
- let alignment = alignmentConvert(cell.vt, cell.ht, cell.tb, cell.tr);
- let value = "";
-
- if (cell.f) {
- value = { formula: cell.f, result: cell.v };
- } else if (!cell.v && cell.ct && cell.ct.s) {
- // xls转为xlsx之后,内部存在不同的格式,都会进到富文本里,即值不存在与cell.v,而是存在于cell.ct.s之后
- // value = cell.ct.s[0].v
- cell.ct.s.forEach((arr) => {
- value += arr.v;
- });
- } else {
- value = cell.v;
- }
- // style 填入到_value中可以实现填充色
- let letter = createCellPos(columnid);
- let target = worksheet.getCell(letter + (rowid + 1));
- // console.log('1233', letter + (rowid + 1))
- for (const key in fill) {
- target.fill = fill;
- break;
- }
- target.font = font;
- target.alignment = alignment;
- target.value = value;
-
- return true;
- });
- });
- };
-
- var fontConvert = function (
- ff = 0,
- fc = "#000000",
- bl = 0,
- it = 0,
- fs = 10,
- cl = 0,
- ul = 0
- ) {
- // luckysheet:ff(样式), fc(颜色), bl(粗体), it(斜体), fs(大小), cl(删除线), ul(下划线)
- const luckyToExcel = {
- 0: "微软雅黑",
- 1: "宋体(Song)",
- 2: "黑体(ST Heiti)",
- 3: "楷体(ST Kaiti)",
- 4: "仿宋(ST FangSong)",
- 5: "新宋体(ST Song)",
- 6: "华文新魏",
- 7: "华文行楷",
- 8: "华文隶书",
- 9: "Arial",
- 10: "Times New Roman ",
- 11: "Tahoma ",
- 12: "Verdana",
- num2bl: function (num) {
- return num === 0 ? false : true;
- },
- };
- // 出现Bug,导入的时候ff为luckyToExcel的val
-
- //设置字体颜色
- fc = fc === "red" ? "FFFF0000" : fc.replace("#", "");
- let font = {
- name: typeof ff === "number" ? luckyToExcel[ff] : ff,
- family: 1,
- size: fs,
- color: { argb: fc },
- bold: luckyToExcel.num2bl(bl),
- italic: luckyToExcel.num2bl(it),
- underline: luckyToExcel.num2bl(ul),
- strike: luckyToExcel.num2bl(cl),
- };
-
- return font;
- };
-
- var alignmentConvert = function (
- vt = "default",
- ht = "default",
- tb = "default",
- tr = "default"
- ) {
- // luckysheet:vt(垂直), ht(水平), tb(换行), tr(旋转)
- const luckyToExcel = {
- vertical: {
- 0: "middle",
- 1: "top",
- 2: "bottom",
- default: "top",
- },
- horizontal: {
- 0: "center",
- 1: "left",
- 2: "right",
- default: "left",
- },
- wrapText: {
- 0: false,
- 1: false,
- 2: true,
- default: false,
- },
- textRotation: {
- 0: 0,
- 1: 45,
- 2: -45,
- 3: "vertical",
- 4: 90,
- 5: -90,
- default: 0,
- },
- };
-
- let alignment = {
- vertical: luckyToExcel.vertical[vt],
- horizontal: luckyToExcel.horizontal[ht],
- wrapText: luckyToExcel.wrapText[tb],
- textRotation: luckyToExcel.textRotation[tr],
- };
- return alignment;
- };
-
- var borderConvert = function (borderType, style = 1, color = "#000") {
- // 对应luckysheet的config中borderinfo的的参数
- if (!borderType) {
- return {};
- }
- const luckyToExcel = {
- type: {
- "border-all": "all",
- "border-top": "top",
- "border-right": "right",
- "border-bottom": "bottom",
- "border-left": "left",
- },
- style: {
- 0: "none",
- 1: "thin",
- 2: "hair",
- 3: "dotted",
- 4: "dashDot", // 'Dashed',
- 5: "dashDot",
- 6: "dashDotDot",
- 7: "double",
- 8: "medium",
- 9: "mediumDashed",
- 10: "mediumDashDot",
- 11: "mediumDashDotDot",
- 12: "slantDashDot",
- 13: "thick",
- },
- };
- let template = {
- style: luckyToExcel.style[style],
- color: { argb: color.replace("#", "") },
- };
- let border = {};
- if (luckyToExcel.type[borderType] === "all") {
- border["top"] = template;
- border["right"] = template;
- border["bottom"] = template;
- border["left"] = template;
- } else {
- border[luckyToExcel.type[borderType]] = template;
- }
- // console.log('border', border)
- return border;
- };
-
- function addborderToCell(borders, row_index, col_index) {
- let border = {};
- const luckyExcel = {
- type: {
- l: "left",
- r: "right",
- b: "bottom",
- t: "top",
- },
- style: {
- 0: "none",
- 1: "thin",
- 2: "hair",
- 3: "dotted",
- 4: "dashDot", // 'Dashed',
- 5: "dashDot",
- 6: "dashDotDot",
- 7: "double",
- 8: "medium",
- 9: "mediumDashed",
- 10: "mediumDashDot",
- 11: "mediumDashDotDot",
- 12: "slantDashDot",
- 13: "thick",
- },
- };
- // console.log('borders', borders)
- for (const bor in borders) {
- // console.log(bor)
- if (borders[bor].color.indexOf("rgb") === -1) {
- border[luckyExcel.type[bor]] = {
- style: luckyExcel.style[borders[bor].style],
- color: { argb: borders[bor].color.replace("#", "") },
- };
- } else {
- border[luckyExcel.type[bor]] = {
- style: luckyExcel.style[borders[bor].style],
- color: { argb: borders[bor].color },
- };
- }
- }
-
- return border;
- }
-
- function createCellPos(n) {
- let ordA = "A".charCodeAt(0);
-
- let ordZ = "Z".charCodeAt(0);
- let len = ordZ - ordA + 1;
- let s = "";
- while (n >= 0) {
- s = String.fromCharCode((n % len) + ordA) + s;
-
- n = Math.floor(n / len) - 1;
- }
- return s;
- }

在excel协同的时候,还需要跟我们quill编辑器类似,绑定fileid:
updateUrl:
"ws://localhost:9000?fileid=" + router.currentRoute.value.params.fileid, // 实现传参,
二开实现websocket的关闭连接:
- // 源码中 server.js 添加方法
- closeWebSocket: function () {
- let _this = this;
- if ("WebSocket" in window) {
- _this.websocket.close();
- } else console.error("## closeWebSocket", locale().websocket.support);
- },
-
- global.api(api.js 文件)
- /**
- * 导出 websocket 的关闭方法:
- * luckysheet.wsclose() 进行调用
- */
- export function wsclose() {
- console.log('调用自定义方法 server.closeWebSocket()')
- server.closeWebSocket();
- }
重新打包,在需要的地方进行调用:

但是每次关闭连接后,都会alert,把这个关了:


与文件关联后,不是同一个文件的不能协同编辑。
到此,功能都已经开发完了。还是那句话哈:
如果侵权了,请联系删除!
如果侵权了,请联系删除!
如果侵权了,请联系删除!
对luckysheet的协同做一下总结吧:
制作不易,点赞收藏~