diff --git a/simple-mind-map/example/exampleData.js b/simple-mind-map/example/exampleData.js index de6c1ee6..e44a0c68 100644 --- a/simple-mind-map/example/exampleData.js +++ b/simple-mind-map/example/exampleData.js @@ -6,29 +6,27 @@ export default { "root": { "data": { - "text": "鱼骨头图", + "text": "根节点", }, "children": [{ "data": { - "text": "分支主题", - "expand": true + "text": "二级节点", + "expand": true, }, "children": [{ "data": { - "text": "分支主题", - "hyperlink": "https://naotu.baidu.com/", - "hyperlinkTitle": "百度脑图", - "image": "https://kityminder-img.gz.bcebos.com/865551aedebd1e02ac6e76d24c093231df9aafda", + "text": "子节点", + "image": "http://aliyuncdn.lxqnsys.com/whbm/enJFNMHnedQTYTESGfDkctCp2", "imageTitle": "图片名称", "imageSize": { - "width": 200, - "height": 112 + "width": 1000, + "height": 563 }, - "note": "我是备注", - "resource": ["标签1", "标签2"], - "priority": 5, - "progress": 7, - // ... 其他类型的图标 + "icon": ['a'], + "tag": ["标签1", "标签2"], + "hyperlink": "http://lxqnsys.com/", + "hyperlinkTitle": "理想青年实验室", + "note": "理想青年实验室\n一个有意思的角落" }, "children": [] }] diff --git a/simple-mind-map/src/Node.js b/simple-mind-map/src/Node.js index 15c8d263..d919ee2f 100644 --- a/simple-mind-map/src/Node.js +++ b/simple-mind-map/src/Node.js @@ -4,12 +4,11 @@ import { } from './utils' import { Image, - Text, SVG, - Circle, - Element + Circle } from '@svgdotjs/svg.js' import btnsSvg from './svg/btns' +import iconsSvg from './svg/icons'; /** * javascript comment @@ -26,7 +25,7 @@ class Node { */ constructor(opt = {}) { // 节点数据 - this.data = opt.data || {} + this.nodeData = this.handleData(opt.data || {}) // id this.uid = opt.uid // 控制实例 @@ -41,16 +40,14 @@ class Node { this.style = new Style(this, this.themeConfig) // 是否是根节点 this.isRoot = opt.isRoot === undefined ? false : opt.isRoot - // 是否激活 - this.isActive = opt.isActive === undefined ? false : opt.isActive - // 是否展开 - this.expand = opt.expand === undefined ? true : opt.expand // 节点层级 this.layerIndex = opt.layerIndex === undefined ? 0 : opt.layerIndex // 节点宽 this.width = opt.width || 0 // 节点高 this.height = opt.height || 0 + // 节点文字内容部分高 + this._textContentHeight = 0 // left this.left = opt.left || 0 // top @@ -61,6 +58,23 @@ class Node { this.children = opt.children || [] // 文本节点 this.textNode = null + // icon间距 + this._textContentItemMargin = 2 + // 图片和文字节点的间距 + this._blockContentMargin = 5 + // 计算节点尺寸 + this.refreshSize() + } + + /** + * @Author: 王林 + * @Date: 2021-06-20 10:12:31 + * @Desc: 处理数据 + */ + handleData(data) { + data.data.expand = data.data.expand === false ? false : true + data.data.isActive = data.data.isActive === true ? true : false + return data; } /** @@ -95,39 +109,62 @@ class Node { * @Desc: 计算节点尺寸信息 */ getNodeRect() { - let width = this.themeConfig.paddingX * 2 - let height = this.themeConfig.paddingY * 2 - let maxWidth = 0 - if (this.img) { - let img = this.createImgNode() - if (img.width > maxWidth) { - maxWidth = img.width - } - height += img.height + // 宽高 + let imgContentWidth = 0 + let imgContentHeight = 0 + let textContentWidth = 0 + let textContentHeight = 0 + // 存在图片 + let imgObj = this.createImgNode() + if (imgObj) { + imgContentWidth = imgObj.width + imgContentHeight = imgObj.height } - if (this.icon && this.text) { - let icon = this.createIconNode() - let text = this.createTextNode() - if (icon.width + text.width > maxWidth) { - maxWidth = icon.width + text.width - } - height += Math.max(text.height, icon.height) - } else if (this.text) { - let text = this.createTextNode() - if (text.width > maxWidth) { - maxWidth = text.width - } - height += text.height - } else if (this.icon) { - let icon = this.createIconNode() - if (icon.width > maxWidth) { - maxWidth = icon.width - } - height += icon.height + // 图标 + let iconObjs = this.createIconNode() + if (iconObjs.length > 0) { + textContentWidth += iconObjs.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) + } + // 超链接 + let hyperlinkObj = this.createHyperlinkNode() + if (hyperlinkObj) { + textContentWidth += hyperlinkObj.width + textContentHeight = Math.max(textContentHeight, hyperlinkObj.height) + hyperlinkObj.node.remove() + } + // 标签 + let tagObjs = this.createTagNode() + if (tagObjs.length > 0) { + textContentWidth += tagObjs.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() + } + // 文字内容部分的高度 + this._textContentHeight = textContentHeight + // 间距 + let margin = imgContentHeight > 0 && textContentHeight > 0 ? this._blockContentMargin : 0 + let { paddingX, paddingY } = this.getPaddingVale() return { - width: width + maxWidth, - height + width: Math.max(imgContentWidth, textContentWidth) + paddingX * 2, + height: imgContentHeight + textContentHeight + paddingY * 2 + margin } } @@ -138,17 +175,55 @@ class Node { * @Desc: 创建图片节点 */ createImgNode() { - if (!this.img) { + let img = this.nodeData.data.image + if (!img) { return } let imgSize = this.getImgShowSize() + let node = new Image().load(img).size(...imgSize) + if (this.nodeData.data.imageTitle) { + node.attr('title', this.nodeData.data.imageTitle) + } return { - node: new Image().load(this.img).size(...imgSize), + node, width: imgSize[0], height: imgSize[1] } } + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-09 10:12:51 + * @Desc: 获取图片显示宽高 + */ + getImgShowSize() { + return resizeImgSize(this.nodeData.data.imageSize.width, this.nodeData.data.imageSize.height, this.themeConfig.imgMaxWidth, this.themeConfig.imgMaxHeight) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-09 14:10:48 + * @Desc: 创建icon节点 + */ + createIconNode() { + let _data = this.nodeData.data + if (!_data.icon || _data.icon.length <= 0) { + return []; + } + let node = SVG('').size(this.themeConfig.iconSize, this.themeConfig.iconSize) + return [{ + node, + width: this.themeConfig.iconSize, + height: this.themeConfig.iconSize + }, { + node: node.clone(), + width: this.themeConfig.iconSize, + height: this.themeConfig.iconSize + }] + } + /** * javascript comment * @Author: 王林25 @@ -156,10 +231,10 @@ class Node { * @Desc: 创建文本节点 */ createTextNode() { - if (!this.text) { + if (!this.nodeData.data.text) { return } - let node = this.draw.text(this.text) + let node = this.draw.text(this.nodeData.data.text) this.style.text(node) let { width, @@ -175,21 +250,101 @@ class Node { } /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-09 14:10:48 - * @Desc: 创建icon节点 + * @Author: 王林 + * @Date: 2021-06-20 15:28:54 + * @Desc: 创建超链接节点 */ - createIconNode() { - if (!this.icon) { + createHyperlinkNode() { + if (!this.nodeData.data.hyperlink) { return } - let node = SVG('').size(this.themeConfig.iconSize, this.themeConfig.iconSize) + let iconSize = this.themeConfig.iconSize + let node = this.draw.element('a') + node.node.addEventListener('click', (e) => { + e.stopPropagation() + }) + node.attr('href', this.nodeData.data.hyperlink).attr('target', '_blank') + if (this.nodeData.data.hyperlinkTitle) { + node.attr('title', this.nodeData.data.hyperlinkTitle) + } + node.add(this.draw.rect(iconSize, iconSize).fill({ color: 'transparent' })) + node.add(SVG(iconsSvg.hyperlink).size(iconSize, iconSize)) + return { + node: this.draw.nested().add(node), + width: iconSize, + height: iconSize + } + } + + /** + * @Author: 王林 + * @Date: 2021-06-20 19:49:15 + * @Desc: 创建标签节点 + */ + createTagNode() { + if (!this.nodeData.data.tag || this.nodeData.data.tag.length <= 0) { + return []; + } + let nodes = [] + this.nodeData.data.tag.slice(0, 5).forEach((item, index) => { + let tag = this.draw.nested() + let text = this.draw.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) + this.style.tagRect(rect, index) + tag.add(rect).add(cloneText) + nodes.push({ + node: tag, + width: width + 16, + height: 20 + }) + }) + return nodes; + } + + /** + * @Author: 王林 + * @Date: 2021-06-20 21:19:36 + * @Desc: 创建备注节点 + */ + createNoteNode() { + if (!this.nodeData.data.note) { + return null; + } + let node = this.draw.nested().attr('cursor', 'pointer') + let iconSize = this.themeConfig.iconSize + node.add(this.draw.rect(iconSize, iconSize).fill({ color: 'transparent' })) + node.add(SVG(iconsSvg.note).size(iconSize, iconSize)) + let el = document.createElement('div') + el.style.cssText = ` + position: absolute; + padding: 10px; + border-radius: 5px; + box-shadow: 0 2px 5px rgb(0 0 0 / 10%); + display: none; + ` + el.innerText = this.nodeData.data.note + document.body.appendChild(el) + node.on('mouseover', () => { + let { left, top } = node.node.getBoundingClientRect() + el.style.left = left + 'px' + el.style.top = top + iconSize + 'px' + el.style.display = 'block' + }) + node.on('mouseout', () => { + el.style.display = 'none' + }) return { node, - width: this.themeConfig.iconSize, - height: this.themeConfig.iconSize - } + width: iconSize, + height: iconSize + }; } /** @@ -203,46 +358,87 @@ class Node { left, top, width, - height + height, + _textContentHeight, + _textContentItemMargin } = this - let paddingY = this.themeConfig.paddingY + let { paddingY } = this.getPaddingVale() // 创建组 let group = this.draw.group() // 节点矩形 - let _rectNode = group.rect(width, height).x(left).y(top) - this.style.rect(_rectNode) - // 内容节点 - let imgNode = this.createImgNode() - let iconNode = this.createIconNode() - let textNode = this.createTextNode() - let imgHeight = imgNode ? imgNode.height : 0 - // 图片 - if (imgNode) { - group.add(imgNode.node) - imgNode.node.cx(left + width / 2).y(top + paddingY) + this.style.rect(group.rect(width, height).x(left).y(top)) + // 图片节点 + let imgObj = this.createImgNode() + let imgHeight = 0 + if (imgObj) { + imgHeight = imgObj.height + group.add(imgObj.node) + imgObj.node.cx(left + width / 2).y(top + paddingY) } + // 内容节点 + let textContentNested = this.draw.nested() + let textContentOffsetX = 0 // icon - if (iconNode) { - group.add(iconNode.node) - iconNode.node.x(left + width / 2).y(top + paddingY + imgHeight + (textNode && textNode.height > iconNode.height ? (textNode.height - iconNode.height) / 2 : 0)).dx(textNode ? -textNode.width / 2 - iconNode.width / 2 : 0) + let iconObjs = this.createIconNode() + let iconNested = this.draw.nested() + if (iconObjs && iconObjs.length > 0) { + let iconLeft = 0 + iconObjs.forEach((item) => { + item.node.x(textContentOffsetX + iconLeft).y((_textContentHeight - item.height) / 2) + iconNested.add(item.node) + iconLeft += item.width + _textContentItemMargin + }) + textContentNested.add(iconNested) + textContentOffsetX += iconLeft } // 文字 - if (textNode) { - this.textNode = textNode - group.add(textNode.node) - textNode.node.cx(left + width / 2).y(top + paddingY + imgHeight).dx(iconNode ? iconNode.width / 2 : 0) + let textObj = this.createTextNode() + if (textObj) { + textObj.node.x(textContentOffsetX).y(0) + this.textNode = textObj + textContentNested.add(textObj.node) + textContentOffsetX += textObj.width + _textContentItemMargin } + // 超链接 + let hyperlinkObj = this.createHyperlinkNode() + if (hyperlinkObj) { + hyperlinkObj.node.x(textContentOffsetX).y((_textContentHeight - hyperlinkObj.height) / 2) + textContentNested.add(hyperlinkObj.node) + textContentOffsetX += hyperlinkObj.width + _textContentItemMargin + } + // 标签 + let tagObjs = this.createTagNode() + let tagNested = this.draw.nested() + if (tagObjs && tagObjs.length > 0) { + let tagLeft = 0 + tagObjs.forEach((item) => { + item.node.x(textContentOffsetX + tagLeft).y((_textContentHeight - item.height) / 2) + tagNested.add(item.node) + tagLeft += item.width + _textContentItemMargin + }) + textContentNested.add(tagNested) + textContentOffsetX += tagLeft + } + // 备注 + let noteObj = this.createNoteNode() + if (noteObj) { + noteObj.node.x(textContentOffsetX).y((_textContentHeight - noteObj.height) / 2) + textContentNested.add(noteObj.node) + textContentOffsetX += noteObj.width + } + // 文字内容整体 + textContentNested.x(left + width / 2).dx(-textContentNested.bbox().width / 2).y(top + imgHeight + paddingY + (imgHeight > 0 && _textContentHeight > 0 ? this._blockContentMargin : 0)) + group.add(textContentNested) // 单击事件 group.click((e) => { e.stopPropagation() - if (this.isActive) { + if (this.nodeData.data.isActive) { return; } this.mindMap.emit('before_node_active', this, this.renderer.activeNodeList) this.renderer.clearActive() - this.isActive = true this.mindMap.execCommand('UPDATE_NODE_DATA', this, { - isActive: this.isActive + isActive: !this.nodeData.data.isActive }) this.renderer.activeNodeList.push(this) this.mindMap.render() @@ -263,13 +459,13 @@ class Node { */ render() { // 连线 - this.drawLine() + this.renderLine() // 按钮 - this.drawBtn() + this.renderExpandBtn() // 节点 this.draw.add(this.createNode()) // 子节点 - if (this.children && this.children.length && this.expand) { + if (this.children && this.children.length && this.nodeData.data.expand !== false) { this.children.forEach((child) => { child.render() }) @@ -281,11 +477,11 @@ class Node { * @Date: 2021-04-10 22:01:53 * @Desc: 连线 */ - drawLine() { - if (!this.expand) { + renderLine() { + if (this.nodeData.data.expand === false) { return; } - let lines = this.renderer.layout.drawLine(this) + let lines = this.renderer.layout.renderLine(this) lines.forEach((line) => { this.style.line(line) }) @@ -296,20 +492,20 @@ class Node { * @Date: 2021-04-11 19:47:01 * @Desc: 展开收缩按钮 */ - drawBtn() { + renderExpandBtn() { if (this.children.length <= 0 || this.isRoot) { return; } let g = this.draw.group() let iconSvg - if (this.expand) { - iconSvg = btnsSvg.close - } else { + if (this.nodeData.data.expand === false) { iconSvg = btnsSvg.open + } else { + iconSvg = btnsSvg.close } let node = SVG(iconSvg).size(20, 20) let fillNode = new Circle().size(20) - this.renderer.layout.drawIcon(this, [node, fillNode]) + this.renderer.layout.renderExpandBtn(this, [node, fillNode]) node.dx(0).dy(-10) fillNode.dx(0).dy(-10) this.style.iconBtn(node, fillNode) @@ -324,10 +520,9 @@ class Node { }) }) g.click(() => { - this.expand = !this.expand // 需要反映到实际数据上 this.mindMap.execCommand('UPDATE_NODE_DATA', this, { - expand: this.expand + expand: !this.nodeData.data.expand }) this.mindMap.render() this.mindMap.emit('expand_btn_click', this) @@ -337,13 +532,15 @@ class Node { } /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-09 10:12:51 - * @Desc: 获取图片显示宽高 + * @Author: 王林 + * @Date: 2021-06-20 22:51:57 + * @Desc: 获取padding值 */ - getImgShowSize() { - return resizeImgSize(this.imgWidth, this.imgHeight, this.themeConfig.imgMaxWidth, this.themeConfig.imgMaxHeight) + getPaddingVale() { + return { + paddingX: this.getStyle('paddingX', true, this.nodeData.data.isActive), + paddingY: this.getStyle('paddingY', true, this.nodeData.data.isActive) + }; } /** @@ -365,7 +562,7 @@ class Node { if (isActive) { this.mindMap.execCommand('UPDATE_NODE_DATA', this, { activeStyle: { - ...(this.data.activeStyle || {}), + ...(this.nodeData.data.activeStyle || {}), [prop]: value } }) diff --git a/simple-mind-map/src/Render.js b/simple-mind-map/src/Render.js index c27eeb2b..d93663bf 100644 --- a/simple-mind-map/src/Render.js +++ b/simple-mind-map/src/Render.js @@ -35,11 +35,30 @@ class Render { // 文本编辑框 this.textEdit = new TextEdit(this) // 布局 - this.layout = new(layouts[this.mindMap.opt.layout] ? layouts[this.mindMap.opt.layout] : layouts.logicalStructure)(this) + this.layout = new (layouts[this.mindMap.opt.layout] ? layouts[this.mindMap.opt.layout] : layouts.logicalStructure)(this) + // 绑定事件 + this.bindEvent() // 注册命令 this.registerCommands() } + /** + * @Author: 王林 + * @Date: 2021-06-20 10:34:06 + * @Desc: 绑定事件 + */ + bindEvent() { + // 点击事件 + this.mindMap.on('draw_click', () => { + // 清除激活状态 + if (this.activeNodeList.length > 0) { + this.clearActive() + this.mindMap.render() + this.mindMap.emit('node_active', null, []) + } + }) + } + /** * @Author: 王林 * @Date: 2021-05-04 13:19:06 @@ -167,7 +186,7 @@ class Render { */ updateNodeData(node, data) { Object.keys(data).forEach((key) => { - node.data[key] = data[key] + node.nodeData.data[key] = data[key] }) } } diff --git a/simple-mind-map/src/Style.js b/simple-mind-map/src/Style.js index 006b6893..76c19754 100644 --- a/simple-mind-map/src/Style.js +++ b/simple-mind-map/src/Style.js @@ -1,3 +1,26 @@ +const tagColorList = [ + { + color: 'rgb(77, 65, 0)', + background: 'rgb(255, 244, 179)' + }, + { + color: 'rgb(0, 50, 77)', + background: 'rgb(179, 229, 255)' + }, + { + color: 'rgb(77, 0, 73)', + background: 'rgb(255, 179, 251)' + }, + { + color: 'rgb(57, 77, 0)', + background: 'rgb(236, 255, 179)' + }, + { + color: 'rgb(0, 77, 47)', + background: 'rgb(179, 255, 226)' + } +] +const rootProp = ['paddingX', 'paddingY'] /** * @Author: 王林 @@ -37,23 +60,23 @@ class Style { merge(prop, root, isActive) { // 三级及以下节点 let defaultConfig = this.themeConfig.node - if (root) {// 直接使用最外层样式 + if (root || rootProp.includes(prop)) {// 直接使用最外层样式 defaultConfig = this.themeConfig } else if (this.ctx.layerIndex === 0) {// 根节点 defaultConfig = this.themeConfig.root } else if (this.ctx.layerIndex === 1) {// 二级节点 - defaultConfig = this.themeConfig.secondLevel + defaultConfig = this.themeConfig.second } // 激活状态 - if (isActive !== undefined ? isActive : this.ctx.isActive) { - if (this.ctx.activeStyle && this.ctx.activeStyle[prop] !== undefined) { - return this.ctx.activeStyle[prop]; + if (isActive !== undefined ? isActive : this.ctx.nodeData.data.isActive) { + if (this.ctx.nodeData.data.activeStyle && this.ctx.nodeData.data.activeStyle[prop] !== undefined) { + return this.ctx.nodeData.data.activeStyle[prop]; } else if (defaultConfig.active && defaultConfig.active[prop]) { return defaultConfig.active[prop] } } // 优先使用节点本身的样式 - return this.ctx[prop] !== undefined ? this.ctx[prop] : defaultConfig[prop] + return this.ctx.nodeData.data[prop] !== undefined ? this.ctx.nodeData.data[prop] : defaultConfig[prop] } /** @@ -99,6 +122,30 @@ class Style { node.style.fontWeight = this.merge('fontWeight') || 'normal' } + /** + * @Author: 王林 + * @Date: 2021-06-20 20:02:18 + * @Desc: 标签文字 + */ + tagText(node, index) { + node.fill({ + color: tagColorList[index].color + }).css({ + 'font-size': '12px' + }) + } + + /** + * @Author: 王林 + * @Date: 2021-06-20 21:04:11 + * @Desc: 标签矩形 + */ + tagRect(node, index) { + node.fill({ + color: tagColorList[index].background + }) + } + /** * @Author: 王林 * @Date: 2021-04-11 14:50:49 diff --git a/simple-mind-map/src/TextEdit.js b/simple-mind-map/src/TextEdit.js index 3472eff0..33a65428 100644 --- a/simple-mind-map/src/TextEdit.js +++ b/simple-mind-map/src/TextEdit.js @@ -16,6 +16,7 @@ export default class TextEdit { * @Desc: 构造函数 */ constructor(renderer) { + this.renderer = renderer this.mindMap = renderer.mindMap // 文本编辑框 this.textEditNode = null @@ -37,12 +38,6 @@ export default class TextEdit { this.mindMap.on('draw_click', () => { // 隐藏文本编辑框 this.hideEditTextBox() - // 清除激活状态 - if (this.activeNodeList.length > 0) { - this.clearActive() - this.mindMap.render() - this.mindMap.emit('node_active', null, []) - } }) // 展开收缩按钮点击事件 this.mindMap.on('expand_btn_click', () => { @@ -64,10 +59,10 @@ export default class TextEdit { * @Desc: 显示文本编辑框 */ show(node) { - if (!node.text) { + if (!node.nodeData.data.text) { return; } - this.showEditTextBox(this, this.textNode.node.node.getBoundingClientRect()) + this.showEditTextBox(node, node.textNode.node.node.getBoundingClientRect()) } /** @@ -83,7 +78,7 @@ export default class TextEdit { document.body.appendChild(this.textEditNode) } node.style.domText(this.textEditNode) - this.textEditNode.innerHTML = node.data.text.split(/\n/img).join('
') + this.textEditNode.innerHTML = node.nodeData.data.text.split(/\n/img).join('
') this.textEditNode.style.minWidth = rect.width + 10 + 'px' this.textEditNode.style.minHeight = rect.height + 6 + 'px' this.textEditNode.style.left = rect.left + 'px' @@ -101,12 +96,12 @@ export default class TextEdit { if (!this.showTextEdit) { return } - this.activeNodeList.forEach((node) => { + this.renderer.activeNodeList.forEach((node) => { let str = getStrWithBrFromHtml(this.textEditNode.innerHTML) - node.data.text = str + node.nodeData.data.text = str this.mindMap.render() }) - this.mindMap.emit('hide_text_edit', this.textEditNode, this.activeNodeList) + this.mindMap.emit('hide_text_edit', this.textEditNode, this.renderer.activeNodeList) this.textEditNode.style.display = 'none' this.textEditNode.innerHTML = '' this.textEditNode.style.fontFamily = 'inherit' diff --git a/simple-mind-map/src/layouts/Base.js b/simple-mind-map/src/layouts/Base.js index 17965b64..d5efe671 100644 --- a/simple-mind-map/src/layouts/Base.js +++ b/simple-mind-map/src/layouts/Base.js @@ -38,17 +38,17 @@ class Base { * @Date: 2021-04-12 22:41:04 * @Desc: 连线 */ - drawLine() { - throw new Error('【drawLine】方法为必要方法,需要子类进行重写!') + renderLine() { + throw new Error('【renderLine】方法为必要方法,需要子类进行重写!') } /** * @Author: 王林 * @Date: 2021-04-12 22:42:08 - * @Desc: 定位显示展开收缩按钮 + * @Desc: 定位展开收缩按钮 */ - drawIcon() { - throw new Error('【drawIcon】方法为必要方法,需要子类进行重写!') + renderExpandBtn() { + throw new Error('【renderExpandBtn】方法为必要方法,需要子类进行重写!') } /** diff --git a/simple-mind-map/src/layouts/LogicalStructure.js b/simple-mind-map/src/layouts/LogicalStructure.js index 40905e61..73c32000 100644 --- a/simple-mind-map/src/layouts/LogicalStructure.js +++ b/simple-mind-map/src/layouts/LogicalStructure.js @@ -26,7 +26,7 @@ class LogicalStructure extends Base { * @Desc: 布局 */ doLayout() { - // 计算节点的left、width、height + // 遍历数据计算节点的left、width、height this.computedBaseValue() // 计算节点的top this.computedTopValue() @@ -40,39 +40,41 @@ class LogicalStructure extends Base { * javascript comment * @Author: 王林25 * @Date: 2021-04-08 09:49:32 - * @Desc: 计算节点的left、width、height + * @Desc: 遍历数据计算节点的left、width、height */ computedBaseValue() { - walk(this.renderTree, null, (node, parent, isRoot, layerIndex) => { - // 遍历子节点前设置left、width、height + walk(this.renderTree, null, (cur, parent, isRoot, layerIndex) => { // 创建节点 let newNode = new Node({ + data: cur, uid: this.mindMap.uid++, - data: node, renderer: this.renderer, mindMap: this.mindMap, draw: this.draw, layerIndex }) - // 计算节点的宽高 - newNode.refreshSize() + // 数据关联实际节点 + cur._node = newNode + // 根节点定位在画布中心位置 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.themeConfig.secondLevel.marginX : this.themeConfig.node.marginX - newNode.left = parent._node.left + parent._node.width + marginX, - newNode.parent = parent._node + // 非根节点 + let marginX = layerIndex === 1 ? this.themeConfig.second.marginX : this.themeConfig.node.marginX + // 定位到父节点右侧 + newNode.left = parent._node.left + parent._node.width + marginX + // 互相收集 + newNode.parent = parent._node parent._node.addChildren(newNode) } - node._node = newNode - }, (node, parent, isRoot, layerIndex) => { + }, (cur, parent, isRoot, layerIndex) => { // 返回时计算节点的areaHeight,也就是子节点所占的高度之和,包括外边距 - let len = node.expand === false ? 0 : node._node.children.length - node._node.childrenAreaHeight = len ? node._node.children.reduce((h, cur) => { - return h + cur.height + 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 }, true, 0) } @@ -81,7 +83,7 @@ class LogicalStructure extends Base { * javascript comment * @Author: 王林25 * @Date: 2021-04-08 09:59:25 - * @Desc: 计算节点的top + * @Desc: 遍历节点树计算节点的top */ computedTopValue() { walk(this.root, null, (node, parent, isRoot, layerIndex) => { @@ -151,7 +153,7 @@ class LogicalStructure extends Base { * @Desc: 获取节点的marginY */ getMarginY(layerIndex) { - return layerIndex === 1 ? this.themeConfig.secondLevel.marginY : this.themeConfig.node.marginY; + return layerIndex === 1 ? this.themeConfig.second.marginY : this.themeConfig.node.marginY; } /** @@ -183,7 +185,7 @@ class LogicalStructure extends Base { * @Date: 2021-04-11 14:42:48 * @Desc: 绘制连线,连接该节点到其子节点 */ - drawLine(node) { + renderLine(node) { if (node.children.length <= 0) { return []; } @@ -220,7 +222,7 @@ class LogicalStructure extends Base { * @Date: 2021-04-11 19:54:26 * @Desc: 渲染按钮 */ - drawIcon(node, icons) { + renderExpandBtn(node, icons) { let { left, top, diff --git a/simple-mind-map/src/svg/icons.js b/simple-mind-map/src/svg/icons.js new file mode 100644 index 00000000..2454f489 --- /dev/null +++ b/simple-mind-map/src/svg/icons.js @@ -0,0 +1,10 @@ +// 超链接图标 +const hyperlink = '' + +// 备注图标 +const note = '' + +export default { + hyperlink, + note +} \ No newline at end of file diff --git a/simple-mind-map/src/themes/blueSky.js b/simple-mind-map/src/themes/blueSky.js index 551ed1db..6b2add3e 100644 --- a/simple-mind-map/src/themes/blueSky.js +++ b/simple-mind-map/src/themes/blueSky.js @@ -19,7 +19,7 @@ export default merge(defaultTheme, { } }, // 二级节点样式 - secondLevel: { + second: { fillColor: 'rgb(238, 243, 246)', color: '#333', borderColor: 'rgb(115, 161, 191)', diff --git a/simple-mind-map/src/themes/brainImpairedPink.js b/simple-mind-map/src/themes/brainImpairedPink.js index 1e91793c..9b43e42f 100644 --- a/simple-mind-map/src/themes/brainImpairedPink.js +++ b/simple-mind-map/src/themes/brainImpairedPink.js @@ -19,7 +19,7 @@ export default merge(defaultTheme, { } }, // 二级节点样式 - secondLevel: { + second: { fillColor: 'rgb(246, 238, 242)', color: '#333', borderColor: 'rgb(191, 115, 148)', diff --git a/simple-mind-map/src/themes/classic.js b/simple-mind-map/src/themes/classic.js index 0661183c..48bed19e 100644 --- a/simple-mind-map/src/themes/classic.js +++ b/simple-mind-map/src/themes/classic.js @@ -29,7 +29,7 @@ export default merge(defaultTheme, { } }, // 二级节点样式 - secondLevel: { + second: { fillColor: 'rgb(164, 197, 192)', borderColor: 'transparent', color: '#333', diff --git a/simple-mind-map/src/themes/classic2.js b/simple-mind-map/src/themes/classic2.js index a2841006..269c863e 100644 --- a/simple-mind-map/src/themes/classic2.js +++ b/simple-mind-map/src/themes/classic2.js @@ -24,7 +24,7 @@ export default merge(defaultTheme, { } }, // 二级节点样式 - secondLevel: { + second: { fillColor: 'rgb(241, 242, 241)', borderColor: 'transparent', color: '#1a1a1a', diff --git a/simple-mind-map/src/themes/classic3.js b/simple-mind-map/src/themes/classic3.js index a14184a3..f37e7917 100644 --- a/simple-mind-map/src/themes/classic3.js +++ b/simple-mind-map/src/themes/classic3.js @@ -26,7 +26,7 @@ export default merge(defaultTheme, { } }, // 二级节点样式 - secondLevel: { + second: { fillColor: 'rgb(255, 245, 214)', borderColor: 'rgb(249, 199, 84)', borderWidth: 1, diff --git a/simple-mind-map/src/themes/dark.js b/simple-mind-map/src/themes/dark.js index 8c4faf86..1bc2adb1 100644 --- a/simple-mind-map/src/themes/dark.js +++ b/simple-mind-map/src/themes/dark.js @@ -24,7 +24,7 @@ export default merge(defaultTheme, { } }, // 二级节点样式 - secondLevel: { + second: { fillColor: 'rgb(55, 56, 58)', color: 'rgb(147,148,149)', fontSize: 18, diff --git a/simple-mind-map/src/themes/default.js b/simple-mind-map/src/themes/default.js index 7a3da117..117422cf 100644 --- a/simple-mind-map/src/themes/default.js +++ b/simple-mind-map/src/themes/default.js @@ -8,9 +8,9 @@ export default { paddingX: 20, paddingY: 10, // 图片显示的最大宽度 - imgMaxWidth: 200, + imgMaxWidth: 100, // 图片显示的最大高度 - imgMaxHeight: 200, + imgMaxHeight: 100, // icon的大小 iconSize: 20, // 连线的粗细 @@ -43,7 +43,7 @@ export default { } }, // 二级节点样式 - secondLevel: { + second: { marginX: 100, marginY: 40, fillColor: '#fff', diff --git a/simple-mind-map/src/themes/earthYellow.js b/simple-mind-map/src/themes/earthYellow.js index c7e906f7..ec9323d7 100644 --- a/simple-mind-map/src/themes/earthYellow.js +++ b/simple-mind-map/src/themes/earthYellow.js @@ -19,7 +19,7 @@ export default merge(defaultTheme, { } }, // 二级节点样式 - secondLevel: { + second: { fillColor: 'rgb(246, 242, 238)', color: '#333', borderColor: 'rgb(191, 147, 115)', diff --git a/simple-mind-map/src/themes/freshGreen.js b/simple-mind-map/src/themes/freshGreen.js index 5db06ac1..e903c788 100644 --- a/simple-mind-map/src/themes/freshGreen.js +++ b/simple-mind-map/src/themes/freshGreen.js @@ -16,7 +16,7 @@ export default merge(defaultTheme, { fillColor: '#1fb27d' }, // 二级节点样式 - secondLevel: { + second: { fillColor: '#fff', color: '#565656', borderColor: 'transparent', diff --git a/simple-mind-map/src/themes/freshRed.js b/simple-mind-map/src/themes/freshRed.js index ec3a754b..3028d920 100644 --- a/simple-mind-map/src/themes/freshRed.js +++ b/simple-mind-map/src/themes/freshRed.js @@ -19,7 +19,7 @@ export default merge(defaultTheme, { } }, // 二级节点样式 - secondLevel: { + second: { fillColor: 'rgb(246, 238, 238)', color: '#333', borderColor: 'rgb(191, 115, 115)', diff --git a/simple-mind-map/src/themes/romanticPurple.js b/simple-mind-map/src/themes/romanticPurple.js index ce8b045b..4cde9383 100644 --- a/simple-mind-map/src/themes/romanticPurple.js +++ b/simple-mind-map/src/themes/romanticPurple.js @@ -19,7 +19,7 @@ export default merge(defaultTheme, { } }, // 二级节点样式 - secondLevel: { + second: { fillColor: 'rgb(239, 238, 246)', color: '#333', borderColor: 'rgb(123, 115, 191)', diff --git a/web/src/pages/Edit/components/NodeImage.vue b/web/src/pages/Edit/components/NodeImage.vue index a8f6e698..dd6389d9 100644 --- a/web/src/pages/Edit/components/NodeImage.vue +++ b/web/src/pages/Edit/components/NodeImage.vue @@ -25,7 +25,6 @@ export default { this.$bus.$on("node_active", (...args) => { let activeNodes = args[1]; this.activeNode = activeNodes[0]; - console.log(args); }); this.$bus.$on("showNodeImage", () => { this.dialogVisible = true; diff --git a/web/src/pages/Edit/components/Style.vue b/web/src/pages/Edit/components/Style.vue index 5bd6284e..352f1ea5 100644 --- a/web/src/pages/Edit/components/Style.vue +++ b/web/src/pages/Edit/components/Style.vue @@ -6,6 +6,7 @@
+
文字
+
边框
@@ -167,6 +169,7 @@
+
背景
@@ -181,6 +184,28 @@
+ +
节点内边距
+
+
+ 水平 + +
+
+
+
+ 垂直 + +
+
@@ -213,6 +238,8 @@ export default { activeNode: null, activeTab: "normal", style: { + paddingX: 0, + paddingY: 0, color: "", fontFamily: "", fontSize: "", @@ -229,7 +256,7 @@ export default { }, created() { this.$bus.$on("node_active", (...args) => { - this.activeTab = 'normal' + this.activeTab = "normal"; let activeNodes = args[1]; this.activeNode = activeNodes[0]; this.$refs.sidebar.show = activeNodes.length > 0; @@ -237,14 +264,14 @@ export default { }); }, methods: { - /** - * @Author: 王林 - * @Date: 2021-05-05 11:42:32 - * @Desc: tab切换 - */ - handleTabClick() { - this.initNodeStyle() - }, + /** + * @Author: 王林 + * @Date: 2021-05-05 11:42:32 + * @Desc: tab切换 + */ + handleTabClick() { + this.initNodeStyle(); + }, /** * @Author: 王林 @@ -253,10 +280,12 @@ export default { */ initNodeStyle() { if (!this.activeNode) { - this.activeTab = 'normal' + this.activeTab = "normal"; return; } [ + "paddingX", + "paddingY", "color", "fontFamily", "fontSize", @@ -269,7 +298,11 @@ export default { "borderDasharray", "borderRadius", ].forEach((item) => { - this.style[item] = this.activeNode.getStyle(item, false, this.activeTab === 'active'); + this.style[item] = this.activeNode.getStyle( + item, + false, + this.activeTab === "active" + ); }); }, @@ -279,7 +312,11 @@ export default { * @Desc: 修改样式 */ update(prop) { - this.activeNode.setStyle(prop, this.style[prop], this.activeTab === 'active'); + this.activeNode.setStyle( + prop, + this.style[prop], + this.activeTab === "active" + ); }, /** @@ -345,21 +382,21 @@ export default {