From f3780baedc61dc5e6dc4b9ab718254a81e5fe3fd Mon Sep 17 00:00:00 2001 From: wanglin <1013335014@qq.com> Date: Sat, 10 Jul 2021 22:06:45 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=9F=BA=E6=9C=AC=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple-mind-map/example/exampleData.js | 668 +++++++++++------- simple-mind-map/index.js | 30 +- simple-mind-map/src/Node.js | 559 ++++++++++----- simple-mind-map/src/Render.js | 230 +++++- simple-mind-map/src/TextEdit.js | 7 +- simple-mind-map/src/layouts/Base.js | 41 +- .../src/layouts/LogicalStructure.js | 66 +- .../src/layouts/_CatalogOrganization.js | 2 +- .../src/layouts/_OrganizationStructure.js | 2 +- simple-mind-map/src/themes/default.js | 9 +- simple-mind-map/src/utils/index.js | 7 +- web/src/components/ImgUpload/index.vue | 7 +- web/src/config/index.js | 3 + .../pages/Edit/components/NodeHyperlink.vue | 5 +- web/src/pages/Edit/components/NodeIcon.vue | 4 +- web/src/pages/Edit/components/NodeImage.vue | 24 +- web/src/pages/Edit/components/NodeNote.vue | 4 +- web/src/pages/Edit/components/NodeTag.vue | 12 +- web/src/pages/Edit/components/Style.vue | 88 ++- 19 files changed, 1182 insertions(+), 586 deletions(-) diff --git a/simple-mind-map/example/exampleData.js b/simple-mind-map/example/exampleData.js index e8c6f429..bea79b9b 100644 --- a/simple-mind-map/example/exampleData.js +++ b/simple-mind-map/example/exampleData.js @@ -24,6 +24,13 @@ export default { "data": { "text": "根节点" }, + "childrens": [ + { + "data": { + "text": "二级节点1" + } + } + ], "children": [ { "data": { @@ -32,286 +39,431 @@ export default { }, "children": [{ "data": { - "text": "子节点1-1", + "text": "子节点", + ...createFullData() }, - }, { - "data": { - "text": "子节点1-2", - } - },] + "children": [{ + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + "children": [{ + "data": { + "text": "子节点", + ...createFullData() + }, + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + "children": [{ + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + "children": [{ + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + "children": [{ + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + "children": [{ + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }] + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }] + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }] + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }] + }, { + "data": { + "text": "子节点", + }, + }] + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }] + }] }, { "data": { - "text": "二级节点2" + "text": "二级节点2", + "expand": true, }, - "children": [ - { - "data": { - "text": "子节点2-1" - }, - "children": [ - { - "data": { - "text": "子节点2-1-1", - } - }, - { - "data": { - "text": "子节点2-1-2", - }, - "children": [ - { - "data": { - "text": "子节点2-1-2-1", - } - }, - { - "data": { - "text": "子节点2-1-2-2", - }, - "children": [ - { - "data": { - "text": "子节点2-1-2-2-1", - } - }, - { - "data": { - "text": "子节点2-1-2-2-2" - } - }, - { - "data": { - "text": "子节点2-1-2-2-3", - } - } - ] - }, - { - "data": { - "text": "子节点4-1-2-3" - } - } - ] - }, - { - "data": { - "text": "子节点2-1-3", - } - } - ] + "children": [{ + "data": { + "text": "子节点", }, - { + }, { + "data": { + "text": "子节点", + }, + "children": [{ "data": { - "text": "子节点2-2", + "text": "子节点", }, - "children": [ - { - "data": { - "text": "子节点2-1-1", - } - }, - { - "data": { - "text": "子节点2-1-2", - }, - "children": [ - { - "data": { - "text": "子节点2-1-2-1", - } - }, - { - "data": { - "text": "子节点2-1-2-2", - }, - "children": [ - { - "data": { - "text": "子节点2-1-2-2-1", - } - }, - { - "data": { - "text": "子节点2-1-2-2-2" - } - }, - { - "data": { - "text": "子节点2-1-2-2-3", - } - } - ] - }, - { - "data": { - "text": "子节点4-1-2-3" - } - } - ] - }, - { - "data": { - "text": "子节点2-1-3", - } - } - ] - } - ] + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }] + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }] }, { "data": { "text": "二级节点3", + "expand": true, }, - "children": [ - { - "data": { - "text": "子节点3-1", - }, - "children": [ - { - "data": { - "text": "子节点2-1-1", - } - }, - { - "data": { - "text": "子节点2-1-2", - }, - "children": [ - { - "data": { - "text": "子节点2-1-2-1", - } - }, - { - "data": { - "text": "子节点2-1-2-2", - }, - "children": [ - { - "data": { - "text": "子节点2-1-2-2-1", - } - }, - { - "data": { - "text": "子节点2-1-2-2-2" - } - }, - { - "data": { - "text": "子节点2-1-2-2-3", - } - } - ] - }, - { - "data": { - "text": "子节点4-1-2-3" - } - } - ] - }, - { - "data": { - "text": "子节点2-1-3", - } - } - ] + "children": [{ + "data": { + "text": "子节点", }, - { + "children": [{ "data": { - "text": "子节点3-2" - } - } - ] + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + "children": [{ + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }] + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }] + }] }, { "data": { "text": "二级节点4", + "expand": true, }, - "children": [ - { - "data": { - "text": "子节点4-1", - }, - "children": [ - { - "data": { - "text": "子节点4-1-1", - }, - "children": [ - { - "data": { - "text": "子节点2-1-1", - } - }, - { - "data": { - "text": "子节点2-1-2", - }, - "children": [ - { - "data": { - "text": "子节点2-1-2-1", - } - }, - { - "data": { - "text": "子节点2-1-2-2", - }, - "children": [ - { - "data": { - "text": "子节点2-1-2-2-1", - } - }, - { - "data": { - "text": "子节点2-1-2-2-2" - } - }, - { - "data": { - "text": "子节点2-1-2-2-3", - } - } - ] - }, - { - "data": { - "text": "子节点4-1-2-3" - } - } - ] - }, - { - "data": { - "text": "子节点2-1-3", - } - } - ] - }, - { - "data": { - "text": "子节点4-1-2", - } - }, - { - "data": { - "text": "子节点4-1-3" - } - } - ] + "children": [{ + "data": { + "text": "子节点", }, - { + "children": [{ "data": { - "text": "子节点4-2", - } - } - ] - } + "text": "子节点", + }, + "children": [{ + "data": { + "text": "子节点", + }, + "children": [{ + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }] + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }] + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + "children": [{ + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + "children": [{ + "data": { + "text": "子节点", + }, + "children": [{ + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }] + }, { + "data": { + "text": "子节点", + }, + "children": [{ + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + "children": [{ + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }] + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }] + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }] + }] + }, { + "data": { + "text": "子节点", + }, + "children": [{ + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + "children": [{ + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + "children": [{ + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }] + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }] + }, { + "data": { + "text": "子节点", + }, + "children": [{ + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }] + }, { + "data": { + "text": "子节点", + }, + "children": [{ + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }, { + "data": { + "text": "子节点", + }, + }] + }] + }] + }] + }, ] }, "theme": { diff --git a/simple-mind-map/index.js b/simple-mind-map/index.js index b48f5f02..504d27c7 100644 --- a/simple-mind-map/index.js +++ b/simple-mind-map/index.js @@ -7,7 +7,7 @@ import Style from './src/Style' import KeyCommand from './src/KeyCommand' import Command from './src/Command' import BatchExecution from './src/BatchExecution' -import Export from './src/Export'; +import Export from './src/Export' import { SVG } from '@svgdotjs/svg.js' @@ -101,10 +101,10 @@ class MindMap { this.batchExecution = new BatchExecution() // 初始渲染 - this.renderer.render() + this.reRender() setTimeout(() => { this.command.addHistory() - }, 0); + }, 0) } /** @@ -128,12 +128,26 @@ class MindMap { * javascript comment * @Author: 王林25 * @Date: 2021-04-06 18:47:29 - * @Desc: 渲染 + * @Desc: 渲染,部分渲染 */ render() { + this.batchExecution.push('render', () => { + this.initTheme() + this.renderer.reRender = false + this.renderer.render() + }) + } + + /** + * @Author: 王林 + * @Date: 2021-07-08 22:05:11 + * @Desc: 重新渲染 + */ + reRender() { this.batchExecution.push('render', () => { this.draw.clear() this.initTheme() + this.renderer.reRender = true this.renderer.render() }) } @@ -184,7 +198,7 @@ class MindMap { */ setTheme(theme) { this.opt.theme = theme - this.render() + this.reRender() } /** @@ -193,7 +207,7 @@ class MindMap { * @Desc: 获取当前主题 */ getTheme() { - return this.opt.theme; + return this.opt.theme } /** @@ -203,7 +217,7 @@ class MindMap { */ setThemeConfig(config) { this.opt.themeConfig = config - this.render() + this.reRender() } /** @@ -231,7 +245,7 @@ class MindMap { */ async export(...args) { let result = await this.doExport.export(...args) - return result; + return result } } diff --git a/simple-mind-map/src/Node.js b/simple-mind-map/src/Node.js index 78e26324..e493d7df 100644 --- a/simple-mind-map/src/Node.js +++ b/simple-mind-map/src/Node.js @@ -1,16 +1,18 @@ import Style from './Style' import { - resizeImgSize, - copyRenderTree, - imgToDataUrl + resizeImgSize } from './utils' import { Image, SVG, - Circle + Circle, + A, + G, + Rect, + Text } from '@svgdotjs/svg.js' import btnsSvg from './svg/btns' -import iconsSvg from './svg/icons'; +import iconsSvg from './svg/icons' /** * javascript comment @@ -48,8 +50,6 @@ class Node { this.width = opt.width || 0 // 节点高 this.height = opt.height || 0 - // 节点文字内容部分高 - this._textContentHeight = 0 // left this.left = opt.left || 0 // top @@ -60,18 +60,47 @@ class Node { this.children = opt.children || [] // 节点内容的容器 this.group = null - // 节点内容是否发生了变化,是的话会重新计算和渲染 - this.changed = true - // 文本节点 - this.textNode = null + // 节点内容对象 + this._imgData = null + this._iconData = null + this._textData = null + this._hyperlinkData = null + this._tagData = null + this._noteData = null + this._expandBtn = null + this._lines = [] + // 尺寸信息 + this._rectInfo = { + imgContentWidth: 0, + imgContentHeight: 0, + textContentHeight: 0, + textContentHeight: 0 + } // icon间距 this._textContentItemMargin = 2 // 图片和文字节点的间距 this._blockContentMargin = 5 // 展开收缩按钮尺寸 this._expandBtnSize = 20 - // 计算节点尺寸 - this.refreshSize() + // 初始渲染 + this._initRender = true + // 初始化 + this.createNodeData() + this.getSize() + } + + /** + * @Author: 王林 + * @Date: 2021-07-05 23:11:39 + * @Desc: 复位部分布局时会重新设置的数据 + */ + reset() { + this.children = [] + this.parent = null + this.isRoot = false + this.layerIndex = 0 + this.left = 0 + this.top = 0 } /** @@ -82,7 +111,8 @@ class Node { handleData(data) { data.data.expand = data.data.expand === false ? false : true data.data.isActive = data.data.isActive === true ? true : false - return data; + data.children = data.children || [] + return data } /** @@ -95,22 +125,84 @@ class Node { this.children.push(node) } + /** + * @Author: 王林 + * @Date: 2021-07-06 22:08:09 + * @Desc: 创建节点的各个内容对象数据 + */ + createNodeData() { + this._imgData = this.createImgNode() + this._iconData = this.createIconNode() + this._textData = this.createTextNode() + this._hyperlinkData = this.createHyperlinkNode() + this._tagData = this.createTagNode() + this._noteData = this.createNoteNode() + } + + /** + * @Author: 王林 + * @Date: 2021-07-10 09:20:02 + * @Desc: 解绑所有事件 + */ + removeAllEvent() { + if (this._noteData) { + this._noteData.node.off(['mouseover', 'mouseout']) + } + if (this._expandBtn) { + this._expandBtn.off(['mouseover', 'mouseout', 'click']) + } + if (this.group) { + this.group.off(['click', 'dblclick']) + } + } + + /** + * @Author: 王林 + * @Date: 2021-07-07 21:27:24 + * @Desc: 移除节点内容 + */ + removeAllNode() { + // 节点内的内容 + ;[this._imgData, this._iconData, this._textData, this._hyperlinkData, this._tagData, this._noteData].forEach((item) => { + if (item && item.node) item.node.remove() + }) + this._imgData = null + this._iconData = null + this._textData = null + this._hyperlinkData = null + this._tagData = null + this._noteData = null + // 展开收缩按钮 + if (this._expandBtn) { + this._expandBtn.remove() + this._expandBtn = null + } + // 组 + if (this.group) { + this.group.clear() + this.group.remove() + this.group = null + } + } + /** * javascript comment * @Author: 王林25 * @Date: 2021-04-09 09:46:23 - * @Desc: 刷新节点的宽高 + * @Desc: 计算节点的宽高 */ - refreshSize() { - if (!this.changed) { - return; - } + getSize() { + this.removeAllNode() + this.createNodeData() let { width, height } = this.getNodeRect() + // 判断节点尺寸是否有变化 + let changed = this.width !== width || this.height !== height this.width = width this.height = height + return changed } /** @@ -126,50 +218,42 @@ class Node { let textContentWidth = 0 let textContentHeight = 0 // 存在图片 - let imgObj = this.createImgNode() - if (imgObj) { - imgContentWidth = imgObj.width - imgContentHeight = imgObj.height + if (this._imgData) { + this._rectInfo.imgContentWidth = imgContentWidth = this._imgData.width + this._rectInfo.imgContentHeight = imgContentHeight = this._imgData.height } // 图标 - let iconObjs = this.createIconNode() - if (iconObjs.length > 0) { - textContentWidth += iconObjs.reduce((sum, cur) => { + if (this._iconData.length > 0) { + textContentWidth += this._iconData.reduce((sum, cur) => { textContentHeight = Math.max(textContentHeight, cur.height) return sum += cur.width + this._textContentItemMargin }, 0) } // 文字 - let textObj = this.createTextNode() - if (textObj) { - textContentWidth += textObj.width - textContentHeight = Math.max(textContentHeight, textObj.height) + if (this._textData) { + textContentWidth += this._textData.width + textContentHeight = Math.max(textContentHeight, this._textData.height) } // 超链接 - let hyperlinkObj = this.createHyperlinkNode() - if (hyperlinkObj) { - textContentWidth += hyperlinkObj.width - textContentHeight = Math.max(textContentHeight, hyperlinkObj.height) - hyperlinkObj.node.remove() + if (this._hyperlinkData) { + textContentWidth += this._hyperlinkData.width + textContentHeight = Math.max(textContentHeight, this._hyperlinkData.height) } // 标签 - let tagObjs = this.createTagNode() - if (tagObjs.length > 0) { - textContentWidth += tagObjs.reduce((sum, cur) => { + if (this._tagData.length > 0) { + textContentWidth += this._tagData.reduce((sum, cur) => { textContentHeight = Math.max(textContentHeight, cur.height) - cur.node.remove() return sum += cur.width + this._textContentItemMargin }, 0) } // 备注 - let noteObj = this.createNoteNode() - if (noteObj) { - textContentWidth += noteObj.width - textContentHeight = Math.max(textContentHeight, noteObj.height) - noteObj.node.remove() + if (this._noteData) { + textContentWidth += this._noteData.width + textContentHeight = Math.max(textContentHeight, this._noteData.height) } - // 文字内容部分的高度 - this._textContentHeight = textContentHeight + // 文字内容部分的尺寸 + this._rectInfo.textContentWidth = textContentWidth + this._rectInfo.textContentHeight = textContentHeight // 间距 let margin = imgContentHeight > 0 && textContentHeight > 0 ? this._blockContentMargin : 0 let { paddingX, paddingY } = this.getPaddingVale() @@ -221,7 +305,7 @@ class Node { createIconNode() { let _data = this.nodeData.data if (!_data.icon || _data.icon.length <= 0) { - return []; + return [] } let iconSize = this.themeConfig.iconSize return _data.icon.map((item) => { @@ -229,8 +313,8 @@ class Node { node: SVG(iconsSvg.getNodeIconListIcon(item)).size(iconSize, iconSize), width: iconSize, height: iconSize - }; - }); + } + }) } /** @@ -240,16 +324,21 @@ class Node { * @Desc: 创建文本节点 */ createTextNode() { - let node = this.draw.text(this.nodeData.data.text || '') - this.style.text(node) + let g = new G() + let fontSize = this.getStyle('fontSize', this.isRoot, this.nodeData.data.isActive) + let lineHeight = this.getStyle('lineHeight', this.isRoot, this.nodeData.data.isActive) + this.nodeData.data.text.split(/\n/img).forEach((item, index) => { + let node = new Text().text(item) + this.style.text(node) + node.y(fontSize * lineHeight * index) + g.add(node) + }) let { width, height - } = node.bbox() - let cloneNode = node.clone() - node.remove() + } = g.bbox() return { - node: cloneNode, + node: g, width, height } @@ -266,19 +355,24 @@ class Node { return } let iconSize = this.themeConfig.iconSize - let node = this.draw.link(hyperlink).target('_blank') - node.node.addEventListener('click', (e) => { + let node = new SVG() + // 超链接节点 + let a = new A().to(hyperlink).target('_blank') + a.node.addEventListener('click', (e) => { e.stopPropagation() }) if (hyperlinkTitle) { - node.attr('title', hyperlinkTitle) + a.attr('title', hyperlinkTitle) } - node.rect(iconSize, iconSize).fill({ color: 'transparent' }) + // 添加一个透明的层,作为鼠标区域 + a.rect(iconSize, iconSize).fill({ color: 'transparent' }) + // 超链接图标 let iconNode = SVG(iconsSvg.hyperlink).size(iconSize, iconSize) this.style.iconNode(iconNode) - node.add(iconNode) + a.add(iconNode) + node.add(a) return { - node: node, + node, width: iconSize, height: iconSize } @@ -292,29 +386,29 @@ class Node { createTagNode() { let tagData = this.nodeData.data.tag if (!tagData || tagData.length <= 0) { - return []; + return [] } let nodes = [] tagData.slice(0, this.mindMap.opt.maxTag).forEach((item, index) => { - let tag = this.draw.group() - let text = this.draw.text(item).x(8).cy(10) + let tag = new G() + // 标签文本 + let text = new Text().text(item).x(8).cy(10) this.style.tagText(text, index) let { width, height } = text.bbox() - let cloneText = text.clone() - text.remove() - let rect = this.draw.rect(width + 16, 20) + // 标签矩形 + let rect = new Rect().size(width + 16, 20) this.style.tagRect(rect, index) - tag.add(rect).add(cloneText) + tag.add(rect).add(text) nodes.push({ node: tag, width: width + 16, height: 20 }) }) - return nodes; + return nodes } /** @@ -324,14 +418,17 @@ class Node { */ createNoteNode() { if (!this.nodeData.data.note) { - return null; + return null } - let node = this.draw.group().attr('cursor', 'pointer') let iconSize = this.themeConfig.iconSize - node.add(this.draw.rect(iconSize, iconSize).fill({ color: 'transparent' })) + let node = new SVG().attr('cursor', 'pointer') + // 透明的层,用来作为鼠标区域 + node.add(new Rect().size(iconSize, iconSize).fill({ color: 'transparent' })) + // 备注图标 let iconNode = SVG(iconsSvg.note).size(iconSize, iconSize) this.style.iconNode(iconNode) node.add(iconNode) + // 备注tooltip let el = document.createElement('div') el.style.cssText = ` position: absolute; @@ -356,47 +453,43 @@ class Node { node, width: iconSize, height: iconSize - }; + } } /** * javascript comment * @Author: 王林25 * @Date: 2021-04-09 11:10:11 - * @Desc: 创建内容节点 + * @Desc: 定位节点内容 */ - createNode() { + layout() { let { - left, - top, width, height, - _textContentHeight, _textContentItemMargin } = this let { paddingY } = this.getPaddingVale() // 创建组 - this.group = this.draw.group() + this.group = new G() + this.updatePos(false) // 节点矩形 - this.style.rect(this.group.rect(width, height).x(left).y(top)) + this.style.rect(this.group.rect(width, height)) // 图片节点 - let imgObj = this.createImgNode() let imgHeight = 0 - if (imgObj) { - imgHeight = imgObj.height - this.group.add(imgObj.node) - imgObj.node.cx(left + width / 2).y(top + paddingY) + if (this._imgData) { + imgHeight = this._imgData.height + this.group.add(this._imgData.node) + this._imgData.node.cx(width / 2).y(paddingY) } // 内容节点 - let textContentNested = this.draw.group() + let textContentNested = new G() let textContentOffsetX = 0 // icon - let iconObjs = this.createIconNode() - let iconNested = this.draw.group() - if (iconObjs && iconObjs.length > 0) { + let iconNested = new G() + if (this._iconData && this._iconData.length > 0) { let iconLeft = 0 - iconObjs.forEach((item) => { - item.node.x(textContentOffsetX + iconLeft).y((_textContentHeight - item.height) / 2) + this._iconData.forEach((item) => { + item.node.x(textContentOffsetX + iconLeft).y((this._rectInfo.textContentHeight - item.height) / 2) iconNested.add(item.node) iconLeft += item.width + _textContentItemMargin }) @@ -404,27 +497,23 @@ class Node { textContentOffsetX += iconLeft } // 文字 - let textObj = this.createTextNode() - if (textObj) { - textObj.node.x(textContentOffsetX).y(0) - this.textNode = textObj - textContentNested.add(textObj.node) - textContentOffsetX += textObj.width + _textContentItemMargin + if (this._textData) { + this._textData.node.x(textContentOffsetX).y(0) + textContentNested.add(this._textData.node) + textContentOffsetX += this._textData.width + _textContentItemMargin } // 超链接 - let hyperlinkObj = this.createHyperlinkNode() - if (hyperlinkObj) { - hyperlinkObj.node.translate(textContentOffsetX, (_textContentHeight - hyperlinkObj.height) / 2) - textContentNested.add(hyperlinkObj.node) - textContentOffsetX += hyperlinkObj.width + _textContentItemMargin + if (this._hyperlinkData) { + this._hyperlinkData.node.x(textContentOffsetX).y((this._rectInfo.textContentHeight - this._hyperlinkData.height) / 2) + textContentNested.add(this._hyperlinkData.node) + textContentOffsetX += this._hyperlinkData.width + _textContentItemMargin } // 标签 - let tagObjs = this.createTagNode() - let tagNested = this.draw.group() - if (tagObjs && tagObjs.length > 0) { + let tagNested = new G() + if (this._tagData && this._tagData.length > 0) { let tagLeft = 0 - tagObjs.forEach((item) => { - item.node.x(textContentOffsetX + tagLeft).y((_textContentHeight - item.height) / 2) + this._tagData.forEach((item) => { + item.node.x(textContentOffsetX + tagLeft).y((this._rectInfo.textContentHeight - item.height) / 2) tagNested.add(item.node) tagLeft += item.width + _textContentItemMargin }) @@ -432,79 +521,91 @@ class Node { textContentOffsetX += tagLeft } // 备注 - let noteObj = this.createNoteNode() - if (noteObj) { - noteObj.node.translate(textContentOffsetX, (_textContentHeight - noteObj.height) / 2) - textContentNested.add(noteObj.node) - textContentOffsetX += noteObj.width + if (this._noteData) { + this._noteData.node.x(textContentOffsetX).y((this._rectInfo.textContentHeight - this._noteData.height) / 2) + textContentNested.add(this._noteData.node) + textContentOffsetX += this._noteData.width } // 文字内容整体 textContentNested.translate( - left + width / 2 - textContentNested.bbox().width / 2, - top + imgHeight + paddingY + (imgHeight > 0 && _textContentHeight > 0 ? this._blockContentMargin : 0) + width / 2 - textContentNested.bbox().width / 2, + imgHeight + paddingY + (imgHeight > 0 && this._rectInfo.textContentHeight > 0 ? this._blockContentMargin : 0) ) this.group.add(textContentNested) // 单击事件,选中节点 - this.group.click((e) => { - e.stopPropagation() - if (this.nodeData.data.isActive) { - return; - } - this.mindMap.emit('before_node_active', this, this.renderer.activeNodeList) - this.renderer.clearActive() - this.mindMap.execCommand('UPDATE_NODE_DATA', this, { - isActive: !this.nodeData.data.isActive - }) - this.renderNode() - this.renderer.activeNodeList.push(this) - this.mindMap.emit('node_active', this, this.renderer.activeNodeList) + this.group.on('click', (e) => { + this.active(e) }) // 双击事件 - this.group.dblclick(() => { + this.group.on('dblclick', () => { this.mindMap.emit('node_dblclick', this) }) } + /** + * @Author: 王林 + * @Date: 2021-07-10 16:44:22 + * @Desc: 激活节点 + */ + active(e) { + e.stopPropagation() + if (this.nodeData.data.isActive) { + return + } + 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.mindMap.emit('node_active', this, this.renderer.activeNodeList) + } + /** * @Author: 王林 * @Date: 2021-07-04 20:20:09 - * @Desc: 渲染节点到画布 + * @Desc: 渲染节点到画布,会移除旧的,创建新的 */ renderNode() { - if (this.group) { - this.group.remove() - } - this.createNode() + this.removeAllEvent() + this.removeAllNode() + this.createNodeData() + this.layout() + this.renderExpandBtn() this.draw.add(this.group) } /** * @Author: 王林 * @Date: 2021-07-04 22:47:01 - * @Desc: 更新整体位置 + * @Desc: 更新节点位置 */ - updatePos() { - + updatePos(animate = true) { + if (!this.group) { + return; + } + let t = this.group.transform() + if (animate) { + this.group.animate(300).translate(this.left - t.translateX, this.top - t.translateY) + } else { + this.group.translate(this.left - t.translateX, this.top - t.translateY) + } } /** * javascript comment * @Author: 王林25 * @Date: 2021-04-07 13:55:58 - * @Desc: 渲染 + * @Desc: 递归渲染 */ render() { // 连线 this.renderLine() - // 按钮 - this.renderExpandBtn() // 节点 - if (this.changed) { + if (this._initRender) { + this._initRender = false this.renderNode() } else { this.updatePos() } - this.changed = false // 子节点 if (this.children && this.children.length && this.nodeData.data.expand !== false) { this.children.forEach((child) => { @@ -513,6 +614,24 @@ class Node { } } + /** + * @Author: 王林 + * @Date: 2021-07-10 09:24:55 + * @Desc: 递归删除 + */ + remove() { + this._initRender = true + this.removeAllEvent() + this.removeAllNode() + this.removeLine() + // 子节点 + if (this.children && this.children.length) { + this.children.forEach((child) => { + child.remove() + }) + } + } + /** * @Author: 王林 * @Date: 2021-04-10 22:01:53 @@ -520,24 +639,50 @@ class Node { */ renderLine() { if (this.nodeData.data.expand === false) { - return; + return } - let lines = this.renderer.layout.renderLine(this) - lines.forEach((line) => { + let childrenLen = this.nodeData.children.length + if (childrenLen > this._lines.length) { + // 创建缺少的线 + new Array(childrenLen - this._lines.length).fill(0).forEach(() => { + this._lines.push(this.draw.path()) + }) + } else if (childrenLen < this._lines.length) { + // 删除多余的线 + this._lines.slice(childrenLen).forEach((line) => { + line.remove() + }) + this._lines = this._lines.slice(0, childrenLen) + } + // 画线 + this.renderer.layout.renderLine(this, this._lines) + // 添加样式 + this._lines.forEach((line) => { this.style.line(line) }) } /** * @Author: 王林 - * @Date: 2021-04-11 19:47:01 - * @Desc: 展开收缩按钮 + * @Date: 2021-07-10 16:40:21 + * @Desc: 移除连线 */ - renderExpandBtn() { - if (this.children.length <= 0 || this.isRoot) { - return; + removeLine() { + this._lines.forEach((line) => { + line.remove() + }) + this._lines = [] + } + + /** + * @Author: 王林 + * @Date: 2021-07-10 17:59:14 + * @Desc: 创建或更新展开收缩按钮内容 + */ + updateExpandBtnNode() { + if (this._expandBtn) { + this._expandBtn.clear() } - let g = this.draw.group() let iconSvg if (this.nodeData.data.expand === false) { iconSvg = btnsSvg.open @@ -546,29 +691,43 @@ class Node { } let node = SVG(iconSvg).size(this._expandBtnSize, this._expandBtnSize) let fillNode = new Circle().size(this._expandBtnSize) - this.renderer.layout.renderExpandBtn(this, [node, fillNode]) - node.dx(0).dy(-10) - fillNode.dx(0).dy(-10) + node.x(0).y(-this._expandBtnSize / 2) + fillNode.x(0).y(-this._expandBtnSize / 2) this.style.iconBtn(node, fillNode) - g.mouseover(() => { - g.css({ + this._expandBtn.add(fillNode).add(node) + } + + /** + * @Author: 王林 + * @Date: 2021-04-11 19:47:01 + * @Desc: 展开收缩按钮 + */ + renderExpandBtn() { + if (!this.nodeData.children || this.nodeData.children.length <= 0 || this.isRoot) { + return + } + this._expandBtn = new G() + this.updateExpandBtnNode() + this._expandBtn.on('mouseover', (e) => { + e.stopPropagation() + this._expandBtn.css({ cursor: 'pointer' }) }) - g.mouseout(() => { - g.css({ + this._expandBtn.on('mouseout', (e) => { + e.stopPropagation() + this._expandBtn.css({ cursor: 'auto' }) }) - g.click(() => { + this._expandBtn.on('click', (e) => { + e.stopPropagation() // 展开收缩 - this.mindMap.execCommand('UPDATE_NODE_DATA', this, { - expand: !this.mindMap.nodeData.data.expand - }, children) + this.mindMap.execCommand('SET_NODE_EXPAND', this, !this.nodeData.data.expand) this.mindMap.emit('expand_btn_click', this) }) - g.add(fillNode) - g.add(node) + this.group.add(this._expandBtn) + this.renderer.layout.renderExpandBtn(this, this._expandBtn) } /** @@ -580,7 +739,7 @@ class Node { return { paddingX: this.getStyle('paddingX', true, this.nodeData.data.isActive), paddingY: this.getStyle('paddingY', true, this.nodeData.data.isActive) - }; + } } /** @@ -599,19 +758,7 @@ class Node { * @Desc: 修改某个样式 */ setStyle(prop, value, isActive) { - if (isActive) { - this.mindMap.execCommand('UPDATE_NODE_DATA', this, { - activeStyle: { - ...(this.nodeData.data.activeStyle || {}), - [prop]: value - } - }) - } else { - this.mindMap.execCommand('UPDATE_NODE_DATA', this, { - [prop]: value - }) - } - this.renderNode() + this.mindMap.execCommand('SET_NODE_STYLE', this, prop, value, isActive) } /** @@ -620,7 +767,7 @@ class Node { * @Desc: 获取数据 */ getData(key) { - return key ? this.nodeData.data[key] || '' : this.nodeData.data; + return key ? this.nodeData.data[key] || '' : this.nodeData.data } /** @@ -629,7 +776,61 @@ class Node { * @Desc: 设置数据 */ setData(data = {}) { - this.mindMap.execCommand('UPDATE_NODE_DATA', this, data) + this.mindMap.execCommand('SET_NODE_DATA', this, data) + } + + /** + * @Author: 王林 + * @Date: 2021-07-10 08:41:28 + * @Desc: 设置文本 + */ + setText(text) { + this.mindMap.execCommand('SET_NODE_TEXT', this, text) + } + + /** + * @Author: 王林 + * @Date: 2021-07-10 08:42:19 + * @Desc: 设置图片 + */ + setImage(imgData) { + this.mindMap.execCommand('SET_NODE_IMAGE', this, imgData) + } + + /** + * @Author: 王林 + * @Date: 2021-07-10 08:47:29 + * @Desc: 设置图标 + */ + setIcon(icons) { + this.mindMap.execCommand('SET_NODE_ICON', this, icons) + } + + /** + * @Author: 王林 + * @Date: 2021-07-10 08:50:41 + * @Desc: 设置超链接 + */ + setHyperlink(link, title) { + this.mindMap.execCommand('SET_NODE_HYPERLINK', this, link, title) + } + + /** + * @Author: 王林 + * @Date: 2021-07-10 08:53:24 + * @Desc: 设置备注 + */ + setNote(note) { + this.mindMap.execCommand('SET_NODE_NOTE', this, note) + } + + /** + * @Author: 王林 + * @Date: 2021-07-10 08:55:08 + * @Desc: 设置标签 + */ + setTag(tag) { + this.mindMap.execCommand('SET_NODE_TAG', this, tag) } } diff --git a/simple-mind-map/src/Render.js b/simple-mind-map/src/Render.js index 9fd49050..cb45bab6 100644 --- a/simple-mind-map/src/Render.js +++ b/simple-mind-map/src/Render.js @@ -34,6 +34,8 @@ class Render { this.draw = this.mindMap.draw // 渲染树,操作过程中修改的都是这里的数据 this.renderTree = merge({}, this.mindMap.opt.data || {}) + // 是否重新渲染 + this.reRender = false // 当前激活的节点列表 this.activeNodeList = [] // 根节点 @@ -70,14 +72,48 @@ class Render { * @Desc: 注册命令 */ registerCommands() { + // 插入同级节点 this.insertNode = this.insertNode.bind(this) this.mindMap.command.add('INSERT_NODE', this.insertNode) + // 插入子节点 this.insertChildNode = this.insertChildNode.bind(this) this.mindMap.command.add('INSERT_CHILD_NODE', this.insertChildNode) + // 删除节点 this.removeNode = this.removeNode.bind(this) this.mindMap.command.add('REMOVE_NODE', this.removeNode) - this.updateNodeData = this.updateNodeData.bind(this) - this.mindMap.command.add('UPDATE_NODE_DATA', this.updateNodeData) + // 修改节点样式 + this.setNodeStyle = this.setNodeStyle.bind(this) + this.mindMap.command.add('SET_NODE_STYLE', this.setNodeStyle) + // 切换节点是否激活 + this.setNodeActive = this.setNodeActive.bind(this) + this.mindMap.command.add('SET_NODE_ACTIVE', this.setNodeActive) + // 清除所有激活节点 + this.clearActive = this.clearActive.bind(this) + this.mindMap.command.add('CLEAR_ACTIVE_NODE', this.clearActive) + // 切换节点是否展开 + this.setNodeExpand = this.setNodeExpand.bind(this) + this.mindMap.command.add('SET_NODE_EXPAND', this.setNodeExpand) + // 设置节点数据 + this.setNodeData = this.setNodeData.bind(this) + this.mindMap.command.add('SET_NODE_DATA', this.setNodeData) + // 设置节点文本 + this.setNodeText = this.setNodeText.bind(this) + this.mindMap.command.add('SET_NODE_TEXT', this.setNodeText) + // 设置节点图片 + this.setNodeImage = this.setNodeImage.bind(this) + this.mindMap.command.add('SET_NODE_IMAGE', this.setNodeImage) + // 设置节点图标 + this.setNodeIcon = this.setNodeIcon.bind(this) + this.mindMap.command.add('SET_NODE_ICON', this.setNodeIcon) + // 设置节点超链接 + this.setNodeHyperlink = this.setNodeHyperlink.bind(this) + this.mindMap.command.add('SET_NODE_HYPERLINK', this.setNodeHyperlink) + // 设置节点备注 + this.setNodeNote = this.setNodeNote.bind(this) + this.mindMap.command.add('SET_NODE_NOTE', this.setNodeNote) + // 设置节点标签 + this.setNodeTag = this.setNodeTag.bind(this) + this.mindMap.command.add('SET_NODE_TAG', this.setNodeTag) } /** @@ -87,7 +123,6 @@ class Render { * @Desc: 渲染 */ render() { - console.log('渲染') this.root = this.layout.doLayout() this.root.render() } @@ -95,18 +130,27 @@ class Render { /** * @Author: 王林 * @Date: 2021-04-12 22:45:01 - * @Desc: 清楚当前激活的节点 + * @Desc: 清除当前激活的节点 */ clearActive() { this.activeNodeList.forEach((item) => { - this.mindMap.execCommand('UPDATE_NODE_DATA', item, { - isActive: false - }) - item.renderNode() + this.mindMap.execCommand('SET_NODE_ACTIVE', item, false) }) this.activeNodeList = [] } + /** + * @Author: 王林 + * @Date: 2021-07-10 10:04:04 + * @Desc: 在激活列表里移除某个节点 + */ + removeActiveNode(node) { + let index = this.activeNodeList.findIndex((item) => { + return item === node; + }) + this.activeNodeList.splice(index, 1) + } + /** * @Author: 王林 * @Date: 2021-05-04 13:46:08 @@ -152,11 +196,12 @@ class Render { if (this.activeNodeList.length <= 0) { return; } - let first = this.activeNodeList[0] - if (!first.nodeData.children) { - first.nodeData.children = [] + let node = this.activeNodeList[0] + if (!node.nodeData.children) { + node.nodeData.children = [] } - first.nodeData.children.push({ + let len = node.nodeData.children.length + node.nodeData.children.push({ "data": { "text": "分支主题", "expand": true @@ -164,6 +209,11 @@ class Render { "children": [] }) this.mindMap.render() + if (node.isRoot || len <= 0) { + this.mindMap.batchExecution.push('renderNode', () => { + node.renderNode() + }) + } } /** @@ -177,32 +227,176 @@ class Render { } this.activeNodeList.forEach((item) => { if (item.isRoot) { + item.children.forEach((child) => { + child.remove() + }) item.children = [] item.nodeData.children = [] } 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.clearActive() this.mindMap.render() } + /** + * @Author: 王林 + * @Date: 2021-07-08 21:54:30 + * @Desc: 设置节点样式 + */ + setNodeStyle(node, prop, value, isActive) { + let data = {} + if (isActive) { + data = { + activeStyle: { + ...(node.nodeData.data.activeStyle || {}), + [prop]: value + } + } + } else { + data = { + [prop]: value + } + } + this.setNodeDataRender(node, data) + } + + /** + * @Author: 王林 + * @Date: 2021-07-08 22:13:03 + * @Desc: 设置节点是否激活 + */ + setNodeActive(node, active) { + this.setNodeData(node, { + isActive: active + }) + node.renderNode() + } + + /** + * @Author: 王林 + * @Date: 2021-07-10 16:52:41 + * @Desc: 设置节点是否展开 + */ + setNodeExpand(node, expand) { + this.setNodeData(node, { + expand + }) + if (expand) {// 展开 + node.children.forEach((item) => { + item.render() + }) + node.renderLine() + node.updateExpandBtnNode() + } else {// 收缩 + node.children.forEach((item) => { + item.remove() + }) + node.removeLine() + node.updateExpandBtnNode() + } + this.mindMap.render() + } + + /** + * @Author: 王林 + * @Date: 2021-07-09 22:04:19 + * @Desc: 设置节点文本 + */ + setNodeText(node, text) { + this.setNodeDataRender(node, { + text + }) + } + + /** + * @Author: 王林 + * @Date: 2021-07-10 08:37:40 + * @Desc: 设置节点图片 + */ + setNodeImage(node, { url, title, width, height }) { + this.setNodeDataRender(node, { + image: url, + imageTitle: title || '', + imageSize: { + width, + height, + }, + }) + } + + /** + * @Author: 王林 + * @Date: 2021-07-10 08:44:06 + * @Desc: 设置节点图标 + */ + setNodeIcon(node, icons) { + this.setNodeDataRender(node, { + icon: icons + }) + } + + /** + * @Author: 王林 + * @Date: 2021-07-10 08:49:33 + * @Desc: 设置节点超链接 + */ + setNodeHyperlink(node, link, title = '') { + this.setNodeDataRender(node, { + hyperlink: link, + hyperlinkTitle: title, + }) + } + + /** + * @Author: 王林 + * @Date: 2021-07-10 08:52:59 + * @Desc: 设置节点备注 + */ + setNodeNote(node, note) { + this.setNodeDataRender(node, { + note + }) + } + + /** + * @Author: 王林 + * @Date: 2021-07-10 08:54:53 + * @Desc: 设置节点标签 + */ + setNodeTag(node, tag) { + this.setNodeDataRender(node, { + tag + }) + } + /** * @Author: 王林 * @Date: 2021-05-04 14:19:48 * @Desc: 更新节点数据 */ - updateNodeData(node, data, children) { + setNodeData(node, data) { Object.keys(data).forEach((key) => { node.nodeData.data[key] = data[key] }) - if (children) { - node.nodeData.children = children + } + + /** + * @Author: 王林 + * @Date: 2021-07-10 08:45:48 + * @Desc: 设置节点数据,并判断是否渲染 + */ + setNodeDataRender(node, data) { + this.setNodeData(node, data) + let changed = node.getSize() + node.renderNode() + if (changed) { + this.mindMap.render() } - node.changed = true - this.mindMap.render() } } diff --git a/simple-mind-map/src/TextEdit.js b/simple-mind-map/src/TextEdit.js index 5f04a371..d5e310dd 100644 --- a/simple-mind-map/src/TextEdit.js +++ b/simple-mind-map/src/TextEdit.js @@ -59,7 +59,7 @@ export default class TextEdit { * @Desc: 显示文本编辑框 */ show(node) { - this.showEditTextBox(node, node.textNode.node.node.getBoundingClientRect()) + this.showEditTextBox(node, node._textData.node.node.getBoundingClientRect()) } /** @@ -95,10 +95,7 @@ export default class TextEdit { } this.renderer.activeNodeList.forEach((node) => { let str = getStrWithBrFromHtml(this.textEditNode.innerHTML) - this.mindMap.execCommand('UPDATE_NODE_DATA', node, { - text: str - }) - node.changed = true + this.mindMap.execCommand('SET_NODE_TEXT', node, str) this.mindMap.render() }) this.mindMap.emit('hide_text_edit', this.textEditNode, this.renderer.activeNodeList) diff --git a/simple-mind-map/src/layouts/Base.js b/simple-mind-map/src/layouts/Base.js index 9f084320..5c96a442 100644 --- a/simple-mind-map/src/layouts/Base.js +++ b/simple-mind-map/src/layouts/Base.js @@ -1,3 +1,5 @@ +import Node from '../Node' + /** * @Author: 王林 * @Date: 2021-04-12 22:24:30 @@ -28,7 +30,6 @@ class Base { * @Desc: 计算节点位置 */ doLayout() { - console.log('布局') throw new Error('【computed】方法为必要方法,需要子类进行重写!') } @@ -50,6 +51,44 @@ class Base { throw new Error('【renderExpandBtn】方法为必要方法,需要子类进行重写!') } + /** + * @Author: 王林 + * @Date: 2021-07-10 21:30:54 + * @Desc: 创建节点实例 + */ + createNode(data, parent, isRoot, layerIndex) { + // 创建节点 + let newNode = null + // 复用节点 + if (data && data._node && !this.renderer.reRender) { + newNode = data._node + newNode.reset() + newNode.layerIndex = layerIndex + } else {// 创建新节点 + newNode = new Node({ + data, + uid: this.mindMap.uid++, + renderer: this.renderer, + mindMap: this.mindMap, + draw: this.draw, + layerIndex + }) + newNode.getSize() + // 数据关联实际节点 + data._node = newNode + } + // 根节点 + if (isRoot) { + newNode.isRoot = true + this.root = newNode + } else { + // 互相收集 + newNode.parent = parent._node + parent._node.addChildren(newNode) + } + return newNode; + } + /** * javascript comment * @Author: 王林25 diff --git a/simple-mind-map/src/layouts/LogicalStructure.js b/simple-mind-map/src/layouts/LogicalStructure.js index b9b00b4a..3234524c 100644 --- a/simple-mind-map/src/layouts/LogicalStructure.js +++ b/simple-mind-map/src/layouts/LogicalStructure.js @@ -2,7 +2,6 @@ import Base from './Base'; import { walk } from '../utils' -import Node from '../Node' /** * @Author: 王林 @@ -44,48 +43,25 @@ class LogicalStructure extends Base { */ computedBaseValue() { walk(this.renderTree, null, (cur, parent, isRoot, layerIndex) => { - // 创建节点 - let newNode = null - if (cur && cur._node) { - newNode = cur._node - newNode.children = [] - newNode.parent = null - if (cur._node.changed) { - newNode.refreshSize() - } - } else { - newNode = new Node({ - data: cur, - uid: this.mindMap.uid++, - renderer: this.renderer, - mindMap: this.mindMap, - draw: this.draw, - layerIndex - }) - // 数据关联实际节点 - cur._node = newNode - } + let newNode = this.createNode(cur, parent, isRoot, layerIndex) // 根节点定位在画布中心位置 if (isRoot) { - newNode.isRoot = true newNode.left = (this.mindMap.width - newNode.width) / 2 newNode.top = (this.mindMap.height - newNode.height) / 2 - this.root = newNode } else { // 非根节点 - let marginX = layerIndex === 1 ? this.mindMap.themeConfig.second.marginX : this.mindMap.themeConfig.node.marginX // 定位到父节点右侧 - newNode.left = parent._node.left + parent._node.width + marginX - // 互相收集 - newNode.parent = parent._node - parent._node.addChildren(newNode) + newNode.left = parent._node.left + parent._node.width + this.getMarginX(layerIndex) + } + if (!cur.data.expand) { + return true; } }, (cur, parent, isRoot, layerIndex) => { // 返回时计算节点的areaHeight,也就是子节点所占的高度之和,包括外边距 let len = cur.data.expand === false ? 0 : cur._node.children.length cur._node.childrenAreaHeight = len ? cur._node.children.reduce((h, item) => { return h + item.height - }, 0) + (len + 1) * this.getMarginY(layerIndex) : 0 + }, 0) + (len + 1) * this.getMarginY(layerIndex + 1) : 0 }, true, 0) } @@ -97,8 +73,8 @@ class LogicalStructure extends Base { */ computedTopValue() { walk(this.root, null, (node, parent, isRoot, layerIndex) => { - if (node.children && node.children.length) { - let marginY = this.getMarginY(layerIndex) + if (node.nodeData.data.expand && node.children && node.children.length) { + let marginY = this.getMarginY(layerIndex + 1) // 第一个子节点的top值 = 该节点中心的top值 - 子节点的高度之和的一半 let top = node.top + node.height / 2 - node.childrenAreaHeight / 2 let totalTop = top + marginY @@ -118,8 +94,11 @@ class LogicalStructure extends Base { */ adjustTopValue() { walk(this.root, null, (node, parent, isRoot, layerIndex) => { + if (!node.nodeData.data.expand) { + return; + } // 判断子节点所占的高度之和是否大于该节点自身,大于则需要调整位置 - let difference = node.childrenAreaHeight - this.getMarginY(layerIndex) - node.height + let difference = node.childrenAreaHeight - this.getMarginY(layerIndex + 1) - node.height if (difference > 0) { this.updateBrothers(node, difference / 2) } @@ -162,7 +141,7 @@ class LogicalStructure extends Base { * @Date: 2021-04-11 14:42:48 * @Desc: 绘制连线,连接该节点到其子节点 */ - renderLine(node) { + renderLine(node, lines) { if (node.children.length <= 0) { return []; } @@ -172,12 +151,7 @@ class LogicalStructure extends Base { width, height } = node - let lines = [] - if (!node.isRoot) { - let line = this.draw.line(left + width, top + height / 2, left + width + 20, top + height / 2) - lines.push(line) - } - node.children.forEach((item) => { + node.children.forEach((item, index) => { let x1 = node.layerIndex === 0 ? left + width / 2 : left + width + 20 let y1 = node.layerIndex === 0 ? top + height / 2 : top + height / 2 let x2 = item.left @@ -188,10 +162,8 @@ class LogicalStructure extends Base { } else { path = this.cubicBezierPath(x1, y1, x2, y2) } - let line = this.draw.path(path) - lines.push(line) + lines[index].plot(path) }) - return lines; } /** @@ -199,16 +171,12 @@ class LogicalStructure extends Base { * @Date: 2021-04-11 19:54:26 * @Desc: 渲染按钮 */ - renderExpandBtn(node, icons) { + renderExpandBtn(node, btn) { let { - left, - top, width, height } = node - icons.forEach((icon) => { - icon.x(left + width).y(top + height / 2) - }) + btn.translate(width, height / 2) } } diff --git a/simple-mind-map/src/layouts/_CatalogOrganization.js b/simple-mind-map/src/layouts/_CatalogOrganization.js index e2796840..0b8eea9a 100644 --- a/simple-mind-map/src/layouts/_CatalogOrganization.js +++ b/simple-mind-map/src/layouts/_CatalogOrganization.js @@ -75,7 +75,7 @@ class Render { draw: this.draw }) // 计算节点的宽高 - newNode.refreshSize() + newNode.getSize() // 计算节点的top if (isRoot) { newNode.isRoot = true diff --git a/simple-mind-map/src/layouts/_OrganizationStructure.js b/simple-mind-map/src/layouts/_OrganizationStructure.js index 7b329b7c..123b2c68 100644 --- a/simple-mind-map/src/layouts/_OrganizationStructure.js +++ b/simple-mind-map/src/layouts/_OrganizationStructure.js @@ -73,7 +73,7 @@ class Render { draw: this.draw }) // 计算节点的宽高 - newNode.refreshSize() + newNode.getSize() // 计算节点的top if (isRoot) { newNode.isRoot = true diff --git a/simple-mind-map/src/themes/default.js b/simple-mind-map/src/themes/default.js index 8a456f7c..8f82c633 100644 --- a/simple-mind-map/src/themes/default.js +++ b/simple-mind-map/src/themes/default.js @@ -5,8 +5,8 @@ */ export default { // 节点内边距 - paddingX: 20, - paddingY: 10, + paddingX: 15, + paddingY: 5, // 图片显示的最大宽度 imgMaxWidth: 100, // 图片显示的最大高度 @@ -31,6 +31,7 @@ export default { fontSize: 16, fontWeight: 'bold', fontStyle: 'normal', + lineHeight: 1.5, borderColor: 'transparent', borderWidth: 0, borderDasharray: 'none', @@ -52,6 +53,7 @@ export default { fontSize: 16, fontWeight: 'noraml', fontStyle: 'normal', + lineHeight: 1.5, borderColor: '#549688', borderWidth: 1, borderDasharray: 'none', @@ -66,13 +68,14 @@ export default { // 三级及以下节点样式 node: { marginX: 50, - marginY: 10, + marginY: 0, fillColor: 'transparent', fontFamily: '微软雅黑, Microsoft YaHei', color: '#6a6d6c', fontSize: 14, fontWeight: 'noraml', fontStyle: 'normal', + lineHeight: 1.5, borderColor: 'transparent', borderWidth: 0, borderRadius: 5, diff --git a/simple-mind-map/src/utils/index.js b/simple-mind-map/src/utils/index.js index f10116bb..a176e07e 100644 --- a/simple-mind-map/src/utils/index.js +++ b/simple-mind-map/src/utils/index.js @@ -5,8 +5,11 @@ * @Desc: 深度优先遍历树 */ export const walk = (root, parent, beforeCallback, afterCallback, isRoot, layerIndex = 0, index = 0) => { - beforeCallback && beforeCallback(root, parent, isRoot, layerIndex, index) - if (root.children && root.children.length > 0) { + 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) => { walk(node, root, beforeCallback, afterCallback, false, _layerIndex, nodeIndex) diff --git a/web/src/components/ImgUpload/index.vue b/web/src/components/ImgUpload/index.vue index 3ec637d1..c27e11b3 100644 --- a/web/src/components/ImgUpload/index.vue +++ b/web/src/components/ImgUpload/index.vue @@ -98,7 +98,10 @@ export default { }); }; img.onerror = (e) => { - reject(e); + resolve({ + width: 0, + height: 0, + }); }; }); }, @@ -109,7 +112,7 @@ export default { * @Desc: 删除图片 */ deleteImg() { - this.$emit("change", "none"); + this.$emit("change", ""); this.file = null; }, }, diff --git a/web/src/config/index.js b/web/src/config/index.js index 44f6f50f..5b7c8768 100644 --- a/web/src/config/index.js +++ b/web/src/config/index.js @@ -46,6 +46,9 @@ export const fontFamilyList = [ // 字号 export const fontSizeList = [10, 12, 16, 18, 24, 32, 48] +// 行高 +export const lineHeightList = [1, 1.5, 2, 2.5, 3] + // 颜色 export const colorList = [ '#4D4D4D', diff --git a/web/src/pages/Edit/components/NodeHyperlink.vue b/web/src/pages/Edit/components/NodeHyperlink.vue index 2af20344..332a69d2 100644 --- a/web/src/pages/Edit/components/NodeHyperlink.vue +++ b/web/src/pages/Edit/components/NodeHyperlink.vue @@ -68,10 +68,7 @@ export default { * @Desc: 确定 */ confirm() { - this.activeNode.setData({ - hyperlink: this.link, - hyperlinkTitle: this.linkTitle, - }); + this.activeNode.setHyperlink(this.link, this.linkTitle); this.cancel(); }, }, diff --git a/web/src/pages/Edit/components/NodeIcon.vue b/web/src/pages/Edit/components/NodeIcon.vue index 0377d747..af9cf6a1 100644 --- a/web/src/pages/Edit/components/NodeIcon.vue +++ b/web/src/pages/Edit/components/NodeIcon.vue @@ -67,9 +67,7 @@ export default { } else { this.icon.push(type + '_' + name) } - this.activeNode.setData({ - icon: [...this.icon] - }) + this.activeNode.setIcon([...this.icon]) } }, }; diff --git a/web/src/pages/Edit/components/NodeImage.vue b/web/src/pages/Edit/components/NodeImage.vue index 600598af..32758596 100644 --- a/web/src/pages/Edit/components/NodeImage.vue +++ b/web/src/pages/Edit/components/NodeImage.vue @@ -20,10 +20,10 @@