diff --git a/README-zh.md b/README-zh.md index 3e7c6de..da2e4c9 100644 --- a/README-zh.md +++ b/README-zh.md @@ -18,9 +18,12 @@ | [Github](https://github.com/mengshukeji/Luckysheet)| [在线文档](https://mengshukeji.github.io/LuckysheetDocs/zh/) | [在线Demo](https://mengshukeji.github.io/LuckysheetDemo) | [导入Excel Demo](https://mengshukeji.github.io/LuckyexcelDemo/) | [中文论坛](https://support.qq.com/product/288322) | [LuckyResources](https://github.com/mengshukeji/LuckyResources) | | [Gitee镜像](https://gitee.com/mengshukeji/Luckysheet)| [Gitee在线文档](https://mengshukeji.gitee.io/LuckysheetDocs/zh/) | [Gitee在线Demo](https://mengshukeji.gitee.io/luckysheetdemo/) | [Gitee导入Excel Demo](https://mengshukeji.gitee.io/luckyexceldemo/) | [Google Group](https://groups.google.com/g/luckysheet) | -  +## 在线案例 + +- [协同编辑Demo](http://luckysheet.lashuju.com/demo/)(注意:官方Java后台待整理后也会开源,采用OT算法。请大家别操作频繁,防止搞崩服务器) + ## 插件 - excel导入导出库: [Luckyexcel](https://github.com/mengshukeji/Luckyexcel) - 图表插件: [chartMix](https://github.com/mengshukeji/chartMix) diff --git a/README.md b/README.md index 517083b..0ab38b2 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,10 @@ English| [简体中文](./README-zh.md)  +## Online Case + +- [Cooperative editing demo](http://luckysheet.lashuju.com/demo/)(Note: The official Java backend will also be open source after finishing,using OT algorithm. Please do not operate frequently to prevent the server from crashing) + ## Plugins - Excel import and export library: [Luckyexcel](https://github.com/mengshukeji/Luckyexcel) - Chart plugin: [chartMix](https://github.com/mengshukeji/chartMix) diff --git a/docs/guide/README.md b/docs/guide/README.md index 656cec1..6e74329 100644 --- a/docs/guide/README.md +++ b/docs/guide/README.md @@ -8,6 +8,10 @@ Luckysheet is an online spreadsheet like excel that is powerful, simple to confi  +## Online Case + +- [Cooperative editing demo](http://luckysheet.lashuju.com/demo/)(Note: The official Java backend will also be open source after finishing,using OT algorithm. Please do not operate frequently to prevent the server from crashing) + ## Features ### 🛠️Formatting diff --git a/docs/guide/config.md b/docs/guide/config.md index e7a9eb9..3897436 100644 --- a/docs/guide/config.md +++ b/docs/guide/config.md @@ -919,6 +919,14 @@ The hook functions are uniformly configured under ʻoptions.hook`, and configura - Parameter: - {Object} [book]:Configuration of the entire workbook (options) +------------ +### updateBefore +- Type: Function +- Default: null +- Usage: The method executed before each operation in collaborative editing updates the data. When undoing and redoing, it is also an operation, of course, the hook function will be triggered. +- Parameter: + - {Object} [operate]: The history information of this operation will have different history records according to different operations. Refer to the source code [History](https://github.com/mengshukeji/Luckysheet/blob/master/src/controllers/controlHistory.js ) + ------------ ### updated - Type: Function diff --git a/docs/guide/data.md b/docs/guide/data.md index d7d8298..bbd2c89 100644 --- a/docs/guide/data.md +++ b/docs/guide/data.md @@ -219,7 +219,7 @@ ``` ### config.borderInfo - - Type:Object + - Type:Array - Default:{} - Usage:The border information of the cell - example: diff --git a/docs/guide/sheet.md b/docs/guide/sheet.md index 389482e..f4dc2f6 100644 --- a/docs/guide/sheet.md +++ b/docs/guide/sheet.md @@ -254,7 +254,7 @@ eg: options.data: ``` #### config.borderInfo -- type:Object +- type:Array - default:{} - usage:The border information of the cell - example: diff --git a/docs/zh/guide/README.md b/docs/zh/guide/README.md index d4560b4..c182ac6 100644 --- a/docs/zh/guide/README.md +++ b/docs/zh/guide/README.md @@ -8,6 +8,10 @@ Luckysheet ,一款纯前端类似excel的在线表格,功能强大、配置  +## 在线案例 + +- [协同编辑Demo](http://luckysheet.lashuju.com/demo/)(注意:官方Java后台待整理后也会开源,采用OT算法。请大家别操作频繁,防止搞崩服务器) + ## 特性 ### 🛠️格式设置 diff --git a/docs/zh/guide/config.md b/docs/zh/guide/config.md index 692c629..f98abd7 100644 --- a/docs/zh/guide/config.md +++ b/docs/zh/guide/config.md @@ -1225,6 +1225,15 @@ Luckysheet开放了更细致的自定义配置选项,分别有 - 参数: - {Object} [book]: 整个工作簿的配置(options) +------------ +### updateBefore +(TODO) +- 类型:Function +- 默认值:null +- 作用:协同编辑中的每次操作更新数据之前执行的方法,撤销重做时因为也算一次操作,也会触发此钩子函数。 +- 参数: + - {Object} [operate]: 本次操作的历史记录信息,根据不同的操作,会有不同的历史记录,参考源码 [历史记录](https://github.com/mengshukeji/Luckysheet/blob/master/src/controllers/controlHistory.js) + ------------ ### updated (TODO) diff --git a/docs/zh/guide/sheet.md b/docs/zh/guide/sheet.md index ba633f9..f91826b 100644 --- a/docs/zh/guide/sheet.md +++ b/docs/zh/guide/sheet.md @@ -256,7 +256,7 @@ options.data示例如下: ``` #### config.borderInfo -- 类型:Object +- 类型:Array - 默认值:{} - 作用:单元格的边框信息 - 示例: diff --git a/gulpfile.js b/gulpfile.js index 6728463..a88fc98 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -126,7 +126,8 @@ function serve(done) { browserSync.init({ server: { baseDir: paths.dist - } + }, + ghostMode: false, //默认true,滚动和表单在任何设备上输入将被镜像到所有设备里,会影响本地的协同编辑消息,故关闭 }, done) } diff --git a/src/controllers/handler.js b/src/controllers/handler.js index 3d3922f..b4d7ccf 100644 --- a/src/controllers/handler.js +++ b/src/controllers/handler.js @@ -289,6 +289,9 @@ export default function luckysheetHandler() { return; } + // 协同编辑其他用户不在操作的时候,用户名框隐藏 + hideUsername(); + $("#luckysheet-cell-selected").find(".luckysheet-cs-fillhandle") .css("cursor","default") .end() @@ -1446,7 +1449,9 @@ export default function luckysheetHandler() { } luckysheetupdateCell(row_index, col_index, Store.flowdata); + } + }); //监听拖拽 @@ -5605,4 +5610,21 @@ export default function luckysheetHandler() { }).mousedown(function (e) { e.stopPropagation(); }); +} + +// 协同编辑其他用户不在操作的时候,且已经展示了用户名10秒,则用户名框隐藏 +function hideUsername(){ + let $showEle = $$('.luckysheet-multipleRange-show'); + + if($showEle.length === undefined){ + $showEle = [$showEle]; + } + + $showEle.forEach((ele)=>{ + const id = ele.id.replace('luckysheet-multipleRange-show-',''); + + if(Store.cooperativeEdit.usernameTimeout['user' + id] === null){ + $$('.username',ele).style.display = 'none'; + } + }) } \ No newline at end of file diff --git a/src/controllers/server.js b/src/controllers/server.js index b38a2b3..bae176d 100644 --- a/src/controllers/server.js +++ b/src/controllers/server.js @@ -88,6 +88,11 @@ const server = { d.i = index; d.v = value; + //切换sheet页不发后台,TODO:改为发后台+后台不广播 + if(type === 'shs'){ + return; + } + if (type == "rv") { //单元格批量更新 d.range = params.range; } @@ -176,15 +181,27 @@ const server = { index = item.i, value = item.v; - if(getObjType(value) != "array"){ + if(getObjType(value) != "array" && getObjType(value) !== "object"){ value = JSON.parse(value); } - if(index == Store.currentSheetIndex){//发送消息者在当前页面 - let r = value[value.length - 1].row[0]; - let c = value[value.length - 1].column[0]; + if(index == Store.currentSheetIndex){//发送消息者在当前页面 + + if(getObjType(value) === "object" && value.op === 'enterEdit'){ + + let r = value.range[value.range.length - 1].row[0]; + let c = value.range[value.range.length - 1].column[0]; + + _this.multipleRangeShow(id, username, r, c, value.op); + + }else{ - _this.multipleRangeShow(id, username, r, c); + let r = value[value.length - 1].row[0]; + let c = value[value.length - 1].column[0]; + + _this.multipleRangeShow(id, username, r, c); + + } } } else if(type == 4){ //批量指令更新 @@ -226,7 +243,7 @@ const server = { let file = Store.luckysheetfile[getSheetIndex(index)]; - if(file == null){ + if(["v","rv","cg","all","fc","drc","arc","f","fsc","fsr","sh","c"].includes(type) && file == null){ return; } @@ -399,7 +416,7 @@ const server = { luckysheetrefreshgrid(); }, 1); } - } + } } else if(type == "fc"){ //函数链calc let op = item.op, pos = item.pos; @@ -614,15 +631,41 @@ const server = { else if(type == "shd"){ //删除sheet for(let i = 0; i < Store.luckysheetfile.length; i++){ if(Store.luckysheetfile[i].index == value.deleIndex){ - server.sheetDeleSave.push(Store.luckysheetfile[i]); + + // 如果删除的是当前sheet,则切换到前一个sheet页 + if(Store.currentSheetIndex === value.deleIndex){ + const index = value.deleIndex; + + Store.luckysheetfile[sheetmanage.getSheetIndex(index)].hide = 1; + + let luckysheetcurrentSheetitem = $("#luckysheet-sheets-item" + index); + luckysheetcurrentSheetitem.hide(); + + $("#luckysheet-sheet-area div.luckysheet-sheets-item").removeClass("luckysheet-sheets-item-active"); + + let indicator = luckysheetcurrentSheetitem.nextAll(":visible"); + if (luckysheetcurrentSheetitem.nextAll(":visible").length > 0) { + indicator = indicator.eq(0).data("index"); + } + else { + indicator = luckysheetcurrentSheetitem.prevAll(":visible").eq(0).data("index"); + } + $("#luckysheet-sheets-item" + indicator).addClass("luckysheet-sheets-item-active"); + + sheetmanage.changeSheetExec(indicator); + } + + server.sheetDeleSave.push(Store.luckysheetfile[i]); + + Store.luckysheetfile.splice(i, 1); - Store.luckysheetfile.splice(i, 1); break; } } $("#luckysheet-sheets-item" + value.deleIndex).remove(); - $("#luckysheet-datavisual-selection-set-" + value.deleIndex).remove(); + $("#luckysheet-datavisual-selection-set-" + value.deleIndex).remove(); + } else if(type == "shr"){ //sheet位置 for(let x in value){ @@ -711,7 +754,7 @@ const server = { } }, multipleIndex: 0, - multipleRangeShow: function(id, name, r, c) { + multipleRangeShow: function(id, name, r, c, value) { let _this = this; let row = Store.visibledatarow[r], @@ -726,19 +769,70 @@ const server = { col = margeset.column[1]; col_pre = margeset.column[0]; - } + } + + // 超出16个字符就显示... + if(getByteLen(name) > 16){ + name = getByteLen(name,16) + "..."; + } + + // 如果正在编辑,就显示“正在输入” + if(value === 'enterEdit'){ + name += " " + locale().edit.typing; + } if($("#luckysheet-multipleRange-show-" + id).length > 0){ - $("#luckysheet-multipleRange-show-" + id).css({ "position": "absolute", "left": col_pre - 1, "width": col - col_pre - 1, "top": row_pre - 1, "height": row - row_pre - 1 }); + $("#luckysheet-multipleRange-show-" + id).css({ "position": "absolute", "left": col_pre - 1, "width": col - col_pre - 1, "top": row_pre - 1, "height": row - row_pre - 1 }); + + $("#luckysheet-multipleRange-show-" + id + " .username").text(name); + $("#luckysheet-multipleRange-show-" + id + " .username").show(); + + if(Store.cooperativeEdit.usernameTimeout['user' + id] != null){ + clearTimeout(Store.cooperativeEdit.usernameTimeout['user' + id]) + } + Store.cooperativeEdit.usernameTimeout['user' + id] = setTimeout(()=>{ + clearTimeout(Store.cooperativeEdit.usernameTimeout['user' + id]); + Store.cooperativeEdit.usernameTimeout['user' + id] = null; + },10 * 1000) + + + } else{ - let itemHtml = '