mirror of
https://github.com/wanglin2/mind-map.git
synced 2026-02-17 22:08:25 +08:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a5b41e3e0 | ||
|
|
8630c47b26 | ||
|
|
6105fd6e3d | ||
|
|
d7b1c4e4fe | ||
|
|
6ccafaa771 | ||
|
|
6b15e469a2 | ||
|
|
5675e29df3 | ||
|
|
52d094a7c7 | ||
|
|
633ed68f92 | ||
|
|
388594e29a | ||
|
|
af148fef27 | ||
|
|
983e55bd1d | ||
|
|
69dab99bf7 | ||
|
|
c94f459ff9 | ||
|
|
cd3baf411b | ||
|
|
042911371c | ||
|
|
8e2fb72309 | ||
|
|
77aacccdad | ||
|
|
5413c867e3 | ||
|
|
4fc7eb084e | ||
|
|
a9b04312d8 | ||
|
|
4cd9b66653 | ||
|
|
9b7b41597f | ||
|
|
e56ff8fdf2 | ||
|
|
8d9299aed7 | ||
|
|
f8506cb75b | ||
|
|
c90ee9e892 | ||
|
|
4d21b84882 | ||
|
|
331f0bdf97 | ||
|
|
0a4898697d | ||
|
|
79ae08fc9a | ||
|
|
1795773af9 | ||
|
|
747a781ad8 | ||
|
|
fcfcb1c3d1 | ||
|
|
d412ae8cce | ||
|
|
d834b76d42 | ||
|
|
3f9da8940f | ||
|
|
26d5f69af2 | ||
|
|
6efe4a3fd6 | ||
|
|
2b4ab4a322 | ||
|
|
cca42d1f89 | ||
|
|
724fef87b1 | ||
|
|
d50c0e4cd5 | ||
|
|
c18c037642 | ||
|
|
cda1da5fd0 | ||
|
|
eba1aa3a37 | ||
|
|
81018bb615 | ||
|
|
5fa0ff7b5c | ||
|
|
cabe286ebb |
@@ -15,6 +15,7 @@ Demo:[https://wanglin2.github.io/mind-map/](https://wanglin2.github.io/mind-ma
|
||||
|
||||
# 特性
|
||||
|
||||
- [x] 插件化架构,除核心功能外,其他功能作为插件提供,按需使用,减小整体体积
|
||||
- [x] 支持逻辑结构图、思维导图、组织结构图、目录组织图四种结构
|
||||
- [x] 内置多种主题,允许高度自定义样式,支持注册新主题
|
||||
- [x] 支持快捷键
|
||||
@@ -24,8 +25,8 @@ Demo:[https://wanglin2.github.io/mind-map/](https://wanglin2.github.io/mind-ma
|
||||
- [x] 支持右键和Ctrl+左键两种多选方式
|
||||
- [x] 支持节点自由拖拽、拖拽调整
|
||||
- [x] 支持多种节点形状
|
||||
- [x] 支持导出为`json`、`png`、`svg`、`pdf`,支持从`json`、`xmind`导入
|
||||
- [x] 支持小地图
|
||||
- [x] 支持导出为`json`、`png`、`svg`、`pdf`、`markdown`,支持从`json`、`xmind`、`markdown`导入
|
||||
- [x] 支持小地图、支持水印
|
||||
- [x] 支持关联线
|
||||
|
||||
# 安装
|
||||
|
||||
@@ -1 +1 @@
|
||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="./dist/logo.png"><title>一个简单的web思维导图实现</title><link href="dist/js/chunk-2d0a3179.05d77cdf.js" rel="prefetch"><link href="dist/js/chunk-2d0aa579.0b1c8d8d.js" rel="prefetch"><link href="dist/js/chunk-2d0aa978.040c6f5c.js" rel="prefetch"><link href="dist/js/chunk-2d0ab10b.4d871bf6.js" rel="prefetch"><link href="dist/js/chunk-2d0abe0f.ae972b36.js" rel="prefetch"><link href="dist/js/chunk-2d0b1c6f.7fcfc285.js" rel="prefetch"><link href="dist/js/chunk-2d0b361e.b094b87c.js" rel="prefetch"><link href="dist/js/chunk-2d0b91e5.34207f33.js" rel="prefetch"><link href="dist/js/chunk-2d0b92c3.ade5a7e0.js" rel="prefetch"><link href="dist/js/chunk-2d0ba309.c9a9ab22.js" rel="prefetch"><link href="dist/js/chunk-2d0bd54e.db6065c6.js" rel="prefetch"><link href="dist/js/chunk-2d0be174.1ffa155d.js" rel="prefetch"><link href="dist/js/chunk-2d0c0a44.e44018ef.js" rel="prefetch"><link href="dist/js/chunk-2d0c14fc.17d4f60a.js" rel="prefetch"><link href="dist/js/chunk-2d0c18d8.0921c70f.js" rel="prefetch"><link href="dist/js/chunk-2d0c191e.52b68dc1.js" rel="prefetch"><link href="dist/js/chunk-2d0c1a01.7cb4182f.js" rel="prefetch"><link href="dist/js/chunk-2d0c20be.57f5b62e.js" rel="prefetch"><link href="dist/js/chunk-2d0d5cb9.c67e6ecf.js" rel="prefetch"><link href="dist/js/chunk-2d0d9fbc.cd8db1ee.js" rel="prefetch"><link href="dist/js/chunk-2d0da701.6c0d2c1e.js" rel="prefetch"><link href="dist/js/chunk-2d0dad5f.2fd89008.js" rel="prefetch"><link href="dist/js/chunk-2d0db0f2.32d1bf7e.js" rel="prefetch"><link href="dist/js/chunk-2d0dddce.836132f8.js" rel="prefetch"><link href="dist/js/chunk-2d0ddf37.7b4a470a.js" rel="prefetch"><link href="dist/js/chunk-2d0de01b.00dad103.js" rel="prefetch"><link href="dist/js/chunk-2d0e2326.3c894463.js" rel="prefetch"><link href="dist/js/chunk-2d0e268c.95aec9d1.js" rel="prefetch"><link href="dist/js/chunk-2d0e5089.a4640577.js" rel="prefetch"><link href="dist/js/chunk-2d0e9742.bd5197f5.js" rel="prefetch"><link href="dist/js/chunk-2d0f026c.c3ec62ff.js" rel="prefetch"><link href="dist/js/chunk-2d2082b9.c7c6517f.js" rel="prefetch"><link href="dist/js/chunk-2d208ffa.af7fe897.js" rel="prefetch"><link href="dist/js/chunk-2d20ec02.917aff76.js" rel="prefetch"><link href="dist/js/chunk-2d20f68f.7639ecc2.js" rel="prefetch"><link href="dist/js/chunk-2d210a7a.e60ccf9b.js" rel="prefetch"><link href="dist/js/chunk-2d216004.fde7548f.js" rel="prefetch"><link href="dist/js/chunk-2d217907.3772894a.js" rel="prefetch"><link href="dist/js/chunk-2d226d0a.5947204c.js" rel="prefetch"><link href="dist/js/chunk-2d2299c3.0bdd83ab.js" rel="prefetch"><link href="dist/js/chunk-2d22bd06.1447b6d2.js" rel="prefetch"><link href="dist/js/chunk-2d2308b0.4fa18681.js" rel="prefetch"><link href="dist/js/chunk-2d238428.61fffbf5.js" rel="prefetch"><link href="dist/js/chunk-3a2f3e67.13278516.js" rel="prefetch"><link href="dist/css/app.2f2d473c.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.c097b26d.css" rel="preload" as="style"><link href="dist/js/app.e8192153.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.f9723ab8.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.c097b26d.css" rel="stylesheet"><link href="dist/css/app.2f2d473c.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="dist/js/chunk-vendors.f9723ab8.js"></script><script src="dist/js/app.e8192153.js"></script></body></html>
|
||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="./dist/logo.png"><title>一个简单的web思维导图实现</title><link href="dist/js/chunk-2d0a3179.05d77cdf.js" rel="prefetch"><link href="dist/js/chunk-2d0a514a.7623bd23.js" rel="prefetch"><link href="dist/js/chunk-2d0aa579.0b1c8d8d.js" rel="prefetch"><link href="dist/js/chunk-2d0aa978.040c6f5c.js" rel="prefetch"><link href="dist/js/chunk-2d0ab10b.4d871bf6.js" rel="prefetch"><link href="dist/js/chunk-2d0abe0f.ae972b36.js" rel="prefetch"><link href="dist/js/chunk-2d0b1c6f.7fcfc285.js" rel="prefetch"><link href="dist/js/chunk-2d0b361e.b094b87c.js" rel="prefetch"><link href="dist/js/chunk-2d0b91e5.34207f33.js" rel="prefetch"><link href="dist/js/chunk-2d0b92c3.ade5a7e0.js" rel="prefetch"><link href="dist/js/chunk-2d0ba309.03c55a34.js" rel="prefetch"><link href="dist/js/chunk-2d0bd54e.db6065c6.js" rel="prefetch"><link href="dist/js/chunk-2d0be174.1ffa155d.js" rel="prefetch"><link href="dist/js/chunk-2d0c0a44.f061b30d.js" rel="prefetch"><link href="dist/js/chunk-2d0c14fc.17d4f60a.js" rel="prefetch"><link href="dist/js/chunk-2d0c18d8.8d576b5f.js" rel="prefetch"><link href="dist/js/chunk-2d0c191e.52b68dc1.js" rel="prefetch"><link href="dist/js/chunk-2d0c1a01.7cb4182f.js" rel="prefetch"><link href="dist/js/chunk-2d0c20be.9e66ac04.js" rel="prefetch"><link href="dist/js/chunk-2d0d5cb9.c67e6ecf.js" rel="prefetch"><link href="dist/js/chunk-2d0d9fbc.5dd106cf.js" rel="prefetch"><link href="dist/js/chunk-2d0da701.6c0d2c1e.js" rel="prefetch"><link href="dist/js/chunk-2d0dad5f.cda8cb71.js" rel="prefetch"><link href="dist/js/chunk-2d0db0f2.32d1bf7e.js" rel="prefetch"><link href="dist/js/chunk-2d0dd3b1.cc328c92.js" rel="prefetch"><link href="dist/js/chunk-2d0dddce.836132f8.js" rel="prefetch"><link href="dist/js/chunk-2d0ddf37.90fa5d56.js" rel="prefetch"><link href="dist/js/chunk-2d0de01b.00dad103.js" rel="prefetch"><link href="dist/js/chunk-2d0e2326.3ce481e5.js" rel="prefetch"><link href="dist/js/chunk-2d0e268c.204b5aa4.js" rel="prefetch"><link href="dist/js/chunk-2d0e5089.a4640577.js" rel="prefetch"><link href="dist/js/chunk-2d0e9742.bd5197f5.js" rel="prefetch"><link href="dist/js/chunk-2d0f026c.bf20a8ff.js" rel="prefetch"><link href="dist/js/chunk-2d2082b9.c7c6517f.js" rel="prefetch"><link href="dist/js/chunk-2d208ffa.b2a14327.js" rel="prefetch"><link href="dist/js/chunk-2d20ec02.917aff76.js" rel="prefetch"><link href="dist/js/chunk-2d20f68f.e02106a0.js" rel="prefetch"><link href="dist/js/chunk-2d210a7a.e60ccf9b.js" rel="prefetch"><link href="dist/js/chunk-2d216004.0615d324.js" rel="prefetch"><link href="dist/js/chunk-2d217907.3772894a.js" rel="prefetch"><link href="dist/js/chunk-2d226d0a.5947204c.js" rel="prefetch"><link href="dist/js/chunk-2d2299c3.0bdd83ab.js" rel="prefetch"><link href="dist/js/chunk-2d22bd06.a4be9e7c.js" rel="prefetch"><link href="dist/js/chunk-2d2308b0.4fa18681.js" rel="prefetch"><link href="dist/js/chunk-2d238428.61fffbf5.js" rel="prefetch"><link href="dist/js/chunk-3a2f3e67.13278516.js" rel="prefetch"><link href="dist/css/app.ed842be8.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.c097b26d.css" rel="preload" as="style"><link href="dist/js/app.ea7dd27a.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.d36f5bd6.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.c097b26d.css" rel="stylesheet"><link href="dist/css/app.ed842be8.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="dist/js/chunk-vendors.d36f5bd6.js"></script><script src="dist/js/app.ea7dd27a.js"></script></body></html>
|
||||
BIN
qrcode.jpg
BIN
qrcode.jpg
Binary file not shown.
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 49 KiB |
@@ -5,9 +5,13 @@ import KeyboardNavigation from './src/KeyboardNavigation.js'
|
||||
import Export from './src/Export.js'
|
||||
import Drag from './src/Drag.js'
|
||||
import Select from './src/Select.js'
|
||||
import AssociativeLine from './src/AssociativeLine'
|
||||
import RichText from './src/RichText'
|
||||
import xmind from './src/parse/xmind.js'
|
||||
import markdown from './src/parse/markdown.js'
|
||||
|
||||
MindMap.xmind = xmind
|
||||
MindMap.markdown = markdown
|
||||
|
||||
MindMap
|
||||
.usePlugin(MiniMap)
|
||||
@@ -16,5 +20,7 @@ MindMap
|
||||
.usePlugin(KeyboardNavigation)
|
||||
.usePlugin(Export)
|
||||
.usePlugin(Select)
|
||||
.usePlugin(AssociativeLine)
|
||||
.usePlugin(RichText)
|
||||
|
||||
export default MindMap
|
||||
@@ -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 { layoutValueList } from './src/utils/constant'
|
||||
import { layoutValueList, CONSTANTS } from './src/utils/constant'
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
import { simpleDeepClone } from './src/utils'
|
||||
import defaultTheme from './src/themes/default'
|
||||
@@ -17,7 +17,7 @@ const defaultOpt = {
|
||||
// 是否只读
|
||||
readonly: false,
|
||||
// 布局
|
||||
layout: 'logicalStructure',
|
||||
layout: CONSTANTS.LAYOUT.LOGICAL_STRUCTURE,
|
||||
// 主题
|
||||
theme: 'default', // 内置主题:default(默认主题)
|
||||
// 主题配置,会和所选择的主题进行合并
|
||||
@@ -66,9 +66,29 @@ const defaultOpt = {
|
||||
// 可以传一个函数,回调参数为事件对象
|
||||
customHandleMousewheel: null,
|
||||
// 鼠标滚动的行为,如果customHandleMousewheel传了自定义函数,这个属性不生效
|
||||
mousewheelAction: 'zoom',// zoom(放大缩小)、move(上下移动)
|
||||
mousewheelAction: CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM,// zoom(放大缩小)、move(上下移动)
|
||||
// 当mousewheelAction设为move时,可以通过该属性控制鼠标滚动一下视图移动的步长,单位px
|
||||
mousewheelMoveStep: 100
|
||||
mousewheelMoveStep: 100,
|
||||
// 默认插入的二级节点的文字
|
||||
defaultInsertSecondLevelNodeText: '二级节点',
|
||||
// 默认插入的二级以下节点的文字
|
||||
defaultInsertBelowSecondLevelNodeText: '分支主题',
|
||||
// 展开收起按钮的颜色
|
||||
expandBtnStyle: {
|
||||
color: '#808080',
|
||||
fill: '#fff'
|
||||
},
|
||||
// 自定义展开收起按钮的图标
|
||||
expandBtnIcon: {
|
||||
open: '',// svg字符串
|
||||
close: ''
|
||||
},
|
||||
// 是否只有当鼠标在画布内才响应快捷键事件
|
||||
enableShortcutOnlyWhenMouseInSvg: true,
|
||||
// 是否开启节点动画过渡
|
||||
enableNodeTransitionMove: true,
|
||||
// 如果开启节点动画过渡,可以通过该属性设置过渡的时间,单位ms
|
||||
nodeTransitionMoveDuration: 300
|
||||
}
|
||||
|
||||
// 思维导图
|
||||
@@ -91,7 +111,7 @@ class MindMap {
|
||||
this.draw = this.svg.group()
|
||||
|
||||
// 节点id
|
||||
this.uid = 0
|
||||
this.uid = 1
|
||||
|
||||
// 初始化主题
|
||||
this.initTheme()
|
||||
@@ -131,7 +151,7 @@ class MindMap {
|
||||
})
|
||||
|
||||
// 初始渲染
|
||||
this.reRender()
|
||||
this.render()
|
||||
setTimeout(() => {
|
||||
this.command.addHistory()
|
||||
}, 0)
|
||||
@@ -141,7 +161,7 @@ class MindMap {
|
||||
handleOpt(opt) {
|
||||
// 检查布局配置
|
||||
if (!layoutValueList.includes(opt.layout)) {
|
||||
opt.layout = 'logicalStructure'
|
||||
opt.layout = CONSTANTS.LAYOUT.LOGICAL_STRUCTURE
|
||||
}
|
||||
// 检查主题配置
|
||||
opt.theme = opt.theme && theme[opt.theme] ? opt.theme : 'default'
|
||||
@@ -149,11 +169,11 @@ class MindMap {
|
||||
}
|
||||
|
||||
// 渲染,部分渲染
|
||||
render(callback) {
|
||||
render(callback, source = '') {
|
||||
this.batchExecution.push('render', () => {
|
||||
this.initTheme()
|
||||
this.renderer.reRender = false
|
||||
this.renderer.render(callback)
|
||||
this.renderer.render(callback, source)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -202,7 +222,7 @@ class MindMap {
|
||||
setTheme(theme) {
|
||||
this.renderer.clearAllActive()
|
||||
this.opt.theme = theme
|
||||
this.reRender()
|
||||
this.render(null, CONSTANTS.CHANGE_THEME)
|
||||
}
|
||||
|
||||
// 获取当前主题
|
||||
@@ -213,7 +233,7 @@ class MindMap {
|
||||
// 设置主题配置
|
||||
setThemeConfig(config) {
|
||||
this.opt.themeConfig = config
|
||||
this.reRender()
|
||||
this.render(null, CONSTANTS.CHANGE_THEME)
|
||||
}
|
||||
|
||||
// 获取自定义主题配置
|
||||
@@ -245,7 +265,7 @@ class MindMap {
|
||||
setLayout(layout) {
|
||||
// 检查布局配置
|
||||
if (!layoutValueList.includes(layout)) {
|
||||
layout = 'logicalStructure'
|
||||
layout = CONSTANTS.LAYOUT.LOGICAL_STRUCTURE
|
||||
}
|
||||
this.opt.layout = layout
|
||||
this.renderer.setLayout()
|
||||
@@ -261,6 +281,7 @@ class MindMap {
|
||||
setData(data) {
|
||||
this.execCommand('CLEAR_ACTIVE_NODE')
|
||||
this.command.clearHistory()
|
||||
this.command.addHistory()
|
||||
this.renderer.renderTree = data
|
||||
this.reRender()
|
||||
}
|
||||
@@ -288,7 +309,7 @@ class MindMap {
|
||||
|
||||
// 获取思维导图数据,节点树、主题、布局等
|
||||
getData(withConfig) {
|
||||
let nodeData = this.command.getCopyData()
|
||||
let nodeData = this.command.removeDataUid(this.command.getCopyData())
|
||||
let data = {}
|
||||
if (withConfig) {
|
||||
data = {
|
||||
@@ -322,10 +343,10 @@ class MindMap {
|
||||
|
||||
// 设置只读模式、编辑模式
|
||||
setMode(mode) {
|
||||
if (!['readonly', 'edit'].includes(mode)) {
|
||||
if (![CONSTANTS.MODE.READONLY, CONSTANTS.MODE.EDIT].includes(mode)) {
|
||||
return
|
||||
}
|
||||
this.opt.readonly = mode === 'readonly'
|
||||
this.opt.readonly = mode === CONSTANTS.MODE.READONLY
|
||||
if (this.opt.readonly) {
|
||||
// 取消当前激活的元素
|
||||
this.renderer.clearAllActive()
|
||||
|
||||
914
simple-mind-map/package-lock.json
generated
914
simple-mind-map/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.4.6",
|
||||
"version": "0.5.2",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
@@ -31,6 +31,7 @@
|
||||
"html2canvas": "^1.4.1",
|
||||
"jspdf": "^2.5.1",
|
||||
"jszip": "^3.10.1",
|
||||
"mdast-util-from-markdown": "^1.3.0",
|
||||
"quill": "^1.3.6",
|
||||
"uuid": "^9.0.0",
|
||||
"xml-js": "^1.6.11"
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
// 在下一个事件循环里执行任务
|
||||
const nextTick = function (fn, ctx) {
|
||||
let pending = false
|
||||
let timerFunc = null
|
||||
let handle = () => {
|
||||
pending = false
|
||||
ctx ? fn.call(ctx) : fn()
|
||||
}
|
||||
// 支持MutationObserver接口的话使用MutationObserver
|
||||
if (typeof MutationObserver !== 'undefined') {
|
||||
let counter = 1
|
||||
let observer = new MutationObserver(handle)
|
||||
let textNode = document.createTextNode(counter)
|
||||
observer.observe(textNode, {
|
||||
characterData: true // 设为 true 表示监视指定目标节点或子节点树中节点所包含的字符数据的变化
|
||||
})
|
||||
timerFunc = function () {
|
||||
counter = (counter + 1) % 2 // counter会在0和1两者循环变化
|
||||
textNode.data = counter // 节点变化会触发回调handle,
|
||||
}
|
||||
} else {
|
||||
// 否则使用定时器
|
||||
timerFunc = setTimeout
|
||||
}
|
||||
return function () {
|
||||
if (pending) return
|
||||
pending = true
|
||||
timerFunc(handle, 0)
|
||||
}
|
||||
}
|
||||
import { nextTick } from './utils'
|
||||
|
||||
// 批量执行
|
||||
class BatchExecution {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { copyRenderTree, simpleDeepClone } from './utils'
|
||||
import { copyRenderTree, simpleDeepClone, nextTick } from './utils'
|
||||
|
||||
// 命令类
|
||||
class Command {
|
||||
@@ -11,6 +11,7 @@ class Command {
|
||||
this.activeHistoryIndex = 0
|
||||
// 注册快捷键
|
||||
this.registerShortcutKeys()
|
||||
this.addHistory = nextTick(this.addHistory, this)
|
||||
}
|
||||
|
||||
// 清空历史数据
|
||||
@@ -36,7 +37,7 @@ class Command {
|
||||
this.commands[name].forEach(fn => {
|
||||
fn(...args)
|
||||
})
|
||||
if (name === 'BACK' || name === 'FORWARD') {
|
||||
if (['BACK', 'FORWARD', 'SET_NODE_ACTIVE', 'CLEAR_ACTIVE_NODE'].includes(name)) {
|
||||
return
|
||||
}
|
||||
this.addHistory()
|
||||
@@ -80,9 +81,11 @@ class Command {
|
||||
if (this.history.length > 0 && JSON.stringify(this.history[this.history.length - 1]) === JSON.stringify(data)) {
|
||||
return
|
||||
}
|
||||
// 删除当前历史指针后面的数据
|
||||
this.history = this.history.slice(0, this.activeHistoryIndex + 1)
|
||||
this.history.push(simpleDeepClone(data))
|
||||
this.activeHistoryIndex = this.history.length - 1
|
||||
this.mindMap.emit('data_change', data)
|
||||
this.mindMap.emit('data_change', this.removeDataUid(data))
|
||||
this.mindMap.emit(
|
||||
'back_forward',
|
||||
this.activeHistoryIndex,
|
||||
@@ -103,7 +106,7 @@ class Command {
|
||||
this.history.length
|
||||
)
|
||||
let data = simpleDeepClone(this.history[this.activeHistoryIndex])
|
||||
this.mindMap.emit('data_change', data)
|
||||
this.mindMap.emit('data_change', this.removeDataUid(data))
|
||||
return data
|
||||
}
|
||||
}
|
||||
@@ -116,16 +119,31 @@ class Command {
|
||||
let len = this.history.length
|
||||
if (this.activeHistoryIndex + step <= len - 1) {
|
||||
this.activeHistoryIndex += step
|
||||
this.mindMap.emit('back_forward', this.activeHistoryIndex)
|
||||
this.mindMap.emit('back_forward', this.activeHistoryIndex, this.history.length)
|
||||
let data = simpleDeepClone(this.history[this.activeHistoryIndex])
|
||||
this.mindMap.emit('data_change', data)
|
||||
this.mindMap.emit('data_change', this.removeDataUid(data))
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
// 获取渲染树数据副本
|
||||
getCopyData() {
|
||||
return copyRenderTree({}, this.mindMap.renderer.renderTree)
|
||||
return copyRenderTree({}, this.mindMap.renderer.renderTree, true)
|
||||
}
|
||||
|
||||
// 移除节点数据中的uid
|
||||
removeDataUid(data) {
|
||||
data = simpleDeepClone(data)
|
||||
let walk = (root) => {
|
||||
delete root.data.uid
|
||||
if (root.children && root.children.length > 0) {
|
||||
root.children.forEach((item) => {
|
||||
walk(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
walk(data)
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import Base from './layouts/Base'
|
||||
|
||||
class Drag extends Base {
|
||||
// 构造函数
|
||||
|
||||
constructor({ mindMap }) {
|
||||
super(mindMap.renderer)
|
||||
this.mindMap = mindMap
|
||||
@@ -14,7 +13,6 @@ class Drag extends Base {
|
||||
}
|
||||
|
||||
// 复位
|
||||
|
||||
reset() {
|
||||
// 当前拖拽节点
|
||||
this.node = null
|
||||
@@ -45,10 +43,11 @@ class Drag extends Base {
|
||||
this.mouseDownY = 0
|
||||
this.mouseMoveX = 0
|
||||
this.mouseMoveY = 0
|
||||
// 鼠标移动的距离距鼠标按下的位置距离多少以上才认为是拖动事件
|
||||
this.checkDragOffset = 10
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
|
||||
bindEvent() {
|
||||
this.checkOverlapNode = throttle(this.checkOverlapNode, 300, this)
|
||||
this.mindMap.on('node_mousedown', (node, e) => {
|
||||
@@ -83,8 +82,8 @@ class Drag extends Base {
|
||||
this.mouseMoveX = x
|
||||
this.mouseMoveY = y
|
||||
if (
|
||||
Math.abs(x - this.mouseDownX) <= 10 &&
|
||||
Math.abs(y - this.mouseDownY) <= 10 &&
|
||||
Math.abs(x - this.mouseDownX) <= this.checkDragOffset &&
|
||||
Math.abs(y - this.mouseDownY) <= this.checkDragOffset &&
|
||||
!this.node.isDrag
|
||||
) {
|
||||
return
|
||||
@@ -98,7 +97,6 @@ class Drag extends Base {
|
||||
}
|
||||
|
||||
// 鼠标松开事件
|
||||
|
||||
onMouseup(e) {
|
||||
if (!this.isMousedown) {
|
||||
return
|
||||
@@ -141,7 +139,6 @@ class Drag extends Base {
|
||||
}
|
||||
|
||||
// 创建克隆节点
|
||||
|
||||
createCloneNode() {
|
||||
if (!this.clone) {
|
||||
// 节点
|
||||
@@ -163,7 +160,6 @@ class Drag extends Base {
|
||||
}
|
||||
|
||||
// 移除克隆节点
|
||||
|
||||
removeCloneNode() {
|
||||
if (!this.clone) {
|
||||
return
|
||||
@@ -174,7 +170,6 @@ class Drag extends Base {
|
||||
}
|
||||
|
||||
// 拖动中
|
||||
|
||||
onMove(x, y) {
|
||||
if (!this.isMousedown) {
|
||||
return
|
||||
@@ -201,14 +196,12 @@ class Drag extends Base {
|
||||
}
|
||||
|
||||
// 检测重叠节点
|
||||
|
||||
checkOverlapNode() {
|
||||
if (!this.drawTransform) {
|
||||
return
|
||||
}
|
||||
let { scaleX, scaleY, translateX, translateY } = this.drawTransform
|
||||
let checkRight = this.cloneNodeLeft + this.node.width * scaleX
|
||||
let checkBottom = this.cloneNodeTop + this.node.height * scaleX
|
||||
let x = this.mouseMoveX
|
||||
let y = this.mouseMoveY
|
||||
this.overlapNode = null
|
||||
this.prevNode = null
|
||||
this.nextNode = null
|
||||
@@ -223,35 +216,71 @@ class Drag extends Base {
|
||||
if (this.overlapNode || (this.prevNode && this.nextNode)) {
|
||||
return
|
||||
}
|
||||
let { left, top, width, height } = node
|
||||
let _left = left
|
||||
let _top = top
|
||||
let _bottom = top + height
|
||||
let right = (left + width) * scaleX + translateX
|
||||
let bottom = (top + height) * scaleY + translateY
|
||||
left = left * scaleX + translateX
|
||||
top = top * scaleY + translateY
|
||||
// 检测是否重叠
|
||||
if (!this.overlapNode) {
|
||||
if (
|
||||
left <= checkRight &&
|
||||
right >= this.cloneNodeLeft &&
|
||||
top <= checkBottom &&
|
||||
bottom >= this.cloneNodeTop
|
||||
) {
|
||||
this.overlapNode = node
|
||||
let nodeRect = this.getNodeRect(node)
|
||||
let oneFourthHeight = nodeRect.height / 4
|
||||
// 前一个和后一个节点
|
||||
let checkList = node.parent ? node.parent.children.filter((item) => {
|
||||
return item !== this.node
|
||||
}) : []
|
||||
let index = checkList.findIndex((item) => {
|
||||
return item === node
|
||||
})
|
||||
let prevBrother = null
|
||||
let nextBrother = null
|
||||
if (index !== -1) {
|
||||
if (index - 1 >= 0) {
|
||||
prevBrother = checkList[index - 1]
|
||||
}
|
||||
if (index + 1 <= checkList.length - 1) {
|
||||
nextBrother = checkList[index + 1]
|
||||
}
|
||||
}
|
||||
// 检测兄弟节点位置
|
||||
if (!this.prevNode && !this.nextNode && !node.isRoot) {
|
||||
// && this.node.isBrother(node)
|
||||
if (left <= checkRight && right >= this.cloneNodeLeft) {
|
||||
if (this.cloneNodeTop > bottom && this.cloneNodeTop <= bottom + 10) {
|
||||
// 和前一个兄弟节点的距离
|
||||
let prevBrotherOffset = 0
|
||||
if (prevBrother) {
|
||||
let prevNodeRect = this.getNodeRect(prevBrother)
|
||||
prevBrotherOffset = nodeRect.top - prevNodeRect.bottom
|
||||
// 间距小于10就当它不存在
|
||||
prevBrotherOffset = prevBrotherOffset >= 10 ? prevBrotherOffset / 2 : 0
|
||||
} else {
|
||||
// 没有前一个兄弟节点,那么假设和前一个节点的距离为20
|
||||
prevBrotherOffset = 10
|
||||
}
|
||||
// 和后一个兄弟节点的距离
|
||||
let nextBrotherOffset = 0
|
||||
if (nextBrother) {
|
||||
let nextNodeRect = this.getNodeRect(nextBrother)
|
||||
nextBrotherOffset = nextNodeRect.top - nodeRect.bottom
|
||||
nextBrotherOffset = nextBrotherOffset >= 10 ? nextBrotherOffset / 2 : 0
|
||||
} else {
|
||||
nextBrotherOffset = 10
|
||||
}
|
||||
if (nodeRect.left <= x && nodeRect.right >= x) {
|
||||
// 检测兄弟节点位置
|
||||
if (!this.overlapNode && !this.prevNode && !this.nextNode && !node.isRoot) {
|
||||
let checkIsPrevNode = nextBrotherOffset > 0 ? // 距离下一个兄弟节点的距离大于0
|
||||
y > nodeRect.bottom && y <= (nodeRect.bottom + nextBrotherOffset) : // 那么在当前节点外底部判断
|
||||
y >= nodeRect.bottom - oneFourthHeight && y <= nodeRect.bottom // 否则在当前节点内底部1/4区间判断
|
||||
let checkIsNextNode = prevBrotherOffset > 0 ? // 距离上一个兄弟节点的距离大于0
|
||||
y < nodeRect.top && y >= (nodeRect.top - prevBrotherOffset) : // 那么在当前节点外底部判断
|
||||
y >= nodeRect.top && y <= nodeRect.top + oneFourthHeight
|
||||
if (checkIsPrevNode) {
|
||||
this.prevNode = node
|
||||
this.placeholder.size(node.width, 10).move(_left, _bottom)
|
||||
} else if (checkBottom < top && checkBottom >= top - 10) {
|
||||
let size = nextBrotherOffset > 0 ? nextBrotherOffset : 5
|
||||
this.placeholder.size(node.width, size).move(nodeRect.originLeft, nodeRect.originBottom)
|
||||
} else if (checkIsNextNode) {
|
||||
this.nextNode = node
|
||||
this.placeholder.size(node.width, 10).move(_left, _top - 10)
|
||||
let size = prevBrotherOffset > 0 ? prevBrotherOffset : 5
|
||||
this.placeholder.size(node.width, size).move(nodeRect.originLeft, nodeRect.originTop - size)
|
||||
}
|
||||
}
|
||||
// 检测是否重叠
|
||||
if (!this.overlapNode && !this.prevNode && !this.nextNode) {
|
||||
if (
|
||||
nodeRect.top + (prevBrotherOffset > 0 ? 0 : oneFourthHeight) <= y &&
|
||||
nodeRect.bottom - (nextBrotherOffset > 0 ? 0 : oneFourthHeight) >= y
|
||||
) {
|
||||
this.overlapNode = node
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,6 +289,30 @@ class Drag extends Base {
|
||||
this.mindMap.renderer.setNodeActive(this.overlapNode, true)
|
||||
}
|
||||
}
|
||||
|
||||
// 计算节点的位置尺寸信息
|
||||
getNodeRect(node) {
|
||||
let { scaleX, scaleY, translateX, translateY } = this.drawTransform
|
||||
let { left, top, width, height } = node
|
||||
let originLeft = left
|
||||
let originTop = top
|
||||
let originBottom = top + height
|
||||
let right = (left + width) * scaleX + translateX
|
||||
let bottom = (top + height) * scaleY + translateY
|
||||
left = left * scaleX + translateX
|
||||
top = top * scaleY + translateY
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
originLeft,
|
||||
originTop,
|
||||
originBottom
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Drag.instanceName = 'drag'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import EventEmitter from 'eventemitter3'
|
||||
import { CONSTANTS } from './utils/constant'
|
||||
|
||||
// 事件类
|
||||
class Event extends EventEmitter {
|
||||
@@ -34,6 +35,8 @@ class Event extends EventEmitter {
|
||||
this.onContextmenu = this.onContextmenu.bind(this)
|
||||
this.onSvgMousedown = this.onSvgMousedown.bind(this)
|
||||
this.onKeyup = this.onKeyup.bind(this)
|
||||
this.onMouseenter = this.onMouseenter.bind(this)
|
||||
this.onMouseleave = this.onMouseleave.bind(this)
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
@@ -45,6 +48,8 @@ class Event extends EventEmitter {
|
||||
window.addEventListener('mouseup', this.onMouseup)
|
||||
this.mindMap.el.addEventListener('wheel', this.onMousewheel)
|
||||
this.mindMap.svg.on('contextmenu', this.onContextmenu)
|
||||
this.mindMap.svg.on('mouseenter', this.onMouseenter)
|
||||
this.mindMap.svg.on('mouseleave', this.onMouseleave)
|
||||
window.addEventListener('keyup', this.onKeyup)
|
||||
}
|
||||
|
||||
@@ -56,6 +61,8 @@ class Event extends EventEmitter {
|
||||
window.removeEventListener('mouseup', this.onMouseup)
|
||||
this.mindMap.el.removeEventListener('wheel', this.onMousewheel)
|
||||
this.mindMap.svg.off('contextmenu', this.onContextmenu)
|
||||
this.mindMap.svg.off('mouseenter', this.onMouseenter)
|
||||
this.mindMap.svg.off('mouseleave', this.onMouseleave)
|
||||
window.removeEventListener('keyup', this.onKeyup)
|
||||
}
|
||||
|
||||
@@ -71,7 +78,6 @@ class Event extends EventEmitter {
|
||||
|
||||
// 鼠标按下事件
|
||||
onMousedown(e) {
|
||||
// e.preventDefault()
|
||||
// 鼠标左键
|
||||
if (e.which === 1) {
|
||||
this.isLeftMousedown = true
|
||||
@@ -83,13 +89,13 @@ class Event extends EventEmitter {
|
||||
|
||||
// 鼠标移动事件
|
||||
onMousemove(e) {
|
||||
// e.preventDefault()
|
||||
this.mousemovePos.x = e.clientX
|
||||
this.mousemovePos.y = e.clientY
|
||||
this.mousemoveOffset.x = e.clientX - this.mousedownPos.x
|
||||
this.mousemoveOffset.y = e.clientY - this.mousedownPos.y
|
||||
this.emit('mousemove', e, this)
|
||||
if (this.isLeftMousedown) {
|
||||
e.preventDefault()
|
||||
this.emit('drag', e, this)
|
||||
}
|
||||
}
|
||||
@@ -107,15 +113,15 @@ class Event extends EventEmitter {
|
||||
let dir
|
||||
// 解决mac触控板双指缩放方向相反的问题
|
||||
if (e.ctrlKey) {
|
||||
if (e.deltaY > 0) dir = 'up'
|
||||
if (e.deltaY < 0) dir = 'down'
|
||||
if (e.deltaX > 0) dir = 'left'
|
||||
if (e.deltaX < 0) dir = 'right'
|
||||
if (e.deltaY > 0) dir = CONSTANTS.DIR.UP
|
||||
if (e.deltaY < 0) dir = CONSTANTS.DIR.DOWN
|
||||
if (e.deltaX > 0) dir = CONSTANTS.DIR.LEFT
|
||||
if (e.deltaX < 0) dir = CONSTANTS.DIR.RIGHT
|
||||
} else {
|
||||
if ((e.wheelDeltaY || e.detail) > 0) dir = 'up'
|
||||
if ((e.wheelDeltaY || e.detail) < 0) dir = 'down'
|
||||
if ((e.wheelDeltaX || e.detail) > 0) dir = 'left'
|
||||
if ((e.wheelDeltaX || e.detail) < 0) dir = 'right'
|
||||
if ((e.wheelDeltaY || e.detail) > 0) dir = CONSTANTS.DIR.UP
|
||||
if ((e.wheelDeltaY || e.detail) < 0) dir = CONSTANTS.DIR.DOWN
|
||||
if ((e.wheelDeltaX || e.detail) > 0) dir = CONSTANTS.DIR.LEFT
|
||||
if ((e.wheelDeltaX || e.detail) < 0) dir = CONSTANTS.DIR.RIGHT
|
||||
}
|
||||
this.emit('mousewheel', e, dir, this)
|
||||
}
|
||||
@@ -130,6 +136,16 @@ class Event extends EventEmitter {
|
||||
onKeyup(e) {
|
||||
this.emit('keyup', e)
|
||||
}
|
||||
|
||||
// 进入
|
||||
onMouseenter(e) {
|
||||
this.emit('svg_mouseenter', e)
|
||||
}
|
||||
|
||||
// 离开
|
||||
onMouseleave(e) {
|
||||
this.emit('svg_mouseleave', e)
|
||||
}
|
||||
}
|
||||
|
||||
export default Event
|
||||
|
||||
@@ -2,6 +2,7 @@ import { imgToDataUrl, downloadFile } from './utils'
|
||||
import JsPDF from 'jspdf'
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
import drawBackgroundImageToCanvas from './utils/simulateCSSBackgroundInCanvas'
|
||||
import { transformToMarkdown } from './parse/toMarkdown'
|
||||
const URL = window.URL || window.webkitURL || window
|
||||
|
||||
// 导出类
|
||||
@@ -40,8 +41,10 @@ class Export {
|
||||
let nodeWithDomToImg = null
|
||||
if (domToImage && this.mindMap.richText) {
|
||||
let res = await this.mindMap.richText.handleSvgDomElements(svg)
|
||||
nodeWithDomToImg = res.svg
|
||||
svgHTML = res.svgHTML
|
||||
if (res) {
|
||||
nodeWithDomToImg = res.svg
|
||||
svgHTML = res.svgHTML
|
||||
}
|
||||
}
|
||||
return {
|
||||
node: svg,
|
||||
@@ -236,6 +239,14 @@ class Export {
|
||||
smm(name, withConfig) {
|
||||
return this.json(name, withConfig)
|
||||
}
|
||||
|
||||
// markdown文件
|
||||
md() {
|
||||
let data = this.mindMap.getData()
|
||||
let content = transformToMarkdown(data)
|
||||
let blob = new Blob([content])
|
||||
return URL.createObjectURL(blob)
|
||||
}
|
||||
}
|
||||
|
||||
Export.instanceName = 'doExport'
|
||||
|
||||
@@ -10,6 +10,7 @@ export default class KeyCommand {
|
||||
}
|
||||
this.shortcutMapCache = {}
|
||||
this.isPause = false
|
||||
this.isInSvg = false
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
@@ -37,8 +38,21 @@ export default class KeyCommand {
|
||||
|
||||
// 绑定事件
|
||||
bindEvent() {
|
||||
// 只有当鼠标在画布内才响应快捷键
|
||||
this.mindMap.on('svg_mouseenter', () => {
|
||||
this.isInSvg = true
|
||||
})
|
||||
this.mindMap.on('svg_mouseleave', () => {
|
||||
if (this.mindMap.richText && this.mindMap.richText.showTextEdit) {
|
||||
return
|
||||
}
|
||||
if (this.mindMap.renderer.textEdit.showTextEdit) {
|
||||
return
|
||||
}
|
||||
this.isInSvg = false
|
||||
})
|
||||
window.addEventListener('keydown', e => {
|
||||
if (this.isPause) {
|
||||
if (this.isPause || (this.mindMap.opt.enableShortcutOnlyWhenMouseInSvg && !this.isInSvg)) {
|
||||
return
|
||||
}
|
||||
Object.keys(this.shortcutMap).forEach(key => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { bfsWalk } from './utils'
|
||||
import { CONSTANTS } from './utils/constant'
|
||||
|
||||
// 键盘导航类
|
||||
class KeyboardNavigation {
|
||||
@@ -7,17 +8,17 @@ class KeyboardNavigation {
|
||||
this.opt = opt
|
||||
this.mindMap = opt.mindMap
|
||||
this.onKeyup = this.onKeyup.bind(this)
|
||||
this.mindMap.keyCommand.addShortcut('Left', () => {
|
||||
this.onKeyup('Left')
|
||||
this.mindMap.keyCommand.addShortcut(CONSTANTS.KEY_DIR.LEFT, () => {
|
||||
this.onKeyup(CONSTANTS.KEY_DIR.LEFT)
|
||||
})
|
||||
this.mindMap.keyCommand.addShortcut('Up', () => {
|
||||
this.onKeyup('Up')
|
||||
this.mindMap.keyCommand.addShortcut(CONSTANTS.KEY_DIR.UP, () => {
|
||||
this.onKeyup(CONSTANTS.KEY_DIR.UP)
|
||||
})
|
||||
this.mindMap.keyCommand.addShortcut('Right', () => {
|
||||
this.onKeyup('Right')
|
||||
this.mindMap.keyCommand.addShortcut(CONSTANTS.KEY_DIR.RIGHT, () => {
|
||||
this.onKeyup(CONSTANTS.KEY_DIR.RIGHT)
|
||||
})
|
||||
this.mindMap.keyCommand.addShortcut('Down', () => {
|
||||
this.onKeyup('Down')
|
||||
this.mindMap.keyCommand.addShortcut(CONSTANTS.KEY_DIR.DOWN, () => {
|
||||
this.onKeyup(CONSTANTS.KEY_DIR.DOWN)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -101,19 +102,19 @@ class KeyboardNavigation {
|
||||
let { left, top, right, bottom } = rect
|
||||
let match = false
|
||||
// 按下了左方向键
|
||||
if (dir === 'Left') {
|
||||
if (dir === CONSTANTS.KEY_DIR.LEFT) {
|
||||
// 判断节点是否在当前节点的左侧
|
||||
match = right <= currentActiveNodeRect.left
|
||||
// 按下了右方向键
|
||||
} else if (dir === 'Right') {
|
||||
} else if (dir === CONSTANTS.KEY_DIR.RIGHT) {
|
||||
// 判断节点是否在当前节点的右侧
|
||||
match = left >= currentActiveNodeRect.right
|
||||
// 按下了上方向键
|
||||
} else if (dir === 'Up') {
|
||||
} else if (dir === CONSTANTS.KEY_DIR.UP) {
|
||||
// 判断节点是否在当前节点的上面
|
||||
match = bottom <= currentActiveNodeRect.top
|
||||
// 按下了下方向键
|
||||
} else if (dir === 'Down') {
|
||||
} else if (dir === CONSTANTS.KEY_DIR.DOWN) {
|
||||
// 判断节点是否在当前节点的下面
|
||||
match = top >= currentActiveNodeRect.bottom
|
||||
}
|
||||
@@ -136,22 +137,22 @@ class KeyboardNavigation {
|
||||
let rect = this.getNodeRect(node)
|
||||
let { left, top, right, bottom } = rect
|
||||
let match = false
|
||||
if (dir === 'Left') {
|
||||
if (dir === CONSTANTS.KEY_DIR.LEFT) {
|
||||
match =
|
||||
left < currentActiveNodeRect.left &&
|
||||
top < currentActiveNodeRect.bottom &&
|
||||
bottom > currentActiveNodeRect.top
|
||||
} else if (dir === 'Right') {
|
||||
} else if (dir === CONSTANTS.KEY_DIR.RIGHT) {
|
||||
match =
|
||||
right > currentActiveNodeRect.right &&
|
||||
top < currentActiveNodeRect.bottom &&
|
||||
bottom > currentActiveNodeRect.top
|
||||
} else if (dir === 'Up') {
|
||||
} else if (dir === CONSTANTS.KEY_DIR.UP) {
|
||||
match =
|
||||
top < currentActiveNodeRect.top &&
|
||||
left < currentActiveNodeRect.right &&
|
||||
right > currentActiveNodeRect.left
|
||||
} else if (dir === 'Down') {
|
||||
} else if (dir === CONSTANTS.KEY_DIR.DOWN) {
|
||||
match =
|
||||
bottom > currentActiveNodeRect.bottom &&
|
||||
left < currentActiveNodeRect.right &&
|
||||
@@ -185,13 +186,13 @@ class KeyboardNavigation {
|
||||
let offsetY = ccY - cY
|
||||
if (offsetX === 0 && offsetY === 0) return
|
||||
let match = false
|
||||
if (dir === 'Left') {
|
||||
if (dir === CONSTANTS.KEY_DIR.LEFT) {
|
||||
match = offsetX <= 0 && offsetX <= offsetY && offsetX <= -offsetY
|
||||
} else if (dir === 'Right') {
|
||||
} else if (dir === CONSTANTS.KEY_DIR.RIGHT) {
|
||||
match = offsetX > 0 && offsetX >= -offsetY && offsetX >= offsetY
|
||||
} else if (dir === 'Up') {
|
||||
} else if (dir === CONSTANTS.KEY_DIR.UP) {
|
||||
match = offsetY <= 0 && offsetY < offsetX && offsetY < -offsetX
|
||||
} else if (dir === 'Down') {
|
||||
} else if (dir === CONSTANTS.KEY_DIR.DOWN) {
|
||||
match = offsetY > 0 && -offsetY < offsetX && offsetY > offsetX
|
||||
}
|
||||
if (match) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,24 +7,24 @@ import TextEdit from './TextEdit'
|
||||
import { copyNodeTree, simpleDeepClone, walk } from './utils'
|
||||
import { shapeList } from './Shape'
|
||||
import { lineStyleProps } from './themes/default'
|
||||
import { CONSTANTS } from './utils/constant'
|
||||
|
||||
// 布局列表
|
||||
const layouts = {
|
||||
// 逻辑结构图
|
||||
logicalStructure: LogicalStructure,
|
||||
[CONSTANTS.LAYOUT.LOGICAL_STRUCTURE]: LogicalStructure,
|
||||
// 思维导图
|
||||
mindMap: MindMap,
|
||||
[CONSTANTS.LAYOUT.MIND_MAP]: MindMap,
|
||||
// 目录组织图
|
||||
catalogOrganization: CatalogOrganization,
|
||||
[CONSTANTS.LAYOUT.CATALOG_ORGANIZATION]: CatalogOrganization,
|
||||
// 组织结构图
|
||||
organizationStructure: OrganizationStructure
|
||||
[CONSTANTS.LAYOUT.ORGANIZATION_STRUCTURE]: OrganizationStructure
|
||||
}
|
||||
|
||||
// 渲染
|
||||
|
||||
class Render {
|
||||
// 构造函数
|
||||
|
||||
constructor(opt = {}) {
|
||||
this.opt = opt
|
||||
this.mindMap = opt.mindMap
|
||||
@@ -34,6 +34,15 @@ class Render {
|
||||
this.renderTree = merge({}, this.mindMap.opt.data || {})
|
||||
// 是否重新渲染
|
||||
this.reRender = false
|
||||
// 是否正在渲染中
|
||||
this.isRendering = false
|
||||
// 是否存在等待渲染
|
||||
this.hasWaitRendering = false
|
||||
// 用于缓存节点
|
||||
this.nodeCache = {}
|
||||
this.lastNodeCache = {}
|
||||
// 触发render的来源
|
||||
this.renderSource = ''
|
||||
// 当前激活的节点列表
|
||||
this.activeNodeList = []
|
||||
// 根节点
|
||||
@@ -51,17 +60,15 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置布局结构
|
||||
|
||||
setLayout() {
|
||||
this.layout = new (
|
||||
layouts[this.mindMap.opt.layout]
|
||||
? layouts[this.mindMap.opt.layout]
|
||||
: layouts.logicalStructure
|
||||
: layouts[CONSTANTS.LAYOUT.LOGICAL_STRUCTURE]
|
||||
)(this)
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
|
||||
bindEvent() {
|
||||
// 点击事件
|
||||
this.mindMap.on('draw_click', () => {
|
||||
@@ -73,7 +80,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 注册命令
|
||||
|
||||
registerCommands() {
|
||||
// 全选
|
||||
this.selectAll = this.selectAll.bind(this)
|
||||
@@ -175,7 +181,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 注册快捷键
|
||||
|
||||
registerShortcutKeys() {
|
||||
// 插入下级节点
|
||||
this.mindMap.keyCommand.addShortcut('Tab', () => {
|
||||
@@ -220,7 +225,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 开启文字编辑,会禁用回车键和删除键相关快捷键防止冲突
|
||||
|
||||
startTextEdit() {
|
||||
this.mindMap.keyCommand.save()
|
||||
// this.mindMap.keyCommand.removeShortcut('Del|Backspace')
|
||||
@@ -229,7 +233,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 结束文字编辑,会恢复回车键和删除键相关快捷键
|
||||
|
||||
endTextEdit() {
|
||||
this.mindMap.keyCommand.restore()
|
||||
// this.mindMap.keyCommand.addShortcut('Del|Backspace', this.removeNodeWrap)
|
||||
@@ -238,23 +241,52 @@ class Render {
|
||||
}
|
||||
|
||||
// 渲染
|
||||
|
||||
render(callback = () => {}) {
|
||||
render(callback = () => {}, source) {
|
||||
// 如果当前还没有渲染完毕,不再触发渲染
|
||||
if (this.isRendering) {
|
||||
// 等待当前渲染完毕后再进行一次渲染
|
||||
this.hasWaitRendering = true
|
||||
return
|
||||
}
|
||||
this.isRendering = true
|
||||
// 触发当前重新渲染的来源
|
||||
this.renderSource = source
|
||||
// 节点缓存
|
||||
this.lastNodeCache = this.nodeCache
|
||||
this.nodeCache = {}
|
||||
// 重新渲染需要清除激活状态
|
||||
if (this.reRender) {
|
||||
this.clearActive()
|
||||
this.layout.clearNodePool()
|
||||
}
|
||||
// 计算布局
|
||||
this.layout.doLayout(root => {
|
||||
// 删除本次渲染时不再需要的节点
|
||||
Object.keys(this.lastNodeCache).forEach((uid) => {
|
||||
if (!this.nodeCache[uid]) {
|
||||
this.lastNodeCache[uid].destroy()
|
||||
if (this.lastNodeCache[uid].parent) {
|
||||
this.lastNodeCache[uid].parent.removeLine()
|
||||
}
|
||||
}
|
||||
})
|
||||
// 更新根节点
|
||||
this.root = root
|
||||
// 渲染节点
|
||||
this.root.render(() => {
|
||||
this.isRendering = false
|
||||
this.mindMap.emit('node_tree_render_end')
|
||||
callback()
|
||||
callback && callback()
|
||||
if (this.hasWaitRendering) {
|
||||
this.hasWaitRendering = false
|
||||
this.render(callback, source)
|
||||
}
|
||||
})
|
||||
})
|
||||
this.mindMap.emit('node_active', null, this.activeNodeList)
|
||||
}
|
||||
|
||||
// 清除当前激活的节点
|
||||
|
||||
clearActive() {
|
||||
this.activeNodeList.forEach(item => {
|
||||
this.setNodeActive(item, false)
|
||||
@@ -263,7 +295,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 清除当前所有激活节点,并会触发事件
|
||||
|
||||
clearAllActive() {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
return
|
||||
@@ -273,7 +304,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 添加节点到激活列表里
|
||||
|
||||
addActiveNode(node) {
|
||||
let index = this.findActiveNodeIndex(node)
|
||||
if (index === -1) {
|
||||
@@ -282,7 +312,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 在激活列表里移除某个节点
|
||||
|
||||
removeActiveNode(node) {
|
||||
let index = this.findActiveNodeIndex(node)
|
||||
if (index === -1) {
|
||||
@@ -292,7 +321,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 检索某个节点在激活列表里的索引
|
||||
|
||||
findActiveNodeIndex(node) {
|
||||
return this.activeNodeList.findIndex(item => {
|
||||
return item === node
|
||||
@@ -300,7 +328,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 获取节点在同级里的索引位置
|
||||
|
||||
getNodeIndex(node) {
|
||||
return node.parent
|
||||
? node.parent.children.findIndex(item => {
|
||||
@@ -310,7 +337,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 全选
|
||||
|
||||
selectAll() {
|
||||
walk(
|
||||
this.root,
|
||||
@@ -320,7 +346,7 @@ class Render {
|
||||
node.nodeData.data.isActive = true
|
||||
this.addActiveNode(node)
|
||||
setTimeout(() => {
|
||||
node.renderNode()
|
||||
node.updateNodeShape()
|
||||
}, 0)
|
||||
}
|
||||
},
|
||||
@@ -332,47 +358,54 @@ class Render {
|
||||
}
|
||||
|
||||
// 回退
|
||||
|
||||
back(step) {
|
||||
this.clearAllActive()
|
||||
let data = this.mindMap.command.back(step)
|
||||
if (data) {
|
||||
this.renderTree = data
|
||||
this.mindMap.reRender()
|
||||
this.mindMap.render()
|
||||
}
|
||||
}
|
||||
|
||||
// 前进
|
||||
|
||||
forward(step) {
|
||||
this.clearAllActive()
|
||||
let data = this.mindMap.command.forward(step)
|
||||
if (data) {
|
||||
this.renderTree = data
|
||||
this.mindMap.reRender()
|
||||
this.mindMap.render()
|
||||
}
|
||||
}
|
||||
|
||||
// 插入同级节点,多个节点只会操作第一个节点
|
||||
// 规范指定节点数据
|
||||
formatAppointNodes(appointNodes) {
|
||||
if (!appointNodes) return []
|
||||
return Array.isArray(appointNodes) ? appointNodes: [appointNodes]
|
||||
}
|
||||
|
||||
insertNode(openEdit = true) {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
// 插入同级节点,多个节点只会操作第一个节点
|
||||
insertNode(openEdit = true, appointNodes = [], appointData = null) {
|
||||
appointNodes = this.formatAppointNodes(appointNodes)
|
||||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||||
return
|
||||
}
|
||||
let first = this.activeNodeList[0]
|
||||
let { defaultInsertSecondLevelNodeText, defaultInsertBelowSecondLevelNodeText } = this.mindMap.opt
|
||||
let list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
|
||||
let first = list[0]
|
||||
if (first.isRoot) {
|
||||
this.insertChildNode()
|
||||
this.insertChildNode(openEdit, appointNodes, appointData)
|
||||
} else {
|
||||
let text = first.layerIndex === 1 ? '二级节点' : '分支主题'
|
||||
let text = first.layerIndex === 1 ? defaultInsertSecondLevelNodeText : defaultInsertBelowSecondLevelNodeText
|
||||
if (first.layerIndex === 1) {
|
||||
first.parent.initRender = true
|
||||
first.parent.destroy()
|
||||
}
|
||||
let index = this.getNodeIndex(first)
|
||||
first.parent.nodeData.children.splice(index + 1, 0, {
|
||||
inserting: openEdit,
|
||||
data: {
|
||||
text: text,
|
||||
expand: true
|
||||
expand: true,
|
||||
...(appointData || {})
|
||||
},
|
||||
children: []
|
||||
})
|
||||
@@ -381,38 +414,37 @@ class Render {
|
||||
}
|
||||
|
||||
// 插入子节点
|
||||
|
||||
insertChildNode(openEdit = true) {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
insertChildNode(openEdit = true, appointNodes = [], appointData = null) {
|
||||
appointNodes = this.formatAppointNodes(appointNodes)
|
||||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||||
return
|
||||
}
|
||||
this.activeNodeList.forEach(node => {
|
||||
let { defaultInsertSecondLevelNodeText, defaultInsertBelowSecondLevelNodeText } = this.mindMap.opt
|
||||
let list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
|
||||
list.forEach(node => {
|
||||
if (!node.nodeData.children) {
|
||||
node.nodeData.children = []
|
||||
}
|
||||
let text = node.isRoot ? '二级节点' : '分支主题'
|
||||
let text = node.isRoot ? defaultInsertSecondLevelNodeText : defaultInsertBelowSecondLevelNodeText
|
||||
node.nodeData.children.push({
|
||||
inserting: openEdit,
|
||||
data: {
|
||||
text: text,
|
||||
expand: true
|
||||
expand: true,
|
||||
...(appointData || {})
|
||||
},
|
||||
children: []
|
||||
})
|
||||
// 插入子节点时自动展开子节点
|
||||
node.nodeData.data.expand = true
|
||||
if (node.isRoot) {
|
||||
node.initRender = true
|
||||
// this.mindMap.batchExecution.push('renderNode' + index, () => {
|
||||
// node.renderNode()
|
||||
// })
|
||||
node.destroy()
|
||||
}
|
||||
})
|
||||
this.mindMap.render()
|
||||
}
|
||||
|
||||
// 上移节点,多个节点只会操作第一个节点
|
||||
|
||||
upNode() {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
return
|
||||
@@ -440,7 +472,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 下移节点,多个节点只会操作第一个节点
|
||||
|
||||
downNode() {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
return
|
||||
@@ -468,7 +499,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 将节点移动到另一个节点的前面
|
||||
|
||||
insertBefore(node, exist) {
|
||||
if (node.isRoot) {
|
||||
return
|
||||
@@ -500,14 +530,12 @@ class Render {
|
||||
existParent.nodeData.children.splice(existIndex, 0, node.nodeData)
|
||||
this.mindMap.render(() => {
|
||||
if (nodeLayerChanged) {
|
||||
node.getSize()
|
||||
node.renderNode()
|
||||
node.reRender()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 将节点移动到另一个节点的后面
|
||||
|
||||
insertAfter(node, exist) {
|
||||
if (node.isRoot) {
|
||||
return
|
||||
@@ -540,19 +568,20 @@ class Render {
|
||||
existParent.nodeData.children.splice(existIndex, 0, node.nodeData)
|
||||
this.mindMap.render(() => {
|
||||
if (nodeLayerChanged) {
|
||||
node.getSize()
|
||||
node.renderNode()
|
||||
node.reRender()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 移除节点
|
||||
|
||||
removeNode() {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
removeNode(appointNodes = []) {
|
||||
appointNodes = this.formatAppointNodes(appointNodes)
|
||||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||||
return
|
||||
}
|
||||
let root = this.activeNodeList.find((node) => {
|
||||
let isAppointNodes = appointNodes.length > 0
|
||||
let list = isAppointNodes ? appointNodes : this.activeNodeList
|
||||
let root = list.find((node) => {
|
||||
return node.isRoot
|
||||
})
|
||||
if (root) {
|
||||
@@ -563,8 +592,9 @@ class Render {
|
||||
root.children = []
|
||||
root.nodeData.children = []
|
||||
} else {
|
||||
for (let i = 0; i < this.activeNodeList.length; i++) {
|
||||
let node = this.activeNodeList[i]
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
let node = list[i]
|
||||
if (isAppointNodes) list.splice(i, 1)
|
||||
if (node.isGeneralization) {
|
||||
// 删除概要节点
|
||||
this.setNodeData(node.generalizationBelongNode, {
|
||||
@@ -580,13 +610,11 @@ class Render {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.activeNodeList = []
|
||||
this.mindMap.emit('node_active', null, [])
|
||||
this.mindMap.emit('node_active', null, this.activeNodeList)
|
||||
this.mindMap.render()
|
||||
}
|
||||
|
||||
// 移除某个指定节点
|
||||
|
||||
removeOneNode(node) {
|
||||
let index = this.getNodeIndex(node)
|
||||
node.remove()
|
||||
@@ -595,7 +623,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 复制节点,多个节点只会操作第一个节点
|
||||
|
||||
copyNode() {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
return
|
||||
@@ -604,7 +631,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 剪切节点,多个节点只会操作第一个节点
|
||||
|
||||
cutNode(callback) {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
return
|
||||
@@ -624,7 +650,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 移动一个节点作为另一个节点的子节点
|
||||
|
||||
moveNodeTo(node, toNode) {
|
||||
if (node.isRoot) {
|
||||
return
|
||||
@@ -636,12 +661,11 @@ class Render {
|
||||
toNode.nodeData.children.push(copyData)
|
||||
this.mindMap.render()
|
||||
if (toNode.isRoot) {
|
||||
toNode.renderNode()
|
||||
toNode.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
// 粘贴节点到节点
|
||||
|
||||
pasteNode(data) {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
return
|
||||
@@ -653,7 +677,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点样式
|
||||
|
||||
setNodeStyle(node, prop, value, isActive) {
|
||||
let data = {}
|
||||
if (isActive) {
|
||||
@@ -670,12 +693,14 @@ class Render {
|
||||
}
|
||||
// 如果开启了富文本,则需要应用到富文本上
|
||||
if (this.mindMap.richText) {
|
||||
this.mindMap.richText.showEditText(node)
|
||||
let config = this.mindMap.richText.normalStyleToRichTextStyle({
|
||||
[prop]: value
|
||||
})
|
||||
this.mindMap.richText.formatAllText(config)
|
||||
this.mindMap.richText.hideEditText()
|
||||
if (Object.keys(config).length > 0) {
|
||||
this.mindMap.richText.showEditText(node)
|
||||
this.mindMap.richText.formatAllText(config)
|
||||
this.mindMap.richText.hideEditText()
|
||||
}
|
||||
}
|
||||
this.setNodeDataRender(node, data)
|
||||
// 更新了连线的样式
|
||||
@@ -685,16 +710,14 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点是否激活
|
||||
|
||||
setNodeActive(node, active) {
|
||||
this.setNodeData(node, {
|
||||
isActive: active
|
||||
})
|
||||
node.renderNode()
|
||||
node.updateNodeShape()
|
||||
}
|
||||
|
||||
// 设置节点是否展开
|
||||
|
||||
setNodeExpand(node, expand) {
|
||||
this.setNodeData(node, {
|
||||
expand
|
||||
@@ -718,7 +741,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 展开所有
|
||||
|
||||
expandAllNode() {
|
||||
walk(
|
||||
this.renderTree,
|
||||
@@ -733,11 +755,10 @@ class Render {
|
||||
0,
|
||||
0
|
||||
)
|
||||
this.mindMap.reRender()
|
||||
this.mindMap.render()
|
||||
}
|
||||
|
||||
// 收起所有
|
||||
|
||||
unexpandAllNode() {
|
||||
walk(
|
||||
this.renderTree,
|
||||
@@ -753,11 +774,12 @@ class Render {
|
||||
0,
|
||||
0
|
||||
)
|
||||
this.mindMap.reRender()
|
||||
this.mindMap.render(() => {
|
||||
this.mindMap.view.reset()
|
||||
})
|
||||
}
|
||||
|
||||
// 展开到指定层级
|
||||
|
||||
expandToLevel(level) {
|
||||
walk(
|
||||
this.renderTree,
|
||||
@@ -771,11 +793,10 @@ class Render {
|
||||
0,
|
||||
0
|
||||
)
|
||||
this.mindMap.reRender()
|
||||
this.mindMap.render()
|
||||
}
|
||||
|
||||
// 切换激活节点的展开状态
|
||||
|
||||
toggleActiveExpand() {
|
||||
this.activeNodeList.forEach(node => {
|
||||
if (node.nodeData.children.length <= 0) {
|
||||
@@ -786,7 +807,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 切换节点展开状态
|
||||
|
||||
toggleNodeExpand(node) {
|
||||
this.mindMap.execCommand(
|
||||
'SET_NODE_EXPAND',
|
||||
@@ -796,7 +816,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点文本
|
||||
|
||||
setNodeText(node, text, richText) {
|
||||
this.setNodeDataRender(node, {
|
||||
text,
|
||||
@@ -805,7 +824,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点图片
|
||||
|
||||
setNodeImage(node, { url, title, width, height }) {
|
||||
this.setNodeDataRender(node, {
|
||||
image: url,
|
||||
@@ -818,7 +836,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点图标
|
||||
|
||||
setNodeIcon(node, icons) {
|
||||
this.setNodeDataRender(node, {
|
||||
icon: icons
|
||||
@@ -826,7 +843,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点超链接
|
||||
|
||||
setNodeHyperlink(node, link, title = '') {
|
||||
this.setNodeDataRender(node, {
|
||||
hyperlink: link,
|
||||
@@ -835,7 +851,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点备注
|
||||
|
||||
setNodeNote(node, note) {
|
||||
this.setNodeDataRender(node, {
|
||||
note
|
||||
@@ -843,7 +858,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点标签
|
||||
|
||||
setNodeTag(node, tag) {
|
||||
this.setNodeDataRender(node, {
|
||||
tag
|
||||
@@ -851,7 +865,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 添加节点概要
|
||||
|
||||
addGeneralization(data) {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
return
|
||||
@@ -871,7 +884,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 删除节点概要
|
||||
|
||||
removeGeneralization() {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
return
|
||||
@@ -889,7 +901,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点自定义位置
|
||||
|
||||
setNodeCustomPosition(node, left = undefined, top = undefined) {
|
||||
let nodeList = [node] || this.activeNodeList
|
||||
nodeList.forEach(item => {
|
||||
@@ -901,7 +912,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 一键整理布局,即去除自定义位置
|
||||
|
||||
resetLayout() {
|
||||
walk(
|
||||
this.root,
|
||||
@@ -923,7 +933,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点形状
|
||||
|
||||
setNodeShape(node, shape) {
|
||||
if (!shape || !shapeList.includes(shape)) {
|
||||
return
|
||||
@@ -935,7 +944,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 更新节点数据
|
||||
|
||||
setNodeData(node, data) {
|
||||
Object.keys(data).forEach(key => {
|
||||
node.nodeData.data[key] = data[key]
|
||||
@@ -943,11 +951,9 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点数据,并判断是否渲染
|
||||
|
||||
setNodeDataRender(node, data) {
|
||||
this.setNodeData(node, data)
|
||||
let changed = node.getSize()
|
||||
node.renderNode()
|
||||
let changed = node.reRender()
|
||||
if (changed) {
|
||||
if (node.isGeneralization) {
|
||||
// 概要节点
|
||||
@@ -958,7 +964,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 移动节点到画布中心
|
||||
|
||||
moveNodeToCenter(node) {
|
||||
let halfWidth = this.mindMap.width / 2
|
||||
let halfHeight = this.mindMap.height / 2
|
||||
|
||||
@@ -4,6 +4,7 @@ import './css/quill.css'
|
||||
import html2canvas from 'html2canvas'
|
||||
import { Image as SvgImage } from '@svgdotjs/svg.js'
|
||||
import { walk } from './utils'
|
||||
import { CONSTANTS } from './utils/constant'
|
||||
|
||||
let extended = false
|
||||
|
||||
@@ -97,13 +98,16 @@ class RichText {
|
||||
this.mindMap.renderer.textEdit.registerTmpShortcut()
|
||||
if (!this.textEditNode) {
|
||||
this.textEditNode = document.createElement('div')
|
||||
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);outline: none; word-break: break-all;`
|
||||
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;box-shadow: 0 0 20px rgba(0,0,0,.5);outline: none; word-break: break-all;padding: 3px 5px;margin-left: -5px;margin-top: -3px;`
|
||||
document.body.appendChild(this.textEditNode)
|
||||
}
|
||||
// 原始宽高
|
||||
let g = node._textData.node
|
||||
let originWidth = g.attr('data-width')
|
||||
let originHeight = g.attr('data-height')
|
||||
// 使用节点的填充色,否则如果节点颜色是白色的话编辑时看不见
|
||||
let bgColor = node.style.merge('fillColor')
|
||||
this.textEditNode.style.backgroundColor = bgColor === 'transparent' ? '#fff' : bgColor
|
||||
this.textEditNode.style.minWidth = originWidth + 'px'
|
||||
this.textEditNode.style.minHeight = originHeight + 'px'
|
||||
this.textEditNode.style.left =
|
||||
@@ -126,7 +130,7 @@ class RichText {
|
||||
this.initQuillEditor()
|
||||
document.querySelector('.ql-editor').style.minHeight = originHeight + 'px'
|
||||
this.showTextEdit = true
|
||||
this.selectAll()
|
||||
this.focus()
|
||||
if (!node.nodeData.data.richText) {
|
||||
// 如果是非富文本的情况,需要手动应用文本样式
|
||||
this.setTextStyleIfNotRichText(node)
|
||||
@@ -144,7 +148,7 @@ class RichText {
|
||||
underline: node.style.merge('textDecoration') === 'underline',
|
||||
strike: node.style.merge('textDecoration') === 'line-through'
|
||||
}
|
||||
this.formatText(style)
|
||||
this.formatAllText(style)
|
||||
}
|
||||
|
||||
// 隐藏文本编辑控件,即完成编辑
|
||||
@@ -228,6 +232,12 @@ class RichText {
|
||||
this.quill.setSelection(0, this.quill.getLength())
|
||||
}
|
||||
|
||||
// 聚焦
|
||||
focus() {
|
||||
let len = this.quill.getLength()
|
||||
this.quill.setSelection(len, len)
|
||||
}
|
||||
|
||||
// 格式化当前选中的文本
|
||||
formatText(config = {}, clear = false) {
|
||||
if (!this.range && !this.lastRange) return
|
||||
@@ -407,7 +417,11 @@ class RichText {
|
||||
reject(error)
|
||||
}
|
||||
}
|
||||
if (len > 0) transform()
|
||||
if (len > 0) {
|
||||
transform()
|
||||
} else {
|
||||
resolve(null)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -422,6 +436,7 @@ class RichText {
|
||||
node.data.richText = false
|
||||
div.innerHTML = node.data.text
|
||||
node.data.text = div.textContent
|
||||
// delete node.data.uid
|
||||
}
|
||||
},
|
||||
null,
|
||||
@@ -429,7 +444,10 @@ class RichText {
|
||||
0,
|
||||
0
|
||||
)
|
||||
this.mindMap.reRender()
|
||||
// 清空历史数据,并且触发数据变化
|
||||
this.mindMap.command.clearHistory()
|
||||
this.mindMap.command.addHistory()
|
||||
this.mindMap.render(null, CONSTANTS.TRANSFORM_TO_NORMAL_NODE)
|
||||
}
|
||||
|
||||
// 插件被移除前做的事情
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { Rect, Polygon, Path } from '@svgdotjs/svg.js'
|
||||
import { CONSTANTS } from './utils/constant'
|
||||
|
||||
// 节点形状类
|
||||
export default class Shape {
|
||||
constructor(node) {
|
||||
@@ -13,37 +16,37 @@ export default class Shape {
|
||||
const actHeight = height + paddingY * 2
|
||||
const actOffset = Math.abs(actWidth - actHeight)
|
||||
switch (shape) {
|
||||
case 'roundedRectangle':
|
||||
case CONSTANTS.SHAPE.ROUNDED_RECTANGLE:
|
||||
return {
|
||||
paddingX: height > width ? (height - width) / 2 : 0,
|
||||
paddingY: 0
|
||||
}
|
||||
case 'diamond':
|
||||
case CONSTANTS.SHAPE.DIAMOND:
|
||||
return {
|
||||
paddingX: width / 2,
|
||||
paddingY: height / 2
|
||||
}
|
||||
case 'parallelogram':
|
||||
case CONSTANTS.SHAPE.PARALLELOGRAM:
|
||||
return {
|
||||
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
|
||||
paddingY: 0
|
||||
}
|
||||
case 'outerTriangularRectangle':
|
||||
case CONSTANTS.SHAPE.OUTER_TRIANGULAR_RECTANGLE:
|
||||
return {
|
||||
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
|
||||
paddingY: 0
|
||||
}
|
||||
case 'innerTriangularRectangle':
|
||||
case CONSTANTS.SHAPE.INNER_TRIANGULAR_RECTANGLE:
|
||||
return {
|
||||
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
|
||||
paddingY: 0
|
||||
}
|
||||
case 'ellipse':
|
||||
case CONSTANTS.SHAPE.ELLIPSE:
|
||||
return {
|
||||
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
|
||||
paddingY: paddingY <= 0 ? defaultPaddingY : 0
|
||||
}
|
||||
case 'circle':
|
||||
case CONSTANTS.SHAPE.CIRCLE:
|
||||
return {
|
||||
paddingX: actHeight > actWidth ? actOffset / 2 : 0,
|
||||
paddingY: actHeight < actWidth ? actOffset / 2 : 0
|
||||
@@ -62,30 +65,30 @@ export default class Shape {
|
||||
let { width, height } = this.node
|
||||
let node = null
|
||||
// 矩形
|
||||
if (shape === 'rectangle') {
|
||||
node = this.node.group.rect(width, height)
|
||||
} else if (shape === 'diamond') {
|
||||
if (shape === CONSTANTS.SHAPE.RECTANGLE) {
|
||||
node = new Rect().size(width, height)
|
||||
} else if (shape === CONSTANTS.SHAPE.DIAMOND) {
|
||||
// 菱形
|
||||
node = this.createDiamond()
|
||||
} else if (shape === 'parallelogram') {
|
||||
} else if (shape === CONSTANTS.SHAPE.PARALLELOGRAM) {
|
||||
// 平行四边形
|
||||
node = this.createParallelogram()
|
||||
} else if (shape === 'roundedRectangle') {
|
||||
} else if (shape === CONSTANTS.SHAPE.ROUNDED_RECTANGLE) {
|
||||
// 圆角矩形
|
||||
node = this.createRoundedRectangle()
|
||||
} else if (shape === 'octagonalRectangle') {
|
||||
} else if (shape === CONSTANTS.SHAPE.OCTAGONAL_RECTANGLE) {
|
||||
// 八角矩形
|
||||
node = this.createOctagonalRectangle()
|
||||
} else if (shape === 'outerTriangularRectangle') {
|
||||
} else if (shape === CONSTANTS.SHAPE.OUTER_TRIANGULAR_RECTANGLE) {
|
||||
// 外三角矩形
|
||||
node = this.createOuterTriangularRectangle()
|
||||
} else if (shape === 'innerTriangularRectangle') {
|
||||
} else if (shape === CONSTANTS.SHAPE.INNER_TRIANGULAR_RECTANGLE) {
|
||||
// 内三角矩形
|
||||
node = this.createInnerTriangularRectangle()
|
||||
} else if (shape === 'ellipse') {
|
||||
} else if (shape === CONSTANTS.SHAPE.ELLIPSE) {
|
||||
// 椭圆
|
||||
node = this.createEllipse()
|
||||
} else if (shape === 'circle') {
|
||||
} else if (shape === CONSTANTS.SHAPE.CIRCLE) {
|
||||
// 圆
|
||||
node = this.createCircle()
|
||||
}
|
||||
@@ -105,12 +108,12 @@ export default class Shape {
|
||||
let bottomY = height
|
||||
let leftX = 0
|
||||
let leftY = halfHeight
|
||||
return this.node.group.polygon(`
|
||||
${topX}, ${topY}
|
||||
${rightX}, ${rightY}
|
||||
${bottomX}, ${bottomY}
|
||||
${leftX}, ${leftY}
|
||||
`)
|
||||
return new Polygon().plot([
|
||||
[topX, topY],
|
||||
[rightX, rightY],
|
||||
[bottomX, bottomY],
|
||||
[leftX, leftY]
|
||||
])
|
||||
}
|
||||
|
||||
// 创建平行四边形
|
||||
@@ -118,41 +121,41 @@ export default class Shape {
|
||||
let { paddingX } = this.node.getPaddingVale()
|
||||
paddingX = paddingX || this.node.shapePadding.paddingX
|
||||
let { width, height } = this.node
|
||||
return this.node.group.polygon(`
|
||||
${paddingX}, ${0}
|
||||
${width}, ${0}
|
||||
${width - paddingX}, ${height}
|
||||
${0}, ${height}
|
||||
`)
|
||||
return new Polygon().plot([
|
||||
[paddingX, 0],
|
||||
[width, 0],
|
||||
[width - paddingX, height],
|
||||
[0, height]
|
||||
])
|
||||
}
|
||||
|
||||
// 创建圆角矩形
|
||||
createRoundedRectangle() {
|
||||
let { width, height } = this.node
|
||||
let halfHeight = height / 2
|
||||
return this.node.group.path(`
|
||||
M${halfHeight},0
|
||||
L${width - halfHeight},0
|
||||
A${height / 2},${height / 2} 0 0,1 ${width - halfHeight},${height}
|
||||
L${halfHeight},${height}
|
||||
A${height / 2},${height / 2} 0 0,1 ${halfHeight},${0}
|
||||
`)
|
||||
return new Path().plot(`
|
||||
M${halfHeight},0
|
||||
L${width - halfHeight},0
|
||||
A${height / 2},${height / 2} 0 0,1 ${width - halfHeight},${height}
|
||||
L${halfHeight},${height}
|
||||
A${height / 2},${height / 2} 0 0,1 ${halfHeight},${0}
|
||||
`)
|
||||
}
|
||||
|
||||
// 创建八角矩形
|
||||
createOctagonalRectangle() {
|
||||
let w = 5
|
||||
let { width, height } = this.node
|
||||
return this.node.group.polygon(`
|
||||
${0}, ${w}
|
||||
${w}, ${0}
|
||||
${width - w}, ${0}
|
||||
${width}, ${w}
|
||||
${width}, ${height - w}
|
||||
${width - w}, ${height}
|
||||
${w}, ${height}
|
||||
${0}, ${height - w}
|
||||
`)
|
||||
return new Polygon().plot([
|
||||
[0, w],
|
||||
[w, 0],
|
||||
[width - w, 0],
|
||||
[width, w],
|
||||
[width, height - w],
|
||||
[width - w, height],
|
||||
[w, height],
|
||||
[0, height - w]
|
||||
])
|
||||
}
|
||||
|
||||
// 创建外三角矩形
|
||||
@@ -160,14 +163,14 @@ export default class Shape {
|
||||
let { paddingX } = this.node.getPaddingVale()
|
||||
paddingX = paddingX || this.node.shapePadding.paddingX
|
||||
let { width, height } = this.node
|
||||
return this.node.group.polygon(`
|
||||
${paddingX}, ${0}
|
||||
${width - paddingX}, ${0}
|
||||
${width}, ${height / 2}
|
||||
${width - paddingX}, ${height}
|
||||
${paddingX}, ${height}
|
||||
${0}, ${height / 2}
|
||||
`)
|
||||
return new Polygon().plot([
|
||||
[paddingX, 0],
|
||||
[width - paddingX, 0],
|
||||
[width, height / 2],
|
||||
[width - paddingX, height],
|
||||
[paddingX, height],
|
||||
[0, height / 2]
|
||||
])
|
||||
}
|
||||
|
||||
// 创建内三角矩形
|
||||
@@ -175,14 +178,14 @@ export default class Shape {
|
||||
let { paddingX } = this.node.getPaddingVale()
|
||||
paddingX = paddingX || this.node.shapePadding.paddingX
|
||||
let { width, height } = this.node
|
||||
return this.node.group.polygon(`
|
||||
${0}, ${0}
|
||||
${width}, ${0}
|
||||
${width - paddingX / 2}, ${height / 2}
|
||||
${width}, ${height}
|
||||
${0}, ${height}
|
||||
${paddingX / 2}, ${height / 2}
|
||||
`)
|
||||
return new Polygon().plot([
|
||||
[0, 0],
|
||||
[width, 0],
|
||||
[width - paddingX / 2, height / 2],
|
||||
[width, height],
|
||||
[0, height],
|
||||
[paddingX / 2, height / 2]
|
||||
])
|
||||
}
|
||||
|
||||
// 创建椭圆
|
||||
@@ -190,12 +193,12 @@ export default class Shape {
|
||||
let { width, height } = this.node
|
||||
let halfWidth = width / 2
|
||||
let halfHeight = height / 2
|
||||
return this.node.group.path(`
|
||||
M${halfWidth},0
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height}
|
||||
M${halfWidth},${height}
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0}
|
||||
`)
|
||||
return new Path().plot(`
|
||||
M${halfWidth},0
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height}
|
||||
M${halfWidth},${height}
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0}
|
||||
`)
|
||||
}
|
||||
|
||||
// 创建圆
|
||||
@@ -203,24 +206,24 @@ export default class Shape {
|
||||
let { width, height } = this.node
|
||||
let halfWidth = width / 2
|
||||
let halfHeight = height / 2
|
||||
return this.node.group.path(`
|
||||
M${halfWidth},0
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height}
|
||||
M${halfWidth},${height}
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0}
|
||||
`)
|
||||
return new Path().plot(`
|
||||
M${halfWidth},0
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height}
|
||||
M${halfWidth},${height}
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0}
|
||||
`)
|
||||
}
|
||||
}
|
||||
|
||||
// 形状列表
|
||||
export const shapeList = [
|
||||
'rectangle',
|
||||
'diamond',
|
||||
'parallelogram',
|
||||
'roundedRectangle',
|
||||
'octagonalRectangle',
|
||||
'outerTriangularRectangle',
|
||||
'innerTriangularRectangle',
|
||||
'ellipse',
|
||||
'circle'
|
||||
CONSTANTS.SHAPE.RECTANGLE,
|
||||
CONSTANTS.SHAPE.DIAMOND,
|
||||
CONSTANTS.SHAPE.PARALLELOGRAM,
|
||||
CONSTANTS.SHAPE.ROUNDED_RECTANGLE,
|
||||
CONSTANTS.SHAPE.OCTAGONAL_RECTANGLE,
|
||||
CONSTANTS.SHAPE.OUTER_TRIANGULAR_RECTANGLE,
|
||||
CONSTANTS.SHAPE.INNER_TRIANGULAR_RECTANGLE,
|
||||
CONSTANTS.SHAPE.ELLIPSE,
|
||||
CONSTANTS.SHAPE.CIRCLE
|
||||
]
|
||||
|
||||
@@ -2,10 +2,8 @@ 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
|
||||
@@ -20,35 +18,27 @@ class Style {
|
||||
}
|
||||
|
||||
// 构造函数
|
||||
|
||||
constructor(ctx, themeConfig) {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.themeConfig = themeConfig
|
||||
}
|
||||
|
||||
// 更新主题配置
|
||||
|
||||
updateThemeConfig(themeConfig) {
|
||||
this.themeConfig = themeConfig
|
||||
}
|
||||
|
||||
// 合并样式
|
||||
|
||||
merge(prop, root, isActive) {
|
||||
let themeConfig = this.ctx.mindMap.themeConfig
|
||||
// 三级及以下节点
|
||||
let defaultConfig = this.themeConfig.node
|
||||
let defaultConfig = themeConfig.node
|
||||
if (root || rootProp.includes(prop)) {
|
||||
// 直接使用最外层样式
|
||||
defaultConfig = this.themeConfig
|
||||
defaultConfig = themeConfig
|
||||
} else if (this.ctx.isGeneralization) {
|
||||
// 概要节点
|
||||
defaultConfig = this.themeConfig.generalization
|
||||
defaultConfig = themeConfig.generalization
|
||||
} else if (this.ctx.layerIndex === 0) {
|
||||
// 根节点
|
||||
defaultConfig = this.themeConfig.root
|
||||
defaultConfig = themeConfig.root
|
||||
} else if (this.ctx.layerIndex === 1) {
|
||||
// 二级节点
|
||||
defaultConfig = this.themeConfig.second
|
||||
defaultConfig = themeConfig.second
|
||||
}
|
||||
// 激活状态
|
||||
if (isActive !== undefined ? isActive : this.ctx.nodeData.data.isActive) {
|
||||
@@ -68,39 +58,35 @@ class Style {
|
||||
}
|
||||
|
||||
// 获取某个样式值
|
||||
|
||||
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
|
||||
}
|
||||
// if (
|
||||
// !this.ctx.isRoot &&
|
||||
// !this.ctx.isGeneralization &&
|
||||
// this.ctx.mindMap.themeConfig.nodeUseLineStyle &&
|
||||
// !this.ctx.nodeData.data.isActive
|
||||
// ) {
|
||||
// return
|
||||
// }
|
||||
node.stroke({
|
||||
color: this.merge('borderColor'),
|
||||
width: this.merge('borderWidth'),
|
||||
@@ -109,7 +95,6 @@ class Style {
|
||||
}
|
||||
|
||||
// 文字
|
||||
|
||||
text(node) {
|
||||
node
|
||||
.fill({
|
||||
@@ -135,17 +120,15 @@ class Style {
|
||||
}
|
||||
|
||||
// html文字节点
|
||||
|
||||
domText(node, fontSizeScale = 1) {
|
||||
domText(node, fontSizeScale = 1, textLines) {
|
||||
node.style.fontFamily = this.merge('fontFamily')
|
||||
node.style.fontSize = this.merge('fontSize') * fontSizeScale + 'px'
|
||||
node.style.fontWeight = this.merge('fontWeight') || 'normal'
|
||||
node.style.lineHeight = this.merge('lineHeight')
|
||||
node.style.lineHeight = textLines === 1 ? 'normal' : this.merge('lineHeight')
|
||||
node.style.fontStyle = this.merge('fontStyle')
|
||||
}
|
||||
|
||||
// 标签文字
|
||||
|
||||
tagText(node, index) {
|
||||
node
|
||||
.fill({
|
||||
@@ -157,7 +140,6 @@ class Style {
|
||||
}
|
||||
|
||||
// 标签矩形
|
||||
|
||||
tagRect(node, index) {
|
||||
node.fill({
|
||||
color: tagColorList[index].background
|
||||
@@ -165,7 +147,6 @@ class Style {
|
||||
}
|
||||
|
||||
// 内置图标
|
||||
|
||||
iconNode(node) {
|
||||
node.attr({
|
||||
fill: this.merge('color')
|
||||
@@ -173,13 +154,11 @@ class Style {
|
||||
}
|
||||
|
||||
// 连线
|
||||
|
||||
line(node, { width, color, dasharray } = {}) {
|
||||
node.stroke({ width, color, dasharray }).fill({ color: 'none' })
|
||||
}
|
||||
|
||||
// 概要连线
|
||||
|
||||
generalizationLine(node) {
|
||||
node
|
||||
.stroke({
|
||||
@@ -189,11 +168,15 @@ class Style {
|
||||
.fill({ color: 'none' })
|
||||
}
|
||||
|
||||
// 按钮
|
||||
|
||||
iconBtn(node, fillNode) {
|
||||
node.fill({ color: '#808080' })
|
||||
fillNode.fill({ color: '#fff' })
|
||||
// 展开收起按钮
|
||||
iconBtn(node, node2, fillNode) {
|
||||
let { color, fill } = this.ctx.mindMap.opt.expandBtnStyle || {
|
||||
color: '#808080',
|
||||
fill: '#fff'
|
||||
}
|
||||
node.fill({ color: color })
|
||||
node2.fill({ color: color })
|
||||
fillNode.fill({ color: fill })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,10 @@ export default class TextEdit {
|
||||
// 隐藏文本编辑框
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
this.mindMap.on('svg_mousedown', () => {
|
||||
// 隐藏文本编辑框
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
// 展开收缩按钮点击事件
|
||||
this.mindMap.on('expand_btn_click', () => {
|
||||
this.hideEditTextBox()
|
||||
@@ -74,17 +78,18 @@ export default class TextEdit {
|
||||
let scale = this.mindMap.view.scale
|
||||
let lineHeight = node.style.merge('lineHeight')
|
||||
let fontSize = node.style.merge('fontSize')
|
||||
node.style.domText(this.textEditNode, scale)
|
||||
this.textEditNode.innerHTML = node.nodeData.data.text
|
||||
.split(/\n/gim)
|
||||
.join('<br>')
|
||||
let textLines = node.nodeData.data.text.split(/\n/gim)
|
||||
node.style.domText(this.textEditNode, scale, textLines.length)
|
||||
this.textEditNode.innerHTML = textLines.join('<br>')
|
||||
this.textEditNode.style.minWidth = rect.width + 10 + 'px'
|
||||
this.textEditNode.style.minHeight = rect.height + 6 + 'px'
|
||||
this.textEditNode.style.left = rect.left + 'px'
|
||||
this.textEditNode.style.top = rect.top + 'px'
|
||||
this.textEditNode.style.display = 'block'
|
||||
this.textEditNode.style.maxWidth = this.mindMap.opt.textAutoWrapWidth * scale + 'px'
|
||||
this.textEditNode.style.transform = `translateY(${-(lineHeight * fontSize - fontSize) / 2 * scale}px)`
|
||||
if (textLines.length > 1 && lineHeight !== 1) {
|
||||
this.textEditNode.style.transform = `translateY(${-((lineHeight * fontSize - fontSize) / 2 - 2) * scale}px)`
|
||||
}
|
||||
this.showTextEdit = true
|
||||
// 选中文本
|
||||
this.selectNodeText()
|
||||
@@ -126,6 +131,7 @@ export default class TextEdit {
|
||||
this.textEditNode.style.fontFamily = 'inherit'
|
||||
this.textEditNode.style.fontSize = 'inherit'
|
||||
this.textEditNode.style.fontWeight = 'normal'
|
||||
this.textEditNode.style.transform = 'translateY(0)'
|
||||
this.showTextEdit = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { CONSTANTS } from './utils/constant'
|
||||
|
||||
// 视图操作类
|
||||
class View {
|
||||
// 构造函数
|
||||
@@ -58,35 +60,35 @@ class View {
|
||||
if (this.mindMap.opt.customHandleMousewheel && typeof this.mindMap.opt.customHandleMousewheel === 'function') {
|
||||
return this.mindMap.opt.customHandleMousewheel(e)
|
||||
}
|
||||
if (this.mindMap.opt.mousewheelAction === 'zoom') {
|
||||
if (this.mindMap.opt.mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM) {
|
||||
switch (dir) {
|
||||
// 鼠标滚轮,向上和向左,都是缩小
|
||||
case 'up':
|
||||
case 'left':
|
||||
case CONSTANTS.DIR.UP:
|
||||
case CONSTANTS.DIR.LEFT:
|
||||
this.narrow()
|
||||
break
|
||||
// 鼠标滚轮,向下和向右,都是放大
|
||||
case 'down':
|
||||
case 'right':
|
||||
case CONSTANTS.DIR.DOWN:
|
||||
case CONSTANTS.DIR.RIGHT:
|
||||
this.enlarge()
|
||||
break
|
||||
}
|
||||
} else {
|
||||
switch (dir){
|
||||
// 上移
|
||||
case 'down':
|
||||
case CONSTANTS.DIR.DOWN:
|
||||
this.translateY(-this.mindMap.opt.mousewheelMoveStep)
|
||||
break
|
||||
// 下移
|
||||
case 'up':
|
||||
case CONSTANTS.DIR.UP:
|
||||
this.translateY(this.mindMap.opt.mousewheelMoveStep)
|
||||
break
|
||||
// 右移
|
||||
case 'left':
|
||||
case CONSTANTS.DIR.LEFT:
|
||||
this.translateX(-this.mindMap.opt.mousewheelMoveStep)
|
||||
break
|
||||
// 左移
|
||||
case 'right':
|
||||
case CONSTANTS.DIR.RIGHT:
|
||||
this.translateX(this.mindMap.opt.mousewheelMoveStep)
|
||||
break
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
height: auto;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.ql-container {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Node from '../Node'
|
||||
import { CONSTANTS } from '../utils/constant'
|
||||
|
||||
// 布局基类
|
||||
class Base {
|
||||
@@ -12,6 +13,8 @@ class Base {
|
||||
this.draw = this.mindMap.draw
|
||||
// 根节点
|
||||
this.root = null
|
||||
// 保存所有uid和节点,用于复用
|
||||
this.nodePool = {}
|
||||
}
|
||||
|
||||
// 计算节点位置
|
||||
@@ -32,26 +35,75 @@ class Base {
|
||||
// 概要节点
|
||||
renderGeneralization() {}
|
||||
|
||||
// 通过uid缓存节点
|
||||
cacheNode(uid, node) {
|
||||
// 记录本次渲染时的节点
|
||||
this.renderer.nodeCache[uid] = node
|
||||
// 记录所有渲染时的节点
|
||||
this.nodePool[uid] = node
|
||||
// 如果总缓存数量达到1000,直接清空
|
||||
if (Object.keys(this.nodePool).length > 1000) {
|
||||
this.clearNodePool()
|
||||
}
|
||||
}
|
||||
|
||||
// 清空节点存储池
|
||||
clearNodePool() {
|
||||
this.nodePool = {}
|
||||
}
|
||||
|
||||
// 检查当前来源是否需要重新计算节点大小
|
||||
checkIsNeedResizeSources() {
|
||||
return [CONSTANTS.CHANGE_THEME, CONSTANTS.TRANSFORM_TO_NORMAL_NODE].includes(this.renderer.renderSource)
|
||||
}
|
||||
|
||||
// 创建节点实例
|
||||
createNode(data, parent, isRoot, layerIndex) {
|
||||
// 创建节点
|
||||
let newNode = null
|
||||
// 复用节点
|
||||
// 数据上保存了节点引用,那么直接复用节点
|
||||
if (data && data._node && !this.renderer.reRender) {
|
||||
newNode = data._node
|
||||
newNode.reset()
|
||||
newNode.layerIndex = layerIndex
|
||||
this.cacheNode(data._node.uid, newNode)
|
||||
// 主题或主题配置改变了需要重新计算节点大小和布局
|
||||
if (this.checkIsNeedResizeSources()) {
|
||||
newNode.getSize()
|
||||
newNode.needLayout = true
|
||||
}
|
||||
} else if (this.nodePool[data.data.uid] && !this.renderer.reRender) {
|
||||
// 数据上没有保存节点引用,但是通过uid找到了缓存的节点,也可以复用
|
||||
newNode = this.nodePool[data.data.uid]
|
||||
// 保存该节点上一次的数据
|
||||
let lastData = JSON.stringify(newNode.nodeData.data)
|
||||
newNode.reset()
|
||||
newNode.nodeData = newNode.handleData(data || {})
|
||||
newNode.layerIndex = layerIndex
|
||||
this.cacheNode(data.data.uid, newNode)
|
||||
data._node = newNode
|
||||
// 主题或主题配置改变了需要重新计算节点大小和布局
|
||||
let isResizeSource = this.checkIsNeedResizeSources()
|
||||
// 节点数据改变了需要重新计算节点大小和布局
|
||||
let isNodeDataChange = lastData !== JSON.stringify(data.data)
|
||||
if (isResizeSource || isNodeDataChange) {
|
||||
newNode.getSize()
|
||||
newNode.needLayout = true
|
||||
}
|
||||
} else {
|
||||
// 创建新节点
|
||||
let uid = this.mindMap.uid++
|
||||
newNode = new Node({
|
||||
data,
|
||||
uid: this.mindMap.uid++,
|
||||
uid,
|
||||
renderer: this.renderer,
|
||||
mindMap: this.mindMap,
|
||||
draw: this.draw,
|
||||
layerIndex
|
||||
})
|
||||
newNode.getSize()
|
||||
// uid保存到数据上,为了节点复用
|
||||
data.data.uid = uid
|
||||
this.cacheNode(uid, newNode)
|
||||
// 数据关联实际节点
|
||||
data._node = newNode
|
||||
if (data.data.isActive) {
|
||||
|
||||
@@ -1,386 +1,386 @@
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun } from '../utils'
|
||||
|
||||
// 目录组织图
|
||||
|
||||
class CatalogOrganization extends Base {
|
||||
// 构造函数
|
||||
|
||||
constructor(opt = {}) {
|
||||
super(opt)
|
||||
}
|
||||
|
||||
// 布局
|
||||
|
||||
doLayout(callback) {
|
||||
let task = [
|
||||
() => {
|
||||
this.computedBaseValue()
|
||||
},
|
||||
() => {
|
||||
this.computedLeftTopValue()
|
||||
},
|
||||
() => {
|
||||
this.adjustLeftTopValue()
|
||||
},
|
||||
() => {
|
||||
callback(this.root)
|
||||
}
|
||||
]
|
||||
asyncRun(task)
|
||||
}
|
||||
|
||||
// 遍历数据计算节点的left、width、height
|
||||
|
||||
computedBaseValue() {
|
||||
walk(
|
||||
this.renderer.renderTree,
|
||||
null,
|
||||
(cur, parent, isRoot, layerIndex) => {
|
||||
let newNode = this.createNode(cur, parent, isRoot, layerIndex)
|
||||
// 根节点定位在画布中心位置
|
||||
if (isRoot) {
|
||||
this.setNodeCenter(newNode)
|
||||
} else {
|
||||
// 非根节点
|
||||
if (parent._node.isRoot) {
|
||||
newNode.top =
|
||||
parent._node.top +
|
||||
parent._node.height +
|
||||
this.getMarginX(layerIndex)
|
||||
}
|
||||
}
|
||||
if (!cur.data.expand) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
(cur, parent, isRoot, layerIndex) => {
|
||||
if (isRoot) {
|
||||
let len = cur.data.expand === false ? 0 : cur._node.children.length
|
||||
cur._node.childrenAreaWidth = len
|
||||
? cur._node.children.reduce((h, item) => {
|
||||
return h + item.width
|
||||
}, 0) +
|
||||
(len + 1) * this.getMarginX(layerIndex + 1)
|
||||
: 0
|
||||
}
|
||||
},
|
||||
true,
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
// 遍历节点树计算节点的left、top
|
||||
|
||||
computedLeftTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (
|
||||
node.nodeData.data.expand &&
|
||||
node.children &&
|
||||
node.children.length
|
||||
) {
|
||||
let marginX = this.getMarginX(layerIndex + 1)
|
||||
let marginY = this.getMarginY(layerIndex + 1)
|
||||
if (isRoot) {
|
||||
let left = node.left + node.width / 2 - node.childrenAreaWidth / 2
|
||||
let totalLeft = left + marginX
|
||||
node.children.forEach(cur => {
|
||||
cur.left = totalLeft
|
||||
totalLeft += cur.width + marginX
|
||||
})
|
||||
} else {
|
||||
let totalTop = node.top + node.height + marginY + node.expandBtnSize
|
||||
node.children.forEach(cur => {
|
||||
cur.left = node.left + node.width * 0.5
|
||||
cur.top = totalTop
|
||||
totalTop += cur.height + marginY + node.expandBtnSize
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 调整节点left、top
|
||||
|
||||
adjustLeftTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (!node.nodeData.data.expand) {
|
||||
return
|
||||
}
|
||||
// 调整left
|
||||
if (parent && parent.isRoot) {
|
||||
let areaWidth = this.getNodeAreaWidth(node)
|
||||
let difference = areaWidth - node.width
|
||||
if (difference > 0) {
|
||||
this.updateBrothersLeft(node, difference / 2)
|
||||
}
|
||||
}
|
||||
// 调整top
|
||||
let len = node.children.length
|
||||
if (parent && !parent.isRoot && len > 0) {
|
||||
let marginY = this.getMarginY(layerIndex + 1)
|
||||
let totalHeight =
|
||||
node.children.reduce((h, item) => {
|
||||
return h + item.height
|
||||
}, 0) +
|
||||
(len + 1) * marginY +
|
||||
len * node.expandBtnSize
|
||||
this.updateBrothersTop(node, totalHeight)
|
||||
}
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 递归计算节点的宽度
|
||||
|
||||
getNodeAreaWidth(node) {
|
||||
let widthArr = []
|
||||
let loop = (node, width) => {
|
||||
if (node.children.length) {
|
||||
width += node.width / 2
|
||||
node.children.forEach(item => {
|
||||
loop(item, width)
|
||||
})
|
||||
} else {
|
||||
width += node.width
|
||||
widthArr.push(width)
|
||||
}
|
||||
}
|
||||
loop(node, 0)
|
||||
return Math.max(...widthArr)
|
||||
}
|
||||
|
||||
// 调整兄弟节点的left
|
||||
|
||||
updateBrothersLeft(node, addWidth) {
|
||||
if (node.parent) {
|
||||
let childrenList = node.parent.children
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item === node
|
||||
})
|
||||
// 存在大于一个节点时,第一个或最后一个节点自身也需要移动,否则两边不对称
|
||||
if (
|
||||
(index === 0 || index === childrenList.length - 1) &&
|
||||
childrenList.length > 1
|
||||
) {
|
||||
let _offset = index === 0 ? -addWidth : addWidth
|
||||
node.left += _offset
|
||||
if (
|
||||
node.children &&
|
||||
node.children.length &&
|
||||
!node.hasCustomPosition()
|
||||
) {
|
||||
this.updateChildren(node.children, 'left', _offset)
|
||||
}
|
||||
}
|
||||
childrenList.forEach((item, _index) => {
|
||||
if (item.hasCustomPosition()) {
|
||||
// 适配自定义位置
|
||||
return
|
||||
}
|
||||
let _offset = 0
|
||||
if (_index < index) {
|
||||
// 左边的节点往左移
|
||||
_offset = -addWidth
|
||||
} else if (_index > index) {
|
||||
// 右边的节点往右移
|
||||
_offset = addWidth
|
||||
}
|
||||
item.left += _offset
|
||||
// 同步更新子节点的位置
|
||||
if (item.children && item.children.length) {
|
||||
this.updateChildren(item.children, 'left', _offset)
|
||||
}
|
||||
})
|
||||
// 更新父节点的位置
|
||||
this.updateBrothersLeft(node.parent, addWidth)
|
||||
}
|
||||
}
|
||||
|
||||
// 调整兄弟节点的top
|
||||
|
||||
updateBrothersTop(node, addHeight) {
|
||||
if (node.parent && !node.parent.isRoot) {
|
||||
let childrenList = node.parent.children
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item === node
|
||||
})
|
||||
childrenList.forEach((item, _index) => {
|
||||
if (item.hasCustomPosition()) {
|
||||
// 适配自定义位置
|
||||
return
|
||||
}
|
||||
let _offset = 0
|
||||
// 下面的节点往下移
|
||||
if (_index > index) {
|
||||
_offset = addHeight
|
||||
}
|
||||
item.top += _offset
|
||||
// 同步更新子节点的位置
|
||||
if (item.children && item.children.length) {
|
||||
this.updateChildren(item.children, 'top', _offset)
|
||||
}
|
||||
})
|
||||
// 更新父节点的位置
|
||||
this.updateBrothersTop(node.parent, addHeight)
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制连线,连接该节点到其子节点
|
||||
|
||||
renderLine(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return []
|
||||
}
|
||||
let { left, top, width, height, expandBtnSize } = node
|
||||
let len = node.children.length
|
||||
let marginX = this.getMarginX(node.layerIndex + 1)
|
||||
if (node.isRoot) {
|
||||
// 根节点
|
||||
let x1 = left + width / 2
|
||||
let y1 = top + height
|
||||
let s1 = marginX * 0.7
|
||||
let minx = Infinity
|
||||
let maxx = -Infinity
|
||||
node.children.forEach((item, index) => {
|
||||
let x2 = item.left + item.width / 2
|
||||
let y2 = item.top
|
||||
if (x2 < minx) {
|
||||
minx = x2
|
||||
}
|
||||
if (x2 > maxx) {
|
||||
maxx = x2
|
||||
}
|
||||
// 节点使用横线风格,需要额外渲染横线
|
||||
let nodeUseLineStylePath = this.mindMap.themeConfig.nodeUseLineStyle
|
||||
? ` L ${item.left},${y2} L ${item.left + item.width},${y2}`
|
||||
: ''
|
||||
let path =
|
||||
`M ${x2},${y1 + s1} L ${x2},${y1 + s1 > y2 ? y2 + item.height : y2}` +
|
||||
nodeUseLineStylePath
|
||||
// 竖线
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
})
|
||||
minx = Math.min(minx, x1)
|
||||
maxx = Math.max(maxx, x1)
|
||||
// 父节点的竖线
|
||||
let line1 = this.draw.path()
|
||||
node.style.line(line1)
|
||||
line1.plot(`M ${x1},${y1} L ${x1},${y1 + s1}`)
|
||||
node._lines.push(line1)
|
||||
style && style(line1, node)
|
||||
// 水平线
|
||||
if (len > 0) {
|
||||
let lin2 = this.draw.path()
|
||||
node.style.line(lin2)
|
||||
lin2.plot(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`)
|
||||
node._lines.push(lin2)
|
||||
style && style(lin2, node)
|
||||
}
|
||||
} else {
|
||||
// 非根节点
|
||||
let y1 = top + height
|
||||
let maxy = -Infinity
|
||||
let x2 = node.left + node.width * 0.3
|
||||
node.children.forEach((item, index) => {
|
||||
// 为了适配自定义位置,下面做了各种位置的兼容
|
||||
let y2 = item.top + item.height / 2
|
||||
if (y2 > maxy) {
|
||||
maxy = y2
|
||||
}
|
||||
// 水平线
|
||||
let path = ''
|
||||
let _left = item.left
|
||||
let _isLeft = item.left + item.width < x2
|
||||
let _isXCenter = false
|
||||
if (_isLeft) {
|
||||
// 水平位置在父节点左边
|
||||
_left = item.left + item.width
|
||||
} else if (item.left < x2 && item.left + item.width > x2) {
|
||||
// 水平位置在父节点之间
|
||||
_isXCenter = true
|
||||
y2 = item.top
|
||||
maxy = y2
|
||||
}
|
||||
if (y2 > top && y2 < y1) {
|
||||
// 自定义位置的情况:垂直位置节点在父节点之间
|
||||
path = `M ${
|
||||
_isLeft ? node.left : node.left + node.width
|
||||
},${y2} L ${_left},${y2}`
|
||||
} else if (y2 < y1) {
|
||||
// 自定义位置的情况:垂直位置节点在父节点上面
|
||||
if (_isXCenter) {
|
||||
y2 = item.top + item.height
|
||||
_left = x2
|
||||
}
|
||||
path = `M ${x2},${top} L ${x2},${y2} L ${_left},${y2}`
|
||||
} else {
|
||||
if (_isXCenter) {
|
||||
_left = x2
|
||||
}
|
||||
path = `M ${x2},${y2} L ${_left},${y2}`
|
||||
}
|
||||
// 节点使用横线风格,需要额外渲染横线
|
||||
let nodeUseLineStylePath = this.mindMap.themeConfig.nodeUseLineStyle
|
||||
? ` L ${_left},${y2 - item.height / 2} L ${_left},${
|
||||
y2 + item.height / 2
|
||||
}`
|
||||
: ''
|
||||
path += nodeUseLineStylePath
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
})
|
||||
// 竖线
|
||||
if (len > 0) {
|
||||
let lin2 = this.draw.path()
|
||||
expandBtnSize = len > 0 ? expandBtnSize : 0
|
||||
node.style.line(lin2)
|
||||
if (maxy < y1 + expandBtnSize) {
|
||||
lin2.hide()
|
||||
} else {
|
||||
lin2.plot(`M ${x2},${y1 + expandBtnSize} L ${x2},${maxy}`)
|
||||
lin2.show()
|
||||
}
|
||||
node._lines.push(lin2)
|
||||
style && style(lin2, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染按钮
|
||||
|
||||
renderExpandBtn(node, btn) {
|
||||
let { width, height, expandBtnSize, isRoot } = node
|
||||
if (!isRoot) {
|
||||
let { translateX, translateY } = btn.transform()
|
||||
btn.translate(
|
||||
width * 0.3 - expandBtnSize / 2 - translateX,
|
||||
height + expandBtnSize / 2 - translateY
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 创建概要节点
|
||||
|
||||
renderGeneralization(node, gLine, gNode) {
|
||||
let {
|
||||
top,
|
||||
bottom,
|
||||
right,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
} = this.getNodeBoundaries(node, 'h')
|
||||
let x1 = right + generalizationLineMargin
|
||||
let y1 = top
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun } from '../utils'
|
||||
|
||||
// 目录组织图
|
||||
class CatalogOrganization extends Base {
|
||||
// 构造函数
|
||||
constructor(opt = {}) {
|
||||
super(opt)
|
||||
}
|
||||
|
||||
// 布局
|
||||
doLayout(callback) {
|
||||
let task = [
|
||||
() => {
|
||||
this.computedBaseValue()
|
||||
},
|
||||
() => {
|
||||
this.computedLeftTopValue()
|
||||
},
|
||||
() => {
|
||||
this.adjustLeftTopValue()
|
||||
},
|
||||
() => {
|
||||
callback(this.root)
|
||||
}
|
||||
]
|
||||
asyncRun(task)
|
||||
}
|
||||
|
||||
// 遍历数据计算节点的left、width、height
|
||||
computedBaseValue() {
|
||||
walk(
|
||||
this.renderer.renderTree,
|
||||
null,
|
||||
(cur, parent, isRoot, layerIndex) => {
|
||||
let newNode = this.createNode(cur, parent, isRoot, layerIndex)
|
||||
// 根节点定位在画布中心位置
|
||||
if (isRoot) {
|
||||
this.setNodeCenter(newNode)
|
||||
} else {
|
||||
// 非根节点
|
||||
if (parent._node.isRoot) {
|
||||
newNode.top =
|
||||
parent._node.top +
|
||||
parent._node.height +
|
||||
this.getMarginX(layerIndex)
|
||||
}
|
||||
}
|
||||
if (!cur.data.expand) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
(cur, parent, isRoot, layerIndex) => {
|
||||
if (isRoot) {
|
||||
let len = cur.data.expand === false ? 0 : cur._node.children.length
|
||||
cur._node.childrenAreaWidth = len
|
||||
? cur._node.children.reduce((h, item) => {
|
||||
return h + item.width
|
||||
}, 0) +
|
||||
(len + 1) * this.getMarginX(layerIndex + 1)
|
||||
: 0
|
||||
}
|
||||
},
|
||||
true,
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
// 遍历节点树计算节点的left、top
|
||||
computedLeftTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (
|
||||
node.nodeData.data.expand &&
|
||||
node.children &&
|
||||
node.children.length
|
||||
) {
|
||||
let marginX = this.getMarginX(layerIndex + 1)
|
||||
let marginY = this.getMarginY(layerIndex + 1)
|
||||
if (isRoot) {
|
||||
let left = node.left + node.width / 2 - node.childrenAreaWidth / 2
|
||||
let totalLeft = left + marginX
|
||||
node.children.forEach(cur => {
|
||||
cur.left = totalLeft
|
||||
totalLeft += cur.width + marginX
|
||||
})
|
||||
} else {
|
||||
let totalTop = node.top + node.height + marginY + node.expandBtnSize
|
||||
node.children.forEach(cur => {
|
||||
cur.left = node.left + node.width * 0.5
|
||||
cur.top = totalTop
|
||||
totalTop += cur.height + marginY + node.expandBtnSize
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 调整节点left、top
|
||||
adjustLeftTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (!node.nodeData.data.expand) {
|
||||
return
|
||||
}
|
||||
// 调整left
|
||||
if (parent && parent.isRoot) {
|
||||
let areaWidth = this.getNodeAreaWidth(node)
|
||||
let difference = areaWidth - node.width
|
||||
if (difference > 0) {
|
||||
this.updateBrothersLeft(node, difference / 2)
|
||||
}
|
||||
}
|
||||
// 调整top
|
||||
let len = node.children.length
|
||||
if (parent && !parent.isRoot && len > 0) {
|
||||
let marginY = this.getMarginY(layerIndex + 1)
|
||||
let totalHeight =
|
||||
node.children.reduce((h, item) => {
|
||||
return h + item.height
|
||||
}, 0) +
|
||||
(len + 1) * marginY +
|
||||
len * node.expandBtnSize
|
||||
this.updateBrothersTop(node, totalHeight)
|
||||
}
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 递归计算节点的宽度
|
||||
getNodeAreaWidth(node) {
|
||||
let widthArr = []
|
||||
let loop = (node, width) => {
|
||||
if (node.children.length) {
|
||||
width += node.width / 2
|
||||
node.children.forEach(item => {
|
||||
loop(item, width)
|
||||
})
|
||||
} else {
|
||||
width += node.width
|
||||
widthArr.push(width)
|
||||
}
|
||||
}
|
||||
loop(node, 0)
|
||||
return Math.max(...widthArr)
|
||||
}
|
||||
|
||||
// 调整兄弟节点的left
|
||||
updateBrothersLeft(node, addWidth) {
|
||||
if (node.parent) {
|
||||
let childrenList = node.parent.children
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item === node
|
||||
})
|
||||
// 存在大于一个节点时,第一个或最后一个节点自身也需要移动,否则两边不对称
|
||||
if (
|
||||
(index === 0 || index === childrenList.length - 1) &&
|
||||
childrenList.length > 1
|
||||
) {
|
||||
let _offset = index === 0 ? -addWidth : addWidth
|
||||
node.left += _offset
|
||||
if (
|
||||
node.children &&
|
||||
node.children.length &&
|
||||
!node.hasCustomPosition()
|
||||
) {
|
||||
this.updateChildren(node.children, 'left', _offset)
|
||||
}
|
||||
}
|
||||
childrenList.forEach((item, _index) => {
|
||||
if (item.hasCustomPosition()) {
|
||||
// 适配自定义位置
|
||||
return
|
||||
}
|
||||
let _offset = 0
|
||||
if (_index < index) {
|
||||
// 左边的节点往左移
|
||||
_offset = -addWidth
|
||||
} else if (_index > index) {
|
||||
// 右边的节点往右移
|
||||
_offset = addWidth
|
||||
}
|
||||
item.left += _offset
|
||||
// 同步更新子节点的位置
|
||||
if (item.children && item.children.length) {
|
||||
this.updateChildren(item.children, 'left', _offset)
|
||||
}
|
||||
})
|
||||
// 更新父节点的位置
|
||||
this.updateBrothersLeft(node.parent, addWidth)
|
||||
}
|
||||
}
|
||||
|
||||
// 调整兄弟节点的top
|
||||
updateBrothersTop(node, addHeight) {
|
||||
if (node.parent && !node.parent.isRoot) {
|
||||
let childrenList = node.parent.children
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item === node
|
||||
})
|
||||
childrenList.forEach((item, _index) => {
|
||||
if (item.hasCustomPosition()) {
|
||||
// 适配自定义位置
|
||||
return
|
||||
}
|
||||
let _offset = 0
|
||||
// 下面的节点往下移
|
||||
if (_index > index) {
|
||||
_offset = addHeight
|
||||
}
|
||||
item.top += _offset
|
||||
// 同步更新子节点的位置
|
||||
if (item.children && item.children.length) {
|
||||
this.updateChildren(item.children, 'top', _offset)
|
||||
}
|
||||
})
|
||||
// 更新父节点的位置
|
||||
this.updateBrothersTop(node.parent, addHeight)
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制连线,连接该节点到其子节点
|
||||
renderLine(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return []
|
||||
}
|
||||
let { left, top, width, height, expandBtnSize } = node
|
||||
let len = node.children.length
|
||||
let marginX = this.getMarginX(node.layerIndex + 1)
|
||||
if (node.isRoot) {
|
||||
// 根节点
|
||||
let x1 = left + width / 2
|
||||
let y1 = top + height
|
||||
let s1 = marginX * 0.7
|
||||
let minx = Infinity
|
||||
let maxx = -Infinity
|
||||
node.children.forEach((item, index) => {
|
||||
let x2 = item.left + item.width / 2
|
||||
let y2 = item.top
|
||||
if (x2 < minx) {
|
||||
minx = x2
|
||||
}
|
||||
if (x2 > maxx) {
|
||||
maxx = x2
|
||||
}
|
||||
// 节点使用横线风格,需要额外渲染横线
|
||||
let nodeUseLineStylePath = this.mindMap.themeConfig.nodeUseLineStyle
|
||||
? ` L ${item.left},${y2} L ${item.left + item.width},${y2}`
|
||||
: ''
|
||||
let path =
|
||||
`M ${x2},${y1 + s1} L ${x2},${y1 + s1 > y2 ? y2 + item.height : y2}` +
|
||||
nodeUseLineStylePath
|
||||
// 竖线
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
})
|
||||
minx = Math.min(minx, x1)
|
||||
maxx = Math.max(maxx, x1)
|
||||
// 父节点的竖线
|
||||
let line1 = this.draw.path()
|
||||
node.style.line(line1)
|
||||
line1.plot(`M ${x1},${y1} L ${x1},${y1 + s1}`)
|
||||
node._lines.push(line1)
|
||||
style && style(line1, node)
|
||||
// 水平线
|
||||
if (len > 0) {
|
||||
let lin2 = this.draw.path()
|
||||
node.style.line(lin2)
|
||||
lin2.plot(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`)
|
||||
node._lines.push(lin2)
|
||||
style && style(lin2, node)
|
||||
}
|
||||
} else {
|
||||
// 非根节点
|
||||
let y1 = top + height
|
||||
let maxy = -Infinity
|
||||
let x2 = node.left + node.width * 0.3
|
||||
node.children.forEach((item, index) => {
|
||||
// 为了适配自定义位置,下面做了各种位置的兼容
|
||||
let y2 = item.top + item.height / 2
|
||||
if (y2 > maxy) {
|
||||
maxy = y2
|
||||
}
|
||||
// 水平线
|
||||
let path = ''
|
||||
let _left = item.left
|
||||
let _isLeft = item.left + item.width < x2
|
||||
let _isXCenter = false
|
||||
if (_isLeft) {
|
||||
// 水平位置在父节点左边
|
||||
_left = item.left + item.width
|
||||
} else if (item.left < x2 && item.left + item.width > x2) {
|
||||
// 水平位置在父节点之间
|
||||
_isXCenter = true
|
||||
y2 = item.top
|
||||
maxy = y2
|
||||
}
|
||||
if (y2 > top && y2 < y1) {
|
||||
// 自定义位置的情况:垂直位置节点在父节点之间
|
||||
path = `M ${
|
||||
_isLeft ? node.left : node.left + node.width
|
||||
},${y2} L ${_left},${y2}`
|
||||
} else if (y2 < y1) {
|
||||
// 自定义位置的情况:垂直位置节点在父节点上面
|
||||
if (_isXCenter) {
|
||||
y2 = item.top + item.height
|
||||
_left = x2
|
||||
}
|
||||
path = `M ${x2},${top} L ${x2},${y2} L ${_left},${y2}`
|
||||
} else {
|
||||
if (_isXCenter) {
|
||||
_left = x2
|
||||
}
|
||||
path = `M ${x2},${y2} L ${_left},${y2}`
|
||||
}
|
||||
// 节点使用横线风格,需要额外渲染横线
|
||||
let nodeUseLineStylePath = this.mindMap.themeConfig.nodeUseLineStyle
|
||||
? ` L ${_left},${y2 - item.height / 2} L ${_left},${
|
||||
y2 + item.height / 2
|
||||
}`
|
||||
: ''
|
||||
path += nodeUseLineStylePath
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
})
|
||||
// 竖线
|
||||
if (len > 0) {
|
||||
let lin2 = this.draw.path()
|
||||
expandBtnSize = len > 0 ? expandBtnSize : 0
|
||||
node.style.line(lin2)
|
||||
if (maxy < y1 + expandBtnSize) {
|
||||
lin2.hide()
|
||||
} else {
|
||||
lin2.plot(`M ${x2},${y1 + expandBtnSize} L ${x2},${maxy}`)
|
||||
lin2.show()
|
||||
}
|
||||
node._lines.push(lin2)
|
||||
style && style(lin2, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染按钮
|
||||
renderExpandBtn(node, btn) {
|
||||
let { width, height, expandBtnSize, isRoot } = node
|
||||
if (!isRoot) {
|
||||
let { translateX, translateY } = btn.transform()
|
||||
btn.translate(
|
||||
width * 0.3 - expandBtnSize / 2 - translateX,
|
||||
height + expandBtnSize / 2 - translateY
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 创建概要节点
|
||||
renderGeneralization(node, gLine, gNode) {
|
||||
let {
|
||||
top,
|
||||
bottom,
|
||||
right,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
} = this.getNodeBoundaries(node, 'h')
|
||||
let x1 = right + generalizationLineMargin
|
||||
let y1 = top
|
||||
let x2 = right + generalizationLineMargin
|
||||
let y2 = bottom
|
||||
let cx = x1 + 20
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
gLine.plot(path)
|
||||
gNode.left = right + generalizationNodeMargin
|
||||
gNode.top = top + (bottom - top - gNode.height) / 2
|
||||
}
|
||||
}
|
||||
|
||||
export default CatalogOrganization
|
||||
|
||||
@@ -2,16 +2,13 @@ import Base from './Base'
|
||||
import { walk, asyncRun } from '../utils'
|
||||
|
||||
// 逻辑结构图
|
||||
|
||||
class LogicalStructure extends Base {
|
||||
// 构造函数
|
||||
|
||||
constructor(opt = {}) {
|
||||
super(opt)
|
||||
}
|
||||
|
||||
// 布局
|
||||
|
||||
doLayout(callback) {
|
||||
let task = [
|
||||
() => {
|
||||
@@ -31,7 +28,6 @@ class LogicalStructure extends Base {
|
||||
}
|
||||
|
||||
// 遍历数据计算节点的left、width、height
|
||||
|
||||
computedBaseValue() {
|
||||
walk(
|
||||
this.renderer.renderTree,
|
||||
@@ -67,7 +63,6 @@ class LogicalStructure extends Base {
|
||||
}
|
||||
|
||||
// 遍历节点树计算节点的top
|
||||
|
||||
computedTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
@@ -94,7 +89,6 @@ class LogicalStructure extends Base {
|
||||
}
|
||||
|
||||
// 调整节点top
|
||||
|
||||
adjustTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
@@ -118,7 +112,6 @@ class LogicalStructure extends Base {
|
||||
}
|
||||
|
||||
// 更新兄弟节点的top
|
||||
|
||||
updateBrothers(node, addHeight) {
|
||||
if (node.parent) {
|
||||
let childrenList = node.parent.children
|
||||
@@ -150,7 +143,6 @@ class LogicalStructure extends Base {
|
||||
}
|
||||
|
||||
// 绘制连线,连接该节点到其子节点
|
||||
|
||||
renderLine(node, lines, style, lineStyle) {
|
||||
if (lineStyle === 'curve') {
|
||||
this.renderLineCurve(node, lines, style)
|
||||
@@ -162,7 +154,6 @@ class LogicalStructure extends Base {
|
||||
}
|
||||
|
||||
// 直线风格连线
|
||||
|
||||
renderLineStraight(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return []
|
||||
@@ -192,7 +183,6 @@ class LogicalStructure extends Base {
|
||||
}
|
||||
|
||||
// 直连风格
|
||||
|
||||
renderLineDirect(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return []
|
||||
@@ -218,7 +208,6 @@ class LogicalStructure extends Base {
|
||||
}
|
||||
|
||||
// 曲线风格连线
|
||||
|
||||
renderLineCurve(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return []
|
||||
@@ -249,7 +238,6 @@ class LogicalStructure extends Base {
|
||||
}
|
||||
|
||||
// 渲染按钮
|
||||
|
||||
renderExpandBtn(node, btn) {
|
||||
let { width, height } = node
|
||||
let { translateX, translateY } = btn.transform()
|
||||
@@ -264,7 +252,6 @@ class LogicalStructure extends Base {
|
||||
}
|
||||
|
||||
// 创建概要节点
|
||||
|
||||
renderGeneralization(node, gLine, gNode) {
|
||||
let {
|
||||
top,
|
||||
|
||||
@@ -2,7 +2,6 @@ import Base from './Base'
|
||||
import { walk, asyncRun } from '../utils'
|
||||
|
||||
// 思维导图
|
||||
|
||||
class MindMap extends Base {
|
||||
// 构造函数
|
||||
// 在逻辑结构图的基础上增加一个变量来记录生长方向,向左还是向右,同时在计算left的时候根据方向来计算、调整top时只考虑同方向的节点即可
|
||||
@@ -11,7 +10,6 @@ class MindMap extends Base {
|
||||
}
|
||||
|
||||
// 布局
|
||||
|
||||
doLayout(callback) {
|
||||
let task = [
|
||||
() => {
|
||||
@@ -31,7 +29,6 @@ class MindMap extends Base {
|
||||
}
|
||||
|
||||
// 遍历数据计算节点的left、width、height
|
||||
|
||||
computedBaseValue() {
|
||||
walk(
|
||||
this.renderer.renderTree,
|
||||
@@ -96,7 +93,6 @@ class MindMap extends Base {
|
||||
}
|
||||
|
||||
// 遍历节点树计算节点的top
|
||||
|
||||
computedTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
@@ -129,7 +125,6 @@ class MindMap extends Base {
|
||||
}
|
||||
|
||||
// 调整节点top
|
||||
|
||||
adjustTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
@@ -152,7 +147,6 @@ class MindMap extends Base {
|
||||
}
|
||||
|
||||
// 更新兄弟节点的top
|
||||
|
||||
updateBrothers(node, leftAddHeight, rightAddHeight) {
|
||||
if (node.parent) {
|
||||
// 过滤出和自己同方向的节点
|
||||
@@ -188,7 +182,6 @@ class MindMap extends Base {
|
||||
}
|
||||
|
||||
// 绘制连线,连接该节点到其子节点
|
||||
|
||||
renderLine(node, lines, style, lineStyle) {
|
||||
if (lineStyle === 'curve') {
|
||||
this.renderLineCurve(node, lines, style)
|
||||
@@ -200,7 +193,6 @@ class MindMap extends Base {
|
||||
}
|
||||
|
||||
// 直线风格连线
|
||||
|
||||
renderLineStraight(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return []
|
||||
@@ -238,7 +230,6 @@ class MindMap extends Base {
|
||||
}
|
||||
|
||||
// 直连风格
|
||||
|
||||
renderLineDirect(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return []
|
||||
@@ -273,7 +264,6 @@ class MindMap extends Base {
|
||||
}
|
||||
|
||||
// 曲线风格连线
|
||||
|
||||
renderLineCurve(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return []
|
||||
@@ -313,7 +303,6 @@ class MindMap extends Base {
|
||||
}
|
||||
|
||||
// 渲染按钮
|
||||
|
||||
renderExpandBtn(node, btn) {
|
||||
let { width, height, expandBtnSize } = node
|
||||
let { translateX, translateY } = btn.transform()
|
||||
@@ -327,7 +316,6 @@ class MindMap extends Base {
|
||||
}
|
||||
|
||||
// 创建概要节点
|
||||
|
||||
renderGeneralization(node, gLine, gNode) {
|
||||
let isLeft = node.dir === 'left'
|
||||
let {
|
||||
|
||||
@@ -5,13 +5,11 @@ import { walk, asyncRun } from '../utils'
|
||||
// 和逻辑结构图基本一样,只是方向变成向下生长,所以先计算节点的top,后计算节点的left、最后调整节点的left即可
|
||||
class OrganizationStructure extends Base {
|
||||
// 构造函数
|
||||
|
||||
constructor(opt = {}) {
|
||||
super(opt)
|
||||
}
|
||||
|
||||
// 布局
|
||||
|
||||
doLayout(callback) {
|
||||
let task = [
|
||||
() => {
|
||||
@@ -31,7 +29,6 @@ class OrganizationStructure extends Base {
|
||||
}
|
||||
|
||||
// 遍历数据计算节点的left、width、height
|
||||
|
||||
computedBaseValue() {
|
||||
walk(
|
||||
this.renderer.renderTree,
|
||||
@@ -67,7 +64,6 @@ class OrganizationStructure extends Base {
|
||||
}
|
||||
|
||||
// 遍历节点树计算节点的left
|
||||
|
||||
computedLeftValue() {
|
||||
walk(
|
||||
this.root,
|
||||
@@ -94,7 +90,6 @@ class OrganizationStructure extends Base {
|
||||
}
|
||||
|
||||
// 调整节点left
|
||||
|
||||
adjustLeftValue() {
|
||||
walk(
|
||||
this.root,
|
||||
@@ -118,7 +113,6 @@ class OrganizationStructure extends Base {
|
||||
}
|
||||
|
||||
// 更新兄弟节点的left
|
||||
|
||||
updateBrothers(node, addWidth) {
|
||||
if (node.parent) {
|
||||
let childrenList = node.parent.children
|
||||
@@ -150,7 +144,6 @@ class OrganizationStructure extends Base {
|
||||
}
|
||||
|
||||
// 绘制连线,连接该节点到其子节点
|
||||
|
||||
renderLine(node, lines, style, lineStyle) {
|
||||
if (lineStyle === 'direct') {
|
||||
this.renderLineDirect(node, lines, style)
|
||||
@@ -160,7 +153,6 @@ class OrganizationStructure extends Base {
|
||||
}
|
||||
|
||||
// 直连风格
|
||||
|
||||
renderLineDirect(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return []
|
||||
@@ -182,7 +174,6 @@ class OrganizationStructure extends Base {
|
||||
}
|
||||
|
||||
// 直线风格连线
|
||||
|
||||
renderLineStraight(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return []
|
||||
@@ -232,7 +223,6 @@ class OrganizationStructure extends Base {
|
||||
}
|
||||
|
||||
// 渲染按钮
|
||||
|
||||
renderExpandBtn(node, btn) {
|
||||
let { width, height, expandBtnSize } = node
|
||||
let { translateX, translateY } = btn.transform()
|
||||
@@ -243,7 +233,6 @@ class OrganizationStructure extends Base {
|
||||
}
|
||||
|
||||
// 创建概要节点
|
||||
|
||||
renderGeneralization(node, gLine, gNode) {
|
||||
let {
|
||||
bottom,
|
||||
|
||||
7
simple-mind-map/src/parse/markdown.js
Normal file
7
simple-mind-map/src/parse/markdown.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { transformToMarkdown } from './toMarkdown'
|
||||
import { transformMarkdownTo } from './markdownTo'
|
||||
|
||||
export default {
|
||||
transformToMarkdown,
|
||||
transformMarkdownTo
|
||||
}
|
||||
98
simple-mind-map/src/parse/markdownTo.js
Normal file
98
simple-mind-map/src/parse/markdownTo.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import { fromMarkdown } from 'mdast-util-from-markdown'
|
||||
|
||||
// 处理list的情况
|
||||
const handleList = node => {
|
||||
let list = []
|
||||
let walk = (arr, newArr) => {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
let cur = arr[i]
|
||||
let node = {}
|
||||
node.data = {
|
||||
// 节点内容
|
||||
text: cur.children[0].children[0].value
|
||||
}
|
||||
node.children = []
|
||||
newArr.push(node)
|
||||
if (cur.children.length > 1) {
|
||||
for (let j = 1; j < cur.children.length; j++) {
|
||||
let cur2 = cur.children[j]
|
||||
if (cur2.type === 'list') {
|
||||
walk(cur2.children, node.children)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(node.children, list)
|
||||
return list
|
||||
}
|
||||
|
||||
// 将markdown转换成节点树
|
||||
export const transformMarkdownTo = async md => {
|
||||
const tree = fromMarkdown(md)
|
||||
let root = {
|
||||
children: []
|
||||
}
|
||||
let childrenQueue = [root.children]
|
||||
let currentChildren = root.children
|
||||
let depthQueue = [-1]
|
||||
let currentDepth = -1
|
||||
for (let i = 0; i < tree.children.length; i++) {
|
||||
let cur = tree.children[i]
|
||||
if (cur.type === 'heading') {
|
||||
if (!cur.children[0]) continue
|
||||
// 创建新节点
|
||||
let node = {}
|
||||
node.data = {
|
||||
// 节点内容
|
||||
text: cur.children[0].value
|
||||
}
|
||||
node.children = []
|
||||
// 如果当前的层级大于上一个节点的层级,那么是其子节点
|
||||
if (cur.depth > currentDepth) {
|
||||
// 添加到上一个节点的子节点列表里
|
||||
currentChildren.push(node)
|
||||
// 更新当前栈和数据
|
||||
childrenQueue.push(node.children)
|
||||
currentChildren = node.children
|
||||
depthQueue.push(cur.depth)
|
||||
currentDepth = cur.depth
|
||||
} else if (cur.depth === currentDepth) {
|
||||
// 如果当前层级等于上一个节点的层级,说明它们是同级节点
|
||||
// 将上一个节点出栈
|
||||
childrenQueue.pop()
|
||||
currentChildren = childrenQueue[childrenQueue.length - 1]
|
||||
depthQueue.pop()
|
||||
currentDepth = depthQueue[depthQueue.length - 1]
|
||||
// 追加到上上个节点的子节点列表里
|
||||
currentChildren.push(node)
|
||||
// 更新当前栈和数据
|
||||
childrenQueue.push(node.children)
|
||||
currentChildren = node.children
|
||||
depthQueue.push(cur.depth)
|
||||
currentDepth = cur.depth
|
||||
} else {
|
||||
// 如果当前层级小于上一个节点的层级,那么一直出栈,直到遇到比当前层级小的节点
|
||||
while (depthQueue.length) {
|
||||
childrenQueue.pop()
|
||||
currentChildren = childrenQueue[childrenQueue.length - 1]
|
||||
depthQueue.pop()
|
||||
currentDepth = depthQueue[depthQueue.length - 1]
|
||||
if (currentDepth < cur.depth) {
|
||||
// 追加到该节点的子节点列表里
|
||||
currentChildren.push(node)
|
||||
// 更新当前栈和数据
|
||||
childrenQueue.push(node.children)
|
||||
currentChildren = node.children
|
||||
depthQueue.push(cur.depth)
|
||||
currentDepth = cur.depth
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (cur.type === 'list') {
|
||||
currentChildren.push(...handleList(cur))
|
||||
}
|
||||
}
|
||||
return root.children[0]
|
||||
}
|
||||
53
simple-mind-map/src/parse/toMarkdown.js
Normal file
53
simple-mind-map/src/parse/toMarkdown.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import { walk } from '../utils'
|
||||
|
||||
let el = null
|
||||
const getText = str => {
|
||||
if (!el) {
|
||||
el = document.createElement('div')
|
||||
}
|
||||
el.innerHTML = str
|
||||
return el.textContent
|
||||
}
|
||||
|
||||
const getTitleMark = level => {
|
||||
return new Array(level).fill('#').join('')
|
||||
}
|
||||
|
||||
const getIndentMark = level => {
|
||||
return new Array(level - 6).fill(' ').join('') + '*'
|
||||
}
|
||||
|
||||
// 转换成markdown格式
|
||||
export const transformToMarkdown = root => {
|
||||
let content = ''
|
||||
walk(
|
||||
root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
let level = layerIndex + 1
|
||||
let text = node.data.richText ? getText(node.data.text) : node.data.text
|
||||
if (level <= 6) {
|
||||
content += getTitleMark(level)
|
||||
} else {
|
||||
content += getIndentMark(level)
|
||||
}
|
||||
content += ' ' + text
|
||||
// 概要
|
||||
let generalization = node.data.generalization
|
||||
if (generalization && generalization.text) {
|
||||
let generalizationText = generalization.richText
|
||||
? getText(generalization.text)
|
||||
: generalization.text
|
||||
content += `[${generalizationText}]`
|
||||
}
|
||||
content += '\n\n'
|
||||
// 备注
|
||||
if (node.data.note) {
|
||||
content += node.data.note + '\n\n'
|
||||
}
|
||||
},
|
||||
() => {},
|
||||
true
|
||||
)
|
||||
return content
|
||||
}
|
||||
@@ -142,14 +142,10 @@ export default {
|
||||
// 简单来说,会改变节点大小的都不支持在激活时设置,为了性能考虑,节点切换激活态时不会重新计算节点大小
|
||||
export const supportActiveStyle = [
|
||||
'fillColor',
|
||||
'color',
|
||||
'fontWeight',
|
||||
'fontStyle',
|
||||
'borderColor',
|
||||
'borderWidth',
|
||||
'borderDasharray',
|
||||
'borderRadius',
|
||||
'textDecoration'
|
||||
'borderRadius'
|
||||
]
|
||||
|
||||
export const lineStyleProps = ['lineColor', 'lineDasharray', 'lineWidth']
|
||||
|
||||
@@ -22,32 +22,6 @@ export const tagColorList = [
|
||||
}
|
||||
]
|
||||
|
||||
// 布局结构列表
|
||||
export const layoutList = [
|
||||
{
|
||||
name: '逻辑结构图',
|
||||
value: 'logicalStructure',
|
||||
},
|
||||
{
|
||||
name: '思维导图',
|
||||
value: 'mindMap',
|
||||
},
|
||||
{
|
||||
name: '组织结构图',
|
||||
value: 'organizationStructure',
|
||||
},
|
||||
{
|
||||
name: '目录组织图',
|
||||
value: 'catalogOrganization',
|
||||
}
|
||||
]
|
||||
export const layoutValueList = [
|
||||
'logicalStructure',
|
||||
'mindMap',
|
||||
'catalogOrganization',
|
||||
'organizationStructure'
|
||||
]
|
||||
|
||||
// 主题列表
|
||||
export const themeList = [
|
||||
{
|
||||
@@ -139,3 +113,72 @@ export const themeList = [
|
||||
value: 'romanticPurple',
|
||||
}
|
||||
]
|
||||
|
||||
// 常量
|
||||
export const CONSTANTS = {
|
||||
CHANGE_THEME: 'changeTheme',
|
||||
TRANSFORM_TO_NORMAL_NODE: 'transformAllNodesToNormalNode',
|
||||
MODE: {
|
||||
READONLY: 'readonly',
|
||||
EDIT: 'edit'
|
||||
},
|
||||
LAYOUT: {
|
||||
LOGICAL_STRUCTURE: 'logicalStructure',
|
||||
MIND_MAP: 'mindMap',
|
||||
ORGANIZATION_STRUCTURE: 'organizationStructure',
|
||||
CATALOG_ORGANIZATION: 'catalogOrganization'
|
||||
},
|
||||
DIR: {
|
||||
UP: 'up',
|
||||
LEFT: 'left',
|
||||
DOWN: 'down',
|
||||
RIGHT: 'right'
|
||||
},
|
||||
KEY_DIR: {
|
||||
LEFT: 'Left',
|
||||
UP: 'Up',
|
||||
RIGHT: 'Right',
|
||||
DOWN: 'Down'
|
||||
},
|
||||
SHAPE: {
|
||||
RECTANGLE: 'rectangle',
|
||||
DIAMOND: 'diamond',
|
||||
PARALLELOGRAM: 'parallelogram',
|
||||
ROUNDED_RECTANGLE: 'roundedRectangle',
|
||||
OCTAGONAL_RECTANGLE: 'octagonalRectangle',
|
||||
OUTER_TRIANGULAR_RECTANGLE: 'outerTriangularRectangle',
|
||||
INNER_TRIANGULAR_RECTANGLE: 'innerTriangularRectangle',
|
||||
ELLIPSE: 'ellipse',
|
||||
CIRCLE: 'circle'
|
||||
},
|
||||
MOUSE_WHEEL_ACTION: {
|
||||
ZOOM: 'zoom',
|
||||
MOVE: 'move'
|
||||
}
|
||||
}
|
||||
|
||||
// 布局结构列表
|
||||
export const layoutList = [
|
||||
{
|
||||
name: '逻辑结构图',
|
||||
value: CONSTANTS.LAYOUT.LOGICAL_STRUCTURE,
|
||||
},
|
||||
{
|
||||
name: '思维导图',
|
||||
value: CONSTANTS.LAYOUT.MIND_MAP,
|
||||
},
|
||||
{
|
||||
name: '组织结构图',
|
||||
value: CONSTANTS.LAYOUT.ORGANIZATION_STRUCTURE,
|
||||
},
|
||||
{
|
||||
name: '目录组织图',
|
||||
value: CONSTANTS.LAYOUT.CATALOG_ORGANIZATION,
|
||||
}
|
||||
]
|
||||
export const layoutValueList = [
|
||||
CONSTANTS.LAYOUT.LOGICAL_STRUCTURE,
|
||||
CONSTANTS.LAYOUT.MIND_MAP,
|
||||
CONSTANTS.LAYOUT.CATALOG_ORGANIZATION,
|
||||
CONSTANTS.LAYOUT.ORGANIZATION_STRUCTURE
|
||||
]
|
||||
@@ -122,12 +122,15 @@ export const simpleDeepClone = data => {
|
||||
}
|
||||
|
||||
// 复制渲染树数据
|
||||
export const copyRenderTree = (tree, root) => {
|
||||
export const copyRenderTree = (tree, root, removeActiveState = false) => {
|
||||
tree.data = simpleDeepClone(root.data)
|
||||
if (removeActiveState) {
|
||||
tree.data.isActive = false
|
||||
}
|
||||
tree.children = []
|
||||
if (root.children && root.children.length > 0) {
|
||||
root.children.forEach((item, index) => {
|
||||
tree.children[index] = copyRenderTree({}, item)
|
||||
tree.children[index] = copyRenderTree({}, item, removeActiveState)
|
||||
})
|
||||
}
|
||||
return tree
|
||||
@@ -138,13 +141,14 @@ export const copyNodeTree = (tree, root, removeActiveState = false, keepId = fal
|
||||
tree.data = simpleDeepClone(root.nodeData ? root.nodeData.data : root.data)
|
||||
// 去除节点id,因为节点id不能重复
|
||||
if (tree.data.id && !keepId) delete tree.data.id
|
||||
if (tree.data.uid) delete tree.data.uid
|
||||
if (removeActiveState) {
|
||||
tree.data.isActive = false
|
||||
}
|
||||
tree.children = []
|
||||
if (root.children && root.children.length > 0) {
|
||||
root.children.forEach((item, index) => {
|
||||
tree.children[index] = copyNodeTree({}, item, removeActiveState)
|
||||
tree.children[index] = copyNodeTree({}, item, removeActiveState, keepId)
|
||||
})
|
||||
} else if (
|
||||
root.nodeData &&
|
||||
@@ -152,7 +156,7 @@ export const copyNodeTree = (tree, root, removeActiveState = false, keepId = fal
|
||||
root.nodeData.children.length > 0
|
||||
) {
|
||||
root.nodeData.children.forEach((item, index) => {
|
||||
tree.children[index] = copyNodeTree({}, item, removeActiveState)
|
||||
tree.children[index] = copyNodeTree({}, item, removeActiveState, keepId)
|
||||
})
|
||||
}
|
||||
return tree
|
||||
@@ -267,4 +271,35 @@ export const measureText = (text, { italic, bold, fontSize, fontFamily }) => {
|
||||
// 拼接font字符串
|
||||
export const joinFontStr = ({ italic, bold, fontSize, fontFamily }) => {
|
||||
return `${italic ? 'italic ' : ''} ${bold ? 'bold ' : ''} ${fontSize}px ${fontFamily} `
|
||||
}
|
||||
|
||||
// 在下一个事件循环里执行任务
|
||||
export const nextTick = function (fn, ctx) {
|
||||
let pending = false
|
||||
let timerFunc = null
|
||||
let handle = () => {
|
||||
pending = false
|
||||
ctx ? fn.call(ctx) : fn()
|
||||
}
|
||||
// 支持MutationObserver接口的话使用MutationObserver
|
||||
if (typeof MutationObserver !== 'undefined') {
|
||||
let counter = 1
|
||||
let observer = new MutationObserver(handle)
|
||||
let textNode = document.createTextNode(counter)
|
||||
observer.observe(textNode, {
|
||||
characterData: true // 设为 true 表示监视指定目标节点或子节点树中节点所包含的字符数据的变化
|
||||
})
|
||||
timerFunc = function () {
|
||||
counter = (counter + 1) % 2 // counter会在0和1两者循环变化
|
||||
textNode.data = counter // 节点变化会触发回调handle,
|
||||
}
|
||||
} else {
|
||||
// 否则使用定时器
|
||||
timerFunc = setTimeout
|
||||
}
|
||||
return function () {
|
||||
if (pending) return
|
||||
pending = true
|
||||
timerFunc(handle, 0)
|
||||
}
|
||||
}
|
||||
56
simple-mind-map/src/utils/nodeCommandWraps.js
Normal file
56
simple-mind-map/src/utils/nodeCommandWraps.js
Normal file
@@ -0,0 +1,56 @@
|
||||
// 设置数据
|
||||
function setData(data = {}) {
|
||||
this.mindMap.execCommand('SET_NODE_DATA', this, data)
|
||||
}
|
||||
|
||||
// 设置文本
|
||||
function setText(text, richText) {
|
||||
this.mindMap.execCommand('SET_NODE_TEXT', this, text, richText)
|
||||
}
|
||||
|
||||
// 设置图片
|
||||
function setImage(imgData) {
|
||||
this.mindMap.execCommand('SET_NODE_IMAGE', this, imgData)
|
||||
}
|
||||
|
||||
// 设置图标
|
||||
function setIcon(icons) {
|
||||
this.mindMap.execCommand('SET_NODE_ICON', this, icons)
|
||||
}
|
||||
|
||||
// 设置超链接
|
||||
function setHyperlink(link, title) {
|
||||
this.mindMap.execCommand('SET_NODE_HYPERLINK', this, link, title)
|
||||
}
|
||||
|
||||
// 设置备注
|
||||
function setNote(note) {
|
||||
this.mindMap.execCommand('SET_NODE_NOTE', this, note)
|
||||
}
|
||||
|
||||
// 设置标签
|
||||
function setTag(tag) {
|
||||
this.mindMap.execCommand('SET_NODE_TAG', this, tag)
|
||||
}
|
||||
|
||||
// 设置形状
|
||||
function setShape(shape) {
|
||||
this.mindMap.execCommand('SET_NODE_SHAPE', this, shape)
|
||||
}
|
||||
|
||||
// 修改某个样式
|
||||
function setStyle(prop, value, isActive) {
|
||||
this.mindMap.execCommand('SET_NODE_STYLE', this, prop, value, isActive)
|
||||
}
|
||||
|
||||
export default {
|
||||
setData,
|
||||
setText,
|
||||
setImage,
|
||||
setIcon,
|
||||
setHyperlink,
|
||||
setNote,
|
||||
setTag,
|
||||
setShape,
|
||||
setStyle
|
||||
}
|
||||
256
simple-mind-map/src/utils/nodeCreateContents.js
Normal file
256
simple-mind-map/src/utils/nodeCreateContents.js
Normal file
@@ -0,0 +1,256 @@
|
||||
import { measureText, resizeImgSize } from '../utils'
|
||||
import { Image, SVG, A, G, Rect, Text, ForeignObject } from '@svgdotjs/svg.js'
|
||||
import iconsSvg from '../svg/icons'
|
||||
|
||||
// 创建图片节点
|
||||
function createImgNode() {
|
||||
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)
|
||||
}
|
||||
node.on('dblclick', e => {
|
||||
this.mindMap.emit('node_img_dblclick', this, e)
|
||||
})
|
||||
return {
|
||||
node,
|
||||
width: imgSize[0],
|
||||
height: imgSize[1]
|
||||
}
|
||||
}
|
||||
|
||||
// 获取图片显示宽高
|
||||
function getImgShowSize() {
|
||||
return resizeImgSize(
|
||||
this.nodeData.data.imageSize.width,
|
||||
this.nodeData.data.imageSize.height,
|
||||
this.mindMap.themeConfig.imgMaxWidth,
|
||||
this.mindMap.themeConfig.imgMaxHeight
|
||||
)
|
||||
}
|
||||
|
||||
// 创建icon节点
|
||||
function createIconNode() {
|
||||
let _data = this.nodeData.data
|
||||
if (!_data.icon || _data.icon.length <= 0) {
|
||||
return []
|
||||
}
|
||||
let iconSize = this.mindMap.themeConfig.iconSize
|
||||
return _data.icon.map(item => {
|
||||
return {
|
||||
node: SVG(iconsSvg.getNodeIconListIcon(item)).size(iconSize, iconSize),
|
||||
width: iconSize,
|
||||
height: iconSize
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 创建富文本节点
|
||||
function createRichTextNode() {
|
||||
let g = new G()
|
||||
let html = `<div>${this.nodeData.data.text}</div>`
|
||||
let div = document.createElement('div')
|
||||
div.innerHTML = html
|
||||
div.style.cssText = `position: fixed; left: -999999px;`
|
||||
let el = div.children[0]
|
||||
el.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml')
|
||||
el.style.maxWidth = this.mindMap.opt.textAutoWrapWidth + 'px'
|
||||
this.mindMap.el.appendChild(div)
|
||||
let { width, height } = el.getBoundingClientRect()
|
||||
width = Math.ceil(width)
|
||||
height = Math.ceil(height)
|
||||
g.attr('data-width', width)
|
||||
g.attr('data-height', height)
|
||||
html = div.innerHTML
|
||||
this.mindMap.el.removeChild(div)
|
||||
let foreignObject = new ForeignObject()
|
||||
foreignObject.width(width)
|
||||
foreignObject.height(height)
|
||||
foreignObject.add(SVG(html))
|
||||
g.add(foreignObject)
|
||||
return {
|
||||
node: g,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
|
||||
// 创建文本节点
|
||||
function createTextNode() {
|
||||
if (this.nodeData.data.richText) {
|
||||
return this.createRichTextNode()
|
||||
}
|
||||
let g = new G()
|
||||
let fontSize = this.getStyle('fontSize', false, this.nodeData.data.isActive)
|
||||
let lineHeight = this.getStyle(
|
||||
'lineHeight',
|
||||
false,
|
||||
this.nodeData.data.isActive
|
||||
)
|
||||
// 文本超长自动换行
|
||||
let textStyle = this.style.getTextFontStyle()
|
||||
let textArr = this.nodeData.data.text.split(/\n/gim)
|
||||
let maxWidth = this.mindMap.opt.textAutoWrapWidth
|
||||
textArr.forEach((item, index) => {
|
||||
let arr = item.split('')
|
||||
let lines = []
|
||||
let line = []
|
||||
while (arr.length) {
|
||||
line.push(arr.shift())
|
||||
let text = line.join('')
|
||||
if (measureText(text, textStyle).width >= maxWidth) {
|
||||
lines.push(text)
|
||||
line = []
|
||||
}
|
||||
}
|
||||
if (line.length > 0) {
|
||||
lines.push(line.join(''))
|
||||
}
|
||||
textArr[index] = lines.join('\n')
|
||||
})
|
||||
textArr = textArr.join('\n').split(/\n/gim)
|
||||
textArr.forEach((item, index) => {
|
||||
let node = new Text().text(item)
|
||||
this.style.text(node)
|
||||
node.y(fontSize * lineHeight * index)
|
||||
g.add(node)
|
||||
})
|
||||
let { width, height } = g.bbox()
|
||||
width = Math.ceil(width)
|
||||
height = Math.ceil(height)
|
||||
g.attr('data-width', width)
|
||||
g.attr('data-height', height)
|
||||
return {
|
||||
node: g,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
|
||||
// 创建超链接节点
|
||||
function createHyperlinkNode() {
|
||||
let { hyperlink, hyperlinkTitle } = this.nodeData.data
|
||||
if (!hyperlink) {
|
||||
return
|
||||
}
|
||||
let iconSize = this.mindMap.themeConfig.iconSize
|
||||
let node = new SVG()
|
||||
// 超链接节点
|
||||
let a = new A().to(hyperlink).target('_blank')
|
||||
a.node.addEventListener('click', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
if (hyperlinkTitle) {
|
||||
a.attr('title', hyperlinkTitle)
|
||||
}
|
||||
// 添加一个透明的层,作为鼠标区域
|
||||
a.rect(iconSize, iconSize).fill({ color: 'transparent' })
|
||||
// 超链接图标
|
||||
let iconNode = SVG(iconsSvg.hyperlink).size(iconSize, iconSize)
|
||||
this.style.iconNode(iconNode)
|
||||
a.add(iconNode)
|
||||
node.add(a)
|
||||
return {
|
||||
node,
|
||||
width: iconSize,
|
||||
height: iconSize
|
||||
}
|
||||
}
|
||||
|
||||
// 创建标签节点
|
||||
function createTagNode() {
|
||||
let tagData = this.nodeData.data.tag
|
||||
if (!tagData || tagData.length <= 0) {
|
||||
return []
|
||||
}
|
||||
let nodes = []
|
||||
tagData.slice(0, this.mindMap.opt.maxTag).forEach((item, index) => {
|
||||
let tag = new G()
|
||||
// 标签文本
|
||||
let text = new Text().text(item).x(8).cy(10)
|
||||
this.style.tagText(text, index)
|
||||
let { width } = text.bbox()
|
||||
// 标签矩形
|
||||
let rect = new Rect().size(width + 16, 20)
|
||||
this.style.tagRect(rect, index)
|
||||
tag.add(rect).add(text)
|
||||
nodes.push({
|
||||
node: tag,
|
||||
width: width + 16,
|
||||
height: 20
|
||||
})
|
||||
})
|
||||
return nodes
|
||||
}
|
||||
|
||||
// 创建备注节点
|
||||
function createNoteNode() {
|
||||
if (!this.nodeData.data.note) {
|
||||
return null
|
||||
}
|
||||
let iconSize = this.mindMap.themeConfig.iconSize
|
||||
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
|
||||
if (!this.mindMap.opt.customNoteContentShow) {
|
||||
if (!this.noteEl) {
|
||||
this.noteEl = document.createElement('div')
|
||||
this.noteEl.style.cssText = `
|
||||
position: absolute;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 5px rgb(0 0 0 / 10%);
|
||||
display: none;
|
||||
background-color: #fff;
|
||||
`
|
||||
document.body.appendChild(this.noteEl)
|
||||
}
|
||||
this.noteEl.innerText = this.nodeData.data.note
|
||||
}
|
||||
node.on('mouseover', () => {
|
||||
let { left, top } = node.node.getBoundingClientRect()
|
||||
if (!this.mindMap.opt.customNoteContentShow) {
|
||||
this.noteEl.style.left = left + 'px'
|
||||
this.noteEl.style.top = top + iconSize + 'px'
|
||||
this.noteEl.style.display = 'block'
|
||||
} else {
|
||||
this.mindMap.opt.customNoteContentShow.show(
|
||||
this.nodeData.data.note,
|
||||
left,
|
||||
top + iconSize
|
||||
)
|
||||
}
|
||||
})
|
||||
node.on('mouseout', () => {
|
||||
if (!this.mindMap.opt.customNoteContentShow) {
|
||||
this.noteEl.style.display = 'none'
|
||||
} else {
|
||||
this.mindMap.opt.customNoteContentShow.hide()
|
||||
}
|
||||
})
|
||||
return {
|
||||
node,
|
||||
width: iconSize,
|
||||
height: iconSize
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
createImgNode,
|
||||
getImgShowSize,
|
||||
createIconNode,
|
||||
createRichTextNode,
|
||||
createTextNode,
|
||||
createHyperlinkNode,
|
||||
createTagNode,
|
||||
createNoteNode
|
||||
}
|
||||
113
simple-mind-map/src/utils/nodeExpandBtn.js
Normal file
113
simple-mind-map/src/utils/nodeExpandBtn.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import btnsSvg from '../svg/btns'
|
||||
import { SVG, Circle, G } from '@svgdotjs/svg.js'
|
||||
|
||||
// 创建展开收起按钮的内容节点
|
||||
function createExpandNodeContent() {
|
||||
if (this._openExpandNode) {
|
||||
return
|
||||
}
|
||||
let { open, close } = this.mindMap.opt.expandBtnIcon || {}
|
||||
// 展开的节点
|
||||
this._openExpandNode = SVG(open || btnsSvg.open).size(
|
||||
this.expandBtnSize,
|
||||
this.expandBtnSize
|
||||
)
|
||||
this._openExpandNode.x(0).y(-this.expandBtnSize / 2)
|
||||
// 收起的节点
|
||||
this._closeExpandNode = SVG(close || btnsSvg.close).size(
|
||||
this.expandBtnSize,
|
||||
this.expandBtnSize
|
||||
)
|
||||
this._closeExpandNode.x(0).y(-this.expandBtnSize / 2)
|
||||
// 填充节点
|
||||
this._fillExpandNode = new Circle().size(this.expandBtnSize)
|
||||
this._fillExpandNode.x(0).y(-this.expandBtnSize / 2)
|
||||
// 设置样式
|
||||
this.style.iconBtn(
|
||||
this._openExpandNode,
|
||||
this._closeExpandNode,
|
||||
this._fillExpandNode
|
||||
)
|
||||
}
|
||||
|
||||
// 创建或更新展开收缩按钮内容
|
||||
function updateExpandBtnNode() {
|
||||
if (this._expandBtn) {
|
||||
this._expandBtn.clear()
|
||||
}
|
||||
this.createExpandNodeContent()
|
||||
let node
|
||||
if (this.nodeData.data.expand === false) {
|
||||
node = this._openExpandNode
|
||||
} else {
|
||||
node = this._closeExpandNode
|
||||
}
|
||||
if (this._expandBtn) this._expandBtn.add(this._fillExpandNode).add(node)
|
||||
}
|
||||
|
||||
// 更新展开收缩按钮位置
|
||||
function updateExpandBtnPos() {
|
||||
if (!this._expandBtn) {
|
||||
return
|
||||
}
|
||||
this.renderer.layout.renderExpandBtn(this, this._expandBtn)
|
||||
}
|
||||
|
||||
// 创建展开收缩按钮
|
||||
function renderExpandBtn() {
|
||||
if (
|
||||
!this.nodeData.children ||
|
||||
this.nodeData.children.length <= 0 ||
|
||||
this.isRoot
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (this._expandBtn) {
|
||||
this.group.add(this._expandBtn)
|
||||
} else {
|
||||
this._expandBtn = new G()
|
||||
this._expandBtn.on('mouseover', e => {
|
||||
e.stopPropagation()
|
||||
this._expandBtn.css({
|
||||
cursor: 'pointer'
|
||||
})
|
||||
})
|
||||
this._expandBtn.on('mouseout', e => {
|
||||
e.stopPropagation()
|
||||
this._expandBtn.css({
|
||||
cursor: 'auto'
|
||||
})
|
||||
})
|
||||
this._expandBtn.on('click', e => {
|
||||
e.stopPropagation()
|
||||
// 展开收缩
|
||||
this.mindMap.execCommand(
|
||||
'SET_NODE_EXPAND',
|
||||
this,
|
||||
!this.nodeData.data.expand
|
||||
)
|
||||
this.mindMap.emit('expand_btn_click', this)
|
||||
})
|
||||
this._expandBtn.on('dblclick', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
this.group.add(this._expandBtn)
|
||||
}
|
||||
this.updateExpandBtnNode()
|
||||
this.updateExpandBtnPos()
|
||||
}
|
||||
|
||||
// 移除展开收缩按钮
|
||||
function removeExpandBtn() {
|
||||
if (this._expandBtn) {
|
||||
this._expandBtn.remove()
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
createExpandNodeContent,
|
||||
updateExpandBtnNode,
|
||||
updateExpandBtnPos,
|
||||
renderExpandBtn,
|
||||
removeExpandBtn
|
||||
}
|
||||
115
simple-mind-map/src/utils/nodeGeneralization.js
Normal file
115
simple-mind-map/src/utils/nodeGeneralization.js
Normal file
@@ -0,0 +1,115 @@
|
||||
import Node from '../Node'
|
||||
|
||||
// 检查是否存在概要
|
||||
function checkHasGeneralization () {
|
||||
return !!this.nodeData.data.generalization
|
||||
}
|
||||
|
||||
// 创建概要节点
|
||||
function createGeneralizationNode () {
|
||||
if (this.isGeneralization || !this.checkHasGeneralization()) {
|
||||
return
|
||||
}
|
||||
if (!this._generalizationLine) {
|
||||
this._generalizationLine = this.draw.path()
|
||||
}
|
||||
if (!this._generalizationNode) {
|
||||
this._generalizationNode = new Node({
|
||||
data: {
|
||||
data: this.nodeData.data.generalization
|
||||
},
|
||||
uid: this.mindMap.uid++,
|
||||
renderer: this.renderer,
|
||||
mindMap: this.mindMap,
|
||||
draw: this.draw,
|
||||
isGeneralization: true
|
||||
})
|
||||
this._generalizationNodeWidth = this._generalizationNode.width
|
||||
this._generalizationNodeHeight = this._generalizationNode.height
|
||||
this._generalizationNode.generalizationBelongNode = this
|
||||
if (this.nodeData.data.generalization.isActive) {
|
||||
this.renderer.addActiveNode(this._generalizationNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新概要节点
|
||||
function updateGeneralization () {
|
||||
this.removeGeneralization()
|
||||
this.createGeneralizationNode()
|
||||
}
|
||||
|
||||
// 渲染概要节点
|
||||
function renderGeneralization () {
|
||||
if (this.isGeneralization) {
|
||||
return
|
||||
}
|
||||
if (!this.checkHasGeneralization()) {
|
||||
this.removeGeneralization()
|
||||
this._generalizationNodeWidth = 0
|
||||
this._generalizationNodeHeight = 0
|
||||
return
|
||||
}
|
||||
if (this.nodeData.data.expand === false) {
|
||||
this.removeGeneralization()
|
||||
return
|
||||
}
|
||||
this.createGeneralizationNode()
|
||||
this.renderer.layout.renderGeneralization(
|
||||
this,
|
||||
this._generalizationLine,
|
||||
this._generalizationNode
|
||||
)
|
||||
this.style.generalizationLine(this._generalizationLine)
|
||||
this._generalizationNode.render()
|
||||
}
|
||||
|
||||
// 删除概要节点
|
||||
function removeGeneralization () {
|
||||
if (this._generalizationLine) {
|
||||
this._generalizationLine.remove()
|
||||
this._generalizationLine = null
|
||||
}
|
||||
if (this._generalizationNode) {
|
||||
// 删除概要节点时要同步从激活节点里删除
|
||||
this.renderer.removeActiveNode(this._generalizationNode)
|
||||
this._generalizationNode.remove()
|
||||
this._generalizationNode = null
|
||||
}
|
||||
// hack修复当激活一个节点时创建概要,然后立即激活创建的概要节点后会重复创建概要节点并且无法删除的问题
|
||||
if (this.generalizationBelongNode) {
|
||||
this.draw
|
||||
.find('.generalization_' + this.generalizationBelongNode.uid)
|
||||
.remove()
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏概要节点
|
||||
function hideGeneralization () {
|
||||
if (this._generalizationLine) {
|
||||
this._generalizationLine.hide()
|
||||
}
|
||||
if (this._generalizationNode) {
|
||||
this._generalizationNode.hide()
|
||||
}
|
||||
}
|
||||
|
||||
// 显示概要节点
|
||||
function showGeneralization () {
|
||||
if (this._generalizationLine) {
|
||||
this._generalizationLine.show()
|
||||
}
|
||||
if (this._generalizationNode) {
|
||||
this._generalizationNode.show()
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
checkHasGeneralization,
|
||||
createGeneralizationNode,
|
||||
updateGeneralization,
|
||||
renderGeneralization,
|
||||
removeGeneralization,
|
||||
hideGeneralization,
|
||||
showGeneralization
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 2479351 */
|
||||
src: url('iconfont.woff2?t=1678955183884') format('woff2'),
|
||||
url('iconfont.woff?t=1678955183884') format('woff'),
|
||||
url('iconfont.ttf?t=1678955183884') format('truetype');
|
||||
src: url('iconfont.woff2?t=1679621707211') format('woff2'),
|
||||
url('iconfont.woff?t=1679621707211') format('woff'),
|
||||
url('iconfont.ttf?t=1679621707211') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,6 +13,30 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.iconwenjian:before {
|
||||
content: "\e607";
|
||||
}
|
||||
|
||||
.iconpdf:before {
|
||||
content: "\e740";
|
||||
}
|
||||
|
||||
.iconPNG:before {
|
||||
content: "\ec18";
|
||||
}
|
||||
|
||||
.iconSVG:before {
|
||||
content: "\e621";
|
||||
}
|
||||
|
||||
.iconmarkdown:before {
|
||||
content: "\ec04";
|
||||
}
|
||||
|
||||
.iconjson:before {
|
||||
content: "\ea42";
|
||||
}
|
||||
|
||||
.iconlianjiexian:before {
|
||||
content: "\e75b";
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -369,3 +369,43 @@ export const sidebarTriggerList = [
|
||||
icon: 'iconjianpan'
|
||||
}
|
||||
]
|
||||
|
||||
// 下载类型列表
|
||||
export const downTypeList = [
|
||||
{
|
||||
name: 'Dedicated file',
|
||||
type: 'smm',
|
||||
icon: 'iconwenjian',
|
||||
desc: 'Available for import'
|
||||
},
|
||||
{
|
||||
name: 'JSON',
|
||||
type: 'json',
|
||||
icon: 'iconjson',
|
||||
desc: 'Popular data exchange formats, Available for import'
|
||||
},
|
||||
{
|
||||
name: 'Image',
|
||||
type: 'png',
|
||||
icon: 'iconPNG',
|
||||
desc: 'Suitable for viewing and sharing'
|
||||
},
|
||||
{
|
||||
name: 'SVG',
|
||||
type: 'svg',
|
||||
icon: 'iconSVG',
|
||||
desc: 'Scalable Vector Graphics'
|
||||
},
|
||||
{
|
||||
name: 'PDF',
|
||||
type: 'pdf',
|
||||
icon: 'iconpdf',
|
||||
desc: 'Suitable for printing'
|
||||
},
|
||||
{
|
||||
name: 'Markdown',
|
||||
type: 'md',
|
||||
icon: 'iconmarkdown',
|
||||
desc: 'Easy for other software to open'
|
||||
}
|
||||
]
|
||||
@@ -15,7 +15,8 @@ import {
|
||||
shortcutKeyList as shortcutKeyListZh,
|
||||
shapeList as shapeListZh,
|
||||
sidebarTriggerList as sidebarTriggerListZh,
|
||||
backgroundSizeList as backgroundSizeListZh
|
||||
backgroundSizeList as backgroundSizeListZh,
|
||||
downTypeList as downTypeListZh
|
||||
} from './zh'
|
||||
import {
|
||||
fontFamilyList as fontFamilyListEn,
|
||||
@@ -26,7 +27,8 @@ import {
|
||||
shortcutKeyList as shortcutKeyListEn,
|
||||
shapeList as shapeListEn,
|
||||
sidebarTriggerList as sidebarTriggerListEn,
|
||||
backgroundSizeList as backgroundSizeListEn
|
||||
backgroundSizeList as backgroundSizeListEn,
|
||||
downTypeList as downTypeListEn
|
||||
} from './en'
|
||||
|
||||
const fontFamilyList = {
|
||||
@@ -74,6 +76,11 @@ const sidebarTriggerList = {
|
||||
en: sidebarTriggerListEn
|
||||
}
|
||||
|
||||
const downTypeList = {
|
||||
zh: downTypeListZh,
|
||||
en: downTypeListEn
|
||||
}
|
||||
|
||||
export {
|
||||
fontSizeList,
|
||||
lineHeightList,
|
||||
@@ -91,5 +98,6 @@ export {
|
||||
backgroundSizeList,
|
||||
shortcutKeyList,
|
||||
shapeList,
|
||||
sidebarTriggerList
|
||||
sidebarTriggerList,
|
||||
downTypeList
|
||||
}
|
||||
|
||||
@@ -441,3 +441,43 @@ export const sidebarTriggerList = [
|
||||
icon: 'iconjianpan'
|
||||
}
|
||||
]
|
||||
|
||||
// 下载类型列表
|
||||
export const downTypeList = [
|
||||
{
|
||||
name: '专有文件',
|
||||
type: 'smm',
|
||||
icon: 'iconwenjian',
|
||||
desc: '可用于导入'
|
||||
},
|
||||
{
|
||||
name: 'JSON',
|
||||
type: 'json',
|
||||
icon: 'iconjson',
|
||||
desc: '流行的数据交换格式,可用于导入'
|
||||
},
|
||||
{
|
||||
name: '图片',
|
||||
type: 'png',
|
||||
icon: 'iconPNG',
|
||||
desc: '适合查看分享'
|
||||
},
|
||||
{
|
||||
name: 'SVG',
|
||||
type: 'svg',
|
||||
icon: 'iconSVG',
|
||||
desc: '可缩放矢量图形'
|
||||
},
|
||||
{
|
||||
name: 'PDF',
|
||||
type: 'pdf',
|
||||
icon: 'iconpdf',
|
||||
desc: '适合打印'
|
||||
},
|
||||
{
|
||||
name: 'Markdown',
|
||||
type: 'md',
|
||||
icon: 'iconmarkdown',
|
||||
desc: '便于其他软件打开'
|
||||
}
|
||||
]
|
||||
@@ -88,6 +88,7 @@ export default {
|
||||
imageFile: 'Image file',
|
||||
svgFile: 'svg file',
|
||||
pdfFile: 'pdf file',
|
||||
markdownFile: 'markdown file',
|
||||
tips: 'tips: .smm and .json file can be import',
|
||||
domToImage: 'Whether to convert rich text nodes in svg into pictures',
|
||||
pngTips: 'tips: Exporting pictures in rich text mode is time-consuming. It is recommended to export to svg format',
|
||||
@@ -103,7 +104,7 @@ export default {
|
||||
import: {
|
||||
title: 'Import',
|
||||
selectFile: 'Select file',
|
||||
supportFile: 'Support .smm、.json、.xmind、.xlsx file'
|
||||
supportFile: 'Support .smm、.json、.xmind、.xlsx、.md file'
|
||||
},
|
||||
navigatorToolbar: {
|
||||
openMiniMap: 'Open mini map',
|
||||
|
||||
@@ -88,6 +88,7 @@ export default {
|
||||
imageFile: '图片文件',
|
||||
svgFile: 'svg文件',
|
||||
pdfFile: 'pdf文件',
|
||||
markdownFile: 'markdown文件',
|
||||
tips: 'tips:.smm和.json文件可用于导入',
|
||||
domToImage: '是否将svg中富文本节点转换成图片',
|
||||
pngTips: 'tips:富文本模式导出图片非常耗时,建议导出为svg格式',
|
||||
@@ -103,7 +104,7 @@ export default {
|
||||
import: {
|
||||
title: '导入',
|
||||
selectFile: '选取文件',
|
||||
supportFile: '支持.smm、.json、.xmind、.xlsx文件'
|
||||
supportFile: '支持.smm、.json、.xmind、.xlsx、.md文件'
|
||||
},
|
||||
navigatorToolbar: {
|
||||
openMiniMap: '开启小地图',
|
||||
|
||||
@@ -28,6 +28,7 @@ let APIList = [
|
||||
'watermark',
|
||||
'associativeLine',
|
||||
'xmind',
|
||||
'markdown',
|
||||
'utils'
|
||||
]
|
||||
|
||||
|
||||
@@ -1,5 +1,51 @@
|
||||
# Changelog
|
||||
|
||||
## 0.5.2
|
||||
|
||||
Fix: 1.Remove `uid` from exported `JSON` data; 2.Clear the node cache pool when re rendering.
|
||||
|
||||
## 0.5.1
|
||||
|
||||
optimization: 1.Only respond to shortcut key events when the mouse is inside the canvas
|
||||
|
||||
Fix: 1.Fix the issue of incorrect node position during fast operation
|
||||
|
||||
## 0.5.0
|
||||
|
||||
This version is mainly about code level changes and optimization, with the core goal of improving rendering performance and reducing stuck issues.
|
||||
|
||||
New: 1.Support custom expansion and collapse node icons and colors;
|
||||
|
||||
optimization: 1.Optimize rendering logic, set the theme, move forward and backward, and other operations no longer require full rendering;
|
||||
|
||||
2.Optimize node drag logic, and fix the problem of being unable to drag between two nodes;
|
||||
|
||||
3.Collapse all nodes adds logic to return to the center point;
|
||||
|
||||
4.Fix the problem of nodes flying and scrambling caused by triggering rendering multiple times in a short time;
|
||||
|
||||
5.Optimize the experience of node editing;
|
||||
|
||||
Fix: 1.Fix the issue where the setData method does not trigger history;
|
||||
|
||||
modify: Starting from version 0.5.0, considering performance issues, the node activation state can only modify shape related styles:
|
||||
|
||||
```js
|
||||
[
|
||||
'fillColor',
|
||||
'borderColor',
|
||||
'borderWidth',
|
||||
'borderDasharray',
|
||||
'borderRadius'
|
||||
]
|
||||
```
|
||||
|
||||
## 0.4.7
|
||||
|
||||
optimization: 1.During rich text editing, when initially focusing, all are no longer selected by default; 2.When editing rich text, use the node fill color as the background color to avoid being invisible when the node color is white. 3.Node activation state switching no longer triggers history. 4.Triggering history multiple times in a short time will only add the last data. 5.Optimize the addition of historical records. When there is a rollback, delete the historical data after the current pointer when adding a new record again.
|
||||
|
||||
New: 1.Support for importing and exporting Markdown format files. 2.Support for configuring initial text when inserting nodes. 3.Expand the commands for inserting and deleting nodes to support specifying nodes.
|
||||
|
||||
## 0.4.6
|
||||
|
||||
New: 1.Associated lines support adjusting control points.
|
||||
|
||||
@@ -1,6 +1,36 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Changelog</h1>
|
||||
<h2>0.5.2</h2>
|
||||
<p>Fix: 1.Remove <code>uid</code> from exported <code>JSON</code> data; 2.Clear the node cache pool when re rendering.</p>
|
||||
<h2>0.5.1</h2>
|
||||
<p>optimization: 1.Only respond to shortcut key events when the mouse is inside the canvas</p>
|
||||
<p>Fix: 1.Fix the issue of incorrect node position during fast operation</p>
|
||||
<h2>0.5.0</h2>
|
||||
<p>This version is mainly about code level changes and optimization, with the core goal of improving rendering performance and reducing stuck issues.</p>
|
||||
<p>New: 1.Support custom expansion and collapse node icons and colors;</p>
|
||||
<p>optimization: 1.Optimize rendering logic, set the theme, move forward and backward, and other operations no longer require full rendering;</p>
|
||||
<pre><code> 2.Optimize node drag logic, and fix the problem of being unable to drag between two nodes;
|
||||
|
||||
3.Collapse all nodes adds logic to return to the center point;
|
||||
|
||||
4.Fix the problem of nodes flying and scrambling caused by triggering rendering multiple times in a short time;
|
||||
|
||||
5.Optimize the experience of node editing;
|
||||
</code></pre>
|
||||
<p>Fix: 1.Fix the issue where the setData method does not trigger history;</p>
|
||||
<p>modify: Starting from version 0.5.0, considering performance issues, the node activation state can only modify shape related styles:</p>
|
||||
<pre class="hljs"><code>[
|
||||
<span class="hljs-string">'fillColor'</span>,
|
||||
<span class="hljs-string">'borderColor'</span>,
|
||||
<span class="hljs-string">'borderWidth'</span>,
|
||||
<span class="hljs-string">'borderDasharray'</span>,
|
||||
<span class="hljs-string">'borderRadius'</span>
|
||||
]
|
||||
</code></pre>
|
||||
<h2>0.4.7</h2>
|
||||
<p>optimization: 1.During rich text editing, when initially focusing, all are no longer selected by default; 2.When editing rich text, use the node fill color as the background color to avoid being invisible when the node color is white. 3.Node activation state switching no longer triggers history. 4.Triggering history multiple times in a short time will only add the last data. 5.Optimize the addition of historical records. When there is a rollback, delete the historical data after the current pointer when adding a new record again.</p>
|
||||
<p>New: 1.Support for importing and exporting Markdown format files. 2.Support for configuring initial text when inserting nodes. 3.Expand the commands for inserting and deleting nodes to support specifying nodes.</p>
|
||||
<h2>0.4.6</h2>
|
||||
<p>New: 1.Associated lines support adjusting control points.</p>
|
||||
<p>optimization: 1.When adding historical data, filter data that has not changed compared to the previous time.</p>
|
||||
|
||||
@@ -44,6 +44,13 @@ const mindMap = new MindMap({
|
||||
| customHandleMousewheel(v0.4.3+) | Function | null | User-defined mouse wheel event processing can pass a function, and the callback parameter is the event object | |
|
||||
| mousewheelAction(v0.4.3+) | String | zoom | The behavior of the mouse wheel, `zoom`(Zoom in and out)、`move`(Move up and down). If `customHandleMousewheel` passes a custom function, this property will not take effect | |
|
||||
| mousewheelMoveStep(v0.4.3+) | Number | 100 | When the `mousewheelAction` is set to `move`, you can use this attribute to control the step length of the view movement when the mouse scrolls. The unit is `px` | |
|
||||
| defaultInsertSecondLevelNodeText(v0.4.7+) | String | 二级节点 | Text of the default inserted secondary node | |
|
||||
| defaultInsertBelowSecondLevelNodeText(v0.4.7+) | String | 分支主题 | Text for nodes below the second level inserted by default | |
|
||||
| expandBtnStyle(v0.5.0+) | Object | { color: '#808080', fill: '#fff' } | Expand the color of the stow button | |
|
||||
| expandBtnIcon(v0.5.0+) | Object | { open: '', close: '' } | Customize the icon of the expand/collapse button, and you can transfer the svg string of the icon | |
|
||||
| enableShortcutOnlyWhenMouseInSvg(v0.5.1+) | Boolean | true | Only respond to shortcut key events when the mouse is inside the canvas | |
|
||||
| enableNodeTransitionMove(v0.5.1+) | Boolean | true | Whether to enable node animation transition | |
|
||||
| nodeTransitionMoveDuration(v0.5.1+) | Number | 300 | If node animation transition is enabled, the transition time can be set using this attribute, in milliseconds | |
|
||||
|
||||
### Watermark config
|
||||
|
||||
@@ -196,6 +203,8 @@ Listen to an event. Event list:
|
||||
| node_dragging(v0.4.5+) | Triggered when a node is dragged | node(The currently dragged node) |
|
||||
| node_dragend(v0.4.5+) | Triggered when the node is dragged and ends | |
|
||||
| associative_line_click(v0.4.5+) | Triggered when an associated line is clicked | path(Connector node)、clickPath(Invisible click line node)、node(Start node)、toNode(Target node) |
|
||||
| svg_mouseenter(v0.5.1+) | Triggered when the mouse moves into the SVG canvas | e(event object) |
|
||||
| svg_mouseleave(v0.5.1+) | Triggered when the mouse moves out of the SVG canvas | e(event object) |
|
||||
|
||||
### emit(event, ...args)
|
||||
|
||||
@@ -269,11 +278,11 @@ redo. All commands are as follows:
|
||||
| SELECT_ALL | Select all | |
|
||||
| BACK | Go back a specified number of steps | step (the number of steps to go back, default is 1) |
|
||||
| FORWARD | Go forward a specified number of steps | step (the number of steps to go forward, default is 1) |
|
||||
| INSERT_NODE | Insert a sibling node, the active node will be the operation node. If there are multiple active nodes, only the first one will be effective | openEdit(v0.4.6+, Whether to activate the newly inserted node and enter editing mode, default is `true`) |
|
||||
| INSERT_CHILD_NODE | Insert a child node, the active node will be the operation node | openEdit(v0.4.6+, Whether to activate the newly inserted node and enter editing mode, default is `true`) |
|
||||
| INSERT_NODE | Insert a sibling node, the active node or appoint node will be the operation node. If there are multiple active nodes, only the first one will be effective | openEdit(v0.4.6+, Whether to activate the newly inserted node and enter editing mode, default is `true`) 、 appointNodes(v0.4.7+, Optional, appoint node, Specifying multiple nodes can pass an array)、 appointData(Optional, Specify the data for the newly created node, Such as {text: 'xxx', ...}, Detailed structure can be referred to [https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js) ) |
|
||||
| INSERT_CHILD_NODE | Insert a child node, the active node or appoint node will be the operation node | openEdit(v0.4.6+, Whether to activate the newly inserted node and enter editing mode, default is `true`)、 appointNodes(v0.4.7+, Optional, appoint node, Specifying multiple nodes can pass an array)、 appointData(Optional, Specify the data for the newly created node, Such as {text: 'xxx', ...}, Detailed structure can be referred to [https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js) ) |
|
||||
| UP_NODE | Move node up, the active node will be the operation node. If there are multiple active nodes, only the first one will be effective. Using this command on the root node or the first node in the list will be invalid | |
|
||||
| DOWN_NODE | Move node down, the active node will be the operation node. If there are multiple active nodes, only the first one will be effective. Using this command on the root node or the last node in the list will be invalid | |
|
||||
| REMOVE_NODE | Remove node, the active node will be the operation node | |
|
||||
| REMOVE_NODE | Remove node, the active node or appoint node will be the operation node | appointNodes(v0.4.7+, Optional, appoint node, Specifying multiple nodes can pass an array) |
|
||||
| PASTE_NODE | Paste node to a node, the active node will be the operation node | data (the node data to paste, usually obtained through the renderer.copyNode() and renderer.cutNode() methods) |
|
||||
| SET_NODE_STYLE | Modify node style | node (the node to set the style of), prop (style property), value (style property value), isActive (boolean, whether the style being set is for the active state) |
|
||||
| SET_NODE_ACTIVE | Set whether the node is active | node (the node to set), active (boolean, whether to activate) |
|
||||
|
||||
@@ -168,6 +168,55 @@
|
||||
<td>When the <code>mousewheelAction</code> is set to <code>move</code>, you can use this attribute to control the step length of the view movement when the mouse scrolls. The unit is <code>px</code></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>defaultInsertSecondLevelNodeText(v0.4.7+)</td>
|
||||
<td>String</td>
|
||||
<td>二级节点</td>
|
||||
<td>Text of the default inserted secondary node</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>defaultInsertBelowSecondLevelNodeText(v0.4.7+)</td>
|
||||
<td>String</td>
|
||||
<td>分支主题</td>
|
||||
<td>Text for nodes below the second level inserted by default</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>expandBtnStyle(v0.5.0+)</td>
|
||||
<td>Object</td>
|
||||
<td>{ color: '#808080', fill: '#fff' }</td>
|
||||
<td>Expand the color of the stow button</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>expandBtnIcon(v0.5.0+)</td>
|
||||
<td>Object</td>
|
||||
<td>{ open: '', close: '' }</td>
|
||||
<td>Customize the icon of the expand/collapse button, and you can transfer the svg string of the icon</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>enableShortcutOnlyWhenMouseInSvg(v0.5.1+)</td>
|
||||
<td>Boolean</td>
|
||||
<td>true</td>
|
||||
<td>Only respond to shortcut key events when the mouse is inside the canvas</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>enableNodeTransitionMove(v0.5.1+)</td>
|
||||
<td>Boolean</td>
|
||||
<td>true</td>
|
||||
<td>Whether to enable node animation transition</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>nodeTransitionMoveDuration(v0.5.1+)</td>
|
||||
<td>Number</td>
|
||||
<td>300</td>
|
||||
<td>If node animation transition is enabled, the transition time can be set using this attribute, in milliseconds</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Watermark config</h3>
|
||||
@@ -462,6 +511,16 @@ poor performance and should be used sparingly.</p>
|
||||
<td>Triggered when an associated line is clicked</td>
|
||||
<td>path(Connector node)、clickPath(Invisible click line node)、node(Start node)、toNode(Target node)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>svg_mouseenter(v0.5.1+)</td>
|
||||
<td>Triggered when the mouse moves into the SVG canvas</td>
|
||||
<td>e(event object)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>svg_mouseleave(v0.5.1+)</td>
|
||||
<td>Triggered when the mouse moves out of the SVG canvas</td>
|
||||
<td>e(event object)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>emit(event, ...args)</h3>
|
||||
@@ -530,13 +589,13 @@ redo. All commands are as follows:</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>INSERT_NODE</td>
|
||||
<td>Insert a sibling node, the active node will be the operation node. If there are multiple active nodes, only the first one will be effective</td>
|
||||
<td>openEdit(v0.4.6+, Whether to activate the newly inserted node and enter editing mode, default is <code>true</code>)</td>
|
||||
<td>Insert a sibling node, the active node or appoint node will be the operation node. If there are multiple active nodes, only the first one will be effective</td>
|
||||
<td>openEdit(v0.4.6+, Whether to activate the newly inserted node and enter editing mode, default is <code>true</code>) 、 appointNodes(v0.4.7+, Optional, appoint node, Specifying multiple nodes can pass an array)、 appointData(Optional, Specify the data for the newly created node, Such as {text: 'xxx', ...}, Detailed structure can be referred to <a href="https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js">https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js</a> )</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>INSERT_CHILD_NODE</td>
|
||||
<td>Insert a child node, the active node will be the operation node</td>
|
||||
<td>openEdit(v0.4.6+, Whether to activate the newly inserted node and enter editing mode, default is <code>true</code>)</td>
|
||||
<td>Insert a child node, the active node or appoint node will be the operation node</td>
|
||||
<td>openEdit(v0.4.6+, Whether to activate the newly inserted node and enter editing mode, default is <code>true</code>)、 appointNodes(v0.4.7+, Optional, appoint node, Specifying multiple nodes can pass an array)、 appointData(Optional, Specify the data for the newly created node, Such as {text: 'xxx', ...}, Detailed structure can be referred to <a href="https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js">https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js</a> )</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>UP_NODE</td>
|
||||
@@ -550,8 +609,8 @@ redo. All commands are as follows:</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>REMOVE_NODE</td>
|
||||
<td>Remove node, the active node will be the operation node</td>
|
||||
<td></td>
|
||||
<td>Remove node, the active node or appoint node will be the operation node</td>
|
||||
<td>appointNodes(v0.4.7+, Optional, appoint node, Specifying multiple nodes can pass an array)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>PASTE_NODE</td>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Introduction
|
||||
|
||||
`simple-mind-map` is a simple and powerful web mind map library, not dependent on any specific framework.
|
||||
`simple-mind-map` is a simple and powerful web mind map library, not dependent on any specific framework. Can help you quickly develop mind mapping products.
|
||||
|
||||
## Features
|
||||
|
||||
@@ -16,15 +16,15 @@
|
||||
- [x] Supports right-click and Ctrl + left-click to select multiple items
|
||||
- [x] Supports free dragging and dragging to adjust nodes
|
||||
- [x] Supports various node shapes
|
||||
- [x] Supports export to json, png, svg, pdf, and import from json, xmind
|
||||
- [x] Supports export to json, png, svg, pdf markdown, and import from json, xmind, markdown
|
||||
- [x] Supports mini map、support watermark
|
||||
- [x] Supports associative lines
|
||||
|
||||
## Table of Contents
|
||||
## Repository Catalog Introduction
|
||||
|
||||
1.`simple-mind-map`
|
||||
|
||||
This is a mind map tool library that is framework-agnostic and can be used with
|
||||
This is a mind map library that is framework-agnostic and can be used with
|
||||
frameworks such as Vue and React, or without a framework.
|
||||
|
||||
2.`web`
|
||||
@@ -63,9 +63,11 @@ The folder containing the packaged resources for the `web` folder.
|
||||
|
||||
## Special Note
|
||||
|
||||
This project is rough and has not been thoroughly tested, its features are not
|
||||
yet fully developed, and there are some performance issues, especially when the number of nodes is large. It is only for
|
||||
learning and reference purposes and please use it carefully for actual projects.
|
||||
This project can be used for learning and reference. Please deeply experience whether it can meet your needs when using it for actual projects.
|
||||
|
||||
This project may not have fully tested every function point, so there may be bugs. In addition, when the number of nodes is very large, there may be some performance issues. Because everyone can accept different levels of congestion, you can test the maximum number of nodes yourself.
|
||||
|
||||
If you have suggestions or find bugs, you can submit [issues](https://github.com/wanglin2/mind-map/issues) here.
|
||||
|
||||
The built-in themes and icons in the project come from:
|
||||
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Introduction</h1>
|
||||
<p><code>simple-mind-map</code> is a simple and powerful web mind map library, not dependent on any specific framework.</p>
|
||||
<p><code>simple-mind-map</code> is a simple and powerful web mind map library, not dependent on any specific framework. Can help you quickly develop mind mapping products.</p>
|
||||
<h2>Features</h2>
|
||||
<ul>
|
||||
<li><input type="checkbox" id="checkbox36" checked="true"><label for="checkbox36">Plugin architecture. In addition to core functions, other functions are provided as plugins, which can be used as needed to reduce the overall volume</label></li>
|
||||
<li><input type="checkbox" id="checkbox37" checked="true"><label for="checkbox37">Supports four types of structures: logical structure diagrams, mind maps,</label>
|
||||
<li><input type="checkbox" id="checkbox216" checked="true"><label for="checkbox216">Plugin architecture. In addition to core functions, other functions are provided as plugins, which can be used as needed to reduce the overall volume</label></li>
|
||||
<li><input type="checkbox" id="checkbox217" checked="true"><label for="checkbox217">Supports four types of structures: logical structure diagrams, mind maps,</label>
|
||||
organizational structure diagrams, and directory organization diagrams</li>
|
||||
<li><input type="checkbox" id="checkbox38" checked="true"><label for="checkbox38">Built-in multiple themes and allows for highly customized styles, and support register new themes</label></li>
|
||||
<li><input type="checkbox" id="checkbox39" checked="true"><label for="checkbox39">Supports shortcuts</label></li>
|
||||
<li><input type="checkbox" id="checkbox40" checked="true"><label for="checkbox40">Node content supports images, icons, hyperlinks, notes, tags, and</label>
|
||||
<li><input type="checkbox" id="checkbox218" checked="true"><label for="checkbox218">Built-in multiple themes and allows for highly customized styles, and support register new themes</label></li>
|
||||
<li><input type="checkbox" id="checkbox219" checked="true"><label for="checkbox219">Supports shortcuts</label></li>
|
||||
<li><input type="checkbox" id="checkbox220" checked="true"><label for="checkbox220">Node content supports images, icons, hyperlinks, notes, tags, and</label>
|
||||
summaries</li>
|
||||
<li><input type="checkbox" id="checkbox41" checked="true"><label for="checkbox41">Supports forward and backward navigation</label></li>
|
||||
<li><input type="checkbox" id="checkbox42" checked="true"><label for="checkbox42">Supports dragging and scaling</label></li>
|
||||
<li><input type="checkbox" id="checkbox43" checked="true"><label for="checkbox43">Supports right-click and Ctrl + left-click to select multiple items</label></li>
|
||||
<li><input type="checkbox" id="checkbox44" checked="true"><label for="checkbox44">Supports free dragging and dragging to adjust nodes</label></li>
|
||||
<li><input type="checkbox" id="checkbox45" checked="true"><label for="checkbox45">Supports various node shapes</label></li>
|
||||
<li><input type="checkbox" id="checkbox46" checked="true"><label for="checkbox46">Supports export to json, png, svg, pdf, and import from json, xmind</label></li>
|
||||
<li><input type="checkbox" id="checkbox47" checked="true"><label for="checkbox47">Supports mini map、support watermark</label></li>
|
||||
<li><input type="checkbox" id="checkbox48" checked="true"><label for="checkbox48">Supports associative lines</label></li>
|
||||
<li><input type="checkbox" id="checkbox221" checked="true"><label for="checkbox221">Supports forward and backward navigation</label></li>
|
||||
<li><input type="checkbox" id="checkbox222" checked="true"><label for="checkbox222">Supports dragging and scaling</label></li>
|
||||
<li><input type="checkbox" id="checkbox223" checked="true"><label for="checkbox223">Supports right-click and Ctrl + left-click to select multiple items</label></li>
|
||||
<li><input type="checkbox" id="checkbox224" checked="true"><label for="checkbox224">Supports free dragging and dragging to adjust nodes</label></li>
|
||||
<li><input type="checkbox" id="checkbox225" checked="true"><label for="checkbox225">Supports various node shapes</label></li>
|
||||
<li><input type="checkbox" id="checkbox226" checked="true"><label for="checkbox226">Supports export to json, png, svg, pdf markdown, and import from json, xmind, markdown</label></li>
|
||||
<li><input type="checkbox" id="checkbox227" checked="true"><label for="checkbox227">Supports mini map、support watermark</label></li>
|
||||
<li><input type="checkbox" id="checkbox228" checked="true"><label for="checkbox228">Supports associative lines</label></li>
|
||||
</ul>
|
||||
<h2>Table of Contents</h2>
|
||||
<h2>Repository Catalog Introduction</h2>
|
||||
<p>1.<code>simple-mind-map</code></p>
|
||||
<p>This is a mind map tool library that is framework-agnostic and can be used with
|
||||
<p>This is a mind map library that is framework-agnostic and can be used with
|
||||
frameworks such as Vue and React, or without a framework.</p>
|
||||
<p>2.<code>web</code></p>
|
||||
<p>This is an online mind map built using the <code>simple-mind-map</code> library and based
|
||||
on <code>Vue2.x</code> and <code>ElementUI</code>. Features include:</p>
|
||||
<ul>
|
||||
<li><input type="checkbox" id="checkbox49" checked="true"><label for="checkbox49">Toolbar, which supports inserting and deleting nodes, and editing node</label>
|
||||
<li><input type="checkbox" id="checkbox229" checked="true"><label for="checkbox229">Toolbar, which supports inserting and deleting nodes, and editing node</label>
|
||||
images, icons, hyperlinks, notes, tags, and summaries</li>
|
||||
<li><input type="checkbox" id="checkbox50" checked="true"><label for="checkbox50">Sidebar, with panels for basic style settings, node style settings,</label>
|
||||
<li><input type="checkbox" id="checkbox230" checked="true"><label for="checkbox230">Sidebar, with panels for basic style settings, node style settings,</label>
|
||||
outline, theme selection, and structure selection</li>
|
||||
<li><input type="checkbox" id="checkbox51" checked="true"><label for="checkbox51">Import and export functionality; data is saved in the browser's local</label>
|
||||
<li><input type="checkbox" id="checkbox231" checked="true"><label for="checkbox231">Import and export functionality; data is saved in the browser's local</label>
|
||||
storage by default, but it also supports creating, opening, and editing
|
||||
local files on the computer directly</li>
|
||||
<li><input type="checkbox" id="checkbox52" checked="true"><label for="checkbox52">Right-click menu, which supports operations such as expanding, collapsing,</label>
|
||||
<li><input type="checkbox" id="checkbox232" checked="true"><label for="checkbox232">Right-click menu, which supports operations such as expanding, collapsing,</label>
|
||||
and organizing layout</li>
|
||||
<li><input type="checkbox" id="checkbox53" checked="true"><label for="checkbox53">Bottom bar, which supports node and word count statistics, switching</label>
|
||||
<li><input type="checkbox" id="checkbox233" checked="true"><label for="checkbox233">Bottom bar, which supports node and word count statistics, switching</label>
|
||||
between edit and read-only modes, zooming in and out, and switching to
|
||||
full screen, support mini map</li>
|
||||
</ul>
|
||||
@@ -50,9 +50,9 @@ full screen, support mini map</li>
|
||||
<p><a href="https://juejin.cn/post/7199666255883927612">When you press the direction key, how does the TV find the next focus</a></p>
|
||||
<p><a href="https://juejin.cn/post/7204854015463538744">How to simulate the background image style of css in canvas</a></p>
|
||||
<h2>Special Note</h2>
|
||||
<p>This project is rough and has not been thoroughly tested, its features are not
|
||||
yet fully developed, and there are some performance issues, especially when the number of nodes is large. It is only for
|
||||
learning and reference purposes and please use it carefully for actual projects.</p>
|
||||
<p>This project can be used for learning and reference. Please deeply experience whether it can meet your needs when using it for actual projects.</p>
|
||||
<p>This project may not have fully tested every function point, so there may be bugs. In addition, when the number of nodes is very large, there may be some performance issues. Because everyone can accept different levels of congestion, you can test the maximum number of nodes yourself.</p>
|
||||
<p>If you have suggestions or find bugs, you can submit <a href="https://github.com/wanglin2/mind-map/issues">issues</a> here.</p>
|
||||
<p>The built-in themes and icons in the project come from:</p>
|
||||
<p><a href="https://naotu.baidu.com/">Baidu Mind Map</a></p>
|
||||
<p><a href="https://www.zhixi.com/">Zhixi Mind Map</a></p>
|
||||
|
||||
35
web/src/pages/Doc/en/markdown/index.md
Normal file
35
web/src/pages/Doc/en/markdown/index.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Markdown parse
|
||||
|
||||
> v0.4.7+
|
||||
|
||||
Provides methods for importing and exporting `Markdown` files.
|
||||
|
||||
## Import
|
||||
|
||||
```js
|
||||
import markdown from 'simple-mind-map/src/parse/markdown.js'
|
||||
```
|
||||
|
||||
If you are using the file in the format of `umd`, you can obtain it in the following way:
|
||||
|
||||
```html
|
||||
<script src="simple-mind-map/dist/simpleMindMap.umd.min.js"></script>
|
||||
```
|
||||
|
||||
```js
|
||||
MindMap.markdown
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
### transformToMarkdown(data)
|
||||
|
||||
- `data`: Mind map data can be obtained using the `mindMap.getData()` method.
|
||||
|
||||
Convert mind map data into `Markdown` format data, and the returned data is a string.
|
||||
|
||||
### transformMarkdownTo(mdContent)
|
||||
|
||||
- `mdContent`: The `Markdown` data to convert, string type.
|
||||
|
||||
Convert the `Markdown` string into node tree data and return a `Promise` instance. You can use the `mindMap.setData()` method to render the converted data onto the canvas.
|
||||
39
web/src/pages/Doc/en/markdown/index.vue
Normal file
39
web/src/pages/Doc/en/markdown/index.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Markdown parse</h1>
|
||||
<blockquote>
|
||||
<p>v0.4.7+</p>
|
||||
</blockquote>
|
||||
<p>Provides methods for importing and exporting <code>Markdown</code> files.</p>
|
||||
<h2>Import</h2>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> markdown <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/parse/markdown.js'</span>
|
||||
</code></pre>
|
||||
<p>If you are using the file in the format of <code>umd</code>, you can obtain it in the following way:</p>
|
||||
<pre class="hljs"><code><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"simple-mind-map/dist/simpleMindMap.umd.min.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
|
||||
</code></pre>
|
||||
<pre class="hljs"><code>MindMap.markdown
|
||||
</code></pre>
|
||||
<h2>Methods</h2>
|
||||
<h3>transformToMarkdown(data)</h3>
|
||||
<ul>
|
||||
<li><code>data</code>: Mind map data can be obtained using the <code>mindMap.getData()</code> method.</li>
|
||||
</ul>
|
||||
<p>Convert mind map data into <code>Markdown</code> format data, and the returned data is a string.</p>
|
||||
<h3>transformMarkdownTo(mdContent)</h3>
|
||||
<ul>
|
||||
<li><code>mdContent</code>: The <code>Markdown</code> data to convert, string type.</li>
|
||||
</ul>
|
||||
<p>Convert the <code>Markdown</code> string into node tree data and return a <code>Promise</code> instance. You can use the <code>mindMap.setData()</code> method to render the converted data onto the canvas.</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -56,29 +56,29 @@ Whether the node is currently being dragged
|
||||
|
||||
## Methods
|
||||
|
||||
### addChildren(node)
|
||||
|
||||
Add a child node
|
||||
|
||||
### getSize()
|
||||
|
||||
Calculate the width and height of the node, return a boolean indicating whether
|
||||
the width and height have changed
|
||||
|
||||
### renderNode()
|
||||
|
||||
Render the node to the canvas, will remove the old content node and create a new
|
||||
one
|
||||
Update the width and height of the node by recreating the node content, and return a Boolean value indicating whether the width and height have changed
|
||||
|
||||
### render()
|
||||
|
||||
Recursively render this node and all its child nodes. The first call will create
|
||||
the node content, subsequent calls will only update the node position. To
|
||||
re-render the content, set the `initRender` attribute to `true` first.
|
||||
Recursively render this node and all its child nodes
|
||||
|
||||
### updateNodeShape()
|
||||
|
||||
> v0.5.0+
|
||||
|
||||
Update node shape nodes. For example, when the node status changes, call this method to display or deactivate the style.
|
||||
|
||||
### remove()
|
||||
|
||||
Recursively delete this node and all its child nodes
|
||||
Recursive deletion, which is only deleted from the canvas. The node container is still present, and can be reinserted back into the canvas in the future
|
||||
|
||||
### destroy()
|
||||
|
||||
> v0.5.0+
|
||||
|
||||
Destroying a node will not only delete it from the canvas, but also directly empty the original node, which cannot be inserted back into the canvas in the future
|
||||
|
||||
### renderLine()
|
||||
|
||||
|
||||
@@ -31,20 +31,22 @@
|
||||
</blockquote>
|
||||
<p>Whether the node is currently being dragged</p>
|
||||
<h2>Methods</h2>
|
||||
<h3>addChildren(node)</h3>
|
||||
<p>Add a child node</p>
|
||||
<h3>getSize()</h3>
|
||||
<p>Calculate the width and height of the node, return a boolean indicating whether
|
||||
the width and height have changed</p>
|
||||
<h3>renderNode()</h3>
|
||||
<p>Render the node to the canvas, will remove the old content node and create a new
|
||||
one</p>
|
||||
<p>Update the width and height of the node by recreating the node content, and return a Boolean value indicating whether the width and height have changed</p>
|
||||
<h3>render()</h3>
|
||||
<p>Recursively render this node and all its child nodes. The first call will create
|
||||
the node content, subsequent calls will only update the node position. To
|
||||
re-render the content, set the <code>initRender</code> attribute to <code>true</code> first.</p>
|
||||
<p>Recursively render this node and all its child nodes</p>
|
||||
<h3>updateNodeShape()</h3>
|
||||
<blockquote>
|
||||
<p>v0.5.0+</p>
|
||||
</blockquote>
|
||||
<p>Update node shape nodes. For example, when the node status changes, call this method to display or deactivate the style.</p>
|
||||
<h3>remove()</h3>
|
||||
<p>Recursively delete this node and all its child nodes</p>
|
||||
<p>Recursive deletion, which is only deleted from the canvas. The node container is still present, and can be reinserted back into the canvas in the future</p>
|
||||
<h3>destroy()</h3>
|
||||
<blockquote>
|
||||
<p>v0.5.0+</p>
|
||||
</blockquote>
|
||||
<p>Destroying a node will not only delete it from the canvas, but also directly empty the original node, which cannot be inserted back into the canvas in the future</p>
|
||||
<h3>renderLine()</h3>
|
||||
<p>Re-render the line from this node to its child nodes</p>
|
||||
<h3>removeLine()</h3>
|
||||
|
||||
@@ -65,6 +65,12 @@ Replace the built-in font size list during rich text editing. The built-in list
|
||||
|
||||
Select All. When the node is being edited, you can select all the text in the node through this method.
|
||||
|
||||
### focus()
|
||||
|
||||
> v0.4.7+
|
||||
|
||||
Focus.
|
||||
|
||||
### formatText(config = {})
|
||||
|
||||
- `config`:Object. The key is the style attribute and the value is the style value. The complete configuration is as follows:
|
||||
|
||||
@@ -50,6 +50,11 @@ MindMap.usePlugin(RichText, opt?)
|
||||
<h2>Method</h2>
|
||||
<h3>selectAll()</h3>
|
||||
<p>Select All. When the node is being edited, you can select all the text in the node through this method.</p>
|
||||
<h3>focus()</h3>
|
||||
<blockquote>
|
||||
<p>v0.4.7+</p>
|
||||
</blockquote>
|
||||
<p>Focus.</p>
|
||||
<h3>formatText(config = {})</h3>
|
||||
<ul>
|
||||
<li><code>config</code>:Object. The key is the style attribute and the value is the style value. The complete configuration is as follows:</li>
|
||||
|
||||
@@ -81,6 +81,8 @@ npm i simple-mind-map
|
||||
|
||||
## Usage
|
||||
|
||||
> The `web` directory of this repository provides a complete project based on `Vue2`. If you encounter any doubts about using it, you can refer to the implementation of this project.
|
||||
|
||||
```html
|
||||
<div id="mindMapContainer"></div>
|
||||
```
|
||||
|
||||
@@ -57,6 +57,9 @@ compile this dependency:</p>
|
||||
<p>Other projects should modify the packaging configuration as needed.</p>
|
||||
</blockquote>
|
||||
<h2>Usage</h2>
|
||||
<blockquote>
|
||||
<p>The <code>web</code> directory of this repository provides a complete project based on <code>Vue2</code>. If you encounter any doubts about using it, you can refer to the implementation of this project.</p>
|
||||
</blockquote>
|
||||
<pre class="hljs"><code><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"mindMapContainer"</span>></span><span class="hljs-tag"></<span class="hljs-name">div</span>></span>
|
||||
</code></pre>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> MindMap <span class="hljs-keyword">from</span> <span class="hljs-string">"simple-mind-map"</span>;
|
||||
|
||||
@@ -12,9 +12,11 @@ import xmind from 'simple-mind-map/src/parse/xmind.js'
|
||||
|
||||
If you are using the file in the format of `umd`, you can obtain it in the following way:
|
||||
|
||||
```js
|
||||
import MindMap from "simple-mind-map/dist/simpleMindMap.umd.min"
|
||||
```html
|
||||
<script src="simple-mind-map/dist/simpleMindMap.umd.min.js"></script>
|
||||
```
|
||||
|
||||
```js
|
||||
MindMap.xmind
|
||||
```
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> xmind <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/parse/xmind.js'</span>
|
||||
</code></pre>
|
||||
<p>If you are using the file in the format of <code>umd</code>, you can obtain it in the following way:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> MindMap <span class="hljs-keyword">from</span> <span class="hljs-string">"simple-mind-map/dist/simpleMindMap.umd.min"</span>
|
||||
|
||||
MindMap.xmind
|
||||
<pre class="hljs"><code><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"simple-mind-map/dist/simpleMindMap.umd.min.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
|
||||
</code></pre>
|
||||
<pre class="hljs"><code>MindMap.xmind
|
||||
</code></pre>
|
||||
<h2>Methods</h2>
|
||||
<h3>xmind.parseXmindFile(file)</h3>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
|
||||
export default [{"lang":"zh","children":[{"path":"associativeLine","title":"AssociativeLine 插件"},{"path":"batchExecution","title":"BatchExecution实例"},{"path":"changelog","title":"Changelog"},{"path":"command","title":"Command实例"},{"path":"constructor","title":"构造函数"},{"path":"doExport","title":"Export 插件"},{"path":"drag","title":"Drag插件"},{"path":"introduction","title":"简介"},{"path":"keyboardNavigation","title":"KeyboardNavigation插件"},{"path":"keyCommand","title":"KeyCommand实例"},{"path":"miniMap","title":"MiniMap插件"},{"path":"node","title":"Node实例"},{"path":"render","title":"Render实例"},{"path":"richText","title":"RichText插件"},{"path":"select","title":"Select 插件 "},{"path":"start","title":"开始"},{"path":"translate","title":"参与翻译"},{"path":"utils","title":"内置工具方法"},{"path":"view","title":"View实例"},{"path":"watermark","title":"Watermark插件"},{"path":"xmind","title":"XMind解析"}]},{"lang":"en","children":[{"path":"associativeLine","title":"AssociativeLine plugin"},{"path":"batchExecution","title":"batchExecution instance"},{"path":"changelog","title":"Changelog"},{"path":"command","title":"command instance"},{"path":"constructor","title":"Constructor"},{"path":"doExport","title":"Export plugin"},{"path":"drag","title":"Drag plugin"},{"path":"introduction","title":"Introduction"},{"path":"keyboardNavigation","title":"KeyboardNavigation plugin"},{"path":"keyCommand","title":"KeyCommand instance"},{"path":"miniMap","title":"MiniMap plugin"},{"path":"node","title":"Node instance"},{"path":"render","title":"Render instance"},{"path":"richText","title":"RichText plugin"},{"path":"select","title":"Select plugin"},{"path":"start","title":"Start"},{"path":"translate","title":"Participate in translation"},{"path":"utils","title":"Utility Methods"},{"path":"view","title":"View instance"},{"path":"watermark","title":"Watermark plugin"},{"path":"xmind","title":"XMind parse"}]}]
|
||||
export default [{"lang":"zh","children":[{"path":"associativeLine","title":"AssociativeLine 插件"},{"path":"batchExecution","title":"BatchExecution实例"},{"path":"changelog","title":"Changelog"},{"path":"command","title":"Command实例"},{"path":"constructor","title":"构造函数"},{"path":"doExport","title":"Export 插件"},{"path":"drag","title":"Drag插件"},{"path":"introduction","title":"简介"},{"path":"keyboardNavigation","title":"KeyboardNavigation插件"},{"path":"keyCommand","title":"KeyCommand实例"},{"path":"miniMap","title":"MiniMap插件"},{"path":"node","title":"Node实例"},{"path":"render","title":"Render实例"},{"path":"richText","title":"RichText插件"},{"path":"select","title":"Select 插件 "},{"path":"start","title":"开始"},{"path":"translate","title":"参与翻译"},{"path":"utils","title":"内置工具方法"},{"path":"view","title":"View实例"},{"path":"watermark","title":"Watermark插件"},{"path":"xmind","title":"XMind解析"},{"path":"markdown","title":"Markdown解析"}]},{"lang":"en","children":[{"path":"associativeLine","title":"AssociativeLine plugin"},{"path":"batchExecution","title":"batchExecution instance"},{"path":"changelog","title":"Changelog"},{"path":"command","title":"command instance"},{"path":"constructor","title":"Constructor"},{"path":"doExport","title":"Export plugin"},{"path":"drag","title":"Drag plugin"},{"path":"introduction","title":"Introduction"},{"path":"keyboardNavigation","title":"KeyboardNavigation plugin"},{"path":"keyCommand","title":"KeyCommand instance"},{"path":"miniMap","title":"MiniMap plugin"},{"path":"node","title":"Node instance"},{"path":"render","title":"Render instance"},{"path":"richText","title":"RichText plugin"},{"path":"select","title":"Select plugin"},{"path":"start","title":"Start"},{"path":"translate","title":"Participate in translation"},{"path":"utils","title":"Utility Methods"},{"path":"view","title":"View instance"},{"path":"watermark","title":"Watermark plugin"},{"path":"xmind","title":"XMind parse"},{"path":"markdown","title":"Markdown parse"}]}]
|
||||
|
||||
@@ -1,5 +1,51 @@
|
||||
# Changelog
|
||||
|
||||
## 0.5.2
|
||||
|
||||
修复:1.导出的`json`数据中去除`uid`;2.重新渲染时清空节点缓存池。
|
||||
|
||||
## 0.5.1
|
||||
|
||||
优化:1.只有当鼠标在画布内才响应快捷键事件。
|
||||
|
||||
修复:1.修复快速操作时节点位置不正确的问题。
|
||||
|
||||
## 0.5.0
|
||||
|
||||
这个版本主要是代码层面的改动和优化,核心是为了提升渲染性能,减少卡顿问题。
|
||||
|
||||
新增:1.支持自定义展开收起节点图标和颜色;
|
||||
|
||||
优化:1.优化渲染逻辑,设置主题、前进回退等操作不再全量渲染;
|
||||
|
||||
2.优化节点拖拽逻辑,修复无法拖动到两个节点之间的问题;
|
||||
|
||||
3.收起全部节点操作增加回到中心点的逻辑;
|
||||
|
||||
4.修复短时间多次触发渲染造成节点乱飞和错乱的问题;
|
||||
|
||||
5.优化节点编辑的体验;
|
||||
|
||||
修复:1.修复setData方法不触发历史记录的问题;
|
||||
|
||||
修改:从0.5.0版本开始,考虑性能问题,节点激活状态只能修改形状相关的样式:
|
||||
|
||||
```js
|
||||
[
|
||||
'fillColor',
|
||||
'borderColor',
|
||||
'borderWidth',
|
||||
'borderDasharray',
|
||||
'borderRadius'
|
||||
]
|
||||
```
|
||||
|
||||
## 0.4.7
|
||||
|
||||
优化:1.富文本编辑时初始聚焦时不再默认全选;2.富文本编辑时使用节点填充色作为背景色,避免节点颜色为白色时看不见。 3.节点激活状态切换不再触发历史记录。 4.短时间多次触发历史记录,只会添加最后一次的数据。 5.优化历史记录添加,当有回退时,再次添加新记录时删除当前指针后面的历史数据。
|
||||
|
||||
新增:1.支持导入和导出Markdown格式文件。 2.支持配置插入节点时的初始文字。 3.扩展插入节点和删除节点的命令,支持指定节点。
|
||||
|
||||
## 0.4.6
|
||||
|
||||
新增:1.关联线支持调整控制点。
|
||||
|
||||
@@ -1,6 +1,36 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Changelog</h1>
|
||||
<h2>0.5.2</h2>
|
||||
<p>修复:1.导出的<code>json</code>数据中去除<code>uid</code>;2.重新渲染时清空节点缓存池。</p>
|
||||
<h2>0.5.1</h2>
|
||||
<p>优化:1.只有当鼠标在画布内才响应快捷键事件。</p>
|
||||
<p>修复:1.修复快速操作时节点位置不正确的问题。</p>
|
||||
<h2>0.5.0</h2>
|
||||
<p>这个版本主要是代码层面的改动和优化,核心是为了提升渲染性能,减少卡顿问题。</p>
|
||||
<p>新增:1.支持自定义展开收起节点图标和颜色;</p>
|
||||
<p>优化:1.优化渲染逻辑,设置主题、前进回退等操作不再全量渲染;</p>
|
||||
<pre><code> 2.优化节点拖拽逻辑,修复无法拖动到两个节点之间的问题;
|
||||
|
||||
3.收起全部节点操作增加回到中心点的逻辑;
|
||||
|
||||
4.修复短时间多次触发渲染造成节点乱飞和错乱的问题;
|
||||
|
||||
5.优化节点编辑的体验;
|
||||
</code></pre>
|
||||
<p>修复:1.修复setData方法不触发历史记录的问题;</p>
|
||||
<p>修改:从0.5.0版本开始,考虑性能问题,节点激活状态只能修改形状相关的样式:</p>
|
||||
<pre class="hljs"><code>[
|
||||
<span class="hljs-string">'fillColor'</span>,
|
||||
<span class="hljs-string">'borderColor'</span>,
|
||||
<span class="hljs-string">'borderWidth'</span>,
|
||||
<span class="hljs-string">'borderDasharray'</span>,
|
||||
<span class="hljs-string">'borderRadius'</span>
|
||||
]
|
||||
</code></pre>
|
||||
<h2>0.4.7</h2>
|
||||
<p>优化:1.富文本编辑时初始聚焦时不再默认全选;2.富文本编辑时使用节点填充色作为背景色,避免节点颜色为白色时看不见。 3.节点激活状态切换不再触发历史记录。 4.短时间多次触发历史记录,只会添加最后一次的数据。 5.优化历史记录添加,当有回退时,再次添加新记录时删除当前指针后面的历史数据。</p>
|
||||
<p>新增:1.支持导入和导出Markdown格式文件。 2.支持配置插入节点时的初始文字。 3.扩展插入节点和删除节点的命令,支持指定节点。</p>
|
||||
<h2>0.4.6</h2>
|
||||
<p>新增:1.关联线支持调整控制点。</p>
|
||||
<p>优化:1.添加历史数据时过滤和上一次相比没有改变的数据。</p>
|
||||
|
||||
@@ -44,6 +44,13 @@ const mindMap = new MindMap({
|
||||
| customHandleMousewheel(v0.4.3+) | Function | null | 自定义鼠标滚轮事件处理,可以传一个函数,回调参数为事件对象 | |
|
||||
| mousewheelAction(v0.4.3+) | String | zoom | 鼠标滚轮的行为,`zoom`(放大缩小)、`move`(上下移动)。如果`customHandleMousewheel`传了自定义函数,这个属性不生效 | |
|
||||
| mousewheelMoveStep(v0.4.3+) | Number | 100 | 当`mousewheelAction`设为`move`时,可以通过该属性控制鼠标滚动一下视图移动的步长,单位`px` | |
|
||||
| defaultInsertSecondLevelNodeText(v0.4.7+) | String | 二级节点 | 默认插入的二级节点的文字 | |
|
||||
| defaultInsertBelowSecondLevelNodeText(v0.4.7+) | String | 分支主题 | 默认插入的二级以下节点的文字 | |
|
||||
| expandBtnStyle(v0.5.0+) | Object | { color: '#808080', fill: '#fff' } | 展开收起按钮的颜色 | |
|
||||
| expandBtnIcon(v0.5.0+) | Object | { open: '', close: '' } | 自定义展开收起按钮的图标,可以传图标的svg字符串 | |
|
||||
| enableShortcutOnlyWhenMouseInSvg(v0.5.1+) | Boolean | true | 是否只有当鼠标在画布内才响应快捷键事件 | |
|
||||
| enableNodeTransitionMove(v0.5.1+) | Boolean | true | 是否开启节点动画过渡 | |
|
||||
| nodeTransitionMoveDuration(v0.5.1+) | Number | 300 | 如果开启节点动画过渡,可以通过该属性设置过渡的时间,单位ms | |
|
||||
|
||||
### 水印配置
|
||||
|
||||
@@ -193,6 +200,8 @@ mindMap.setTheme('主题名称')
|
||||
| node_dragging(v0.4.5+) | 当某个节点被拖拽时触发 | node(当前被拖拽的节点) |
|
||||
| node_dragend(v0.4.5+) | 节点被拖拽结束时触发 | |
|
||||
| associative_line_click(v0.4.5+) | 点击某条关联线时触发 | path(连接线节点)、clickPath(不可见的点击线节点)、node(起始节点)、toNode(目标节点) |
|
||||
| svg_mouseenter(v0.5.1+) | 鼠标移入svg画布时触发 | e(事件对象) |
|
||||
| svg_mouseleave(v0.5.1+) | 鼠标移出svg画布时触发 | e(事件对象) |
|
||||
|
||||
### emit(event, ...args)
|
||||
|
||||
@@ -263,11 +272,11 @@ mindMap.updateConfig({
|
||||
| SELECT_ALL | 全选 | |
|
||||
| BACK | 回退指定的步数 | step(要回退的步数,默认为1) |
|
||||
| FORWARD | 前进指定的步数 | step(要前进的步数,默认为1) |
|
||||
| INSERT_NODE | 插入同级节点,操作节点为当前激活的节点,如果有多个激活节点,只会对第一个有效 | openEdit(v0.4.6+,是否激活新插入的节点并进入编辑模式,默认为`true`) |
|
||||
| INSERT_CHILD_NODE | 插入子节点,操作节点为当前激活的节点 | openEdit(v0.4.6+,是否激活新插入的节点并进入编辑模式,默认为`true`) |
|
||||
| INSERT_NODE | 插入同级节点,操作节点为当前激活的节点或指定节点,如果有多个激活节点,只会对第一个有效 | openEdit(v0.4.6+,是否激活新插入的节点并进入编辑模式,默认为`true`)、 appointNodes(v0.4.7+,可选,指定节点,指定多个节点可以传一个数组)、 appointData(可选,指定新创建节点的数据,比如{text: 'xxx', ...},详细结构可以参考[https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js)) |
|
||||
| INSERT_CHILD_NODE | 插入子节点,操作节点为当前激活的节点或指定节点 | openEdit(v0.4.6+,是否激活新插入的节点并进入编辑模式,默认为`true`)、 appointNodes(v0.4.7+,可选,指定节点,指定多个节点可以传一个数组)、 appointData(可选,指定新创建节点的数据,比如{text: 'xxx', ...},详细结构可以参考[https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js)) |
|
||||
| UP_NODE | 上移节点,操作节点为当前激活的节点,如果有多个激活节点,只会对第一个有效,对根节点或在列表里的第一个节点使用无效 | |
|
||||
| DOWN_NODE | 操作节点为当前激活的节点,如果有多个激活节点,只会对第一个有效,对根节点或在列表里的最后一个节点使用无效 | |
|
||||
| REMOVE_NODE | 删除节点,操作节点为当前激活的节点 | |
|
||||
| REMOVE_NODE | 删除节点,操作节点为当前激活的节点或指定节点 | appointNodes(v0.4.7+,可选,指定节点,指定多个节点可以传一个数组) |
|
||||
| PASTE_NODE | 粘贴节点到节点,操作节点为当前激活的节点 | data(要粘贴的节点数据,一般通过`renderer.copyNode()`方法和`renderer.cutNode()`方法获取) |
|
||||
| CUT_NODE | 剪切节点,操作节点为当前激活的节点,如果有多个激活节点,只会对第一个有效,对根节点使用无效 | callback(回调函数,剪切的节点数据会通过调用该函数并通过参数返回) |
|
||||
| SET_NODE_STYLE | 修改节点样式 | node(要设置样式的节点)、prop(样式属性)、value(样式属性值)、isActive(布尔值,是否设置的是激活状态的样式) |
|
||||
|
||||
@@ -168,6 +168,55 @@
|
||||
<td>当<code>mousewheelAction</code>设为<code>move</code>时,可以通过该属性控制鼠标滚动一下视图移动的步长,单位<code>px</code></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>defaultInsertSecondLevelNodeText(v0.4.7+)</td>
|
||||
<td>String</td>
|
||||
<td>二级节点</td>
|
||||
<td>默认插入的二级节点的文字</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>defaultInsertBelowSecondLevelNodeText(v0.4.7+)</td>
|
||||
<td>String</td>
|
||||
<td>分支主题</td>
|
||||
<td>默认插入的二级以下节点的文字</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>expandBtnStyle(v0.5.0+)</td>
|
||||
<td>Object</td>
|
||||
<td>{ color: '#808080', fill: '#fff' }</td>
|
||||
<td>展开收起按钮的颜色</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>expandBtnIcon(v0.5.0+)</td>
|
||||
<td>Object</td>
|
||||
<td>{ open: '', close: '' }</td>
|
||||
<td>自定义展开收起按钮的图标,可以传图标的svg字符串</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>enableShortcutOnlyWhenMouseInSvg(v0.5.1+)</td>
|
||||
<td>Boolean</td>
|
||||
<td>true</td>
|
||||
<td>是否只有当鼠标在画布内才响应快捷键事件</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>enableNodeTransitionMove(v0.5.1+)</td>
|
||||
<td>Boolean</td>
|
||||
<td>true</td>
|
||||
<td>是否开启节点动画过渡</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>nodeTransitionMoveDuration(v0.5.1+)</td>
|
||||
<td>Number</td>
|
||||
<td>300</td>
|
||||
<td>如果开启节点动画过渡,可以通过该属性设置过渡的时间,单位ms</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>水印配置</h3>
|
||||
@@ -455,6 +504,16 @@ mindMap.setTheme(<span class="hljs-string">'主题名称'</span>)
|
||||
<td>点击某条关联线时触发</td>
|
||||
<td>path(连接线节点)、clickPath(不可见的点击线节点)、node(起始节点)、toNode(目标节点)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>svg_mouseenter(v0.5.1+)</td>
|
||||
<td>鼠标移入svg画布时触发</td>
|
||||
<td>e(事件对象)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>svg_mouseleave(v0.5.1+)</td>
|
||||
<td>鼠标移出svg画布时触发</td>
|
||||
<td>e(事件对象)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>emit(event, ...args)</h3>
|
||||
@@ -520,13 +579,13 @@ mindMap.setTheme(<span class="hljs-string">'主题名称'</span>)
|
||||
</tr>
|
||||
<tr>
|
||||
<td>INSERT_NODE</td>
|
||||
<td>插入同级节点,操作节点为当前激活的节点,如果有多个激活节点,只会对第一个有效</td>
|
||||
<td>openEdit(v0.4.6+,是否激活新插入的节点并进入编辑模式,默认为<code>true</code>)</td>
|
||||
<td>插入同级节点,操作节点为当前激活的节点或指定节点,如果有多个激活节点,只会对第一个有效</td>
|
||||
<td>openEdit(v0.4.6+,是否激活新插入的节点并进入编辑模式,默认为<code>true</code>)、 appointNodes(v0.4.7+,可选,指定节点,指定多个节点可以传一个数组)、 appointData(可选,指定新创建节点的数据,比如{text: 'xxx', ...},详细结构可以参考<a href="https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js">https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js</a>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>INSERT_CHILD_NODE</td>
|
||||
<td>插入子节点,操作节点为当前激活的节点</td>
|
||||
<td>openEdit(v0.4.6+,是否激活新插入的节点并进入编辑模式,默认为<code>true</code>)</td>
|
||||
<td>插入子节点,操作节点为当前激活的节点或指定节点</td>
|
||||
<td>openEdit(v0.4.6+,是否激活新插入的节点并进入编辑模式,默认为<code>true</code>)、 appointNodes(v0.4.7+,可选,指定节点,指定多个节点可以传一个数组)、 appointData(可选,指定新创建节点的数据,比如{text: 'xxx', ...},详细结构可以参考<a href="https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js">https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js</a>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>UP_NODE</td>
|
||||
@@ -540,8 +599,8 @@ mindMap.setTheme(<span class="hljs-string">'主题名称'</span>)
|
||||
</tr>
|
||||
<tr>
|
||||
<td>REMOVE_NODE</td>
|
||||
<td>删除节点,操作节点为当前激活的节点</td>
|
||||
<td></td>
|
||||
<td>删除节点,操作节点为当前激活的节点或指定节点</td>
|
||||
<td>appointNodes(v0.4.7+,可选,指定节点,指定多个节点可以传一个数组)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>PASTE_NODE</td>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 简介
|
||||
|
||||
`simple-mind-map`是一个简单&强大的Web思维导图库,不依赖任何特定框架。
|
||||
`simple-mind-map`是一个简单&强大的Web思维导图库,不依赖任何特定框架。可以帮助你快速开发思维导图产品。
|
||||
|
||||
## 特性
|
||||
|
||||
@@ -14,19 +14,19 @@
|
||||
- [x] 支持右键和Ctrl+左键两种多选方式
|
||||
- [x] 支持节点自由拖拽、拖拽调整
|
||||
- [x] 支持多种节点形状
|
||||
- [x] 支持导出为`json`、`png`、`svg`、`pdf`,支持从`json`、`xmind`导入
|
||||
- [x] 支持导出为`json`、`png`、`svg`、`pdf`、`markdown`,支持从`json`、`xmind`、`markdown`导入
|
||||
- [x] 支持小地图、支持水印
|
||||
- [x] 支持关联线
|
||||
|
||||
## 目录介绍
|
||||
## 仓库目录介绍
|
||||
|
||||
1.`simple-mind-map`
|
||||
|
||||
思维导图工具库,框架无关,`Vue`、`React`等框架或无框架都可以使用。
|
||||
思维导图库,框架无关,`Vue`、`React`等框架或无框架都可以使用。
|
||||
|
||||
2.`web`
|
||||
|
||||
使用`simple-mind-map`工具库,基于`vue2.x`、`ElementUI`搭建的在线思维导图。特性:
|
||||
使用`simple-mind-map`库,基于`vue2.x`、`ElementUI`搭建的在线思维导图。特性:
|
||||
|
||||
- [x] 工具栏,支持插入节点、删除节点;编辑节点图片、图标、超链接、备注、标签、概要
|
||||
- [x] 侧边栏,基础样式设置面板、节点样式设置面板、大纲面板、主题选择面板、结构选择面板
|
||||
@@ -52,7 +52,11 @@
|
||||
|
||||
## 特别说明
|
||||
|
||||
本项目较粗糙,未进行完整测试,功能尚不是很完善,性能也存在一些问题,尤其当节点数量比较庞大的时候,仅用于学习和参考,请谨慎用于实际项目。
|
||||
本项目可用于学习和参考,用于实际项目时请先深度体验一下是否能满足您的需求。
|
||||
|
||||
本项目可能没有完整测试到每一个功能点,所以可能存在bug,另外,当节点数量非常多的时候,性能也存在一些问题,因为每个人能接受的卡顿程度不一样,所以你可以自行测试节点数量上限。
|
||||
|
||||
如果有建议或发现了bug,可以在此提交[issues](https://github.com/wanglin2/mind-map/issues)。
|
||||
|
||||
项目内置的主题和图标来自于:
|
||||
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>简介</h1>
|
||||
<p><code>simple-mind-map</code>是一个简单&强大的Web思维导图库,不依赖任何特定框架。</p>
|
||||
<p><code>simple-mind-map</code>是一个简单&强大的Web思维导图库,不依赖任何特定框架。可以帮助你快速开发思维导图产品。</p>
|
||||
<h2>特性</h2>
|
||||
<ul>
|
||||
<li><input type="checkbox" id="checkbox54" checked="true"><label for="checkbox54">插件化架构,除核心功能外,其他功能作为插件提供,按需使用,减小整体体积</label></li>
|
||||
<li><input type="checkbox" id="checkbox55" checked="true"><label for="checkbox55">支持逻辑结构图、思维导图、组织结构图、目录组织图四种结构</label></li>
|
||||
<li><input type="checkbox" id="checkbox56" checked="true"><label for="checkbox56">内置多种主题,允许高度自定义样式,支持注册新主题</label></li>
|
||||
<li><input type="checkbox" id="checkbox57" checked="true"><label for="checkbox57">支持快捷键</label></li>
|
||||
<li><input type="checkbox" id="checkbox58" checked="true"><label for="checkbox58">节点内容支持图片、图标、超链接、备注、标签、概要</label></li>
|
||||
<li><input type="checkbox" id="checkbox59" checked="true"><label for="checkbox59">支持前进后退</label></li>
|
||||
<li><input type="checkbox" id="checkbox60" checked="true"><label for="checkbox60">支持拖动、缩放</label></li>
|
||||
<li><input type="checkbox" id="checkbox61" checked="true"><label for="checkbox61">支持右键和Ctrl+左键两种多选方式</label></li>
|
||||
<li><input type="checkbox" id="checkbox62" checked="true"><label for="checkbox62">支持节点自由拖拽、拖拽调整</label></li>
|
||||
<li><input type="checkbox" id="checkbox63" checked="true"><label for="checkbox63">支持多种节点形状</label></li>
|
||||
<li><input type="checkbox" id="checkbox64" checked="true"><label for="checkbox64">支持导出为</label><code>json</code>、<code>png</code>、<code>svg</code>、<code>pdf</code>,支持从<code>json</code>、<code>xmind</code>导入</li>
|
||||
<li><input type="checkbox" id="checkbox65" checked="true"><label for="checkbox65">支持小地图、支持水印</label></li>
|
||||
<li><input type="checkbox" id="checkbox66" checked="true"><label for="checkbox66">支持关联线</label></li>
|
||||
<li><input type="checkbox" id="checkbox198" checked="true"><label for="checkbox198">插件化架构,除核心功能外,其他功能作为插件提供,按需使用,减小整体体积</label></li>
|
||||
<li><input type="checkbox" id="checkbox199" checked="true"><label for="checkbox199">支持逻辑结构图、思维导图、组织结构图、目录组织图四种结构</label></li>
|
||||
<li><input type="checkbox" id="checkbox200" checked="true"><label for="checkbox200">内置多种主题,允许高度自定义样式,支持注册新主题</label></li>
|
||||
<li><input type="checkbox" id="checkbox201" checked="true"><label for="checkbox201">支持快捷键</label></li>
|
||||
<li><input type="checkbox" id="checkbox202" checked="true"><label for="checkbox202">节点内容支持图片、图标、超链接、备注、标签、概要</label></li>
|
||||
<li><input type="checkbox" id="checkbox203" checked="true"><label for="checkbox203">支持前进后退</label></li>
|
||||
<li><input type="checkbox" id="checkbox204" checked="true"><label for="checkbox204">支持拖动、缩放</label></li>
|
||||
<li><input type="checkbox" id="checkbox205" checked="true"><label for="checkbox205">支持右键和Ctrl+左键两种多选方式</label></li>
|
||||
<li><input type="checkbox" id="checkbox206" checked="true"><label for="checkbox206">支持节点自由拖拽、拖拽调整</label></li>
|
||||
<li><input type="checkbox" id="checkbox207" checked="true"><label for="checkbox207">支持多种节点形状</label></li>
|
||||
<li><input type="checkbox" id="checkbox208" checked="true"><label for="checkbox208">支持导出为</label><code>json</code>、<code>png</code>、<code>svg</code>、<code>pdf</code>、<code>markdown</code>,支持从<code>json</code>、<code>xmind</code>、<code>markdown</code>导入</li>
|
||||
<li><input type="checkbox" id="checkbox209" checked="true"><label for="checkbox209">支持小地图、支持水印</label></li>
|
||||
<li><input type="checkbox" id="checkbox210" checked="true"><label for="checkbox210">支持关联线</label></li>
|
||||
</ul>
|
||||
<h2>目录介绍</h2>
|
||||
<h2>仓库目录介绍</h2>
|
||||
<p>1.<code>simple-mind-map</code></p>
|
||||
<p>思维导图工具库,框架无关,<code>Vue</code>、<code>React</code>等框架或无框架都可以使用。</p>
|
||||
<p>思维导图库,框架无关,<code>Vue</code>、<code>React</code>等框架或无框架都可以使用。</p>
|
||||
<p>2.<code>web</code></p>
|
||||
<p>使用<code>simple-mind-map</code>工具库,基于<code>vue2.x</code>、<code>ElementUI</code>搭建的在线思维导图。特性:</p>
|
||||
<p>使用<code>simple-mind-map</code>库,基于<code>vue2.x</code>、<code>ElementUI</code>搭建的在线思维导图。特性:</p>
|
||||
<ul>
|
||||
<li><input type="checkbox" id="checkbox67" checked="true"><label for="checkbox67">工具栏,支持插入节点、删除节点;编辑节点图片、图标、超链接、备注、标签、概要</label></li>
|
||||
<li><input type="checkbox" id="checkbox68" checked="true"><label for="checkbox68">侧边栏,基础样式设置面板、节点样式设置面板、大纲面板、主题选择面板、结构选择面板</label></li>
|
||||
<li><input type="checkbox" id="checkbox69" checked="true"><label for="checkbox69">导入导出功能;数据默认保存在浏览器本地存储,也支持直接创建、打开、编辑电脑本地文件</label></li>
|
||||
<li><input type="checkbox" id="checkbox70" checked="true"><label for="checkbox70">右键菜单,支持展开、收起、整理布局等操作</label></li>
|
||||
<li><input type="checkbox" id="checkbox71" checked="true"><label for="checkbox71">底部栏,支持节点数量、字数统计;支持切换编辑和只读模式;支持放大缩小;支持全屏切换;支持小地图</label></li>
|
||||
<li><input type="checkbox" id="checkbox211" checked="true"><label for="checkbox211">工具栏,支持插入节点、删除节点;编辑节点图片、图标、超链接、备注、标签、概要</label></li>
|
||||
<li><input type="checkbox" id="checkbox212" checked="true"><label for="checkbox212">侧边栏,基础样式设置面板、节点样式设置面板、大纲面板、主题选择面板、结构选择面板</label></li>
|
||||
<li><input type="checkbox" id="checkbox213" checked="true"><label for="checkbox213">导入导出功能;数据默认保存在浏览器本地存储,也支持直接创建、打开、编辑电脑本地文件</label></li>
|
||||
<li><input type="checkbox" id="checkbox214" checked="true"><label for="checkbox214">右键菜单,支持展开、收起、整理布局等操作</label></li>
|
||||
<li><input type="checkbox" id="checkbox215" checked="true"><label for="checkbox215">底部栏,支持节点数量、字数统计;支持切换编辑和只读模式;支持放大缩小;支持全屏切换;支持小地图</label></li>
|
||||
</ul>
|
||||
<p>提供文档页面服务。</p>
|
||||
<p>3.<code>dist</code></p>
|
||||
@@ -39,7 +39,9 @@
|
||||
<p><a href="https://juejin.cn/post/7199666255883927612">当你按下方向键,电视是如何寻找下一个焦点的</a></p>
|
||||
<p><a href="https://juejin.cn/post/7204854015463538744">如何在canvas中模拟css的背景图片样式</a></p>
|
||||
<h2>特别说明</h2>
|
||||
<p>本项目较粗糙,未进行完整测试,功能尚不是很完善,性能也存在一些问题,尤其当节点数量比较庞大的时候,仅用于学习和参考,请谨慎用于实际项目。</p>
|
||||
<p>本项目可用于学习和参考,用于实际项目时请先深度体验一下是否能满足您的需求。</p>
|
||||
<p>本项目可能没有完整测试到每一个功能点,所以可能存在bug,另外,当节点数量非常多的时候,性能也存在一些问题,因为每个人能接受的卡顿程度不一样,所以你可以自行测试节点数量上限。</p>
|
||||
<p>如果有建议或发现了bug,可以在此提交<a href="https://github.com/wanglin2/mind-map/issues">issues</a>。</p>
|
||||
<p>项目内置的主题和图标来自于:</p>
|
||||
<p><a href="https://naotu.baidu.com/">百度脑图</a></p>
|
||||
<p><a href="https://www.zhixi.com/">知犀思维导图</a></p>
|
||||
|
||||
35
web/src/pages/Doc/zh/markdown/index.md
Normal file
35
web/src/pages/Doc/zh/markdown/index.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Markdown解析
|
||||
|
||||
> v0.4.7+
|
||||
|
||||
提供导入和导出`Markdown`文件的方法。
|
||||
|
||||
## 引入
|
||||
|
||||
```js
|
||||
import markdown from 'simple-mind-map/src/parse/markdown.js'
|
||||
```
|
||||
|
||||
如果使用的是`umd`格式的文件,那么可以通过如下方式获取:
|
||||
|
||||
```html
|
||||
<script src="simple-mind-map/dist/simpleMindMap.umd.min.js"></script>
|
||||
```
|
||||
|
||||
```js
|
||||
MindMap.markdown
|
||||
```
|
||||
|
||||
## 方法
|
||||
|
||||
### transformToMarkdown(data)
|
||||
|
||||
- `data`:思维导图数据,可以通过`mindMap.getData()`方法获取。
|
||||
|
||||
将思维导图数据转换成`Markdown`格式数据,返回的是字符串。
|
||||
|
||||
### transformMarkdownTo(mdContent)
|
||||
|
||||
- `mdContent`:要转换的`Markdown`数据,字符串类型。
|
||||
|
||||
将`Markdown`字符串转换成节点树数据,返回一个`Promise`实例。可以使用`mindMap.setData()`方法将转换后的数据渲染到画布上。
|
||||
39
web/src/pages/Doc/zh/markdown/index.vue
Normal file
39
web/src/pages/Doc/zh/markdown/index.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Markdown解析</h1>
|
||||
<blockquote>
|
||||
<p>v0.4.7+</p>
|
||||
</blockquote>
|
||||
<p>提供导入和导出<code>Markdown</code>文件的方法。</p>
|
||||
<h2>引入</h2>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> markdown <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/parse/markdown.js'</span>
|
||||
</code></pre>
|
||||
<p>如果使用的是<code>umd</code>格式的文件,那么可以通过如下方式获取:</p>
|
||||
<pre class="hljs"><code><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"simple-mind-map/dist/simpleMindMap.umd.min.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
|
||||
</code></pre>
|
||||
<pre class="hljs"><code>MindMap.markdown
|
||||
</code></pre>
|
||||
<h2>方法</h2>
|
||||
<h3>transformToMarkdown(data)</h3>
|
||||
<ul>
|
||||
<li><code>data</code>:思维导图数据,可以通过<code>mindMap.getData()</code>方法获取。</li>
|
||||
</ul>
|
||||
<p>将思维导图数据转换成<code>Markdown</code>格式数据,返回的是字符串。</p>
|
||||
<h3>transformMarkdownTo(mdContent)</h3>
|
||||
<ul>
|
||||
<li><code>mdContent</code>:要转换的<code>Markdown</code>数据,字符串类型。</li>
|
||||
</ul>
|
||||
<p>将<code>Markdown</code>字符串转换成节点树数据,返回一个<code>Promise</code>实例。可以使用<code>mindMap.setData()</code>方法将转换后的数据渲染到画布上。</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -56,25 +56,29 @@
|
||||
|
||||
## 方法
|
||||
|
||||
### addChildren(node)
|
||||
|
||||
添加子节点
|
||||
|
||||
### getSize()
|
||||
|
||||
计算节点的宽高,返回一个布尔值,代表是否宽高发生了变化
|
||||
|
||||
### renderNode()
|
||||
|
||||
渲染节点到画布,会移除旧的内容节点,创建新的
|
||||
通过重新创建节点内容更新节点的宽高,返回一个布尔值,代表是否宽高发生了变化
|
||||
|
||||
### render()
|
||||
|
||||
递归渲染该节点及其所有子节点,第一次调用会创建节点内容,后续只会更新节点位置,想要重新渲染内容,可以先把`initRender`属性设为`true`
|
||||
递归渲染该节点及其所有子节点
|
||||
|
||||
### updateNodeShape()
|
||||
|
||||
> v0.5.0+
|
||||
|
||||
更新节点形状节点。比如当节点状态改变后,调用该方法显示或取消激活样式。
|
||||
|
||||
### remove()
|
||||
|
||||
递归删除该节点及其所有子节点
|
||||
递归删除,只是从画布删除,节点容器还在,后续还可以重新插回画布
|
||||
|
||||
### destroy()
|
||||
|
||||
> v0.5.0+
|
||||
|
||||
销毁节点,不但会从画布删除,而且原节点直接置空,后续无法再插回画布
|
||||
|
||||
### renderLine()
|
||||
|
||||
|
||||
@@ -31,16 +31,22 @@
|
||||
</blockquote>
|
||||
<p>节点是否正在拖拽中</p>
|
||||
<h2>方法</h2>
|
||||
<h3>addChildren(node)</h3>
|
||||
<p>添加子节点</p>
|
||||
<h3>getSize()</h3>
|
||||
<p>计算节点的宽高,返回一个布尔值,代表是否宽高发生了变化</p>
|
||||
<h3>renderNode()</h3>
|
||||
<p>渲染节点到画布,会移除旧的内容节点,创建新的</p>
|
||||
<p>通过重新创建节点内容更新节点的宽高,返回一个布尔值,代表是否宽高发生了变化</p>
|
||||
<h3>render()</h3>
|
||||
<p>递归渲染该节点及其所有子节点,第一次调用会创建节点内容,后续只会更新节点位置,想要重新渲染内容,可以先把<code>initRender</code>属性设为<code>true</code></p>
|
||||
<p>递归渲染该节点及其所有子节点</p>
|
||||
<h3>updateNodeShape()</h3>
|
||||
<blockquote>
|
||||
<p>v0.5.0+</p>
|
||||
</blockquote>
|
||||
<p>更新节点形状节点。比如当节点状态改变后,调用该方法显示或取消激活样式。</p>
|
||||
<h3>remove()</h3>
|
||||
<p>递归删除该节点及其所有子节点</p>
|
||||
<p>递归删除,只是从画布删除,节点容器还在,后续还可以重新插回画布</p>
|
||||
<h3>destroy()</h3>
|
||||
<blockquote>
|
||||
<p>v0.5.0+</p>
|
||||
</blockquote>
|
||||
<p>销毁节点,不但会从画布删除,而且原节点直接置空,后续无法再插回画布</p>
|
||||
<h3>renderLine()</h3>
|
||||
<p>重新渲染该节点到其子节点之间的连线</p>
|
||||
<h3>removeLine()</h3>
|
||||
|
||||
@@ -65,6 +65,12 @@ MindMap.usePlugin(RichText, opt?)
|
||||
|
||||
选中全部。当节点正在编辑中可以通过该方法选中节点内的所有文本。
|
||||
|
||||
### focus()
|
||||
|
||||
> v0.4.7+
|
||||
|
||||
聚焦。
|
||||
|
||||
### formatText(config = {})
|
||||
|
||||
- `config`:对象,键为样式属性,值为样式值,完整的配置如下:
|
||||
|
||||
@@ -50,6 +50,11 @@ MindMap.usePlugin(RichText, opt?)
|
||||
<h2>方法</h2>
|
||||
<h3>selectAll()</h3>
|
||||
<p>选中全部。当节点正在编辑中可以通过该方法选中节点内的所有文本。</p>
|
||||
<h3>focus()</h3>
|
||||
<blockquote>
|
||||
<p>v0.4.7+</p>
|
||||
</blockquote>
|
||||
<p>聚焦。</p>
|
||||
<h3>formatText(config = {})</h3>
|
||||
<ul>
|
||||
<li><code>config</code>:对象,键为样式属性,值为样式值,完整的配置如下:</li>
|
||||
|
||||
@@ -72,6 +72,8 @@ npm i simple-mind-map
|
||||
|
||||
## 使用
|
||||
|
||||
> 本仓库的`web`目录提供了一个基于`Vue2`的完整项目,如果你遇到了一些使用上的疑惑,可以参考该项目的实现。
|
||||
|
||||
```html
|
||||
<div id="mindMapContainer"></div>
|
||||
```
|
||||
|
||||
@@ -47,6 +47,9 @@ npm run build
|
||||
<p>其他项目请自行修改打包配置。</p>
|
||||
</blockquote>
|
||||
<h2>使用</h2>
|
||||
<blockquote>
|
||||
<p>本仓库的<code>web</code>目录提供了一个基于<code>Vue2</code>的完整项目,如果你遇到了一些使用上的疑惑,可以参考该项目的实现。</p>
|
||||
</blockquote>
|
||||
<pre class="hljs"><code><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"mindMapContainer"</span>></span><span class="hljs-tag"></<span class="hljs-name">div</span>></span>
|
||||
</code></pre>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> MindMap <span class="hljs-keyword">from</span> <span class="hljs-string">"simple-mind-map"</span>;
|
||||
|
||||
@@ -12,9 +12,11 @@ import xmind from 'simple-mind-map/src/parse/xmind.js'
|
||||
|
||||
如果使用的是`umd`格式的文件,那么可以通过如下方式获取:
|
||||
|
||||
```js
|
||||
import MindMap from "simple-mind-map/dist/simpleMindMap.umd.min"
|
||||
```html
|
||||
<script src="simple-mind-map/dist/simpleMindMap.umd.min.js"></script>
|
||||
```
|
||||
|
||||
```js
|
||||
MindMap.xmind
|
||||
```
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> xmind <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/parse/xmind.js'</span>
|
||||
</code></pre>
|
||||
<p>如果使用的是<code>umd</code>格式的文件,那么可以通过如下方式获取:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> MindMap <span class="hljs-keyword">from</span> <span class="hljs-string">"simple-mind-map/dist/simpleMindMap.umd.min"</span>
|
||||
|
||||
MindMap.xmind
|
||||
<pre class="hljs"><code><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"simple-mind-map/dist/simpleMindMap.umd.min.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
|
||||
</code></pre>
|
||||
<pre class="hljs"><code>MindMap.xmind
|
||||
</code></pre>
|
||||
<h2>方法</h2>
|
||||
<h3>xmind.parseXmindFile(file)</h3>
|
||||
|
||||
@@ -126,6 +126,9 @@ export default {
|
||||
this.$bus.$on('createAssociativeLine', () => {
|
||||
this.mindMap.associativeLine.createLineFromActiveNode()
|
||||
})
|
||||
window.addEventListener('resize', () => {
|
||||
this.mindMap.resize()
|
||||
})
|
||||
if (this.openTest) {
|
||||
setTimeout(() => {
|
||||
this.test()
|
||||
@@ -309,6 +312,20 @@ export default {
|
||||
})
|
||||
})
|
||||
this.bindSaveEvent()
|
||||
// setTimeout(() => {
|
||||
// 动态给指定节点添加子节点
|
||||
// this.mindMap.execCommand('INSERT_CHILD_NODE', false, this.mindMap.renderer.root, {
|
||||
// text: '自定义内容'
|
||||
// })
|
||||
|
||||
// 动态给指定节点添加同级节点
|
||||
// this.mindMap.execCommand('INSERT_NODE', false, this.mindMap.renderer.root, {
|
||||
// text: '自定义内容'
|
||||
// })
|
||||
|
||||
// 动态删除指定节点
|
||||
// this.mindMap.execCommand('REMOVE_NODE', this.mindMap.renderer.root.children[0])
|
||||
// }, 5000);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -322,7 +339,8 @@ export default {
|
||||
} else {
|
||||
this.mindMap.setData(data)
|
||||
}
|
||||
this.manualSave()
|
||||
this.mindMap.view.reset()
|
||||
// this.manualSave()
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,23 +30,21 @@
|
||||
>{{ $t('export.domToImage') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
<el-radio-group v-model="exportType" size="mini">
|
||||
<el-radio-button label="smm"
|
||||
>{{ $t('export.dedicatedFile') }}(.smm)</el-radio-button
|
||||
<div class="downloadTypeList">
|
||||
<div
|
||||
class="downloadTypeItem"
|
||||
v-for="item in downTypeList"
|
||||
:key="item.type"
|
||||
:class="{active: exportType === item.type}"
|
||||
@click="exportType = item.type"
|
||||
>
|
||||
<el-radio-button label="json"
|
||||
>{{ $t('export.jsonFile') }}(.json)</el-radio-button
|
||||
>
|
||||
<el-radio-button label="png"
|
||||
>{{ $t('export.imageFile') }}(.png)</el-radio-button
|
||||
>
|
||||
<el-radio-button label="svg"
|
||||
>{{ $t('export.svgFile') }}(.svg)</el-radio-button
|
||||
>
|
||||
<el-radio-button label="pdf"
|
||||
>{{ $t('export.pdfFile') }}(.pdf)</el-radio-button
|
||||
>
|
||||
</el-radio-group>
|
||||
<div class="icon iconfont" :class="[item.icon, item.type]"></div>
|
||||
<div class="info">
|
||||
<div class="name">{{ item.name }}</div>
|
||||
<div class="desc">{{ item.desc }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tip">{{ $t('export.tips') }}</div>
|
||||
<div class="tip warning" v-if="openNodeRichText && ['png', 'pdf'].includes(exportType)">{{ $t('export.pngTips') }}</div>
|
||||
<div class="tip warning" v-if="openNodeRichText && exportType === 'svg' && domToImage">{{ $t('export.svgTips') }}</div>
|
||||
@@ -62,6 +60,7 @@
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { downTypeList } from '@/config'
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
@@ -84,7 +83,11 @@ export default {
|
||||
computed: {
|
||||
...mapState({
|
||||
openNodeRichText: state => state.localConfig.openNodeRichText,
|
||||
})
|
||||
}),
|
||||
|
||||
downTypeList() {
|
||||
return downTypeList[this.$i18n.locale] || downTypeList.zh
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$bus.$on('showExport', () => {
|
||||
@@ -148,6 +151,10 @@ export default {
|
||||
|
||||
<style lang="less" scoped>
|
||||
.nodeDialog {
|
||||
/deep/ .el-dialog__body {
|
||||
background-color: #f2f4f7;
|
||||
}
|
||||
|
||||
.nameInputBox {
|
||||
margin-bottom: 20px;
|
||||
|
||||
@@ -163,5 +170,70 @@ export default {
|
||||
color: #F56C6C;
|
||||
}
|
||||
}
|
||||
|
||||
.downloadTypeList {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
.downloadTypeItem {
|
||||
width: 200px;
|
||||
height: 88px;
|
||||
padding: 22px;
|
||||
overflow: hidden;
|
||||
margin: 10px;
|
||||
border-radius: 11px;
|
||||
box-shadow: 0 0 20px 0 rgba(0,0,0,.02);
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
|
||||
&.active {
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 30px;
|
||||
margin-right: 10px;
|
||||
|
||||
&.png {
|
||||
color: #ffc038;
|
||||
}
|
||||
|
||||
&.pdf {
|
||||
color: #ff6c4d;
|
||||
}
|
||||
|
||||
&.md {
|
||||
color: #2b2b2b;
|
||||
}
|
||||
|
||||
&.json {
|
||||
color: #12c87e;
|
||||
}
|
||||
|
||||
&.svg {
|
||||
color: #4380ff;
|
||||
}
|
||||
|
||||
&.smm {
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
.name {
|
||||
color: #1a1a1a;
|
||||
font-size: 15px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.desc {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
|
||||
<script>
|
||||
import xmind from 'simple-mind-map/src/parse/xmind.js'
|
||||
import markdown from 'simple-mind-map/src/parse/markdown.js'
|
||||
import { fileToBuffer } from '@/utils'
|
||||
import { read, utils } from 'xlsx'
|
||||
|
||||
@@ -68,9 +69,9 @@ export default {
|
||||
* @Desc: 文件选择
|
||||
*/
|
||||
onChange(file) {
|
||||
let reg = /\.(smm|xmind|json|xlsx)$/
|
||||
let reg = /\.(smm|xmind|json|xlsx|md)$/
|
||||
if (!reg.test(file.name)) {
|
||||
this.$message.error('请选择.smm、.json、.xmind、.xlsx文件')
|
||||
this.$message.error('请选择.smm、.json、.xmind、.xlsx、.md文件')
|
||||
this.fileList = []
|
||||
} else {
|
||||
this.fileList.push(file)
|
||||
@@ -112,7 +113,9 @@ export default {
|
||||
this.handleXmind(file)
|
||||
} else if (/\.xlsx$/.test(file.name)) {
|
||||
this.handleExcel(file)
|
||||
}
|
||||
} else if (/\.md$/.test(file.name)) {
|
||||
this.handleMd(file)
|
||||
}
|
||||
this.cancel()
|
||||
},
|
||||
|
||||
@@ -220,6 +223,22 @@ export default {
|
||||
console.log(error)
|
||||
this.$message.error('文件解析失败')
|
||||
}
|
||||
},
|
||||
|
||||
// 处理markdown文件
|
||||
async handleMd(file) {
|
||||
let fileReader = new FileReader()
|
||||
fileReader.readAsText(file.raw)
|
||||
fileReader.onload = async evt => {
|
||||
try {
|
||||
let data = await markdown.transformMarkdownTo(evt.target.result)
|
||||
this.$bus.$emit('setData', data)
|
||||
this.$message.success('导入成功')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
this.$message.error('文件解析失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ export default {
|
||||
}
|
||||
},
|
||||
notHandleDataChange: false,
|
||||
isCreateNode: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -76,6 +77,10 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
onBlur(e, node) {
|
||||
if (this.isCreateNode) {
|
||||
this.isCreateNode = false
|
||||
return
|
||||
}
|
||||
node.data._node.setText(e.target.innerText)
|
||||
},
|
||||
|
||||
@@ -97,12 +102,14 @@ export default {
|
||||
// 插入兄弟节点
|
||||
insertNode() {
|
||||
this.notHandleDataChange = false
|
||||
this.isCreateNode = true
|
||||
this.mindMap.execCommand('INSERT_NODE', false)
|
||||
},
|
||||
|
||||
// 插入下级节点
|
||||
insertChildNode() {
|
||||
this.notHandleDataChange = false
|
||||
this.isCreateNode = true
|
||||
this.mindMap.execCommand('INSERT_CHILD_NODE', false)
|
||||
},
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
{{ item }}px
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn" slot="reference" :style="{ color: formatInfo.color }">
|
||||
<div class="btn" slot="reference">
|
||||
<span class="icon iconfont iconcase fontColor"></span>
|
||||
</div>
|
||||
</el-popover>
|
||||
@@ -85,7 +85,7 @@
|
||||
<el-tooltip content="字体颜色" placement="top">
|
||||
<el-popover placement="bottom" trigger="hover">
|
||||
<Color :color="fontColor" @change="changeFontColor"></Color>
|
||||
<div class="btn" slot="reference">
|
||||
<div class="btn" slot="reference" :style="{ color: formatInfo.color }">
|
||||
<span class="icon iconfont iconzitiyanse"></span>
|
||||
</div>
|
||||
</el-popover>
|
||||
|
||||
@@ -449,12 +449,12 @@ export default {
|
||||
.toolbarContainer {
|
||||
.toolbar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
top: 20px;
|
||||
width: max-content;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
padding: 0 20px;
|
||||
padding-top: 20px;
|
||||
font-size: 12px;
|
||||
font-family: PingFangSC-Regular, PingFang SC;
|
||||
font-weight: 400;
|
||||
|
||||
Reference in New Issue
Block a user