From 6d2e24f455243b8b2710a70b23f8538d3be676fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9A=AE=E5=85=89=EF=BC=9A=E5=9F=8E=E4=B8=AD=E5=9F=8E?= <806783409@qq.com> Date: Sun, 2 Aug 2020 18:02:37 +0800 Subject: [PATCH] =?UTF-8?q?wiki=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../support/EsAbstractService.java | 15 +- .../manage/impl/WikiPageServiceImpl.java | 7 +- .../sql/zyplayer_doc_manage.1.0.6.sql | 18 + zyplayer-doc-ui/wiki-ui/.env.development | 4 +- .../src/components/editor2/MgEditor.vue | 29 +- .../src/components/editor2/util/dom.js | 2 +- .../src/components/editor2/util/undoRedo.js | 14 +- .../src/components/editor3/MgEditor.vue | 342 ++++++++++++++++++ .../src/components/editor3/css/MgEditor.css | 223 ++++++++++++ .../editor3/css/MgEditorIconfont.css | 117 ++++++ .../src/components/editor3/toolbar/common.js | 30 ++ .../src/components/editor3/util/dom.js | 156 ++++++++ .../src/components/editor3/util/dom.js.old | 242 +++++++++++++ .../src/components/editor3/util/styleRange.js | 45 +++ .../src/components/editor3/util/textStyle.js | 50 +++ .../src/components/editor3/util/undoInfo.js | 18 + .../src/components/editor3/util/undoRedo.js | 118 ++++++ .../wiki/controller/WikiCommonController.java | 9 +- .../wiki/controller/WikiPageController.java | 5 +- 19 files changed, 1407 insertions(+), 37 deletions(-) create mode 100644 zyplayer-doc-ui/wiki-ui/src/components/editor3/MgEditor.vue create mode 100644 zyplayer-doc-ui/wiki-ui/src/components/editor3/css/MgEditor.css create mode 100644 zyplayer-doc-ui/wiki-ui/src/components/editor3/css/MgEditorIconfont.css create mode 100644 zyplayer-doc-ui/wiki-ui/src/components/editor3/toolbar/common.js create mode 100644 zyplayer-doc-ui/wiki-ui/src/components/editor3/util/dom.js create mode 100644 zyplayer-doc-ui/wiki-ui/src/components/editor3/util/dom.js.old create mode 100644 zyplayer-doc-ui/wiki-ui/src/components/editor3/util/styleRange.js create mode 100644 zyplayer-doc-ui/wiki-ui/src/components/editor3/util/textStyle.js create mode 100644 zyplayer-doc-ui/wiki-ui/src/components/editor3/util/undoInfo.js create mode 100644 zyplayer-doc-ui/wiki-ui/src/components/editor3/util/undoRedo.js diff --git a/zyplayer-doc-data/src/main/java/com/zyplayer/doc/data/service/elasticsearch/support/EsAbstractService.java b/zyplayer-doc-data/src/main/java/com/zyplayer/doc/data/service/elasticsearch/support/EsAbstractService.java index 9459bbab..ae6bfd0b 100644 --- a/zyplayer-doc-data/src/main/java/com/zyplayer/doc/data/service/elasticsearch/support/EsAbstractService.java +++ b/zyplayer-doc-data/src/main/java/com/zyplayer/doc/data/service/elasticsearch/support/EsAbstractService.java @@ -37,6 +37,7 @@ import java.util.concurrent.TimeUnit; /** * es抽象类 + * * @author 暮光:城中城 * @since 2019-07-07 */ @@ -101,6 +102,7 @@ public abstract class EsAbstractService { /** * 多条件 模糊查询,查询前100条 + * * @param condition 查询条件 */ public List getDataByCondition(List condition) { @@ -109,9 +111,10 @@ public abstract class EsAbstractService { /** * 多条件 模糊查询 - * @param condition 查询条件 + * + * @param condition 查询条件 * @param startIndex 开始行 - * @param pageSize 每页数量 + * @param pageSize 每页数量 */ public EsPage getDataByCondition(List condition, String[] fields, Integer startIndex, Integer pageSize) { BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); @@ -147,11 +150,13 @@ public abstract class EsAbstractService { }); return this.getDataByQuery(boolQueryBuilder, fields, startIndex, pageSize); } + /** * 多条件 模糊查询 + * * @param queryBuilders 查询条件 - * @param startIndex 开始行 - * @param pageSize 每页数量 + * @param startIndex 开始行 + * @param pageSize 每页数量 */ public EsPage getDataByQuery(QueryBuilder queryBuilders, String[] fields, Integer startIndex, Integer pageSize) { // 设置高亮标签 @@ -208,5 +213,5 @@ public abstract class EsAbstractService { esPage.setData(tableList); return esPage; } - + } diff --git a/zyplayer-doc-data/src/main/java/com/zyplayer/doc/data/service/manage/impl/WikiPageServiceImpl.java b/zyplayer-doc-data/src/main/java/com/zyplayer/doc/data/service/manage/impl/WikiPageServiceImpl.java index 545bea67..7b90a213 100644 --- a/zyplayer-doc-data/src/main/java/com/zyplayer/doc/data/service/manage/impl/WikiPageServiceImpl.java +++ b/zyplayer-doc-data/src/main/java/com/zyplayer/doc/data/service/manage/impl/WikiPageServiceImpl.java @@ -52,10 +52,11 @@ public class WikiPageServiceImpl extends ServiceImpl i } this.updateById(wikiPage); // 给相关人发送消息 + WikiPage wikiPageSel = this.getById(wikiPage.getId()); DocUserDetails currentUser = DocUserUtil.getCurrentUser(); - UserMessage userMessage = userMessageService.createUserMessage(currentUser, wikiPage.getId(), wikiPage.getName(), UserMsgSysType.WIKI, UserMsgType.WIKI_PAGE_PARENT); - userMessage.setAffectUserId(wikiPage.getCreateUserId()); - userMessage.setAffectUserName(wikiPage.getCreateUserName()); + UserMessage userMessage = userMessageService.createUserMessage(currentUser, wikiPageSel.getId(), wikiPageSel.getName(), UserMsgSysType.WIKI, UserMsgType.WIKI_PAGE_PARENT); + userMessage.setAffectUserId(wikiPageSel.getCreateUserId()); + userMessage.setAffectUserName(wikiPageSel.getCreateUserName()); userMessageService.addWikiMessage(userMessage); } diff --git a/zyplayer-doc-manage/src/main/resources/sql/zyplayer_doc_manage.1.0.6.sql b/zyplayer-doc-manage/src/main/resources/sql/zyplayer_doc_manage.1.0.6.sql index a0b144ab..688ac1cc 100644 --- a/zyplayer-doc-manage/src/main/resources/sql/zyplayer_doc_manage.1.0.6.sql +++ b/zyplayer-doc-manage/src/main/resources/sql/zyplayer_doc_manage.1.0.6.sql @@ -426,4 +426,22 @@ CREATE TABLE `db_transfer_task` ( PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; +DROP TABLE IF EXISTS `user_message`; +CREATE TABLE `user_message` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键自增ID', + `sys_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '系统类型 1=manage 2=wiki 3=db', + `msg_type` int NOT NULL DEFAULT 1 COMMENT '消息类型 1=普通文本消息 2=wiki文档创建 3=wiki文档删除 4=wiki文档编辑 5=wiki文档权限修改 6=wiki文档评论 7=wiki文档删除评论 8=wiki文档上传附件', + `data_id` bigint(20) NULL DEFAULT NULL COMMENT '消息关联的数据ID', + `data_desc` varchar(100) NULL DEFAULT NULL COMMENT '消息关联的数据说明', + `msg_content` varchar(255) NULL DEFAULT NULL COMMENT '消息内容', + `msg_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '消息状态 0=未读 1=已读 2=已删除', + `operator_user_id` bigint(20) NULL DEFAULT NULL COMMENT '操作人用户ID', + `operator_user_name` varchar(20) NULL DEFAULT NULL COMMENT '操作人用户名', + `affect_user_id` bigint(20) NULL DEFAULT NULL COMMENT '影响人用户ID', + `affect_user_name` varchar(20) NULL DEFAULT NULL COMMENT '影响人用户名', + `accept_user_id` bigint(20) NULL DEFAULT NULL COMMENT '接收人用户ID', + `creation_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户消息表' ROW_FORMAT = Compact; + SET FOREIGN_KEY_CHECKS = 1; diff --git a/zyplayer-doc-ui/wiki-ui/.env.development b/zyplayer-doc-ui/wiki-ui/.env.development index 58e9a01d..fb7181ed 100644 --- a/zyplayer-doc-ui/wiki-ui/.env.development +++ b/zyplayer-doc-ui/wiki-ui/.env.development @@ -2,8 +2,8 @@ ENV = 'development' # base api -VUE_APP_BASE_API = 'http://local.zyplayer.com:8083/zyplayer-doc-manage' -# VUE_APP_BASE_API = 'http://doc.zyplayer.com/zyplayer-doc-manage' +# VUE_APP_BASE_API = 'http://local.zyplayer.com:8083/zyplayer-doc-manage' +VUE_APP_BASE_API = 'http://doc.zyplayer.com/zyplayer-doc-manage' VUE_CLI_BABEL_TRANSPILE_MODULES = true diff --git a/zyplayer-doc-ui/wiki-ui/src/components/editor2/MgEditor.vue b/zyplayer-doc-ui/wiki-ui/src/components/editor2/MgEditor.vue index 3554d89d..d8317838 100644 --- a/zyplayer-doc-ui/wiki-ui/src/components/editor2/MgEditor.vue +++ b/zyplayer-doc-ui/wiki-ui/src/components/editor2/MgEditor.vue @@ -82,10 +82,11 @@ this.userInput = this.$refs.userInput; this.editorDom.push(new Dom('text', 'head head-h1')); document.body.addEventListener('click', e => { - for (let i = 0; i < this.editorDom.length; i++) { + let endIndex = Math.min(this.editorDom.length, this.editorRange.endDomIndex); + for (let i = this.editorRange.startDomIndex; i < endIndex; i++) { this.editorDom[i].clearRange(); } - this.editorToolbarStyle.display = 'none'; + this.hideToolbar(); }); // 监听输入框的特殊按键 this.userInput.addEventListener('keydown', e => { @@ -262,25 +263,25 @@ } }, handleToolbarBold() { - for (let i = this.editorRange.startDomIndex; i < this.editorRange.endDomIndex; i++) { - this.editorDom[i].addSelectionTextStyle('bold', 1); - } - this.editorToolbarStyle.display = 'none'; - window.getSelection().removeAllRanges(); + this.handleToolbarCommon(dom => dom.addSelectionTextStyle('bold', 1)); }, handleToolbarStrikeThrough() { - for (let i = this.editorRange.startDomIndex; i < this.editorRange.endDomIndex; i++) { - this.editorDom[i].addSelectionTextStyle('strikethrough', 1); - } - this.editorToolbarStyle.display = 'none'; - window.getSelection().removeAllRanges(); + this.handleToolbarCommon(dom => dom.addSelectionTextStyle('strikethrough', 1)); }, handleToolbarHn(hn) { + this.handleToolbarCommon(dom => dom.addSelectionTextHead(hn)); + }, + handleToolbarCommon(callback) { + let undoDomBefore = [], undoDomAfter = []; for (let i = this.editorRange.startDomIndex; i < this.editorRange.endDomIndex; i++) { - this.editorDom[i].addSelectionTextHead(hn); + undoDomBefore.push(JSON.parse(JSON.stringify(this.editorDom[i]))); + callback(this.editorDom[i]); + undoDomAfter.push(this.editorDom[i]); } - this.editorToolbarStyle.display = 'none'; + this.undoRedo.execute(1, this.editorRange.startDomIndex, undoDomBefore, undoDomAfter); + this.hideToolbar(); window.getSelection().removeAllRanges(); + setTimeout(() => this.userInput.focus(), 50); }, hideToolbar() { this.editorRange.startDomIndex = -1; diff --git a/zyplayer-doc-ui/wiki-ui/src/components/editor2/util/dom.js b/zyplayer-doc-ui/wiki-ui/src/components/editor2/util/dom.js index f2eb98e4..80b7125a 100644 --- a/zyplayer-doc-ui/wiki-ui/src/components/editor2/util/dom.js +++ b/zyplayer-doc-ui/wiki-ui/src/components/editor2/util/dom.js @@ -14,7 +14,7 @@ function Dom(type = 'text', cls = '', text = '', styleRange = []) { // 一个范围的样式,例:{start: 1, end: 2, class: 'xx xxx'} this.styleRange = []; styleRange.forEach(item => { - this.styleRange.push(new StyleRange(item.start, item.end, this.styleRange.cls)); + this.styleRange.push(new StyleRange(item.start, item.end, item.cls)); }); } diff --git a/zyplayer-doc-ui/wiki-ui/src/components/editor2/util/undoRedo.js b/zyplayer-doc-ui/wiki-ui/src/components/editor2/util/undoRedo.js index c67c98fe..26d6ff4d 100644 --- a/zyplayer-doc-ui/wiki-ui/src/components/editor2/util/undoRedo.js +++ b/zyplayer-doc-ui/wiki-ui/src/components/editor2/util/undoRedo.js @@ -52,7 +52,7 @@ UndoRedo.prototype = { let undoIndex = undoInfo.index; changeContent.forEach(item => { this.undoObjDomToEditor(undoInfo, undoIndex, item); - undoIndex--; + undoIndex++; }); this.undoRedoIndex = Math.max(this.undoRedoIndex - 1, -1); }, @@ -62,17 +62,18 @@ UndoRedo.prototype = { this.undoRedoIndex--; return; } - let undoInfo = this.undoRedoList[this.undoRedoIndex]; - let actionText = (undoInfo.type == 1) ? undoInfo.after : undoInfo.before; + let redoInfo = this.undoRedoList[this.undoRedoIndex]; + let actionText = (redoInfo.type == 1) ? redoInfo.after : redoInfo.before; let changeContent = JSON.parse(actionText); - let undoIndex = undoInfo.index; + let redoIndex = redoInfo.index; changeContent.forEach(item => { - this.redoObjDomToEditor(undoInfo, item); - undoIndex++; + this.redoObjDomToEditor(redoInfo, redoIndex, item); + redoIndex++; }); }, redoObjDomToEditor(undoInfo, undoIndex, domObj) { let dom = new Dom(domObj.type, domObj.cls, domObj.text, domObj.styleRange); + dom.computerStyleRangeToDom(); if (undoInfo.type == 1) { // 1=修改 2=添加 3=删除 if (this.editorDom.length > undoIndex) { @@ -94,6 +95,7 @@ UndoRedo.prototype = { }, undoObjDomToEditor(undoInfo, undoIndex, domObj) { let dom = new Dom(domObj.type, domObj.cls, domObj.text, domObj.styleRange); + dom.computerStyleRangeToDom(); if (undoInfo.type == 1) { // 1=修改 2=添加 3=删除 if (this.editorDom.length > undoIndex) { diff --git a/zyplayer-doc-ui/wiki-ui/src/components/editor3/MgEditor.vue b/zyplayer-doc-ui/wiki-ui/src/components/editor3/MgEditor.vue new file mode 100644 index 00000000..011a85ee --- /dev/null +++ b/zyplayer-doc-ui/wiki-ui/src/components/editor3/MgEditor.vue @@ -0,0 +1,342 @@ + + + + + diff --git a/zyplayer-doc-ui/wiki-ui/src/components/editor3/css/MgEditor.css b/zyplayer-doc-ui/wiki-ui/src/components/editor3/css/MgEditor.css new file mode 100644 index 00000000..600ae416 --- /dev/null +++ b/zyplayer-doc-ui/wiki-ui/src/components/editor3/css/MgEditor.css @@ -0,0 +1,223 @@ + +.mg-editor-box { + overflow: auto; +} +.mg-editor-box .user-input { + width: 1px; + height: 1px; + font-size: 1px; + background: transparent; + opacity: 0; + + position: fixed; + top: 0; + left: 0; + display: none; + z-index: 1; + -moz-appearance: none; + appearance: none; + border: none; + resize: none; + outline: none; + overflow: hidden; + padding: 0 1px; + margin: 0 -1px; + contain: strict; + -ms-user-select: text; + -moz-user-select: text; + -webkit-user-select: text; + user-select: text; + white-space: pre !important; +} + +.mg-editor-box .mg-editor-cursor { + display: block; + top: 42px; + left: 202px; + width: 7px; + height: 14px; + color: #F8F8F0; + opacity: 0.2; + z-index: 4; + position: absolute; + box-sizing: border-box; + border-left: 2px solid; + animation-duration: 1000ms; + + animation-timing-function: step-end; + animation-name: blink-ace-animate; + animation-iteration-count: infinite; + + transform: translatez(0); +} + +@keyframes blink-ace-animate { + from, to {opacity: 1;} + 60% {opacity: 0;} +} + +@keyframes blink-ace-animate-smooth { + from, to {opacity: 1;} + 45% {opacity: 1;} + 60% {opacity: 0;} + 85% {opacity: 0;} +} + + +.mg-editor { + padding: 10px; + height: 100%; + width: 100%; + font-size: 16px; + box-sizing: border-box; + cursor: text; + background: #272822; + color: #fff; +} + +.mg-editor:focus { + outline: none; +} + +.mg-editor:empty::before { + content: attr(placeholder); + font-size: 14px; + color: #ccc; + line-height: 20px; + padding-top: 10px; +} + +.mg-editor-toolbar { + position: fixed; + top: 0; + left: 0; + display: none; + z-index: 1; + padding: 5px 6px; + background-color: #fff; + border-radius: 4px; + border: 1px solid #dee0e3; + user-select: none; + box-shadow: 0 6px 24px 0 rgba(31, 35, 41, .1); +} + +.mg-editor-toolbar .iconfont { + padding: 5px; + font-size: 24px; + cursor: pointer; +} + + +.mg-editor .list-code { + font-size: 14px !important; + line-height: 1.68 !important +} + +.mg-editor .list-code code { + border: 1px solid #dee0e3; + display: block; + background-color: #f5f6f7; + line-height: 1.5; + padding: 6px 8px 3px 45px; + font-size: 14px; + word-break: break-word; + margin: 5px 0; +} + +.mg-editor .list-code code .list-code-span { + padding-top: 0 !important; + padding-bottom: 0 !important; + line-height: 1.5; +} + +.mg-editor .list-code code:before { + content: attr(start); + display: inline-block; + font-size: 14px; + text-align: right; + direction: rtl; + -o-text-overflow: ellipsis; + text-overflow: ellipsis; + overflow: visible; + white-space: nowrap; + width: 0; + margin-left: -11px; + padding-right: 11px; + font-weight: 400; + background-color: transparent; + line-height: 1.5; + color: #8f959e; + vertical-align: bottom; +} + +.mg-editor .list-code code + code { + border-top: none; + margin-top: -9px; +} + +.mg-editor .ace-editor .list-code code:before { + left: 4px; +} + +/*Hn样式-S-*/ +/*我很懒,样式都是抄的*/ +.mg-editor .head-h1, .mg-editor .head-h2, .mg-editor .head-h3, .mg-editor .head-h4, .mg-editor .head-h5, .mg-editor .head-h6, .mg-editor .head-h7, .mg-editor .head-h8, .mg-editor .head-h9 { + font-weight: 600; + letter-spacing: .02em; + line-height: 1.65 +} + +.mg-editor .head-h1 { + font-size: 26px; + margin-bottom: 10px +} + +.mg-editor .head-h1 .collapsable-section-handle { + font-size: 26px +} + +.mg-editor .head-h2 { + font-size: 22px; + margin-bottom: 8px +} + +.mg-editor .head-h2 .collapsable-section-handle { + font-size: 22px +} + +.mg-editor .head-h3 { + font-size: 20px; + margin-bottom: 8px +} + +.mg-editor .head-h3 .collapsable-section-handle { + font-size: 20px +} + +.mg-editor .head-h4 { + font-size: 18px; + margin-bottom: 8px +} + +.mg-editor .head-h4 .collapsable-section-handle { + font-size: 18px +} + +.mg-editor .head-h5,.mg-editor .head-h6,.mg-editor .head-h7,.mg-editor .head-h8,.mg-editor .head-h9 { + font-size: 16px; + margin-bottom: 6px +} + +.mg-editor .head-h5 .collapsable-section-handle,.mg-editor .head-h6 .collapsable-section-handle,.mg-editor .head-h7 .collapsable-section-handle,.mg-editor .head-h8 .collapsable-section-handle,.mg-editor .head-h9 .collapsable-section-handle { + font-size: 16px +} + +/*Hn样式-E-*/ + +.mg-editor .bold { + font-weight: bold; +} + +.mg-editor .strikethrough { + text-decoration: line-through; +} diff --git a/zyplayer-doc-ui/wiki-ui/src/components/editor3/css/MgEditorIconfont.css b/zyplayer-doc-ui/wiki-ui/src/components/editor3/css/MgEditorIconfont.css new file mode 100644 index 00000000..3b1d5333 --- /dev/null +++ b/zyplayer-doc-ui/wiki-ui/src/components/editor3/css/MgEditorIconfont.css @@ -0,0 +1,117 @@ +@font-face { + font-family: "iconfont"; + src: url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'); /* iOS 4.1- */ +} + +.iconfont { + font-family: "iconfont" !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-h2:before { + content: "\e503"; +} + +.icon-h1:before { + content: "\e504"; +} + +.icon-h3:before { + content: "\e505"; +} + +.icon-hn:before { + content: "\e506"; +} + +.icon-h4:before { + content: "\e507"; +} + +.icon-h5:before { + content: "\e508"; +} + +.icon-h6:before { + content: "\e509"; +} + +.icon-h7:before { + content: "\e50b"; +} + +.icon-h8:before { + content: "\e50c"; +} + +.icon-h9:before { + content: "\e50d"; +} + +.icon-bold:before { + content: "\e50e"; +} + +.icon-delete:before { + content: "\e50f"; +} + +.icon-backcolor:before { + content: "\e510"; +} + +.icon-orderedlist:before { + content: "\e511"; +} + +.icon-unorderedlist:before { + content: "\e512"; +} + +.icon-checkbox:before { + content: "\e513"; +} + +.icon-link:before { + content: "\e514"; +} + +.icon-more:before { + content: "\e515"; +} + +.icon-underline:before { + content: "\e516"; +} + +.icon-aligncenter:before { + content: "\e517"; +} + +.icon-alignleft:before { + content: "\e518"; +} + +.icon-blockquote:before { + content: "\e519"; +} + +.icon-italic:before { + content: "\e51a"; +} + +.icon-alignright:before { + content: "\e51b"; +} + +.icon-inlinecode:before { + content: "\e51c"; +} + +.icon-inlinecodelist:before { + content: "\e51d"; +} + diff --git a/zyplayer-doc-ui/wiki-ui/src/components/editor3/toolbar/common.js b/zyplayer-doc-ui/wiki-ui/src/components/editor3/toolbar/common.js new file mode 100644 index 00000000..82e2692f --- /dev/null +++ b/zyplayer-doc-ui/wiki-ui/src/components/editor3/toolbar/common.js @@ -0,0 +1,30 @@ + +export default { + getSelectionContainer(judgeRoot) { + let container = window.getSelection().getRangeAt(0).commonAncestorContainer; + if (container.nodeType != 1) { + container = container.parentNode; + } + if (container.nodeType != 1 || (judgeRoot && this.isRootBox(container))) { + return null; + } + return container; + }, + getRootDom(nowDom) { + let newDom = this.getRealElem(nowDom); + if (!newDom || this.isRootBox(nowDom)) return null; + if (this.isRootBox(newDom.parentNode)) return newDom; + return this.getRootDom(newDom.parentNode); + }, + getRealElem: function (elem) { + return !elem || elem.nodeType === 1 ? elem : elem.parentNode; + }, + isRootBox(container) { + return this.domHaveClass(container, "mg-editor"); + }, + domHaveClass(container, cls) { + return container && container.classList && container.classList.contains(cls); + }, + +} + diff --git a/zyplayer-doc-ui/wiki-ui/src/components/editor3/util/dom.js b/zyplayer-doc-ui/wiki-ui/src/components/editor3/util/dom.js new file mode 100644 index 00000000..f2eb98e4 --- /dev/null +++ b/zyplayer-doc-ui/wiki-ui/src/components/editor3/util/dom.js @@ -0,0 +1,156 @@ +// 构造函数 +import StyleRange from "./styleRange"; +import toolbarCommon from "../../editor/toolbar/common"; + +function Dom(type = 'text', cls = '', text = '', styleRange = []) { + this.type = type; + this.text = text; + this.target = ''; + this.cls = cls; + this.clsSet = new Set(cls.split(" ")); + this.startOffset = -1; + this.endOffset = -1; + this.dom = []; + // 一个范围的样式,例:{start: 1, end: 2, class: 'xx xxx'} + this.styleRange = []; + styleRange.forEach(item => { + this.styleRange.push(new StyleRange(item.start, item.end, this.styleRange.cls)); + }); +} + +// 原型 +Dom.prototype = { + constructor: Dom, + // 删除class + removeClass(cls) { + if (!this.hasClass(cls)) return; + this.clsSet.delete(cls); + this.cls = Array.from(this.clsSet).join(" "); + }, + hasClass(cls) { + return this.clsSet.has(cls); + }, + addClass(cls) { + if (this.hasClass(cls)) return this; + this.clsSet.add(cls); + this.cls = Array.from(this.clsSet).join(" "); + return this; + }, + setOffset(start, end) { + this.startOffset = start; + this.endOffset = end; + }, + setOffsetAll() { + this.startOffset = 0; + this.endOffset = this.text.length; + }, + clearRange() { + this.startOffset = this.endOffset = -1; + }, + addText(startOffset, data) { + if (!data) return; + // todo 删除选中的内容 + // 如果在某个样式范围内,则加入 + for (let i = 0; i < this.styleRange.length; i++) { + let item = this.styleRange[i]; + if (startOffset >= item.start && startOffset < item.end) { + item.end += data.length; + } + } + if (startOffset < this.text.length) { + this.text = this.text.substring(0, startOffset) + data + this.text.substring(startOffset, this.text.length); + } else { + this.text = this.text + data; + } + this.computerStyleRangeToDom(); + }, + addSelectionTextHead(hn) { + if (this.startOffset < 0 || this.endOffset < 0) { + return; + } + this.addClass('head').addClass('head-' + hn); + this.clearRange(); + }, + addSelectionTextStyle(cls) { + if (this.startOffset < 0 || this.endOffset < 0 || this.startOffset == this.endOffset) { + return; + } + // 添加新的分区 + let styleRangeNew = [].concat(this.styleRange); + styleRangeNew.push(new StyleRange(this.startOffset, this.endOffset, cls)); + let textStyleArr = []; + let textLen = this.text.length; + for (let i = 0; i < textLen; i++) { + textStyleArr[i] = ''; + } + // 所有范围值拆分到每个字 + let rangeLen = styleRangeNew.length; + for (let i = 0; i < rangeLen; i++) { + let item = styleRangeNew[i]; + for (let j = item.start; j < item.end && j < textLen; j++) { + textStyleArr[j] += ' ' + item.cls; + } + } + // 去重再排序,获取排序后的字符串 + for (let i = 0; i < textLen; i++) { + if (!!textStyleArr[i]) { + let clsSet = new Set(textStyleArr[i].split(" ")); + textStyleArr[i] = Array.from(clsSet).sort((val1, val2) => val1 - val2).join(" "); + } + } + // 合并同一个范围内样式表重叠的 + let styleRangeMerged = []; + for (let i = 0; i < textLen; i++) { + if (!!textStyleArr[i]) { + let start = i; + for (let j = i + 1; j < textLen; j++, i++) { + if (textStyleArr[i] !== textStyleArr[j]) break; + } + styleRangeMerged.push(new StyleRange(start, i + 1, textStyleArr[i])); + } + } + this.styleRange = styleRangeMerged; + this.clearRange(); + this.computerStyleRangeToDom(); + }, + computerStyleRangeToDom() { + // 把样式表渲染为dom列表 + this.dom = []; + let lastStart = 0; + for (let i = 0; i < this.styleRange.length; i++) { + let item = this.styleRange[i]; + if (lastStart < item.start) { + this.dom.push(new Dom('', '', this.text.substring(lastStart, item.start))); + } + let text = this.text.substring(item.start, item.end); + this.dom.push(new Dom('', item.cls, text)); + lastStart = item.end; + } + if (lastStart < this.text.length) { + this.dom.push(new Dom('', '', this.text.substring(lastStart, this.text.length))); + } + }, + clone() { + return new Dom(this.type, this.cls, this.text, this.styleRange); + }, + // 回车事件处理 + keyEnter(editorDom, editorRange, undoRedo) { + let nextText = ''; + let oldText = this.text || ''; + // 如果文字的中间位置点击,则把内容分割到两行 + if (editorRange.startOffset < oldText.length) { + let beforeDom = this.clone(); + this.text = oldText.substring(0, editorRange.startOffset); + undoRedo.execute(1, editIndex, beforeDom, this); + nextText = oldText.substring(editorRange.startOffset, oldText.length); + } + let editDomNode = toolbarCommon.getRootDom(this.target); + let editIndex = parseInt(editDomNode.getAttribute("index")); + let domNew = new Dom('text', this.cls, nextText); + editorDom.splice(editIndex + 1, 0, domNew); + undoRedo.execute(2, editIndex + 1, domNew, ''); + return domNew; + }, +}; + +export default Dom; diff --git a/zyplayer-doc-ui/wiki-ui/src/components/editor3/util/dom.js.old b/zyplayer-doc-ui/wiki-ui/src/components/editor3/util/dom.js.old new file mode 100644 index 00000000..f147c6c2 --- /dev/null +++ b/zyplayer-doc-ui/wiki-ui/src/components/editor3/util/dom.js.old @@ -0,0 +1,242 @@ +// 构造函数 +import TextStyle from "./textStyle"; + +function Dom(type = 'text', cls = '', text = '') { + this.type = type; + this.text = text; + this.target = ''; + this.cls = cls; + this.clsSet = new Set(cls.split(" ")); + this.startOffset = -1; + this.endOffset = -1; + this.dom = []; + this.textStyle = []; +} + +// 原型 +Dom.prototype = { + constructor: Dom, + // 删除class + removeClass(cls) { + if (!this.hasClass(cls)) return; + this.clsSet.delete(cls); + this.cls = Array.from(this.clsSet).join(" "); + }, + hasClass(cls) { + return this.clsSet.has(cls); + }, + addClass(cls) { + if (this.hasClass(cls)) return; + this.clsSet.add(cls); + this.cls = Array.from(this.clsSet).join(" "); + }, + setOffset(start, end) { + this.startOffset = start; + this.endOffset = end; + }, + setOffsetAll() { + this.startOffset = 0; + this.endOffset = this.text.length; + }, + addText(startOffset, data) { + if (!data) return; + // todo 删除选中的内容 + let textStyleNew = []; + let len = this.text.length; + if (len > 0) { + if (startOffset < this.text.length) { + for (let i = 0; i < len; i++) { + if (i == startOffset) { + let styleCopy = this.textStyle[i]; + for (let j = 0; j < data.length; j++) { + textStyleNew.push(styleCopy.clone()); + } + } + textStyleNew.push(this.textStyle[i]); + } + this.text = this.text.substring(0, startOffset) + data + this.text.substring(startOffset, this.text.length); + } else { + textStyleNew = textStyleNew.concat(this.textStyle); + let styleCopyNew = this.textStyle[this.textStyle.length - 1]; + for (let j = 0; j < data.length; j++) { + textStyleNew.push(styleCopyNew.clone()); + } + this.text = this.text + data; + } + } else { + for (let j = 0; j < data.length; j++) { + textStyleNew.push(new TextStyle()); + } + this.text = data; + } + this.textStyle = textStyleNew; + this.computerTextStyleToDom(); + }, + addSelectionTextStyle(type, value) { + if (this.startOffset < 0 || this.endOffset < 0) { + return; + } + for (let i = this.startOffset; i < this.endOffset; i++) { + this.textStyle[i][type] = value; + } + this.startOffset = this.endOffset = -1; + this.computerTextStyleToDom(); + }, + computerTextStyleToDom() { + this.dom = []; + let len = this.text.length; + for (let i = 0; i < len; i++) { + let startIndex = i; + for (let j = i + 1; j < len; j++, i++) { + if (this.textStyle[i].notEq(this.textStyle[j])) break; + } + let text = this.text.substring(startIndex, i + 1); + this.dom.push(new Dom('', this.textStyle[i].getCls(), text)); + } + }, + borderSelection1() { + this.removeClass('border'); + if (this.startOffset == 0 && this.endOffset > 0) { + if (this.endOffset == this.text.length) { + // 全选 + this.addClass('border'); + } else { + // 选中前面的部分 xx-xxxxxx-xx + if (this.dom.length > 0) { + // 有元素的时候 + let len = 0; + let newDomArr = []; + this.dom.forEach(item => { + let domSub = false; + let textLen = item.text.length; + if (len < this.endOffset) { + if (!this.hasClass('border') && !item.hasClass('border')) { + if (len + textLen <= this.endOffset) { + item.addClass('border'); + } else { + // 再次拆分 + domSub = true; + let textEnd = this.endOffset - len; + newDomArr.push(new Dom('', item.cls + ' border', item.text.substring(0, textEnd))); + newDomArr.push(new Dom('', item.cls, item.text.substring(textEnd, textLen))); + } + } + } + if (!domSub) { + newDomArr.push(item); + } + len += textLen; + }); + this.dom = newDomArr; + } else { + let newDom = new Dom('', 'border', this.text.substring(0, this.endOffset)); + let lastDom = new Dom('', '', this.text.substring(this.endOffset, this.text.length)); + this.dom = [newDom, lastDom]; + } + } + } + else if (this.startOffset > 0) { + if (this.dom.length > 0) { + // 有元素的时候 + if (this.endOffset == this.text.length) { + // 选中后面的部分 xx-xxxxxx-xx + let len = 0; + let newDomArr = []; + this.dom.forEach(item => { + let domSub = false; + let textLen = item.text.length; + if (len + textLen > this.startOffset) { + if (!this.hasClass('border') && !item.hasClass('border')) { + if (len == this.startOffset) { + item.addClass('border'); + } else { + // 再次拆分 + domSub = true; + let textStart = this.startOffset - len; + newDomArr.push(new Dom('', item.cls, item.text.substring(0, textStart))); + newDomArr.push(new Dom('', item.cls + ' border', item.text.substring(textStart, textLen))); + } + } + } + if (!domSub) { + newDomArr.push(item); + } + len += textLen; + }); + this.dom = newDomArr; + } else { + // 选中中间一部分 xx xx-xxxxxx-xx + let len = 0; + let newDomArr = []; + this.dom.forEach(item => { + let domSub = false; + let textLen = item.text.length; + len += textLen; + if (len > this.startOffset && len <= this.endOffset) { + if (!this.hasClass('border') && !item.hasClass('border')) { + let textEnd = textLen - (len - this.endOffset); + if (len - textLen <= this.startOffset) { + if (this.endOffset >= len) { + // 全选 + item.addClass('border'); + } else { + // 选择前面 + domSub = true; + newDomArr.push(new Dom('', item.cls + ' border', item.text.substring(0, textEnd))); + newDomArr.push(new Dom('', item.cls, item.text.substring(textEnd, textLen))); + } + } else { + domSub = true; + let textStart = len - this.startOffset; + newDomArr.push(new Dom('', item.cls, item.text.substring(0, textStart))); + if (this.endOffset >= len) { + // 选择后面 + newDomArr.push(new Dom('', item.cls + ' border', item.text.substring(textStart, textLen))); + } else { + // 选择中间 + newDomArr.push(new Dom('', 'border', item.text.substring(textStart, textEnd))); + newDomArr.push(new Dom('', '', item.text.substring(textEnd, textLen))); + } + } + } + } + if (!domSub) { + newDomArr.push(item); + } + }); + this.dom = newDomArr; + } + } else { + // 空的时候 + this.dom.push(new Dom('', '', this.text.substring(0, this.startOffset))); + if (this.endOffset == this.text.length) { + // 选中后面的部分 + this.dom.push(new Dom('', 'border', this.text.substring(this.startOffset, this.text.length))); + } else { + // 选中中间一部分 + this.dom.push(new Dom('', 'border', this.text.substring(this.startOffset, this.endOffset))); + this.dom.push(new Dom('', '', this.text.substring(this.endOffset, this.text.length))); + } + } + } + this.startOffset = this.endOffset = -1; + }, + // 回车事件处理 + keyEnter(editorDom, editorRange) { + let nextText = ''; + let oldText = this.text || ''; + // 如果文字的中间位置点击,则把内容分割到两行 + if (editorRange.startOffset < oldText.length) { + this.text = oldText.substring(0, editorRange.startOffset); + nextText = oldText.substring(editorRange.startOffset, oldText.length); + } + for (let i = 0; i < editorDom.length; i++) { + if (this == editorDom[i]) { + editorDom.splice(i + 1, 0, new Dom('text', this.cls, nextText)); + break; + } + } + }, +}; + +export default Dom; diff --git a/zyplayer-doc-ui/wiki-ui/src/components/editor3/util/styleRange.js b/zyplayer-doc-ui/wiki-ui/src/components/editor3/util/styleRange.js new file mode 100644 index 00000000..7e3eadef --- /dev/null +++ b/zyplayer-doc-ui/wiki-ui/src/components/editor3/util/styleRange.js @@ -0,0 +1,45 @@ +// 构造函数 +function StyleRange(start, end, cls = '') { + cls = cls.trim(); + this.start = start; + this.end = end; + this.cls = cls; + this.clsSet = new Set(cls.split(" ")); +} + +// 原型 +StyleRange.prototype = { + constructor: StyleRange, + // 删除class + removeClass(cls = '') { + cls = cls.trim(); + if (!this.hasClass(cls)) return; + this.clsSet.delete(cls); + this.cls = Array.from(this.clsSet).join(" "); + }, + hasClass(cls = '') { + cls = cls.trim(); + return this.clsSet.has(cls); + }, + addClass(cls = '') { + cls = cls.trim(); + if (this.hasClass(cls)) return; + this.clsSet.add(cls); + this.cls = Array.from(this.clsSet).join(" "); + }, + getSortClass() { + return Array.from(this.clsSet).sort((val1, val2) => val1 - val2).join(" "); + }, + classSameAll(compare) { + if (compare.clsSet.size != this.clsSet.size) return false; + let values = Array.from(compare.clsSet); + for (let i = 0; i < values.length; i++) { + if (!this.clsSet.has(values[i])) { + return false; + } + } + return true; + }, +}; + +export default StyleRange; diff --git a/zyplayer-doc-ui/wiki-ui/src/components/editor3/util/textStyle.js b/zyplayer-doc-ui/wiki-ui/src/components/editor3/util/textStyle.js new file mode 100644 index 00000000..a55caebb --- /dev/null +++ b/zyplayer-doc-ui/wiki-ui/src/components/editor3/util/textStyle.js @@ -0,0 +1,50 @@ +// 构造函数 + +function TextStyle() { + this.hn = '';// hn 标题,1~9 + this.strikeThrough = 0;// 删除线 1=有 + this.backColor = '';// 背景色,颜色值 + this.underline = 0;// 下划线 1=有 + this.align = 0;// 对齐 1=左 2=中 3=右 + this.bold = 0;// 加粗 1=有 +} + +// 原型 +TextStyle.prototype = { + constructor: TextStyle, + notEq(compare) { + return !this.eq(compare); + }, + eq(compare) { + if (!compare) return false; + if (this == compare) return true; + return this.hn == compare.hn + && this.strikeThrough == compare.strikeThrough + && this.backColor == compare.backColor + && this.underline == compare.underline + && this.align == compare.align + && this.bold == compare.bold; + }, + getCls() { + let clsSet = new Set(); + if (this.hn) clsSet.add('head').add('head-' + this.hn); + if (this.strikeThrough) clsSet.add('strikethrough'); + if (this.backColor) clsSet.add('backcolor-' + this.backColor); + if (this.underline) clsSet.add('underline'); + if (this.align) clsSet.add('align-' + this.align); + if (this.bold) clsSet.add('bold'); + return Array.from(clsSet).join(" "); + }, + clone() { + let result = new TextStyle(); + result.hn = this.hn;// hn 标题,1~9 + result.strikeThrough = this.strikeThrough;// 删除线 1=有 + result.backColor = this.backColor;// 背景色,颜色值 + result.underline = this.underline;// 下划线 1=有 + result.align = this.align;// 对齐 1=左 2=中 3=右 + result.bold = this.bold;// 加粗 1=有 + return result; + }, +}; + +export default TextStyle; diff --git a/zyplayer-doc-ui/wiki-ui/src/components/editor3/util/undoInfo.js b/zyplayer-doc-ui/wiki-ui/src/components/editor3/util/undoInfo.js new file mode 100644 index 00000000..5d0d6e85 --- /dev/null +++ b/zyplayer-doc-ui/wiki-ui/src/components/editor3/util/undoInfo.js @@ -0,0 +1,18 @@ +// 构造函数 +function UndoInfo(type = 1, index = -1, before = '', after = '') { + // 操作类型 1=修改 2=添加 3=删除 + this.type = type; + // 数组下标 + this.index = index; + // 修改前内容json + this.before = before; + // 修改后内容json + this.after = after; +} + +// 原型 +UndoInfo.prototype = { + constructor: UndoInfo, +}; + +export default UndoInfo; diff --git a/zyplayer-doc-ui/wiki-ui/src/components/editor3/util/undoRedo.js b/zyplayer-doc-ui/wiki-ui/src/components/editor3/util/undoRedo.js new file mode 100644 index 00000000..966161f9 --- /dev/null +++ b/zyplayer-doc-ui/wiki-ui/src/components/editor3/util/undoRedo.js @@ -0,0 +1,118 @@ +// 构造函数 + +import UndoInfo from "./undoInfo"; +import Dom from "./dom"; +import vue from '../../../main' + +function UndoRedo(editorDom) { + this.editorDom = editorDom; + this.undoRedoList = []; + this.undoRedoIndex = -1; +} + +// 原型 +UndoRedo.prototype = { + constructor: UndoRedo, + execute(type, index, before, after) { + // 忽略后面的操作步骤 + if (this.undoRedoIndex >= 0 && this.undoRedoIndex < this.undoRedoList.length - 1) { + this.undoRedoList.splice(this.undoRedoIndex, this.undoRedoList.length - this.undoRedoIndex); + } + // 最多保留50步 + if (this.undoRedoList.length >= 50) { + this.undoRedoList.splice(0, 1); + } + // 处理下,只保留有用的字段 + before = this.handleDomColumn(before); + after = this.handleDomColumn(after); + this.undoRedoList.push(new UndoInfo(type, index, before, after)); + this.undoRedoIndex = this.undoRedoList.length - 1; + }, + handleDomColumn(content) { + if (!content) return ''; + let beforeObj = []; + if (content instanceof Array) { + content.forEach(item => { + beforeObj.push({type: item.type, cls: item.cls, text: item.text, styleRange: item.styleRange}); + }); + } else { + beforeObj.push({type: content.type, cls: content.cls, text: content.text, styleRange: content.styleRange}); + } + return JSON.stringify(beforeObj); + }, + undo() { + if (this.undoRedoIndex >= this.undoRedoList.length) { + this.undoRedoIndex = this.undoRedoList.length - 1; + } + if (this.undoRedoIndex < 0) { + return; + } + let undoInfo = this.undoRedoList[this.undoRedoIndex]; + let changeContent = JSON.parse(undoInfo.before); + let undoIndex = undoInfo.index; + changeContent.forEach(item => { + this.undoObjDomToEditor(undoInfo, undoIndex, item); + undoIndex--; + }); + this.undoRedoIndex = Math.max(this.undoRedoIndex - 1, -1); + }, + redo() { + this.undoRedoIndex++; + if (this.undoRedoIndex < 0 || this.undoRedoIndex >= this.undoRedoList.length) { + this.undoRedoIndex--; + return; + } + let undoInfo = this.undoRedoList[this.undoRedoIndex]; + let actionText = (undoInfo.type == 1) ? undoInfo.after : undoInfo.before; + let changeContent = JSON.parse(actionText); + let undoIndex = undoInfo.index; + changeContent.forEach(item => { + this.redoObjDomToEditor(undoInfo, undoIndex, item); + undoIndex++; + }); + }, + redoObjDomToEditor(undoInfo, undoIndex, domObj) { + let dom = new Dom(domObj.type, domObj.cls, domObj.text, domObj.styleRange); + if (undoInfo.type == 1) { + // 1=修改 2=添加 3=删除 + if (this.editorDom.length > undoIndex) { + vue.$set(this.editorDom, undoIndex, dom); + } + } else if (undoInfo.type == 2) { + // 1=修改 2=添加 3=删除 + if (this.editorDom.length == undoIndex) { + this.editorDom.push(dom); + } else if (this.editorDom.length > undoIndex) { + this.editorDom.splice(undoIndex, 0, dom); + } + } else if (undoInfo.type == 3) { + // 1=修改 2=添加 3=删除 + if (this.editorDom.length > undoIndex) { + this.editorDom.splice(undoIndex, 1); + } + } + }, + undoObjDomToEditor(undoInfo, undoIndex, domObj) { + let dom = new Dom(domObj.type, domObj.cls, domObj.text, domObj.styleRange); + if (undoInfo.type == 1) { + // 1=修改 2=添加 3=删除 + if (this.editorDom.length > undoIndex) { + vue.$set(this.editorDom, undoIndex, dom); + } + } else if (undoInfo.type == 2) { + // 1=修改 2=添加 3=删除 + if (this.editorDom.length > undoIndex) { + this.editorDom.splice(undoIndex, 1); + } + } else if (undoInfo.type == 3) { + // 1=修改 2=添加 3=删除 + if (this.editorDom.length == undoIndex) { + this.editorDom.push(dom); + } else if (this.editorDom.length > undoIndex) { + this.editorDom.splice(undoIndex, 0, dom); + } + } + }, +}; + +export default UndoRedo; diff --git a/zyplayer-doc-wiki/src/main/java/com/zyplayer/doc/wiki/controller/WikiCommonController.java b/zyplayer-doc-wiki/src/main/java/com/zyplayer/doc/wiki/controller/WikiCommonController.java index c7cf5a26..081b0d01 100644 --- a/zyplayer-doc-wiki/src/main/java/com/zyplayer/doc/wiki/controller/WikiCommonController.java +++ b/zyplayer-doc-wiki/src/main/java/com/zyplayer/doc/wiki/controller/WikiCommonController.java @@ -2,6 +2,8 @@ package com.zyplayer.doc.wiki.controller; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.zyplayer.doc.core.annotation.AuthMan; import com.zyplayer.doc.core.json.DocResponseJson; import com.zyplayer.doc.core.json.ResponseJson; @@ -32,7 +34,6 @@ import java.io.FileInputStream; import java.io.InputStream; import java.io.OutputStream; import java.net.URLEncoder; -import java.util.List; import java.util.Optional; /** @@ -67,8 +68,10 @@ public class WikiCommonController { queryWrapper.like("user_name", search).or().like("user_no", search) .or().like("email", search); queryWrapper.select("id", "user_name"); - List userInfoList = userInfoService.list(queryWrapper); - return DocResponseJson.ok(userInfoList); + // 搜索最多返回20条 + IPage page = new Page<>(1, 20, false); + userInfoService.page(page, queryWrapper); + return DocResponseJson.ok(page); } @GetMapping("/file") diff --git a/zyplayer-doc-wiki/src/main/java/com/zyplayer/doc/wiki/controller/WikiPageController.java b/zyplayer-doc-wiki/src/main/java/com/zyplayer/doc/wiki/controller/WikiPageController.java index 8a73daa3..d4212e5b 100644 --- a/zyplayer-doc-wiki/src/main/java/com/zyplayer/doc/wiki/controller/WikiPageController.java +++ b/zyplayer-doc-wiki/src/main/java/com/zyplayer/doc/wiki/controller/WikiPageController.java @@ -161,8 +161,7 @@ public class WikiPageController { @PostMapping("/changeParent") public ResponseJson changeParent(WikiPage wikiPage, Integer beforeSeq, Integer afterSeq) { DocUserDetails currentUser = DocUserUtil.getCurrentUser(); - Long id = wikiPage.getId(); - WikiPage wikiPageSel = wikiPageService.getById(id); + WikiPage wikiPageSel = wikiPageService.getById(wikiPage.getId()); // 编辑权限判断 WikiSpace wikiSpaceSel = wikiSpaceService.getById(wikiPageSel.getSpaceId()); String canEdit = wikiPageAuthService.canEdit(wikiSpaceSel, wikiPageSel.getEditType(), wikiPageSel.getId(), currentUser.getUserId()); @@ -175,7 +174,7 @@ public class WikiPageController { wikiPageUp.setUpdateTime(new Date()); wikiPageUp.setUpdateUserId(currentUser.getUserId()); wikiPageUp.setUpdateUserName(currentUser.getUsername()); - wikiPageService.changeParent(wikiPage, beforeSeq, afterSeq); + wikiPageService.changeParent(wikiPageUp, beforeSeq, afterSeq); return DocResponseJson.ok(); }