From f547f741f2f4bbd3d2547f9891624c9a5ad3c449 Mon Sep 17 00:00:00 2001 From: wanglin2 <1013335014@qq.com> Date: Tue, 31 Jan 2023 15:04:38 +0800 Subject: [PATCH] =?UTF-8?q?1.=E4=BF=AE=E5=A4=8D=E5=88=A0=E9=99=A4=E8=83=8C?= =?UTF-8?q?=E6=99=AF=E5=9B=BE=E7=89=87=E4=B8=8D=E7=94=9F=E6=95=88=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=9B2.=E8=83=8C=E6=99=AF=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E5=B1=95=E7=A4=BA=E5=A2=9E=E5=8A=A0=E4=BD=8D=E7=BD=AE?= =?UTF-8?q?=E5=92=8C=E5=A4=A7=E5=B0=8F=E8=AE=BE=E7=BD=AE;3.=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E8=8A=82=E7=82=B9=E6=8B=96=E6=8B=BD=E5=88=B0=E6=A0=B9?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E6=97=B6=E8=BF=9E=E6=8E=A5=E7=BA=BF=E8=B7=91?= =?UTF-8?q?=E5=88=B0=E6=A0=B9=E8=8A=82=E7=82=B9=E4=B8=8A=E6=96=B9=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple-mind-map/src/Render.js | 1841 ++++++++++--------- simple-mind-map/src/Style.js | 355 ++-- simple-mind-map/src/themes/default.js | 289 +-- web/src/config/en.js | 16 + web/src/config/index.js | 12 +- web/src/config/zh.js | 16 + web/src/lang/en_us.js | 2 + web/src/lang/zh_cn.js | 2 + web/src/pages/Edit/components/BaseStyle.vue | 61 +- 9 files changed, 1388 insertions(+), 1206 deletions(-) diff --git a/simple-mind-map/src/Render.js b/simple-mind-map/src/Render.js index 821173db..19c315e4 100644 --- a/simple-mind-map/src/Render.js +++ b/simple-mind-map/src/Render.js @@ -1,893 +1,948 @@ -import merge from 'deepmerge' -import LogicalStructure from './layouts/LogicalStructure' -import MindMap from './layouts/MindMap' -import CatalogOrganization from './layouts/CatalogOrganization' -import OrganizationStructure from './layouts/OrganizationStructure' -import TextEdit from './TextEdit' -import { copyNodeTree, simpleDeepClone, walk } from './utils' -import { shapeList } from './Shape' -import { lineStyleProps } from './themes/default' - -// 布局列表 -const layouts = { - // 逻辑结构图 - logicalStructure: LogicalStructure, - // 思维导图 - mindMap: MindMap, - // 目录组织图 - catalogOrganization: CatalogOrganization, - // 组织结构图 - organizationStructure: OrganizationStructure -} - -// 渲染 -class Render { - // 构造函数 - constructor(opt = {}) { - this.opt = opt - this.mindMap = opt.mindMap - this.themeConfig = this.mindMap.themeConfig - this.draw = this.mindMap.draw - // 渲染树,操作过程中修改的都是这里的数据 - this.renderTree = merge({}, this.mindMap.opt.data || {}) - // 是否重新渲染 - this.reRender = false - // 当前激活的节点列表 - this.activeNodeList = [] - // 根节点 - this.root = null - // 文本编辑框,需要再bindEvent之前实例化,否则单击事件只能触发隐藏文本编辑框,而无法保存文本修改 - this.textEdit = new TextEdit(this) - // 布局 - this.setLayout() - // 绑定事件 - this.bindEvent() - // 注册命令 - this.registerCommands() - // 注册快捷键 - this.registerShortcutKeys() - } - - // 设置布局结构 - setLayout() { - this.layout = new ( - layouts[this.mindMap.opt.layout] - ? layouts[this.mindMap.opt.layout] - : layouts.logicalStructure - )(this) - } - - // 绑定事件 - bindEvent() { - // 点击事件 - this.mindMap.on('draw_click', () => { - // 清除激活状态 - if (this.activeNodeList.length > 0) { - this.mindMap.execCommand('CLEAR_ACTIVE_NODE') - } - }) - } - - // 注册命令 - registerCommands() { - // 全选 - this.selectAll = this.selectAll.bind(this) - this.mindMap.command.add('SELECT_ALL', this.selectAll) - // 回退 - this.back = this.back.bind(this) - this.mindMap.command.add('BACK', this.back) - // 前进 - this.forward = this.forward.bind(this) - this.mindMap.command.add('FORWARD', this.forward) - // 插入同级节点 - 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.upNode = this.upNode.bind(this) - this.mindMap.command.add('UP_NODE', this.upNode) - // 下移节点 - this.downNode = this.downNode.bind(this) - this.mindMap.command.add('DOWN_NODE', this.downNode) - // 移动节点 - this.insertAfter = this.insertAfter.bind(this) - this.mindMap.command.add('INSERT_AFTER', this.insertAfter) - this.insertBefore = this.insertBefore.bind(this) - this.mindMap.command.add('INSERT_BEFORE', this.insertBefore) - this.moveNodeTo = this.moveNodeTo.bind(this) - this.mindMap.command.add('MOVE_NODE_TO', this.moveNodeTo) - // 删除节点 - this.removeNode = this.removeNode.bind(this) - this.mindMap.command.add('REMOVE_NODE', this.removeNode) - // 粘贴节点 - this.pasteNode = this.pasteNode.bind(this) - this.mindMap.command.add('PASTE_NODE', this.pasteNode) - // 剪切节点 - this.cutNode = this.cutNode.bind(this) - this.mindMap.command.add('CUT_NODE', this.cutNode) - // 修改节点样式 - 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.clearAllActive = this.clearAllActive.bind(this) - this.mindMap.command.add('CLEAR_ACTIVE_NODE', this.clearAllActive) - // 切换节点是否展开 - this.setNodeExpand = this.setNodeExpand.bind(this) - this.mindMap.command.add('SET_NODE_EXPAND', this.setNodeExpand) - // 展开所有节点 - this.expandAllNode = this.expandAllNode.bind(this) - this.mindMap.command.add('EXPAND_ALL', this.expandAllNode) - // 收起所有节点 - this.unexpandAllNode = this.unexpandAllNode.bind(this) - this.mindMap.command.add('UNEXPAND_ALL', this.unexpandAllNode) - // 展开到指定层级 - this.expandToLevel = this.expandToLevel.bind(this) - this.mindMap.command.add('UNEXPAND_TO_LEVEL', this.expandToLevel) - // 设置节点数据 - 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) - // 添加节点概要 - this.addGeneralization = this.addGeneralization.bind(this) - this.mindMap.command.add('ADD_GENERALIZATION', this.addGeneralization) - // 删除节点概要 - this.removeGeneralization = this.removeGeneralization.bind(this) - this.mindMap.command.add('REMOVE_GENERALIZATION', this.removeGeneralization) - // 设置节点自定义位置 - this.setNodeCustomPosition = this.setNodeCustomPosition.bind(this) - this.mindMap.command.add( - 'SET_NODE_CUSTOM_POSITION', - this.setNodeCustomPosition - ) - // 一键整理布局 - this.resetLayout = this.resetLayout.bind(this) - this.mindMap.command.add('RESET_LAYOUT', this.resetLayout) - // 设置节点形状 - this.setNodeShape = this.setNodeShape.bind(this) - this.mindMap.command.add('SET_NODE_SHAPE', this.setNodeShape) - } - - // 注册快捷键 - registerShortcutKeys() { - // 插入下级节点 - this.mindMap.keyCommand.addShortcut('Tab', () => { - this.mindMap.execCommand('INSERT_CHILD_NODE') - }) - // 插入同级节点 - this.insertNodeWrap = () => { - if (this.textEdit.showTextEdit) { - return - } - this.mindMap.execCommand('INSERT_NODE') - } - this.mindMap.keyCommand.addShortcut('Enter', this.insertNodeWrap) - // 插入概要 - this.mindMap.keyCommand.addShortcut('Control+s', this.addGeneralization) - // 展开/收起节点 - this.toggleActiveExpand = this.toggleActiveExpand.bind(this) - this.mindMap.keyCommand.addShortcut('/', this.toggleActiveExpand) - // 删除节点 - this.removeNodeWrap = () => { - this.mindMap.execCommand('REMOVE_NODE') - } - this.mindMap.keyCommand.addShortcut('Del|Backspace', this.removeNodeWrap) - // 节点编辑时某些快捷键会存在冲突,需要暂时去除 - this.mindMap.on('before_show_text_edit', () => { - this.startTextEdit() - }) - this.mindMap.on('hide_text_edit', () => { - this.endTextEdit() - }) - // 全选 - this.mindMap.keyCommand.addShortcut('Control+a', () => { - this.mindMap.execCommand('SELECT_ALL') - }) - // 一键整理布局 - this.mindMap.keyCommand.addShortcut('Control+l', this.resetLayout) - // 上移节点 - this.mindMap.keyCommand.addShortcut('Control+Up', this.upNode) - // 下移节点 - this.mindMap.keyCommand.addShortcut('Control+Down', this.downNode) - // 复制节点、剪切节点、粘贴节点的快捷键需开发者自行注册实现,可参考demo - } - - // 开启文字编辑,会禁用回车键和删除键相关快捷键防止冲突 - startTextEdit() { - this.mindMap.keyCommand.save() - // this.mindMap.keyCommand.removeShortcut('Del|Backspace') - // this.mindMap.keyCommand.removeShortcut('/') - // this.mindMap.keyCommand.removeShortcut('Enter', this.insertNodeWrap) - } - - // 结束文字编辑,会恢复回车键和删除键相关快捷键 - endTextEdit() { - this.mindMap.keyCommand.restore() - // this.mindMap.keyCommand.addShortcut('Del|Backspace', this.removeNodeWrap) - // this.mindMap.keyCommand.addShortcut('/', this.toggleActiveExpand) - // this.mindMap.keyCommand.addShortcut('Enter', this.insertNodeWrap) - } - - // 渲染 - render() { - if (this.reRender) { - this.clearActive() - } - this.layout.doLayout(root => { - this.root = root - this.root.render(() => { - this.mindMap.emit('node_tree_render_end') - }) - }) - this.mindMap.emit('node_active', null, this.activeNodeList) - } - - // 清除当前激活的节点 - clearActive() { - this.activeNodeList.forEach(item => { - this.setNodeActive(item, false) - }) - this.activeNodeList = [] - } - - // 清除当前所有激活节点,并会触发事件 - clearAllActive() { - if (this.activeNodeList.length <= 0) { - return - } - this.clearActive() - this.mindMap.emit('node_active', null, []) - } - - // 添加节点到激活列表里 - addActiveNode(node) { - let index = this.findActiveNodeIndex(node) - if (index === -1) { - this.activeNodeList.push(node) - } - } - - // 在激活列表里移除某个节点 - removeActiveNode(node) { - let index = this.findActiveNodeIndex(node) - if (index === -1) { - return - } - this.activeNodeList.splice(index, 1) - } - - // 检索某个节点在激活列表里的索引 - findActiveNodeIndex(node) { - return this.activeNodeList.findIndex(item => { - return item === node - }) - } - - // 获取节点在同级里的索引位置 - getNodeIndex(node) { - return node.parent - ? node.parent.children.findIndex(item => { - return item === node - }) - : 0 - } - - // 全选 - selectAll() { - walk( - this.root, - null, - node => { - if (!node.nodeData.data.isActive) { - node.nodeData.data.isActive = true - this.addActiveNode(node) - setTimeout(() => { - node.renderNode() - }, 0) - } - }, - null, - true, - 0, - 0 - ) - } - - // 回退 - back(step) { - this.clearAllActive() - let data = this.mindMap.command.back(step) - if (data) { - this.renderTree = data - this.mindMap.reRender() - } - } - - // 前进 - forward(step) { - this.clearAllActive() - let data = this.mindMap.command.forward(step) - if (data) { - this.renderTree = data - this.mindMap.reRender() - } - } - - // 插入同级节点,多个节点只会操作第一个节点 - insertNode() { - if (this.activeNodeList.length <= 0) { - return - } - let first = this.activeNodeList[0] - if (first.isRoot) { - this.insertChildNode() - } else { - let text = first.layerIndex === 1 ? '二级节点' : '分支主题' - if (first.layerIndex === 1) { - first.parent.initRender = true - } - let index = this.getNodeIndex(first) - first.parent.nodeData.children.splice(index + 1, 0, { - inserting: true, - data: { - text: text, - expand: true - }, - children: [] - }) - this.mindMap.render() - } - } - - // 插入子节点 - insertChildNode() { - if (this.activeNodeList.length <= 0) { - return - } - this.activeNodeList.forEach(node => { - if (!node.nodeData.children) { - node.nodeData.children = [] - } - let text = node.isRoot ? '二级节点' : '分支主题' - node.nodeData.children.push({ - inserting: true, - data: { - text: text, - expand: true - }, - children: [] - }) - // 插入子节点时自动展开子节点 - node.nodeData.data.expand = true - if (node.isRoot) { - node.initRender = true - // this.mindMap.batchExecution.push('renderNode' + index, () => { - // node.renderNode() - // }) - } - }) - this.mindMap.render() - } - - // 上移节点,多个节点只会操作第一个节点 - upNode() { - if (this.activeNodeList.length <= 0) { - return - } - let node = this.activeNodeList[0] - if (node.isRoot) { - return - } - let parent = node.parent - let childList = parent.children - let index = childList.findIndex(item => { - return item === node - }) - if (index === -1 || index === 0) { - return - } - let insertIndex = index - 1 - // 节点实例 - childList.splice(index, 1) - childList.splice(insertIndex, 0, node) - // 节点数据 - parent.nodeData.children.splice(index, 1) - parent.nodeData.children.splice(insertIndex, 0, node.nodeData) - this.mindMap.render() - } - - // 下移节点,多个节点只会操作第一个节点 - downNode() { - if (this.activeNodeList.length <= 0) { - return - } - let node = this.activeNodeList[0] - if (node.isRoot) { - return - } - let parent = node.parent - let childList = parent.children - let index = childList.findIndex(item => { - return item === node - }) - if (index === -1 || index === childList.length - 1) { - return - } - let insertIndex = index + 1 - // 节点实例 - childList.splice(index, 1) - childList.splice(insertIndex, 0, node) - // 节点数据 - parent.nodeData.children.splice(index, 1) - parent.nodeData.children.splice(insertIndex, 0, node.nodeData) - this.mindMap.render() - } - - // 将节点移动到另一个节点的前面 - insertBefore(node, exist) { - if (node.isRoot) { - return - } - // 移动节点 - let nodeParent = node.parent - let nodeBorthers = nodeParent.children - let nodeIndex = nodeBorthers.findIndex(item => { - return item === node - }) - if (nodeIndex === -1) { - return - } - nodeBorthers.splice(nodeIndex, 1) - nodeParent.nodeData.children.splice(nodeIndex, 1) - - // 目标节点 - let existParent = exist.parent - let existBorthers = existParent.children - let existIndex = existBorthers.findIndex(item => { - return item === exist - }) - if (existIndex === -1) { - return - } - existBorthers.splice(existIndex, 0, node) - existParent.nodeData.children.splice(existIndex, 0, node.nodeData) - this.mindMap.render() - } - - // 将节点移动到另一个节点的后面 - insertAfter(node, exist) { - if (node.isRoot) { - return - } - // 移动节点 - let nodeParent = node.parent - let nodeBorthers = nodeParent.children - let nodeIndex = nodeBorthers.findIndex(item => { - return item === node - }) - if (nodeIndex === -1) { - return - } - nodeBorthers.splice(nodeIndex, 1) - nodeParent.nodeData.children.splice(nodeIndex, 1) - - // 目标节点 - let existParent = exist.parent - let existBorthers = existParent.children - let existIndex = existBorthers.findIndex(item => { - return item === exist - }) - if (existIndex === -1) { - return - } - existIndex++ - existBorthers.splice(existIndex, 0, node) - existParent.nodeData.children.splice(existIndex, 0, node.nodeData) - this.mindMap.render() - } - - // 移除节点 - removeNode() { - if (this.activeNodeList.length <= 0) { - return - } - for (let i = 0; i < this.activeNodeList.length; i++) { - let node = this.activeNodeList[i] - if (node.isGeneralization) { - // 删除概要节点 - this.setNodeData(node.generalizationBelongNode, { - generalization: null - }) - node.generalizationBelongNode.update() - this.removeActiveNode(node) - i-- - } else if (node.isRoot) { - node.children.forEach(child => { - child.remove() - }) - node.children = [] - node.nodeData.children = [] - break - } else { - this.removeActiveNode(node) - this.removeOneNode(node) - i-- - } - } - this.mindMap.emit('node_active', null, []) - this.mindMap.render() - } - - // 移除某个指定节点 - removeOneNode(node) { - let index = this.getNodeIndex(node) - node.remove() - node.parent.children.splice(index, 1) - node.parent.nodeData.children.splice(index, 1) - } - - // 复制节点,多个节点只会操作第一个节点 - copyNode() { - if (this.activeNodeList.length <= 0) { - return - } - return copyNodeTree({}, this.activeNodeList[0], true) - } - - // 剪切节点,多个节点只会操作第一个节点 - cutNode(callback) { - if (this.activeNodeList.length <= 0) { - return - } - let node = this.activeNodeList[0] - if (node.isRoot) { - return null - } - let copyData = copyNodeTree({}, node, true) - this.removeActiveNode(node) - this.removeOneNode(node) - this.mindMap.emit('node_active', null, this.activeNodeList) - this.mindMap.render() - if (callback && typeof callback === 'function') { - callback(copyData) - } - } - - // 移动一个节点作为另一个节点的子节点 - moveNodeTo(node, toNode) { - if (node.isRoot) { - return - } - let copyData = copyNodeTree({}, node) - this.removeActiveNode(node) - this.removeOneNode(node) - this.mindMap.emit('node_active', null, this.activeNodeList) - toNode.nodeData.children.push(copyData) - this.mindMap.render() - } - - // 粘贴节点到节点 - pasteNode(data) { - if (this.activeNodeList.length <= 0) { - return - } - this.activeNodeList.forEach(item => { - item.nodeData.children.push(simpleDeepClone(data)) - }) - this.mindMap.render() - } - - // 设置节点样式 - 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) - // 更新了连线的样式 - if (lineStyleProps.includes(prop)) { - ;(node.parent || node).renderLine(true) - } - } - - // 设置节点是否激活 - setNodeActive(node, active) { - this.setNodeData(node, { - isActive: active - }) - node.renderNode() - } - - // 设置节点是否展开 - 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() - } - - // 展开所有 - expandAllNode() { - walk( - this.renderTree, - null, - node => { - if (!node.data.expand) { - node.data.expand = true - } - }, - null, - true, - 0, - 0 - ) - this.mindMap.reRender() - } - - // 收起所有 - unexpandAllNode() { - walk( - this.renderTree, - null, - (node, parent, isRoot) => { - node._node = null - if (!isRoot) { - node.data.expand = false - } - }, - null, - true, - 0, - 0 - ) - this.mindMap.reRender() - } - - // 展开到指定层级 - expandToLevel(level) { - walk( - this.renderTree, - null, - (node, parent, isRoot, layerIndex) => { - node._node = null - node.data.expand = layerIndex < level - }, - null, - true, - 0, - 0 - ) - this.mindMap.reRender() - } - - // 切换激活节点的展开状态 - toggleActiveExpand() { - this.activeNodeList.forEach(node => { - if (node.nodeData.children.length <= 0) { - return - } - this.toggleNodeExpand(node) - }) - } - - // 切换节点展开状态 - toggleNodeExpand(node) { - this.mindMap.execCommand( - 'SET_NODE_EXPAND', - node, - !node.nodeData.data.expand - ) - } - - // 设置节点文本 - setNodeText(node, text) { - this.setNodeDataRender(node, { - text - }) - } - - // 设置节点图片 - setNodeImage(node, { url, title, width, height }) { - this.setNodeDataRender(node, { - image: url, - imageTitle: title || '', - imageSize: { - width, - height - } - }) - } - - // 设置节点图标 - setNodeIcon(node, icons) { - this.setNodeDataRender(node, { - icon: icons - }) - } - - // 设置节点超链接 - setNodeHyperlink(node, link, title = '') { - this.setNodeDataRender(node, { - hyperlink: link, - hyperlinkTitle: title - }) - } - - // 设置节点备注 - setNodeNote(node, note) { - this.setNodeDataRender(node, { - note - }) - } - - // 设置节点标签 - setNodeTag(node, tag) { - this.setNodeDataRender(node, { - tag - }) - } - - // 添加节点概要 - addGeneralization(data) { - if (this.activeNodeList.length <= 0) { - return - } - this.activeNodeList.forEach(node => { - if (node.nodeData.data.generalization || node.isRoot) { - return - } - this.setNodeData(node, { - generalization: data || { - text: '概要' - } - }) - node.update() - }) - this.mindMap.render() - } - - // 删除节点概要 - removeGeneralization() { - if (this.activeNodeList.length <= 0) { - return - } - this.activeNodeList.forEach(node => { - if (!node.nodeData.data.generalization) { - return - } - this.setNodeData(node, { - generalization: null - }) - node.update() - }) - this.mindMap.render() - } - - // 设置节点自定义位置 - setNodeCustomPosition(node, left = undefined, top = undefined) { - let nodeList = [node] || this.activeNodeList - nodeList.forEach(item => { - this.setNodeData(item, { - customLeft: left, - customTop: top - }) - }) - } - - // 一键整理布局,即去除自定义位置 - resetLayout() { - walk( - this.root, - null, - node => { - node.customLeft = undefined - node.customTop = undefined - this.setNodeData(node, { - customLeft: undefined, - customTop: undefined - }) - this.mindMap.render() - }, - null, - true, - 0, - 0 - ) - } - - // 设置节点形状 - setNodeShape(node, shape) { - if (!shape || !shapeList.includes(shape)) { - return - } - let nodeList = [node] || this.activeNodeList - nodeList.forEach(item => { - this.setNodeStyle(item, 'shape', shape) - }) - } - - // 更新节点数据 - setNodeData(node, data) { - Object.keys(data).forEach(key => { - node.nodeData.data[key] = data[key] - }) - } - - // 设置节点数据,并判断是否渲染 - setNodeDataRender(node, data) { - this.setNodeData(node, data) - let changed = node.getSize() - node.renderNode() - if (changed) { - if (node.isGeneralization) { - // 概要节点 - node.generalizationBelongNode.updateGeneralization() - } - this.mindMap.render() - } - } - - // 移动节点到画布中心 - moveNodeToCenter(node) { - let halfWidth = this.mindMap.width / 2 - let halfHeight = this.mindMap.height / 2 - let { left, top, width, height } = node - let nodeCenterX = left + width / 2 - let nodeCenterY = top + height / 2 - let { state } = this.mindMap.view.getTransformData() - let targetX = halfWidth - state.x - let targetY = halfHeight - state.y - let offsetX = targetX - nodeCenterX - let offsetY = targetY - nodeCenterY - this.mindMap.view.translateX(offsetX) - this.mindMap.view.translateY(offsetY) - this.mindMap.view.setScale(1) - } -} - -export default Render +import merge from 'deepmerge' +import LogicalStructure from './layouts/LogicalStructure' +import MindMap from './layouts/MindMap' +import CatalogOrganization from './layouts/CatalogOrganization' +import OrganizationStructure from './layouts/OrganizationStructure' +import TextEdit from './TextEdit' +import { copyNodeTree, simpleDeepClone, walk } from './utils' +import { shapeList } from './Shape' +import { lineStyleProps } from './themes/default' + +// 布局列表 +const layouts = { + // 逻辑结构图 + logicalStructure: LogicalStructure, + // 思维导图 + mindMap: MindMap, + // 目录组织图 + catalogOrganization: CatalogOrganization, + // 组织结构图 + organizationStructure: OrganizationStructure +} + +// 渲染 + +class Render { + // 构造函数 + + constructor(opt = {}) { + this.opt = opt + this.mindMap = opt.mindMap + this.themeConfig = this.mindMap.themeConfig + this.draw = this.mindMap.draw + // 渲染树,操作过程中修改的都是这里的数据 + this.renderTree = merge({}, this.mindMap.opt.data || {}) + // 是否重新渲染 + this.reRender = false + // 当前激活的节点列表 + this.activeNodeList = [] + // 根节点 + this.root = null + // 文本编辑框,需要再bindEvent之前实例化,否则单击事件只能触发隐藏文本编辑框,而无法保存文本修改 + this.textEdit = new TextEdit(this) + // 布局 + this.setLayout() + // 绑定事件 + this.bindEvent() + // 注册命令 + this.registerCommands() + // 注册快捷键 + this.registerShortcutKeys() + } + + // 设置布局结构 + + setLayout() { + this.layout = new ( + layouts[this.mindMap.opt.layout] + ? layouts[this.mindMap.opt.layout] + : layouts.logicalStructure + )(this) + } + + // 绑定事件 + + bindEvent() { + // 点击事件 + this.mindMap.on('draw_click', () => { + // 清除激活状态 + if (this.activeNodeList.length > 0) { + this.mindMap.execCommand('CLEAR_ACTIVE_NODE') + } + }) + } + + // 注册命令 + + registerCommands() { + // 全选 + this.selectAll = this.selectAll.bind(this) + this.mindMap.command.add('SELECT_ALL', this.selectAll) + // 回退 + this.back = this.back.bind(this) + this.mindMap.command.add('BACK', this.back) + // 前进 + this.forward = this.forward.bind(this) + this.mindMap.command.add('FORWARD', this.forward) + // 插入同级节点 + 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.upNode = this.upNode.bind(this) + this.mindMap.command.add('UP_NODE', this.upNode) + // 下移节点 + this.downNode = this.downNode.bind(this) + this.mindMap.command.add('DOWN_NODE', this.downNode) + // 移动节点 + this.insertAfter = this.insertAfter.bind(this) + this.mindMap.command.add('INSERT_AFTER', this.insertAfter) + this.insertBefore = this.insertBefore.bind(this) + this.mindMap.command.add('INSERT_BEFORE', this.insertBefore) + this.moveNodeTo = this.moveNodeTo.bind(this) + this.mindMap.command.add('MOVE_NODE_TO', this.moveNodeTo) + // 删除节点 + this.removeNode = this.removeNode.bind(this) + this.mindMap.command.add('REMOVE_NODE', this.removeNode) + // 粘贴节点 + this.pasteNode = this.pasteNode.bind(this) + this.mindMap.command.add('PASTE_NODE', this.pasteNode) + // 剪切节点 + this.cutNode = this.cutNode.bind(this) + this.mindMap.command.add('CUT_NODE', this.cutNode) + // 修改节点样式 + 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.clearAllActive = this.clearAllActive.bind(this) + this.mindMap.command.add('CLEAR_ACTIVE_NODE', this.clearAllActive) + // 切换节点是否展开 + this.setNodeExpand = this.setNodeExpand.bind(this) + this.mindMap.command.add('SET_NODE_EXPAND', this.setNodeExpand) + // 展开所有节点 + this.expandAllNode = this.expandAllNode.bind(this) + this.mindMap.command.add('EXPAND_ALL', this.expandAllNode) + // 收起所有节点 + this.unexpandAllNode = this.unexpandAllNode.bind(this) + this.mindMap.command.add('UNEXPAND_ALL', this.unexpandAllNode) + // 展开到指定层级 + this.expandToLevel = this.expandToLevel.bind(this) + this.mindMap.command.add('UNEXPAND_TO_LEVEL', this.expandToLevel) + // 设置节点数据 + 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) + // 添加节点概要 + this.addGeneralization = this.addGeneralization.bind(this) + this.mindMap.command.add('ADD_GENERALIZATION', this.addGeneralization) + // 删除节点概要 + this.removeGeneralization = this.removeGeneralization.bind(this) + this.mindMap.command.add('REMOVE_GENERALIZATION', this.removeGeneralization) + // 设置节点自定义位置 + this.setNodeCustomPosition = this.setNodeCustomPosition.bind(this) + this.mindMap.command.add( + 'SET_NODE_CUSTOM_POSITION', + this.setNodeCustomPosition + ) + // 一键整理布局 + this.resetLayout = this.resetLayout.bind(this) + this.mindMap.command.add('RESET_LAYOUT', this.resetLayout) + // 设置节点形状 + this.setNodeShape = this.setNodeShape.bind(this) + this.mindMap.command.add('SET_NODE_SHAPE', this.setNodeShape) + } + + // 注册快捷键 + + registerShortcutKeys() { + // 插入下级节点 + this.mindMap.keyCommand.addShortcut('Tab', () => { + this.mindMap.execCommand('INSERT_CHILD_NODE') + }) + // 插入同级节点 + this.insertNodeWrap = () => { + if (this.textEdit.showTextEdit) { + return + } + this.mindMap.execCommand('INSERT_NODE') + } + this.mindMap.keyCommand.addShortcut('Enter', this.insertNodeWrap) + // 插入概要 + this.mindMap.keyCommand.addShortcut('Control+s', this.addGeneralization) + // 展开/收起节点 + this.toggleActiveExpand = this.toggleActiveExpand.bind(this) + this.mindMap.keyCommand.addShortcut('/', this.toggleActiveExpand) + // 删除节点 + this.removeNodeWrap = () => { + this.mindMap.execCommand('REMOVE_NODE') + } + this.mindMap.keyCommand.addShortcut('Del|Backspace', this.removeNodeWrap) + // 节点编辑时某些快捷键会存在冲突,需要暂时去除 + this.mindMap.on('before_show_text_edit', () => { + this.startTextEdit() + }) + this.mindMap.on('hide_text_edit', () => { + this.endTextEdit() + }) + // 全选 + this.mindMap.keyCommand.addShortcut('Control+a', () => { + this.mindMap.execCommand('SELECT_ALL') + }) + // 一键整理布局 + this.mindMap.keyCommand.addShortcut('Control+l', this.resetLayout) + // 上移节点 + this.mindMap.keyCommand.addShortcut('Control+Up', this.upNode) + // 下移节点 + this.mindMap.keyCommand.addShortcut('Control+Down', this.downNode) + // 复制节点、剪切节点、粘贴节点的快捷键需开发者自行注册实现,可参考demo + } + + // 开启文字编辑,会禁用回车键和删除键相关快捷键防止冲突 + + startTextEdit() { + this.mindMap.keyCommand.save() + // this.mindMap.keyCommand.removeShortcut('Del|Backspace') + // this.mindMap.keyCommand.removeShortcut('/') + // this.mindMap.keyCommand.removeShortcut('Enter', this.insertNodeWrap) + } + + // 结束文字编辑,会恢复回车键和删除键相关快捷键 + + endTextEdit() { + this.mindMap.keyCommand.restore() + // this.mindMap.keyCommand.addShortcut('Del|Backspace', this.removeNodeWrap) + // this.mindMap.keyCommand.addShortcut('/', this.toggleActiveExpand) + // this.mindMap.keyCommand.addShortcut('Enter', this.insertNodeWrap) + } + + // 渲染 + + render() { + if (this.reRender) { + this.clearActive() + } + this.layout.doLayout(root => { + this.root = root + this.root.render(() => { + this.mindMap.emit('node_tree_render_end') + }) + }) + this.mindMap.emit('node_active', null, this.activeNodeList) + } + + // 清除当前激活的节点 + + clearActive() { + this.activeNodeList.forEach(item => { + this.setNodeActive(item, false) + }) + this.activeNodeList = [] + } + + // 清除当前所有激活节点,并会触发事件 + + clearAllActive() { + if (this.activeNodeList.length <= 0) { + return + } + this.clearActive() + this.mindMap.emit('node_active', null, []) + } + + // 添加节点到激活列表里 + + addActiveNode(node) { + let index = this.findActiveNodeIndex(node) + if (index === -1) { + this.activeNodeList.push(node) + } + } + + // 在激活列表里移除某个节点 + + removeActiveNode(node) { + let index = this.findActiveNodeIndex(node) + if (index === -1) { + return + } + this.activeNodeList.splice(index, 1) + } + + // 检索某个节点在激活列表里的索引 + + findActiveNodeIndex(node) { + return this.activeNodeList.findIndex(item => { + return item === node + }) + } + + // 获取节点在同级里的索引位置 + + getNodeIndex(node) { + return node.parent + ? node.parent.children.findIndex(item => { + return item === node + }) + : 0 + } + + // 全选 + + selectAll() { + walk( + this.root, + null, + node => { + if (!node.nodeData.data.isActive) { + node.nodeData.data.isActive = true + this.addActiveNode(node) + setTimeout(() => { + node.renderNode() + }, 0) + } + }, + null, + true, + 0, + 0 + ) + } + + // 回退 + + back(step) { + this.clearAllActive() + let data = this.mindMap.command.back(step) + if (data) { + this.renderTree = data + this.mindMap.reRender() + } + } + + // 前进 + + forward(step) { + this.clearAllActive() + let data = this.mindMap.command.forward(step) + if (data) { + this.renderTree = data + this.mindMap.reRender() + } + } + + // 插入同级节点,多个节点只会操作第一个节点 + + insertNode() { + if (this.activeNodeList.length <= 0) { + return + } + let first = this.activeNodeList[0] + if (first.isRoot) { + this.insertChildNode() + } else { + let text = first.layerIndex === 1 ? '二级节点' : '分支主题' + if (first.layerIndex === 1) { + first.parent.initRender = true + } + let index = this.getNodeIndex(first) + first.parent.nodeData.children.splice(index + 1, 0, { + inserting: true, + data: { + text: text, + expand: true + }, + children: [] + }) + this.mindMap.render() + } + } + + // 插入子节点 + + insertChildNode() { + if (this.activeNodeList.length <= 0) { + return + } + this.activeNodeList.forEach(node => { + if (!node.nodeData.children) { + node.nodeData.children = [] + } + let text = node.isRoot ? '二级节点' : '分支主题' + node.nodeData.children.push({ + inserting: true, + data: { + text: text, + expand: true + }, + children: [] + }) + // 插入子节点时自动展开子节点 + node.nodeData.data.expand = true + if (node.isRoot) { + node.initRender = true + // this.mindMap.batchExecution.push('renderNode' + index, () => { + // node.renderNode() + // }) + } + }) + this.mindMap.render() + } + + // 上移节点,多个节点只会操作第一个节点 + + upNode() { + if (this.activeNodeList.length <= 0) { + return + } + let node = this.activeNodeList[0] + if (node.isRoot) { + return + } + let parent = node.parent + let childList = parent.children + let index = childList.findIndex(item => { + return item === node + }) + if (index === -1 || index === 0) { + return + } + let insertIndex = index - 1 + // 节点实例 + childList.splice(index, 1) + childList.splice(insertIndex, 0, node) + // 节点数据 + parent.nodeData.children.splice(index, 1) + parent.nodeData.children.splice(insertIndex, 0, node.nodeData) + this.mindMap.render() + } + + // 下移节点,多个节点只会操作第一个节点 + + downNode() { + if (this.activeNodeList.length <= 0) { + return + } + let node = this.activeNodeList[0] + if (node.isRoot) { + return + } + let parent = node.parent + let childList = parent.children + let index = childList.findIndex(item => { + return item === node + }) + if (index === -1 || index === childList.length - 1) { + return + } + let insertIndex = index + 1 + // 节点实例 + childList.splice(index, 1) + childList.splice(insertIndex, 0, node) + // 节点数据 + parent.nodeData.children.splice(index, 1) + parent.nodeData.children.splice(insertIndex, 0, node.nodeData) + this.mindMap.render() + } + + // 将节点移动到另一个节点的前面 + + insertBefore(node, exist) { + if (node.isRoot) { + return + } + // 移动节点 + let nodeParent = node.parent + let nodeBorthers = nodeParent.children + let nodeIndex = nodeBorthers.findIndex(item => { + return item === node + }) + if (nodeIndex === -1) { + return + } + nodeBorthers.splice(nodeIndex, 1) + nodeParent.nodeData.children.splice(nodeIndex, 1) + + // 目标节点 + let existParent = exist.parent + let existBorthers = existParent.children + let existIndex = existBorthers.findIndex(item => { + return item === exist + }) + if (existIndex === -1) { + return + } + existBorthers.splice(existIndex, 0, node) + existParent.nodeData.children.splice(existIndex, 0, node.nodeData) + this.mindMap.render() + } + + // 将节点移动到另一个节点的后面 + + insertAfter(node, exist) { + if (node.isRoot) { + return + } + // 移动节点 + let nodeParent = node.parent + let nodeBorthers = nodeParent.children + let nodeIndex = nodeBorthers.findIndex(item => { + return item === node + }) + if (nodeIndex === -1) { + return + } + nodeBorthers.splice(nodeIndex, 1) + nodeParent.nodeData.children.splice(nodeIndex, 1) + + // 目标节点 + let existParent = exist.parent + let existBorthers = existParent.children + let existIndex = existBorthers.findIndex(item => { + return item === exist + }) + if (existIndex === -1) { + return + } + existIndex++ + existBorthers.splice(existIndex, 0, node) + existParent.nodeData.children.splice(existIndex, 0, node.nodeData) + this.mindMap.render() + } + + // 移除节点 + + removeNode() { + if (this.activeNodeList.length <= 0) { + return + } + for (let i = 0; i < this.activeNodeList.length; i++) { + let node = this.activeNodeList[i] + if (node.isGeneralization) { + // 删除概要节点 + this.setNodeData(node.generalizationBelongNode, { + generalization: null + }) + node.generalizationBelongNode.update() + this.removeActiveNode(node) + i-- + } else if (node.isRoot) { + node.children.forEach(child => { + child.remove() + }) + node.children = [] + node.nodeData.children = [] + break + } else { + this.removeActiveNode(node) + this.removeOneNode(node) + i-- + } + } + this.mindMap.emit('node_active', null, []) + this.mindMap.render() + } + + // 移除某个指定节点 + + removeOneNode(node) { + let index = this.getNodeIndex(node) + node.remove() + node.parent.children.splice(index, 1) + node.parent.nodeData.children.splice(index, 1) + } + + // 复制节点,多个节点只会操作第一个节点 + + copyNode() { + if (this.activeNodeList.length <= 0) { + return + } + return copyNodeTree({}, this.activeNodeList[0], true) + } + + // 剪切节点,多个节点只会操作第一个节点 + + cutNode(callback) { + if (this.activeNodeList.length <= 0) { + return + } + let node = this.activeNodeList[0] + if (node.isRoot) { + return null + } + let copyData = copyNodeTree({}, node, true) + this.removeActiveNode(node) + this.removeOneNode(node) + this.mindMap.emit('node_active', null, this.activeNodeList) + this.mindMap.render() + if (callback && typeof callback === 'function') { + callback(copyData) + } + } + + // 移动一个节点作为另一个节点的子节点 + + moveNodeTo(node, toNode) { + if (node.isRoot) { + return + } + let copyData = copyNodeTree({}, node) + this.removeActiveNode(node) + this.removeOneNode(node) + this.mindMap.emit('node_active', null, this.activeNodeList) + toNode.nodeData.children.push(copyData) + this.mindMap.render() + if (toNode.isRoot) { + toNode.renderNode() + } + } + + // 粘贴节点到节点 + + pasteNode(data) { + if (this.activeNodeList.length <= 0) { + return + } + this.activeNodeList.forEach(item => { + item.nodeData.children.push(simpleDeepClone(data)) + }) + this.mindMap.render() + } + + // 设置节点样式 + + 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) + // 更新了连线的样式 + if (lineStyleProps.includes(prop)) { + ;(node.parent || node).renderLine(true) + } + } + + // 设置节点是否激活 + + setNodeActive(node, active) { + this.setNodeData(node, { + isActive: active + }) + node.renderNode() + } + + // 设置节点是否展开 + + 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() + } + + // 展开所有 + + expandAllNode() { + walk( + this.renderTree, + null, + node => { + if (!node.data.expand) { + node.data.expand = true + } + }, + null, + true, + 0, + 0 + ) + this.mindMap.reRender() + } + + // 收起所有 + + unexpandAllNode() { + walk( + this.renderTree, + null, + (node, parent, isRoot) => { + node._node = null + if (!isRoot) { + node.data.expand = false + } + }, + null, + true, + 0, + 0 + ) + this.mindMap.reRender() + } + + // 展开到指定层级 + + expandToLevel(level) { + walk( + this.renderTree, + null, + (node, parent, isRoot, layerIndex) => { + node._node = null + node.data.expand = layerIndex < level + }, + null, + true, + 0, + 0 + ) + this.mindMap.reRender() + } + + // 切换激活节点的展开状态 + + toggleActiveExpand() { + this.activeNodeList.forEach(node => { + if (node.nodeData.children.length <= 0) { + return + } + this.toggleNodeExpand(node) + }) + } + + // 切换节点展开状态 + + toggleNodeExpand(node) { + this.mindMap.execCommand( + 'SET_NODE_EXPAND', + node, + !node.nodeData.data.expand + ) + } + + // 设置节点文本 + + setNodeText(node, text) { + this.setNodeDataRender(node, { + text + }) + } + + // 设置节点图片 + + setNodeImage(node, { url, title, width, height }) { + this.setNodeDataRender(node, { + image: url, + imageTitle: title || '', + imageSize: { + width, + height + } + }) + } + + // 设置节点图标 + + setNodeIcon(node, icons) { + this.setNodeDataRender(node, { + icon: icons + }) + } + + // 设置节点超链接 + + setNodeHyperlink(node, link, title = '') { + this.setNodeDataRender(node, { + hyperlink: link, + hyperlinkTitle: title + }) + } + + // 设置节点备注 + + setNodeNote(node, note) { + this.setNodeDataRender(node, { + note + }) + } + + // 设置节点标签 + + setNodeTag(node, tag) { + this.setNodeDataRender(node, { + tag + }) + } + + // 添加节点概要 + + addGeneralization(data) { + if (this.activeNodeList.length <= 0) { + return + } + this.activeNodeList.forEach(node => { + if (node.nodeData.data.generalization || node.isRoot) { + return + } + this.setNodeData(node, { + generalization: data || { + text: '概要' + } + }) + node.update() + }) + this.mindMap.render() + } + + // 删除节点概要 + + removeGeneralization() { + if (this.activeNodeList.length <= 0) { + return + } + this.activeNodeList.forEach(node => { + if (!node.nodeData.data.generalization) { + return + } + this.setNodeData(node, { + generalization: null + }) + node.update() + }) + this.mindMap.render() + } + + // 设置节点自定义位置 + + setNodeCustomPosition(node, left = undefined, top = undefined) { + let nodeList = [node] || this.activeNodeList + nodeList.forEach(item => { + this.setNodeData(item, { + customLeft: left, + customTop: top + }) + }) + } + + // 一键整理布局,即去除自定义位置 + + resetLayout() { + walk( + this.root, + null, + node => { + node.customLeft = undefined + node.customTop = undefined + this.setNodeData(node, { + customLeft: undefined, + customTop: undefined + }) + this.mindMap.render() + }, + null, + true, + 0, + 0 + ) + } + + // 设置节点形状 + + setNodeShape(node, shape) { + if (!shape || !shapeList.includes(shape)) { + return + } + let nodeList = [node] || this.activeNodeList + nodeList.forEach(item => { + this.setNodeStyle(item, 'shape', shape) + }) + } + + // 更新节点数据 + + setNodeData(node, data) { + Object.keys(data).forEach(key => { + node.nodeData.data[key] = data[key] + }) + } + + // 设置节点数据,并判断是否渲染 + + setNodeDataRender(node, data) { + this.setNodeData(node, data) + let changed = node.getSize() + node.renderNode() + if (changed) { + if (node.isGeneralization) { + // 概要节点 + node.generalizationBelongNode.updateGeneralization() + } + this.mindMap.render() + } + } + + // 移动节点到画布中心 + + moveNodeToCenter(node) { + let halfWidth = this.mindMap.width / 2 + let halfHeight = this.mindMap.height / 2 + let { left, top, width, height } = node + let nodeCenterX = left + width / 2 + let nodeCenterY = top + height / 2 + let { state } = this.mindMap.view.getTransformData() + let targetX = halfWidth - state.x + let targetY = halfHeight - state.y + let offsetX = targetX - nodeCenterX + let offsetY = targetY - nodeCenterY + this.mindMap.view.translateX(offsetX) + this.mindMap.view.translateY(offsetY) + this.mindMap.view.setScale(1) + } +} + +export default Render diff --git a/simple-mind-map/src/Style.js b/simple-mind-map/src/Style.js index 6917db6d..97ed9b49 100644 --- a/simple-mind-map/src/Style.js +++ b/simple-mind-map/src/Style.js @@ -1,167 +1,188 @@ -import { tagColorList } from './utils/constant' -const rootProp = ['paddingX', 'paddingY'] - -// 样式类 -class Style { - // 设置背景样式 - static setBackgroundStyle(el, themeConfig) { - let { backgroundColor, backgroundImage, backgroundRepeat } = themeConfig - el.style.backgroundColor = backgroundColor - if (backgroundImage) { - el.style.backgroundImage = `url(${backgroundImage})` - el.style.backgroundRepeat = backgroundRepeat - } - } - - // 构造函数 - constructor(ctx, themeConfig) { - this.ctx = ctx - this.themeConfig = themeConfig - } - - // 更新主题配置 - updateThemeConfig(themeConfig) { - this.themeConfig = themeConfig - } - - // 合并样式 - merge(prop, root, isActive) { - // 三级及以下节点 - let defaultConfig = this.themeConfig.node - if (root || rootProp.includes(prop)) { - // 直接使用最外层样式 - defaultConfig = this.themeConfig - } else if (this.ctx.isGeneralization) { - // 概要节点 - defaultConfig = this.themeConfig.generalization - } else if (this.ctx.layerIndex === 0) { - // 根节点 - defaultConfig = this.themeConfig.root - } else if (this.ctx.layerIndex === 1) { - // 二级节点 - defaultConfig = this.themeConfig.second - } - // 激活状态 - 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.getSelfStyle(prop) !== undefined - ? this.getSelfStyle(prop) - : defaultConfig[prop] - } - - // 获取某个样式值 - getStyle(prop, root, isActive) { - return this.merge(prop, root, isActive) - } - - // 获取自身自定义样式 - getSelfStyle(prop) { - return this.ctx.nodeData.data[prop] - } - - // 矩形 - rect(node) { - this.shape(node) - node.radius(this.merge('borderRadius')) - } - - // 矩形外的其他形状 - shape(node) { - node.fill({ - color: this.merge('fillColor') - }) - // 节点使用横线样式,不需要渲染非激活状态的边框样式 - if ( - !this.ctx.isRoot && - !this.ctx.isGeneralization && - this.themeConfig.nodeUseLineStyle && - !this.ctx.nodeData.data.isActive - ) { - return - } - node.stroke({ - color: this.merge('borderColor'), - width: this.merge('borderWidth'), - dasharray: this.merge('borderDasharray') - }) - } - - // 文字 - text(node) { - node - .fill({ - color: this.merge('color') - }) - .css({ - 'font-family': this.merge('fontFamily'), - 'font-size': this.merge('fontSize'), - 'font-weight': this.merge('fontWeight'), - 'font-style': this.merge('fontStyle'), - 'text-decoration': this.merge('textDecoration') - }) - } - - // html文字节点 - domText(node, fontSizeScale = 1) { - node.style.fontFamily = this.merge('fontFamily') - node.style.fontSize = this.merge('fontSize') * fontSizeScale + 'px' - node.style.fontWeight = this.merge('fontWeight') || 'normal' - } - - // 标签文字 - tagText(node, index) { - node - .fill({ - color: tagColorList[index].color - }) - .css({ - 'font-size': '12px' - }) - } - - // 标签矩形 - tagRect(node, index) { - node.fill({ - color: tagColorList[index].background - }) - } - - // 内置图标 - iconNode(node) { - node.attr({ - fill: this.merge('color') - }) - } - - // 连线 - line(node, { width, color, dasharray } = {}) { - node.stroke({ width, color, dasharray }).fill({ color: 'none' }) - } - - // 概要连线 - generalizationLine(node) { - node - .stroke({ - width: this.merge('generalizationLineWidth', true), - color: this.merge('generalizationLineColor', true) - }) - .fill({ color: 'none' }) - } - - // 按钮 - iconBtn(node, fillNode) { - node.fill({ color: '#808080' }) - fillNode.fill({ color: '#fff' }) - } -} - -export default Style +import { tagColorList } from './utils/constant' +const rootProp = ['paddingX', 'paddingY'] + +// 样式类 + +class Style { + // 设置背景样式 + + static setBackgroundStyle(el, themeConfig) { + let { backgroundColor, backgroundImage, backgroundRepeat, backgroundPosition, backgroundSize } = themeConfig + el.style.backgroundColor = backgroundColor + if (backgroundImage) { + el.style.backgroundImage = `url(${backgroundImage})` + el.style.backgroundRepeat = backgroundRepeat + el.style.backgroundPosition = backgroundPosition + el.style.backgroundSize = backgroundSize + } else { + el.style.backgroundImage = 'none' + } + } + + // 构造函数 + + constructor(ctx, themeConfig) { + this.ctx = ctx + this.themeConfig = themeConfig + } + + // 更新主题配置 + + updateThemeConfig(themeConfig) { + this.themeConfig = themeConfig + } + + // 合并样式 + + merge(prop, root, isActive) { + // 三级及以下节点 + let defaultConfig = this.themeConfig.node + if (root || rootProp.includes(prop)) { + // 直接使用最外层样式 + defaultConfig = this.themeConfig + } else if (this.ctx.isGeneralization) { + // 概要节点 + defaultConfig = this.themeConfig.generalization + } else if (this.ctx.layerIndex === 0) { + // 根节点 + defaultConfig = this.themeConfig.root + } else if (this.ctx.layerIndex === 1) { + // 二级节点 + defaultConfig = this.themeConfig.second + } + // 激活状态 + 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.getSelfStyle(prop) !== undefined + ? this.getSelfStyle(prop) + : defaultConfig[prop] + } + + // 获取某个样式值 + + getStyle(prop, root, isActive) { + return this.merge(prop, root, isActive) + } + + // 获取自身自定义样式 + + getSelfStyle(prop) { + return this.ctx.nodeData.data[prop] + } + + // 矩形 + + rect(node) { + this.shape(node) + node.radius(this.merge('borderRadius')) + } + + // 矩形外的其他形状 + + shape(node) { + node.fill({ + color: this.merge('fillColor') + }) + // 节点使用横线样式,不需要渲染非激活状态的边框样式 + if ( + !this.ctx.isRoot && + !this.ctx.isGeneralization && + this.themeConfig.nodeUseLineStyle && + !this.ctx.nodeData.data.isActive + ) { + return + } + node.stroke({ + color: this.merge('borderColor'), + width: this.merge('borderWidth'), + dasharray: this.merge('borderDasharray') + }) + } + + // 文字 + + text(node) { + node + .fill({ + color: this.merge('color') + }) + .css({ + 'font-family': this.merge('fontFamily'), + 'font-size': this.merge('fontSize'), + 'font-weight': this.merge('fontWeight'), + 'font-style': this.merge('fontStyle'), + 'text-decoration': this.merge('textDecoration') + }) + } + + // html文字节点 + + domText(node, fontSizeScale = 1) { + node.style.fontFamily = this.merge('fontFamily') + node.style.fontSize = this.merge('fontSize') * fontSizeScale + 'px' + node.style.fontWeight = this.merge('fontWeight') || 'normal' + } + + // 标签文字 + + tagText(node, index) { + node + .fill({ + color: tagColorList[index].color + }) + .css({ + 'font-size': '12px' + }) + } + + // 标签矩形 + + tagRect(node, index) { + node.fill({ + color: tagColorList[index].background + }) + } + + // 内置图标 + + iconNode(node) { + node.attr({ + fill: this.merge('color') + }) + } + + // 连线 + + line(node, { width, color, dasharray } = {}) { + node.stroke({ width, color, dasharray }).fill({ color: 'none' }) + } + + // 概要连线 + + generalizationLine(node) { + node + .stroke({ + width: this.merge('generalizationLineWidth', true), + color: this.merge('generalizationLineColor', true) + }) + .fill({ color: 'none' }) + } + + // 按钮 + + iconBtn(node, fillNode) { + node.fill({ color: '#808080' }) + fillNode.fill({ color: '#fff' }) + } +} + +export default Style diff --git a/simple-mind-map/src/themes/default.js b/simple-mind-map/src/themes/default.js index b101a7ed..ecc4b856 100644 --- a/simple-mind-map/src/themes/default.js +++ b/simple-mind-map/src/themes/default.js @@ -1,142 +1,147 @@ -// 默认主题 -export default { - // 节点内边距 - paddingX: 15, - paddingY: 5, - // 图片显示的最大宽度 - imgMaxWidth: 100, - // 图片显示的最大高度 - imgMaxHeight: 100, - // icon的大小 - iconSize: 20, - // 连线的粗细 - lineWidth: 1, - // 连线的颜色 - lineColor: '#549688', - // 连线样式 - lineDasharray: 'none', - // 连线风格 - lineStyle: 'straight', // 针对logicalStructure、mindMap两种结构。曲线(curve)、直线(straight)、直连(direct) - // 概要连线的粗细 - generalizationLineWidth: 1, - // 概要连线的颜色 - generalizationLineColor: '#549688', - // 概要曲线距节点的距离 - generalizationLineMargin: 0, - // 概要节点距节点的距离 - generalizationNodeMargin: 20, - // 背景颜色 - backgroundColor: '#fafafa', - // 背景图片 - backgroundImage: 'none', - // 背景重复 - backgroundRepeat: 'no-repeat', - // 节点使用横线样式 - nodeUseLineStyle: false, - // 根节点样式 - root: { - shape: 'rectangle', - fillColor: '#549688', - fontFamily: '微软雅黑, Microsoft YaHei', - color: '#fff', - fontSize: 16, - fontWeight: 'bold', - fontStyle: 'normal', - lineHeight: 1.5, - borderColor: 'transparent', - borderWidth: 0, - borderDasharray: 'none', - borderRadius: 5, - textDecoration: 'none', - active: { - borderColor: 'rgb(57, 80, 96)', - borderWidth: 3, - borderDasharray: 'none' - } - }, - // 二级节点样式 - second: { - shape: 'rectangle', - marginX: 100, - marginY: 40, - fillColor: '#fff', - fontFamily: '微软雅黑, Microsoft YaHei', - color: '#565656', - fontSize: 16, - fontWeight: 'noraml', - fontStyle: 'normal', - lineHeight: 1.5, - borderColor: '#549688', - borderWidth: 1, - borderDasharray: 'none', - borderRadius: 5, - textDecoration: 'none', - active: { - borderColor: 'rgb(57, 80, 96)', - borderWidth: 3, - borderDasharray: 'none' - } - }, - // 三级及以下节点样式 - node: { - shape: 'rectangle', - marginX: 50, - marginY: 0, - fillColor: 'transparent', - fontFamily: '微软雅黑, Microsoft YaHei', - color: '#6a6d6c', - fontSize: 14, - fontWeight: 'noraml', - fontStyle: 'normal', - lineHeight: 1.5, - borderColor: 'transparent', - borderWidth: 0, - borderRadius: 5, - borderDasharray: 'none', - textDecoration: 'none', - active: { - borderColor: 'rgb(57, 80, 96)', - borderWidth: 3, - borderDasharray: 'none' - } - }, - // 概要节点样式 - generalization: { - shape: 'rectangle', - marginX: 100, - marginY: 40, - fillColor: '#fff', - fontFamily: '微软雅黑, Microsoft YaHei', - color: '#565656', - fontSize: 16, - fontWeight: 'noraml', - fontStyle: 'normal', - lineHeight: 1.5, - borderColor: '#549688', - borderWidth: 1, - borderDasharray: 'none', - borderRadius: 5, - textDecoration: 'none', - active: { - borderColor: 'rgb(57, 80, 96)', - borderWidth: 3, - borderDasharray: 'none' - } - } -} - -// 支持激活样式的属性 -// 简单来说,会改变节点大小的都不支持在激活时设置,为了性能考虑,节点切换激活态时不会重新计算节点大小 -export const supportActiveStyle = [ - 'fillColor', - 'color', - 'fontWeight', - 'fontStyle', - 'borderColor', - 'borderWidth', - 'borderDasharray', - 'borderRadius', - 'textDecoration' -] - -export const lineStyleProps = ['lineColor', 'lineDasharray', 'lineWidth'] +// 默认主题 + +export default { + // 节点内边距 + paddingX: 15, + paddingY: 5, + // 图片显示的最大宽度 + imgMaxWidth: 100, + // 图片显示的最大高度 + imgMaxHeight: 100, + // icon的大小 + iconSize: 20, + // 连线的粗细 + lineWidth: 1, + // 连线的颜色 + lineColor: '#549688', + // 连线样式 + lineDasharray: 'none', + // 连线风格 + lineStyle: 'straight', // 针对logicalStructure、mindMap两种结构。曲线(curve)、直线(straight)、直连(direct) + // 概要连线的粗细 + generalizationLineWidth: 1, + // 概要连线的颜色 + generalizationLineColor: '#549688', + // 概要曲线距节点的距离 + generalizationLineMargin: 0, + // 概要节点距节点的距离 + generalizationNodeMargin: 20, + // 背景颜色 + backgroundColor: '#fafafa', + // 背景图片 + backgroundImage: 'none', + // 背景重复 + backgroundRepeat: 'no-repeat', + // 设置背景图像的起始位置 + backgroundPosition: 'center center', + // 设置背景图片大小 + backgroundSize: 'cover', + // 节点使用横线样式 + nodeUseLineStyle: false, + // 根节点样式 + root: { + shape: 'rectangle', + fillColor: '#549688', + fontFamily: '微软雅黑, Microsoft YaHei', + color: '#fff', + fontSize: 16, + fontWeight: 'bold', + fontStyle: 'normal', + lineHeight: 1.5, + borderColor: 'transparent', + borderWidth: 0, + borderDasharray: 'none', + borderRadius: 5, + textDecoration: 'none', + active: { + borderColor: 'rgb(57, 80, 96)', + borderWidth: 3, + borderDasharray: 'none' + } + }, + // 二级节点样式 + second: { + shape: 'rectangle', + marginX: 100, + marginY: 40, + fillColor: '#fff', + fontFamily: '微软雅黑, Microsoft YaHei', + color: '#565656', + fontSize: 16, + fontWeight: 'noraml', + fontStyle: 'normal', + lineHeight: 1.5, + borderColor: '#549688', + borderWidth: 1, + borderDasharray: 'none', + borderRadius: 5, + textDecoration: 'none', + active: { + borderColor: 'rgb(57, 80, 96)', + borderWidth: 3, + borderDasharray: 'none' + } + }, + // 三级及以下节点样式 + node: { + shape: 'rectangle', + marginX: 50, + marginY: 0, + fillColor: 'transparent', + fontFamily: '微软雅黑, Microsoft YaHei', + color: '#6a6d6c', + fontSize: 14, + fontWeight: 'noraml', + fontStyle: 'normal', + lineHeight: 1.5, + borderColor: 'transparent', + borderWidth: 0, + borderRadius: 5, + borderDasharray: 'none', + textDecoration: 'none', + active: { + borderColor: 'rgb(57, 80, 96)', + borderWidth: 3, + borderDasharray: 'none' + } + }, + // 概要节点样式 + generalization: { + shape: 'rectangle', + marginX: 100, + marginY: 40, + fillColor: '#fff', + fontFamily: '微软雅黑, Microsoft YaHei', + color: '#565656', + fontSize: 16, + fontWeight: 'noraml', + fontStyle: 'normal', + lineHeight: 1.5, + borderColor: '#549688', + borderWidth: 1, + borderDasharray: 'none', + borderRadius: 5, + textDecoration: 'none', + active: { + borderColor: 'rgb(57, 80, 96)', + borderWidth: 3, + borderDasharray: 'none' + } + } +} + +// 支持激活样式的属性 +// 简单来说,会改变节点大小的都不支持在激活时设置,为了性能考虑,节点切换激活态时不会重新计算节点大小 +export const supportActiveStyle = [ + 'fillColor', + 'color', + 'fontWeight', + 'fontStyle', + 'borderColor', + 'borderWidth', + 'borderDasharray', + 'borderRadius', + 'textDecoration' +] + +export const lineStyleProps = ['lineColor', 'lineDasharray', 'lineWidth'] diff --git a/web/src/config/en.js b/web/src/config/en.js index bece8c4e..a10eba35 100644 --- a/web/src/config/en.js +++ b/web/src/config/en.js @@ -166,6 +166,22 @@ export const backgroundPositionList = [ } ] +// 背景图片大小 +export const backgroundSizeList = [ + { + name: 'Auto', + value: 'auto' + }, + { + name: 'Cover', + value: 'cover' + }, + { + name: 'Contain', + value: 'contain' + } +] + // 快捷键列表 export const shortcutKeyList = [ { diff --git a/web/src/config/index.js b/web/src/config/index.js index 20e4549b..447347c4 100644 --- a/web/src/config/index.js +++ b/web/src/config/index.js @@ -14,7 +14,8 @@ import { backgroundPositionList as backgroundPositionListZh, shortcutKeyList as shortcutKeyListZh, shapeList as shapeListZh, - sidebarTriggerList as sidebarTriggerListZh + sidebarTriggerList as sidebarTriggerListZh, + backgroundSizeList as backgroundSizeListZh } from './zh' import { fontFamilyList as fontFamilyListEn, @@ -24,7 +25,8 @@ import { backgroundPositionList as backgroundPositionListEn, shortcutKeyList as shortcutKeyListEn, shapeList as shapeListEn, - sidebarTriggerList as sidebarTriggerListEn + sidebarTriggerList as sidebarTriggerListEn, + backgroundSizeList as backgroundSizeListEn } from './en' const fontFamilyList = { @@ -52,6 +54,11 @@ const backgroundPositionList = { en: backgroundPositionListEn } +const backgroundSizeList = { + zh: backgroundSizeListZh, + en: backgroundSizeListEn +} + const shortcutKeyList = { zh: shortcutKeyListZh, en: shortcutKeyListEn @@ -81,6 +88,7 @@ export { lineStyleList, backgroundRepeatList, backgroundPositionList, + backgroundSizeList, shortcutKeyList, shapeList, sidebarTriggerList diff --git a/web/src/config/zh.js b/web/src/config/zh.js index 1a919f7d..e5cd9567 100644 --- a/web/src/config/zh.js +++ b/web/src/config/zh.js @@ -221,6 +221,22 @@ export const backgroundPositionList = [ } ] +// 背景图片大小 +export const backgroundSizeList = [ + { + name: '自动', + value: 'auto' + }, + { + name: '覆盖', + value: 'cover' + }, + { + name: '保持', + value: 'contain' + } +] + // 数据存储 export const store = { sidebarZIndex: 1 //侧边栏zIndex diff --git a/web/src/lang/en_us.js b/web/src/lang/en_us.js index 22bdee95..c3eafdde 100644 --- a/web/src/lang/en_us.js +++ b/web/src/lang/en_us.js @@ -5,6 +5,8 @@ export default { color: 'Color', image: 'Image', imageRepeat: 'Image repeat', + imagePosition: 'Image position', + imageSize: 'Image size', line: 'Line', width: 'Width', style: 'Style', diff --git a/web/src/lang/zh_cn.js b/web/src/lang/zh_cn.js index 4d2f2739..250d2999 100644 --- a/web/src/lang/zh_cn.js +++ b/web/src/lang/zh_cn.js @@ -5,6 +5,8 @@ export default { color: '颜色', image: '图片', imageRepeat: '图片重复', + imagePosition: '图片位置', + imageSize: '图片大小', line: '连线', width: '粗细', style: '风格', diff --git a/web/src/pages/Edit/components/BaseStyle.vue b/web/src/pages/Edit/components/BaseStyle.vue index 0dc35e84..015f870b 100644 --- a/web/src/pages/Edit/components/BaseStyle.vue +++ b/web/src/pages/Edit/components/BaseStyle.vue @@ -25,6 +25,7 @@ } " > +
{{ $t('baseStyle.imageRepeat') }}
+ +
+ {{ $t('baseStyle.imagePosition') }} + + + + +
+ +
+ {{ $t('baseStyle.imageSize') }} + + + + +
@@ -389,7 +436,7 @@