From eeba3153ef540e76be12906f41f5a089832d0abe Mon Sep 17 00:00:00 2001 From: wanglin <1013335014@qq.com> Date: Sun, 11 Jul 2021 13:33:06 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=A4=9A=E9=80=89=E6=93=8D?= =?UTF-8?q?=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple-mind-map/index.js | 17 ++- simple-mind-map/src/Event.js | 14 ++ simple-mind-map/src/Node.js | 39 +++++- simple-mind-map/src/Render.js | 84 +++++++----- simple-mind-map/src/Select.js | 123 ++++++++++++++++++ simple-mind-map/src/utils/index.js | 20 ++- .../pages/Edit/components/NodeHyperlink.vue | 60 +++++---- web/src/pages/Edit/components/NodeIcon.vue | 84 ++++++------ web/src/pages/Edit/components/NodeImage.vue | 25 ++-- web/src/pages/Edit/components/NodeNote.vue | 24 ++-- web/src/pages/Edit/components/NodeTag.vue | 14 +- web/src/pages/Edit/components/Style.vue | 21 ++- 12 files changed, 372 insertions(+), 153 deletions(-) create mode 100644 simple-mind-map/src/Select.js diff --git a/simple-mind-map/index.js b/simple-mind-map/index.js index 504d27c7..132a67f6 100644 --- a/simple-mind-map/index.js +++ b/simple-mind-map/index.js @@ -8,6 +8,7 @@ import KeyCommand from './src/KeyCommand' import Command from './src/Command' import BatchExecution from './src/BatchExecution' import Export from './src/Export' +import Select from './src/Select' import { SVG } from '@svgdotjs/svg.js' @@ -47,17 +48,14 @@ class MindMap { // 容器元素 this.el = this.opt.el - let { - width, - height - } = this.el.getBoundingClientRect() + this.elRect = this.el.getBoundingClientRect() // 画布宽高 - this.width = width - this.height = height + this.width = this.elRect.width + this.height = this.elRect.height // 画布 - this.svg = SVG().addTo(this.el).size(width, height) + this.svg = SVG().addTo(this.el).size(this.width, this.height) this.draw = this.svg.group() // 节点id @@ -97,6 +95,11 @@ class MindMap { mindMap: this }) + // 选择类 + this.select = new Select({ + mindMap: this + }) + // 批量执行类 this.batchExecution = new BatchExecution() diff --git a/simple-mind-map/src/Event.js b/simple-mind-map/src/Event.js index 990f3dec..194cfedd 100644 --- a/simple-mind-map/src/Event.js +++ b/simple-mind-map/src/Event.js @@ -46,6 +46,7 @@ class Event extends EventEmitter { this.onMousemove = this.onMousemove.bind(this) this.onMouseup = this.onMouseup.bind(this) this.onMousewheel = this.onMousewheel.bind(this) + this.onContextmenu = this.onContextmenu.bind(this) } /** @@ -65,6 +66,7 @@ class Event extends EventEmitter { } else { this.mindMap.el.addEventListener('mousewheel', this.onMousewheel) } + this.mindMap.svg.on('contextmenu', this.onContextmenu) } /** @@ -74,10 +76,12 @@ class Event extends EventEmitter { * @Desc: 解绑事件 */ unbind() { + this.mindMap.svg.off('click', this.onDrawClick) this.mindMap.el.removeEventListener('mousedown', this.onMousedown) window.removeEventListener('mousemove', this.onMousemove) window.removeEventListener('mouseup', this.onMouseup) this.mindMap.el.removeEventListener('mousewheel', this.onMousewheel) + this.mindMap.svg.off('contextmenu', this.onContextmenu) } /** @@ -152,6 +156,16 @@ class Event extends EventEmitter { } this.emit('mousewheel', e, dir, this) } + + /** + * @Author: 王林 + * @Date: 2021-07-10 22:34:13 + * @Desc: 鼠标右键菜单事件 + */ + onContextmenu(e) { + e.preventDefault() + this.emit('contextmenu', e) + } } export default Event \ No newline at end of file diff --git a/simple-mind-map/src/Node.js b/simple-mind-map/src/Node.js index e493d7df..36d268bf 100644 --- a/simple-mind-map/src/Node.js +++ b/simple-mind-map/src/Node.js @@ -152,7 +152,7 @@ class Node { this._expandBtn.off(['mouseover', 'mouseout', 'click']) } if (this.group) { - this.group.off(['click', 'dblclick']) + this.group.off(['click', 'dblclick', 'contextmenu']) } } @@ -471,7 +471,7 @@ class Node { let { paddingY } = this.getPaddingVale() // 创建组 this.group = new G() - this.updatePos(false) + this.update(false) // 节点矩形 this.style.rect(this.group.rect(width, height)) // 图片节点 @@ -540,6 +540,11 @@ class Node { this.group.on('dblclick', () => { this.mindMap.emit('node_dblclick', this) }) + // 右键菜单事件 + this.group.on('contextmenu', (e) => { + e.stopPropagation() + e.preventDefault() + }) } /** @@ -555,7 +560,7 @@ class Node { this.mindMap.emit('before_node_active', this, this.renderer.activeNodeList) this.renderer.clearActive() this.mindMap.execCommand('SET_NODE_ACTIVE', this, !this.nodeData.data.isActive) - this.renderer.activeNodeList.push(this) + this.renderer.addActiveNode(this) this.mindMap.emit('node_active', this, this.renderer.activeNodeList) } @@ -576,12 +581,18 @@ class Node { /** * @Author: 王林 * @Date: 2021-07-04 22:47:01 - * @Desc: 更新节点位置 + * @Desc: 更新节点 */ - updatePos(animate = true) { + update(animate = true) { if (!this.group) { return; } + // 需要移除展开收缩按钮 + if (this._expandBtn && this.nodeData.children.length <= 0) { + this.removeExpandBtn() + } else if (!this._expandBtn && this.nodeData.children.length > 0) {// 需要添加展开收缩按钮 + this.renderExpandBtn() + } let t = this.group.transform() if (animate) { this.group.animate(300).translate(this.left - t.translateX, this.top - t.translateY) @@ -604,7 +615,7 @@ class Node { this._initRender = false this.renderNode() } else { - this.updatePos() + this.update() } // 子节点 if (this.children && this.children.length && this.nodeData.data.expand !== false) { @@ -730,6 +741,22 @@ class Node { this.renderer.layout.renderExpandBtn(this, this._expandBtn) } + /** + * @Author: 王林 + * @Date: 2021-07-11 13:26:00 + * @Desc: 移除展开收缩按钮 + */ + removeExpandBtn() { + if (this._expandBtn) { + this._expandBtn.off(['mouseover', 'mouseout', 'click']) + } + // 展开收缩按钮 + if (this._expandBtn) { + this._expandBtn.remove() + this._expandBtn = null + } + } + /** * @Author: 王林 * @Date: 2021-06-20 22:51:57 diff --git a/simple-mind-map/src/Render.js b/simple-mind-map/src/Render.js index cb45bab6..5d9432ea 100644 --- a/simple-mind-map/src/Render.js +++ b/simple-mind-map/src/Render.js @@ -139,16 +139,37 @@ class Render { this.activeNodeList = [] } + /** + * @Author: 王林 + * @Date: 2021-07-11 10:54:00 + * @Desc: 添加节点到激活列表里 + */ + addActiveNode(node) { + let index = this.findActiveNodeIndex(node) + if (index === -1) { + this.activeNodeList.push(node) + } + } + /** * @Author: 王林 * @Date: 2021-07-10 10:04:04 * @Desc: 在激活列表里移除某个节点 */ removeActiveNode(node) { - let index = this.activeNodeList.findIndex((item) => { + let index = this.findActiveNodeIndex(node) + this.activeNodeList.splice(index, 1) + } + + /** + * @Author: 王林 + * @Date: 2021-07-11 10:55:23 + * @Desc: 检索某个节点在激活列表里的索引 + */ + findActiveNodeIndex(node) { + return this.activeNodeList.findIndex((item) => { return item === node; }) - this.activeNodeList.splice(index, 1) } /** @@ -165,7 +186,7 @@ class Render { /** * @Author: 王林 * @Date: 2021-05-04 13:19:54 - * @Desc: 插入同级节点 + * @Desc: 插入同级节点,多个节点只会操作第一个节点 */ insertNode() { if (this.activeNodeList.length <= 0) { @@ -196,24 +217,24 @@ class Render { if (this.activeNodeList.length <= 0) { return; } - let node = this.activeNodeList[0] - if (!node.nodeData.children) { - node.nodeData.children = [] - } - let len = node.nodeData.children.length - node.nodeData.children.push({ - "data": { - "text": "分支主题", - "expand": true - }, - "children": [] + this.activeNodeList.forEach((node, index) => { + if (!node.nodeData.children) { + node.nodeData.children = [] + } + node.nodeData.children.push({ + "data": { + "text": "分支主题", + "expand": true + }, + "children": [] + }) + if (node.isRoot) { + this.mindMap.batchExecution.push('renderNode' + index, () => { + node.renderNode() + }) + } }) this.mindMap.render() - if (node.isRoot || len <= 0) { - this.mindMap.batchExecution.push('renderNode', () => { - node.renderNode() - }) - } } /** @@ -225,21 +246,24 @@ class Render { if (this.activeNodeList.length <= 0) { return; } - this.activeNodeList.forEach((item) => { - if (item.isRoot) { - item.children.forEach((child) => { + for (let i = 0; i < this.activeNodeList.length; i++) { + let node = this.activeNodeList[i] + if (node.isRoot) { + node.children.forEach((child) => { child.remove() }) - item.children = [] - item.nodeData.children = [] + node.children = [] + node.nodeData.children = [] + break } else { - this.removeActiveNode(item) - let index = this.getNodeIndex(item) - item.remove() - item.parent.children.splice(index, 1) - item.parent.nodeData.children.splice(index, 1) + this.removeActiveNode(node) + let index = this.getNodeIndex(node) + node.remove() + node.parent.children.splice(index, 1) + node.parent.nodeData.children.splice(index, 1) + i-- } - }) + } this.mindMap.render() } diff --git a/simple-mind-map/src/Select.js b/simple-mind-map/src/Select.js new file mode 100644 index 00000000..2474ba86 --- /dev/null +++ b/simple-mind-map/src/Select.js @@ -0,0 +1,123 @@ +import { bfsWalk, throttle } from './utils' + +/** + * @Author: 王林 + * @Date: 2021-07-10 22:34:51 + * @Desc: 选择节点类 + */ + +class Select { + /** + * @Author: 王林 + * @Date: 2021-07-10 22:35:16 + * @Desc: 构造函数 + */ + constructor({ mindMap }) { + this.mindMap = mindMap + this.rect = null + this.isMousedown = false + this.mouseDownX = 0 + this.mouseDownY = 0 + this.mouseMoveX = 0 + this.mouseMoveY = 0 + this.bindEvent() + } + + /** + * @Author: 王林 + * @Date: 2021-07-10 22:36:36 + * @Desc: 绑定事件 + */ + bindEvent() { + this.checkInNodes = throttle(this.checkInNodes, 500, this) + this.mindMap.on('contextmenu', (e) => { + this.isMousedown = true + let { x, y } = this.toPos(e.clientX, e.clientY) + this.mouseDownX = x + this.mouseDownY = y + this.createRect(x, y) + }) + this.mindMap.on('mousemove', (e) => { + if (!this.isMousedown) { + return + } + this.mouseMoveX = e.clientX + this.mouseMoveY = e.clientY + this.rect.plot([ + [this.mouseDownX, this.mouseDownY], + [this.mouseMoveX, this.mouseDownY], + [this.mouseMoveX, this.mouseMoveY], + [this.mouseDownX, this.mouseMoveY] + ]) + this.mindMap.batchExecution.push('checkInNodes', this.checkInNodes) + }) + this.mindMap.on('mouseup', (e) => { + if (!this.isMousedown) { + return; + } + this.mindMap.emit('node_active', null, this.mindMap.renderer.activeNodeList) + this.isMousedown = false + if (this.rect) this.rect.remove() + this.rect = null + }) + } + + /** + * @Author: 王林 + * @Date: 2021-07-11 10:19:37 + * @Desc: 创建矩形 + */ + createRect(x, y) { + this.rect = this.mindMap.svg.polygon().stroke({ + color: '#0984e3' + }).fill({ + color: 'rgba(9,132,227,0.3)' + }).plot([[x, y]]) + } + + /** + * @Author: 王林 + * @Date: 2021-07-11 09:20:03 + * @Desc: 转换位置 + */ + toPos(x, y) { + return { + x: x - this.mindMap.elRect.left, + y: y - this.mindMap.elRect.top + } + } + + /** + * @Author: 王林 + * @Date: 2021-07-11 10:20:43 + * @Desc: 检测在选区里的节点 + */ + checkInNodes() { + let { scaleX, scaleY, translateX, translateY } = this.mindMap.draw.transform() + let minx = Math.min(this.mouseDownX, this.mouseMoveX) + let miny = Math.min(this.mouseDownY, this.mouseMoveY) + let maxx = Math.max(this.mouseDownX, this.mouseMoveX) + let maxy = Math.max(this.mouseDownY, this.mouseMoveY) + bfsWalk(this.mindMap.renderer.root, (node) => { + let { left, top, width, height } = node + let right = (left + width) * scaleX + translateX + let bottom = (top + height) * scaleY + translateY + left = left * scaleX + translateX + top = top * scaleY + translateY + if ( + left >= minx && + right <= maxx && + top >= miny && + bottom <= maxy + ) { + this.mindMap.execCommand('SET_NODE_ACTIVE', node, true) + this.mindMap.renderer.addActiveNode(node) + } else if (node.nodeData.data.isActive) { + this.mindMap.execCommand('SET_NODE_ACTIVE', node, false) + this.mindMap.renderer.removeActiveNode(node) + } + }) + } +} + +export default Select \ No newline at end of file diff --git a/simple-mind-map/src/utils/index.js b/simple-mind-map/src/utils/index.js index a176e07e..b41baf47 100644 --- a/simple-mind-map/src/utils/index.js +++ b/simple-mind-map/src/utils/index.js @@ -8,7 +8,7 @@ export const walk = (root, parent, beforeCallback, afterCallback, isRoot, layerI let stop = false if (beforeCallback) { stop = beforeCallback(root, parent, isRoot, layerIndex, index) - } + } if (!stop && root.children && root.children.length > 0) { let _layerIndex = layerIndex + 1 root.children.forEach((node, nodeIndex) => { @@ -176,4 +176,22 @@ export const downloadFile = (file, fileName) => { a.href = file a.download = fileName a.click() +} + +/** + * @Author: 王林 + * @Date: 2021-07-11 10:36:47 + * @Desc: 节流函数 + */ +export const throttle = (fn, time = 300, ctx) => { + let timer = null + return () => { + if (timer) { + return + } + timer = setTimeout(() => { + fn.call(ctx) + timer = null + }, 300) + }; } \ No newline at end of file diff --git a/web/src/pages/Edit/components/NodeHyperlink.vue b/web/src/pages/Edit/components/NodeHyperlink.vue index 332a69d2..66fa6e91 100644 --- a/web/src/pages/Edit/components/NodeHyperlink.vue +++ b/web/src/pages/Edit/components/NodeHyperlink.vue @@ -6,13 +6,17 @@ width="500" >