Compare commits
82 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9879a25f9b | ||
|
|
16fb8eb092 | ||
|
|
de859927ed | ||
|
|
7bde59f664 | ||
|
|
be9668c7b8 | ||
|
|
95fe3189d5 | ||
|
|
9c60857c6a | ||
|
|
3b7cea9ee3 | ||
|
|
3f081e5021 | ||
|
|
d959420d6e | ||
|
|
79d81b92e6 | ||
|
|
940c60f23d | ||
|
|
965ab8151e | ||
|
|
87d55b31ca | ||
|
|
bb68575aca | ||
|
|
e561e804be | ||
|
|
5a8c3aa9d3 | ||
|
|
f84639debd | ||
|
|
de77a2b613 | ||
|
|
3825c3769f | ||
|
|
876908e922 | ||
|
|
25ecde8948 | ||
|
|
2de0334e3b | ||
|
|
1e32338d23 | ||
|
|
21f487321a | ||
|
|
750af31996 | ||
|
|
dc1aaee75d | ||
|
|
2122fa59d7 | ||
|
|
5b7c65adad | ||
|
|
fabf4535a8 | ||
|
|
04566080e0 | ||
|
|
0f9d9cdb21 | ||
|
|
11538d1789 | ||
|
|
49bae6cc6c | ||
|
|
9644ba0c8d | ||
|
|
ac2df3cb2e | ||
|
|
aad1f911c8 | ||
|
|
27b50bf4ae | ||
|
|
2d22837e32 | ||
|
|
866f2071ce | ||
|
|
9cb35eac29 | ||
|
|
6ff63d9d56 | ||
|
|
43539df25b | ||
|
|
cbafc24670 | ||
|
|
cc83a18b18 | ||
|
|
5ae931da92 | ||
|
|
853b999a7d | ||
|
|
505622f3dc | ||
|
|
0a5b41e3e0 | ||
|
|
8630c47b26 | ||
|
|
6105fd6e3d | ||
|
|
d7b1c4e4fe | ||
|
|
6ccafaa771 | ||
|
|
6b15e469a2 | ||
|
|
5675e29df3 | ||
|
|
52d094a7c7 | ||
|
|
633ed68f92 | ||
|
|
388594e29a | ||
|
|
af148fef27 | ||
|
|
983e55bd1d | ||
|
|
69dab99bf7 | ||
|
|
c94f459ff9 | ||
|
|
cd3baf411b | ||
|
|
042911371c | ||
|
|
8e2fb72309 | ||
|
|
77aacccdad | ||
|
|
5413c867e3 | ||
|
|
4fc7eb084e | ||
|
|
a9b04312d8 | ||
|
|
4cd9b66653 | ||
|
|
8d9299aed7 | ||
|
|
f8506cb75b | ||
|
|
c90ee9e892 | ||
|
|
4d21b84882 | ||
|
|
331f0bdf97 | ||
|
|
0a4898697d | ||
|
|
79ae08fc9a | ||
|
|
1795773af9 | ||
|
|
747a781ad8 | ||
|
|
fcfcb1c3d1 | ||
|
|
d412ae8cce | ||
|
|
d834b76d42 |
36
README.md
@@ -7,15 +7,22 @@
|
||||
[](https://github.com/wanglin2/mind-map/network/members)
|
||||

|
||||
|
||||
> 一个简单&强大的Web思维导图库
|
||||
> 一个简单&强大的Web思维导图
|
||||
|
||||
Demo:[https://wanglin2.github.io/mind-map/](https://wanglin2.github.io/mind-map/)
|
||||
本项目包含两部分:
|
||||
|
||||
文档:[https://wanglin2.github.io/mind-map/#/doc/zh/](https://wanglin2.github.io/mind-map/#/doc/zh/)
|
||||
1.一个js思维导图库,不依赖任何框架,你可以使用它来快速完成Web思维导图产品的开发。
|
||||
|
||||
开发文档:[https://wanglin2.github.io/mind-map/#/doc/zh/](https://wanglin2.github.io/mind-map/#/doc/zh/)
|
||||
|
||||
2.一个Web思维导图,基于思维导图库、Vue2.x、ElementUI开发,可以操作电脑本地文件,所以你可以直接把它当做一个在线版思维导图应用使用,如果觉得github的响应速度慢,你也可以部署到你的服务器上。
|
||||
|
||||
在线地址:[https://wanglin2.github.io/mind-map/](https://wanglin2.github.io/mind-map/)
|
||||
|
||||
# 特性
|
||||
|
||||
- [x] 支持逻辑结构图、思维导图、组织结构图、目录组织图四种结构
|
||||
- [x] 插件化架构,除核心功能外,其他功能作为插件提供,按需使用,减小打包体积
|
||||
- [x] 支持逻辑结构图、思维导图、组织结构图、目录组织图、时间轴、鱼骨图六种结构
|
||||
- [x] 内置多种主题,允许高度自定义样式,支持注册新主题
|
||||
- [x] 支持快捷键
|
||||
- [x] 节点内容支持图片、图标、超链接、备注、标签、概要
|
||||
@@ -24,8 +31,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] 支持关联线
|
||||
|
||||
# 安装
|
||||
@@ -36,10 +43,23 @@ npm i simple-mind-map
|
||||
|
||||
# 使用
|
||||
|
||||
提供一个宽高不为0的容器元素:
|
||||
|
||||
```html
|
||||
<div id="mindMapContainer"></div>
|
||||
```
|
||||
|
||||
另外再设置一下`css`样式:
|
||||
|
||||
```css
|
||||
#mindMapContainer * {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
```
|
||||
|
||||
然后创建一个实例:
|
||||
|
||||
```js
|
||||
import MindMap from "simple-mind-map";
|
||||
|
||||
@@ -54,6 +74,10 @@ const mindMap = new MindMap({
|
||||
});
|
||||
```
|
||||
|
||||
即可得到一个思维导图。
|
||||
|
||||
想要实现更多功能?可以查看[开发文档](https://wanglin2.github.io/mind-map/#/doc/zh/)。
|
||||
|
||||
# License
|
||||
|
||||
MIT
|
||||
|
||||
@@ -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.3b27bed0.js" rel="prefetch"><link href="dist/js/chunk-2d0a514a.64cc7822.js" rel="prefetch"><link href="dist/js/chunk-2d0aa579.61afd8c1.js" rel="prefetch"><link href="dist/js/chunk-2d0aa978.84fc06da.js" rel="prefetch"><link href="dist/js/chunk-2d0ab10b.4d405645.js" rel="prefetch"><link href="dist/js/chunk-2d0abe0f.a9abff6a.js" rel="prefetch"><link href="dist/js/chunk-2d0b1c6f.b8417307.js" rel="prefetch"><link href="dist/js/chunk-2d0b361e.d657e190.js" rel="prefetch"><link href="dist/js/chunk-2d0b91e5.433fdc5c.js" rel="prefetch"><link href="dist/js/chunk-2d0b92c3.9d7f8382.js" rel="prefetch"><link href="dist/js/chunk-2d0ba309.a3915823.js" rel="prefetch"><link href="dist/js/chunk-2d0bd54e.906e86ec.js" rel="prefetch"><link href="dist/js/chunk-2d0be174.0cf53d60.js" rel="prefetch"><link href="dist/js/chunk-2d0c0a44.0f9ebf1b.js" rel="prefetch"><link href="dist/js/chunk-2d0c14fc.7163274e.js" rel="prefetch"><link href="dist/js/chunk-2d0c18d8.2e6ddc70.js" rel="prefetch"><link href="dist/js/chunk-2d0c191e.cd5e6df3.js" rel="prefetch"><link href="dist/js/chunk-2d0c1a01.2e73bf0a.js" rel="prefetch"><link href="dist/js/chunk-2d0c20be.dab092ff.js" rel="prefetch"><link href="dist/js/chunk-2d0d5cb9.2e73ab77.js" rel="prefetch"><link href="dist/js/chunk-2d0d9fbc.4800807d.js" rel="prefetch"><link href="dist/js/chunk-2d0da701.80759043.js" rel="prefetch"><link href="dist/js/chunk-2d0dad5f.f670c26a.js" rel="prefetch"><link href="dist/js/chunk-2d0db0f2.b5ce4946.js" rel="prefetch"><link href="dist/js/chunk-2d0dd3b1.c4647d07.js" rel="prefetch"><link href="dist/js/chunk-2d0dddce.3eea98de.js" rel="prefetch"><link href="dist/js/chunk-2d0ddf37.d960b122.js" rel="prefetch"><link href="dist/js/chunk-2d0de01b.a2a047cf.js" rel="prefetch"><link href="dist/js/chunk-2d0e2326.5072607c.js" rel="prefetch"><link href="dist/js/chunk-2d0e268c.ed75c510.js" rel="prefetch"><link href="dist/js/chunk-2d0e5089.10135360.js" rel="prefetch"><link href="dist/js/chunk-2d0e9742.9abceada.js" rel="prefetch"><link href="dist/js/chunk-2d0f026c.4757f6bb.js" rel="prefetch"><link href="dist/js/chunk-2d2082b9.f52387a2.js" rel="prefetch"><link href="dist/js/chunk-2d208ffa.766e8fa6.js" rel="prefetch"><link href="dist/js/chunk-2d20ec02.917aff76.js" rel="prefetch"><link href="dist/js/chunk-2d20f68f.6c1b14e1.js" rel="prefetch"><link href="dist/js/chunk-2d210a7a.6a4911c4.js" rel="prefetch"><link href="dist/js/chunk-2d216004.704073c5.js" rel="prefetch"><link href="dist/js/chunk-2d217907.32a00939.js" rel="prefetch"><link href="dist/js/chunk-2d226d0a.6b1238d2.js" rel="prefetch"><link href="dist/js/chunk-2d2299c3.8f3151dd.js" rel="prefetch"><link href="dist/js/chunk-2d22bd06.9f7bbd4c.js" rel="prefetch"><link href="dist/js/chunk-2d2308b0.2797f6b4.js" rel="prefetch"><link href="dist/js/chunk-2d238428.7c9ae7c7.js" rel="prefetch"><link href="dist/js/chunk-3a2f3e67.13278516.js" rel="prefetch"><link href="dist/css/app.3b1827be.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.c097b26d.css" rel="preload" as="style"><link href="dist/js/app.e9e06d36.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.49b07a77.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.c097b26d.css" rel="stylesheet"><link href="dist/css/app.3b1827be.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.49b07a77.js"></script><script src="dist/js/app.e9e06d36.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-0b4c0228.9a1b85ac.js" rel="prefetch"><link href="dist/js/chunk-2d0a3179.ecdc0449.js" rel="prefetch"><link href="dist/js/chunk-2d0a514a.6d16a18f.js" rel="prefetch"><link href="dist/js/chunk-2d0aa579.fcf43a73.js" rel="prefetch"><link href="dist/js/chunk-2d0aa978.4e43a508.js" rel="prefetch"><link href="dist/js/chunk-2d0ab10b.2151bd9a.js" rel="prefetch"><link href="dist/js/chunk-2d0abe0f.a1773017.js" rel="prefetch"><link href="dist/js/chunk-2d0ac5dc.434496ae.js" rel="prefetch"><link href="dist/js/chunk-2d0afe0d.106affda.js" rel="prefetch"><link href="dist/js/chunk-2d0b1c6f.40c3a4c6.js" rel="prefetch"><link href="dist/js/chunk-2d0b361e.bd367278.js" rel="prefetch"><link href="dist/js/chunk-2d0b91e5.1d4a5492.js" rel="prefetch"><link href="dist/js/chunk-2d0b92c3.1ed97f5c.js" rel="prefetch"><link href="dist/js/chunk-2d0b978a.e3dec256.js" rel="prefetch"><link href="dist/js/chunk-2d0b9b64.03e02abe.js" rel="prefetch"><link href="dist/js/chunk-2d0ba309.cad55829.js" rel="prefetch"><link href="dist/js/chunk-2d0bd54e.bc2402de.js" rel="prefetch"><link href="dist/js/chunk-2d0be174.8e142e17.js" rel="prefetch"><link href="dist/js/chunk-2d0c09f6.46dff9b5.js" rel="prefetch"><link href="dist/js/chunk-2d0c0a44.5e69748f.js" rel="prefetch"><link href="dist/js/chunk-2d0c14fc.2b0a3aa0.js" rel="prefetch"><link href="dist/js/chunk-2d0c191e.41f869ae.js" rel="prefetch"><link href="dist/js/chunk-2d0c1a01.d27d7537.js" rel="prefetch"><link href="dist/js/chunk-2d0c20be.a826dbb3.js" rel="prefetch"><link href="dist/js/chunk-2d0c4a65.d6ccf037.js" rel="prefetch"><link href="dist/js/chunk-2d0c5538.480c6adf.js" rel="prefetch"><link href="dist/js/chunk-2d0d5cb9.53817128.js" rel="prefetch"><link href="dist/js/chunk-2d0d9fbc.ab7b8752.js" rel="prefetch"><link href="dist/js/chunk-2d0da701.cfa871f6.js" rel="prefetch"><link href="dist/js/chunk-2d0dad5f.6b307c90.js" rel="prefetch"><link href="dist/js/chunk-2d0db0f2.10ad3333.js" rel="prefetch"><link href="dist/js/chunk-2d0dd3b1.a4748b26.js" rel="prefetch"><link href="dist/js/chunk-2d0dddce.d8e94894.js" rel="prefetch"><link href="dist/js/chunk-2d0ddf37.2bb0ee69.js" rel="prefetch"><link href="dist/js/chunk-2d0de01b.f8eea9ae.js" rel="prefetch"><link href="dist/js/chunk-2d0e2326.5f8f761a.js" rel="prefetch"><link href="dist/js/chunk-2d0e4e1f.53e37fe0.js" rel="prefetch"><link href="dist/js/chunk-2d0e5089.25303766.js" rel="prefetch"><link href="dist/js/chunk-2d0e9726.588baf8d.js" rel="prefetch"><link href="dist/js/chunk-2d0e9742.428d2cf2.js" rel="prefetch"><link href="dist/js/chunk-2d0e9802.6c3450dd.js" rel="prefetch"><link href="dist/js/chunk-2d0f026c.37892dce.js" rel="prefetch"><link href="dist/js/chunk-2d2082b9.390e6ae8.js" rel="prefetch"><link href="dist/js/chunk-2d208ffa.613d8ec9.js" rel="prefetch"><link href="dist/js/chunk-2d20ec02.917aff76.js" rel="prefetch"><link href="dist/js/chunk-2d20f68f.76d5e91c.js" rel="prefetch"><link href="dist/js/chunk-2d20ff53.c0447317.js" rel="prefetch"><link href="dist/js/chunk-2d210a7a.999247ea.js" rel="prefetch"><link href="dist/js/chunk-2d216004.6bc315a8.js" rel="prefetch"><link href="dist/js/chunk-2d216037.735c6ef2.js" rel="prefetch"><link href="dist/js/chunk-2d2160a3.667b0cef.js" rel="prefetch"><link href="dist/js/chunk-2d216642.43efb068.js" rel="prefetch"><link href="dist/js/chunk-2d217907.fcb88ac3.js" rel="prefetch"><link href="dist/js/chunk-2d221c76.0f835399.js" rel="prefetch"><link href="dist/js/chunk-2d2254a4.aba788ef.js" rel="prefetch"><link href="dist/js/chunk-2d226d0a.4ed5fff9.js" rel="prefetch"><link href="dist/js/chunk-2d2299c3.6c80e04d.js" rel="prefetch"><link href="dist/js/chunk-2d22bd06.20c08643.js" rel="prefetch"><link href="dist/js/chunk-2d230098.2901ec53.js" rel="prefetch"><link href="dist/js/chunk-2d2308b0.c6cec8ad.js" rel="prefetch"><link href="dist/js/chunk-2d2375fa.949e865a.js" rel="prefetch"><link href="dist/js/chunk-2d238428.816b9c47.js" rel="prefetch"><link href="dist/js/chunk-3a2f3e67.13278516.js" rel="prefetch"><link href="dist/js/chunk-a48508c0.755bad4a.js" rel="prefetch"><link href="dist/css/app.f08aba10.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.c097b26d.css" rel="preload" as="style"><link href="dist/js/app.79ffd099.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.f08aba10.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.79ffd099.js"></script></body></html>
|
||||
BIN
qrcode.jpg
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
@@ -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,9 @@ const defaultOpt = {
|
||||
// 是否只读
|
||||
readonly: false,
|
||||
// 布局
|
||||
layout: 'logicalStructure',
|
||||
layout: CONSTANTS.LAYOUT.LOGICAL_STRUCTURE,
|
||||
// 如果结构为鱼骨图,那么可以通过该选项控制倾斜角度
|
||||
fishboneDeg: 45,
|
||||
// 主题
|
||||
theme: 'default', // 内置主题:default(默认主题)
|
||||
// 主题配置,会和所选择的主题进行合并
|
||||
@@ -66,13 +68,31 @@ const defaultOpt = {
|
||||
// 可以传一个函数,回调参数为事件对象
|
||||
customHandleMousewheel: null,
|
||||
// 鼠标滚动的行为,如果customHandleMousewheel传了自定义函数,这个属性不生效
|
||||
mousewheelAction: 'zoom',// zoom(放大缩小)、move(上下移动)
|
||||
mousewheelAction: CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM,// zoom(放大缩小)、move(上下移动)
|
||||
// 当mousewheelAction设为move时,可以通过该属性控制鼠标滚动一下视图移动的步长,单位px
|
||||
mousewheelMoveStep: 100,
|
||||
// 默认插入的二级节点的文字
|
||||
defaultInsertSecondLevelNodeText: '二级节点',
|
||||
// 默认插入的二级以下节点的文字
|
||||
defaultInsertBelowSecondLevelNodeText: '分支主题'
|
||||
defaultInsertBelowSecondLevelNodeText: '分支主题',
|
||||
// 展开收起按钮的颜色
|
||||
expandBtnStyle: {
|
||||
color: '#808080',
|
||||
fill: '#fff'
|
||||
},
|
||||
// 自定义展开收起按钮的图标
|
||||
expandBtnIcon: {
|
||||
open: '',// svg字符串
|
||||
close: ''
|
||||
},
|
||||
// 是否只有当鼠标在画布内才响应快捷键事件
|
||||
enableShortcutOnlyWhenMouseInSvg: true,
|
||||
// 是否开启节点动画过渡
|
||||
enableNodeTransitionMove: true,
|
||||
// 如果开启节点动画过渡,可以通过该属性设置过渡的时间,单位ms
|
||||
nodeTransitionMoveDuration: 300,
|
||||
// 初始根节点的位置
|
||||
initRootNodePosition: null
|
||||
}
|
||||
|
||||
// 思维导图
|
||||
@@ -95,7 +115,7 @@ class MindMap {
|
||||
this.draw = this.svg.group()
|
||||
|
||||
// 节点id
|
||||
this.uid = 0
|
||||
this.uid = 1
|
||||
|
||||
// 初始化主题
|
||||
this.initTheme()
|
||||
@@ -135,7 +155,7 @@ class MindMap {
|
||||
})
|
||||
|
||||
// 初始渲染
|
||||
this.reRender()
|
||||
this.render()
|
||||
setTimeout(() => {
|
||||
this.command.addHistory()
|
||||
}, 0)
|
||||
@@ -145,7 +165,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'
|
||||
@@ -153,11 +173,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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -206,7 +226,7 @@ class MindMap {
|
||||
setTheme(theme) {
|
||||
this.renderer.clearAllActive()
|
||||
this.opt.theme = theme
|
||||
this.reRender()
|
||||
this.render(null, CONSTANTS.CHANGE_THEME)
|
||||
}
|
||||
|
||||
// 获取当前主题
|
||||
@@ -217,7 +237,7 @@ class MindMap {
|
||||
// 设置主题配置
|
||||
setThemeConfig(config) {
|
||||
this.opt.themeConfig = config
|
||||
this.reRender()
|
||||
this.render(null, CONSTANTS.CHANGE_THEME)
|
||||
}
|
||||
|
||||
// 获取自定义主题配置
|
||||
@@ -249,7 +269,7 @@ class MindMap {
|
||||
setLayout(layout) {
|
||||
// 检查布局配置
|
||||
if (!layoutValueList.includes(layout)) {
|
||||
layout = 'logicalStructure'
|
||||
layout = CONSTANTS.LAYOUT.LOGICAL_STRUCTURE
|
||||
}
|
||||
this.opt.layout = layout
|
||||
this.renderer.setLayout()
|
||||
@@ -265,6 +285,7 @@ class MindMap {
|
||||
setData(data) {
|
||||
this.execCommand('CLEAR_ACTIVE_NODE')
|
||||
this.command.clearHistory()
|
||||
this.command.addHistory()
|
||||
this.renderer.renderTree = data
|
||||
this.reRender()
|
||||
}
|
||||
@@ -292,7 +313,7 @@ class MindMap {
|
||||
|
||||
// 获取思维导图数据,节点树、主题、布局等
|
||||
getData(withConfig) {
|
||||
let nodeData = this.command.getCopyData()
|
||||
let nodeData = this.command.removeDataUid(this.command.getCopyData())
|
||||
let data = {}
|
||||
if (withConfig) {
|
||||
data = {
|
||||
@@ -326,10 +347,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()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.4.7",
|
||||
"version": "0.5.4",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
|
||||
@@ -85,7 +85,7 @@ class Command {
|
||||
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,
|
||||
@@ -106,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
|
||||
}
|
||||
}
|
||||
@@ -119,9 +119,9 @@ 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
|
||||
}
|
||||
}
|
||||
@@ -130,6 +130,21 @@ class Command {
|
||||
getCopyData() {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
export default Command
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -37,6 +37,9 @@ class Export {
|
||||
item.attr('href', imgData)
|
||||
})
|
||||
await Promise.all(task)
|
||||
if (imageList.length > 0) {
|
||||
svgHTML = svg.svg()
|
||||
}
|
||||
// 如果开启了富文本编辑,需要把svg中的dom元素转换成图片
|
||||
let nodeWithDomToImg = null
|
||||
if (domToImage && this.mindMap.richText) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -3,28 +3,36 @@ import LogicalStructure from './layouts/LogicalStructure'
|
||||
import MindMap from './layouts/MindMap'
|
||||
import CatalogOrganization from './layouts/CatalogOrganization'
|
||||
import OrganizationStructure from './layouts/OrganizationStructure'
|
||||
import Timeline from './layouts/Timeline'
|
||||
import Fishbone from './layouts/Fishbone'
|
||||
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,
|
||||
// 时间轴
|
||||
[CONSTANTS.LAYOUT.TIMELINE]: Timeline,
|
||||
// 时间轴2
|
||||
[CONSTANTS.LAYOUT.TIMELINE2]: Timeline,
|
||||
// 鱼骨图
|
||||
[CONSTANTS.LAYOUT.FISHBONE]: Fishbone,
|
||||
}
|
||||
|
||||
// 渲染
|
||||
|
||||
class Render {
|
||||
// 构造函数
|
||||
|
||||
constructor(opt = {}) {
|
||||
this.opt = opt
|
||||
this.mindMap = opt.mindMap
|
||||
@@ -34,6 +42,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 +68,15 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置布局结构
|
||||
|
||||
setLayout() {
|
||||
this.layout = new (
|
||||
layouts[this.mindMap.opt.layout]
|
||||
? layouts[this.mindMap.opt.layout]
|
||||
: layouts.logicalStructure
|
||||
)(this)
|
||||
: layouts[CONSTANTS.LAYOUT.LOGICAL_STRUCTURE]
|
||||
)(this, this.mindMap.opt.layout)
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
|
||||
bindEvent() {
|
||||
// 点击事件
|
||||
this.mindMap.on('draw_click', () => {
|
||||
@@ -73,7 +88,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 注册命令
|
||||
|
||||
registerCommands() {
|
||||
// 全选
|
||||
this.selectAll = this.selectAll.bind(this)
|
||||
@@ -175,7 +189,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 注册快捷键
|
||||
|
||||
registerShortcutKeys() {
|
||||
// 插入下级节点
|
||||
this.mindMap.keyCommand.addShortcut('Tab', () => {
|
||||
@@ -220,7 +233,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 开启文字编辑,会禁用回车键和删除键相关快捷键防止冲突
|
||||
|
||||
startTextEdit() {
|
||||
this.mindMap.keyCommand.save()
|
||||
// this.mindMap.keyCommand.removeShortcut('Del|Backspace')
|
||||
@@ -229,7 +241,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 结束文字编辑,会恢复回车键和删除键相关快捷键
|
||||
|
||||
endTextEdit() {
|
||||
this.mindMap.keyCommand.restore()
|
||||
// this.mindMap.keyCommand.addShortcut('Del|Backspace', this.removeNodeWrap)
|
||||
@@ -238,23 +249,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 +303,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 清除当前所有激活节点,并会触发事件
|
||||
|
||||
clearAllActive() {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
return
|
||||
@@ -273,7 +312,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 添加节点到激活列表里
|
||||
|
||||
addActiveNode(node) {
|
||||
let index = this.findActiveNodeIndex(node)
|
||||
if (index === -1) {
|
||||
@@ -282,7 +320,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 在激活列表里移除某个节点
|
||||
|
||||
removeActiveNode(node) {
|
||||
let index = this.findActiveNodeIndex(node)
|
||||
if (index === -1) {
|
||||
@@ -292,7 +329,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 检索某个节点在激活列表里的索引
|
||||
|
||||
findActiveNodeIndex(node) {
|
||||
return this.activeNodeList.findIndex(item => {
|
||||
return item === node
|
||||
@@ -300,7 +336,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 获取节点在同级里的索引位置
|
||||
|
||||
getNodeIndex(node) {
|
||||
return node.parent
|
||||
? node.parent.children.findIndex(item => {
|
||||
@@ -310,7 +345,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 全选
|
||||
|
||||
selectAll() {
|
||||
walk(
|
||||
this.root,
|
||||
@@ -320,7 +354,7 @@ class Render {
|
||||
node.nodeData.data.isActive = true
|
||||
this.addActiveNode(node)
|
||||
setTimeout(() => {
|
||||
node.renderNode()
|
||||
node.updateNodeShape()
|
||||
}, 0)
|
||||
}
|
||||
},
|
||||
@@ -332,24 +366,22 @@ 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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,7 +392,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 插入同级节点,多个节点只会操作第一个节点
|
||||
|
||||
insertNode(openEdit = true, appointNodes = [], appointData = null) {
|
||||
appointNodes = this.formatAppointNodes(appointNodes)
|
||||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||||
@@ -374,7 +405,7 @@ class Render {
|
||||
} else {
|
||||
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, {
|
||||
@@ -391,7 +422,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 插入子节点
|
||||
|
||||
insertChildNode(openEdit = true, appointNodes = [], appointData = null) {
|
||||
appointNodes = this.formatAppointNodes(appointNodes)
|
||||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||||
@@ -416,17 +446,13 @@ class Render {
|
||||
// 插入子节点时自动展开子节点
|
||||
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
|
||||
@@ -454,7 +480,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 下移节点,多个节点只会操作第一个节点
|
||||
|
||||
downNode() {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
return
|
||||
@@ -482,7 +507,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 将节点移动到另一个节点的前面
|
||||
|
||||
insertBefore(node, exist) {
|
||||
if (node.isRoot) {
|
||||
return
|
||||
@@ -514,14 +538,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
|
||||
@@ -554,14 +576,12 @@ class Render {
|
||||
existParent.nodeData.children.splice(existIndex, 0, node.nodeData)
|
||||
this.mindMap.render(() => {
|
||||
if (nodeLayerChanged) {
|
||||
node.getSize()
|
||||
node.renderNode()
|
||||
node.reRender()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 移除节点
|
||||
|
||||
removeNode(appointNodes = []) {
|
||||
appointNodes = this.formatAppointNodes(appointNodes)
|
||||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||||
@@ -603,7 +623,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 移除某个指定节点
|
||||
|
||||
removeOneNode(node) {
|
||||
let index = this.getNodeIndex(node)
|
||||
node.remove()
|
||||
@@ -612,7 +631,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 复制节点,多个节点只会操作第一个节点
|
||||
|
||||
copyNode() {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
return
|
||||
@@ -621,7 +639,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 剪切节点,多个节点只会操作第一个节点
|
||||
|
||||
cutNode(callback) {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
return
|
||||
@@ -641,7 +658,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 移动一个节点作为另一个节点的子节点
|
||||
|
||||
moveNodeTo(node, toNode) {
|
||||
if (node.isRoot) {
|
||||
return
|
||||
@@ -653,12 +669,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
|
||||
@@ -670,7 +685,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点样式
|
||||
|
||||
setNodeStyle(node, prop, value, isActive) {
|
||||
let data = {}
|
||||
if (isActive) {
|
||||
@@ -687,12 +701,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([node])
|
||||
}
|
||||
}
|
||||
this.setNodeDataRender(node, data)
|
||||
// 更新了连线的样式
|
||||
@@ -702,16 +718,14 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点是否激活
|
||||
|
||||
setNodeActive(node, active) {
|
||||
this.setNodeData(node, {
|
||||
isActive: active
|
||||
})
|
||||
node.renderNode()
|
||||
node.updateNodeShape()
|
||||
}
|
||||
|
||||
// 设置节点是否展开
|
||||
|
||||
setNodeExpand(node, expand) {
|
||||
this.setNodeData(node, {
|
||||
expand
|
||||
@@ -735,7 +749,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 展开所有
|
||||
|
||||
expandAllNode() {
|
||||
walk(
|
||||
this.renderTree,
|
||||
@@ -750,11 +763,10 @@ class Render {
|
||||
0,
|
||||
0
|
||||
)
|
||||
this.mindMap.reRender()
|
||||
this.mindMap.render()
|
||||
}
|
||||
|
||||
// 收起所有
|
||||
|
||||
unexpandAllNode() {
|
||||
walk(
|
||||
this.renderTree,
|
||||
@@ -770,11 +782,12 @@ class Render {
|
||||
0,
|
||||
0
|
||||
)
|
||||
this.mindMap.reRender()
|
||||
this.mindMap.render(() => {
|
||||
this.mindMap.view.reset()
|
||||
})
|
||||
}
|
||||
|
||||
// 展开到指定层级
|
||||
|
||||
expandToLevel(level) {
|
||||
walk(
|
||||
this.renderTree,
|
||||
@@ -788,11 +801,10 @@ class Render {
|
||||
0,
|
||||
0
|
||||
)
|
||||
this.mindMap.reRender()
|
||||
this.mindMap.render()
|
||||
}
|
||||
|
||||
// 切换激活节点的展开状态
|
||||
|
||||
toggleActiveExpand() {
|
||||
this.activeNodeList.forEach(node => {
|
||||
if (node.nodeData.children.length <= 0) {
|
||||
@@ -803,7 +815,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 切换节点展开状态
|
||||
|
||||
toggleNodeExpand(node) {
|
||||
this.mindMap.execCommand(
|
||||
'SET_NODE_EXPAND',
|
||||
@@ -813,7 +824,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点文本
|
||||
|
||||
setNodeText(node, text, richText) {
|
||||
this.setNodeDataRender(node, {
|
||||
text,
|
||||
@@ -822,7 +832,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点图片
|
||||
|
||||
setNodeImage(node, { url, title, width, height }) {
|
||||
this.setNodeDataRender(node, {
|
||||
image: url,
|
||||
@@ -835,7 +844,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点图标
|
||||
|
||||
setNodeIcon(node, icons) {
|
||||
this.setNodeDataRender(node, {
|
||||
icon: icons
|
||||
@@ -843,7 +851,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点超链接
|
||||
|
||||
setNodeHyperlink(node, link, title = '') {
|
||||
this.setNodeDataRender(node, {
|
||||
hyperlink: link,
|
||||
@@ -852,7 +859,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点备注
|
||||
|
||||
setNodeNote(node, note) {
|
||||
this.setNodeDataRender(node, {
|
||||
note
|
||||
@@ -860,7 +866,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点标签
|
||||
|
||||
setNodeTag(node, tag) {
|
||||
this.setNodeDataRender(node, {
|
||||
tag
|
||||
@@ -868,7 +873,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 添加节点概要
|
||||
|
||||
addGeneralization(data) {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
return
|
||||
@@ -888,7 +892,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 删除节点概要
|
||||
|
||||
removeGeneralization() {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
return
|
||||
@@ -906,7 +909,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点自定义位置
|
||||
|
||||
setNodeCustomPosition(node, left = undefined, top = undefined) {
|
||||
let nodeList = [node] || this.activeNodeList
|
||||
nodeList.forEach(item => {
|
||||
@@ -918,7 +920,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 一键整理布局,即去除自定义位置
|
||||
|
||||
resetLayout() {
|
||||
walk(
|
||||
this.root,
|
||||
@@ -940,7 +941,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点形状
|
||||
|
||||
setNodeShape(node, shape) {
|
||||
if (!shape || !shapeList.includes(shape)) {
|
||||
return
|
||||
@@ -952,7 +952,6 @@ class Render {
|
||||
}
|
||||
|
||||
// 更新节点数据
|
||||
|
||||
setNodeData(node, data) {
|
||||
Object.keys(data).forEach(key => {
|
||||
node.nodeData.data[key] = data[key]
|
||||
@@ -960,11 +959,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) {
|
||||
// 概要节点
|
||||
@@ -975,7 +972,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,7 +98,7 @@ 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;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)
|
||||
}
|
||||
// 原始宽高
|
||||
@@ -147,18 +148,19 @@ class RichText {
|
||||
underline: node.style.merge('textDecoration') === 'underline',
|
||||
strike: node.style.merge('textDecoration') === 'line-through'
|
||||
}
|
||||
this.formatText(style)
|
||||
this.formatAllText(style)
|
||||
}
|
||||
|
||||
// 隐藏文本编辑控件,即完成编辑
|
||||
hideEditText() {
|
||||
hideEditText(nodes) {
|
||||
if (!this.showTextEdit) {
|
||||
return
|
||||
}
|
||||
let html = this.quill.container.firstChild.innerHTML
|
||||
// 去除最后的空行
|
||||
html = html.replace(/<p><br><\/p>$/, '')
|
||||
this.mindMap.renderer.activeNodeList.forEach(node => {
|
||||
let list = nodes && nodes.length > 0 ? nodes : this.mindMap.renderer.activeNodeList
|
||||
list.forEach(node => {
|
||||
this.mindMap.execCommand('SET_NODE_TEXT', node, html, true)
|
||||
if (node.isGeneralization) {
|
||||
// 概要节点
|
||||
@@ -169,7 +171,7 @@ class RichText {
|
||||
this.mindMap.emit(
|
||||
'hide_text_edit',
|
||||
this.textEditNode,
|
||||
this.mindMap.renderer.activeNodeList
|
||||
list
|
||||
)
|
||||
this.textEditNode.style.display = 'none'
|
||||
this.showTextEdit = false
|
||||
@@ -435,6 +437,7 @@ class RichText {
|
||||
node.data.richText = false
|
||||
div.innerHTML = node.data.text
|
||||
node.data.text = div.textContent
|
||||
// delete node.data.uid
|
||||
}
|
||||
},
|
||||
null,
|
||||
@@ -445,7 +448,7 @@ class RichText {
|
||||
// 清空历史数据,并且触发数据变化
|
||||
this.mindMap.command.clearHistory()
|
||||
this.mindMap.command.addHistory()
|
||||
this.mindMap.reRender()
|
||||
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, initRootNodePositionMap } 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) {
|
||||
@@ -70,10 +122,28 @@ class Base {
|
||||
return newNode
|
||||
}
|
||||
|
||||
// 格式化节点位置
|
||||
formatPosition(value, size, nodeSize) {
|
||||
if (typeof value === 'number') {
|
||||
return value
|
||||
} else if (initRootNodePositionMap[value] !== undefined) {
|
||||
return size * initRootNodePositionMap[value]
|
||||
} else if (/^\d\d*%$/.test(value)) {
|
||||
return Number.parseFloat(value) / 100 * size
|
||||
} else {
|
||||
return (size - nodeSize) / 2
|
||||
}
|
||||
}
|
||||
|
||||
// 定位节点到画布中间
|
||||
setNodeCenter(node) {
|
||||
node.left = (this.mindMap.width - node.width) / 2
|
||||
node.top = (this.mindMap.height - node.height) / 2
|
||||
let { initRootNodePosition } = this.mindMap.opt
|
||||
let { CENTER }= CONSTANTS.INIT_ROOT_NODE_POSITION
|
||||
if (!initRootNodePosition || !Array.isArray(initRootNodePosition) || initRootNodePosition.length < 2) {
|
||||
initRootNodePosition = [CENTER, CENTER]
|
||||
}
|
||||
node.left = this.formatPosition(initRootNodePosition[0], this.mindMap.width, node.width)
|
||||
node.top = this.formatPosition(initRootNodePosition[1], this.mindMap.height, node.height)
|
||||
}
|
||||
|
||||
// 更新子节点属性
|
||||
@@ -87,6 +157,37 @@ class Base {
|
||||
})
|
||||
}
|
||||
|
||||
// 更新子节点多个属性
|
||||
updateChildrenPro(children, props) {
|
||||
children.forEach(item => {
|
||||
Object.keys(props).forEach((prop) => {
|
||||
item[prop] += props[prop]
|
||||
})
|
||||
if (item.children && item.children.length && !item.hasCustomPosition()) {
|
||||
// 适配自定义位置
|
||||
this.updateChildrenPro(item.children, props)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 递归计算节点的宽度
|
||||
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)
|
||||
}
|
||||
|
||||
// 二次贝塞尔曲线
|
||||
quadraticCurvePath(x1, y1, x2, y2) {
|
||||
let cx = x1 + (x2 - x1) * 0.2
|
||||
@@ -196,6 +297,11 @@ class Base {
|
||||
generalizationNodeMargin
|
||||
}
|
||||
}
|
||||
|
||||
// 获取节点实际存在几个子节点
|
||||
getNodeActChildrenLength(node) {
|
||||
return node.nodeData.children && node.nodeData.children.length
|
||||
}
|
||||
}
|
||||
|
||||
export default Base
|
||||
|
||||
@@ -1,386 +1,351 @@
|
||||
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 + (this.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0)
|
||||
node.children.forEach(cur => {
|
||||
cur.left = node.left + node.width * 0.5
|
||||
cur.top = totalTop
|
||||
totalTop += cur.height + marginY + (this.getNodeActChildrenLength(cur) > 0 ? cur.expandBtnSize : 0)
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
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)
|
||||
}
|
||||
}
|
||||
// 调整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 + (this.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
}, 0) +
|
||||
len * marginY
|
||||
this.updateBrothersTop(node, totalHeight)
|
||||
}
|
||||
},
|
||||
(node, parent, isRoot) => {
|
||||
if (isRoot) {
|
||||
let { right, left } = this.getNodeBoundaries(node, 'h')
|
||||
let childrenWidth = right - left
|
||||
let offset = (node.left - left) - (childrenWidth - node.width) / 2
|
||||
this.updateChildren(node.children, 'left', offset)
|
||||
}
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 调整兄弟节点的left
|
||||
updateBrothersLeft(node, addWidth) {
|
||||
if (node.parent) {
|
||||
let childrenList = node.parent.children
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item === node
|
||||
})
|
||||
childrenList.forEach((item, _index) => {
|
||||
if (item.hasCustomPosition() || _index <= index) {
|
||||
// 适配自定义位置
|
||||
return
|
||||
}
|
||||
item.left += addWidth
|
||||
// 同步更新子节点的位置
|
||||
if (item.children && item.children.length) {
|
||||
this.updateChildren(item.children, 'left', addWidth)
|
||||
}
|
||||
})
|
||||
// 更新父节点的位置
|
||||
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
|
||||
|
||||
373
simple-mind-map/src/layouts/Fishbone.js
Normal file
@@ -0,0 +1,373 @@
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun, degToRad } from '../utils'
|
||||
import { CONSTANTS } from '../utils/constant'
|
||||
import utils from './fishboneUtils'
|
||||
|
||||
// 鱼骨图
|
||||
class Fishbone extends Base {
|
||||
// 构造函数
|
||||
constructor(opt = {}) {
|
||||
super(opt)
|
||||
}
|
||||
|
||||
// 布局
|
||||
doLayout(callback) {
|
||||
let task = [
|
||||
() => {
|
||||
this.computedBaseValue()
|
||||
},
|
||||
() => {
|
||||
this.computedLeftTopValue()
|
||||
},
|
||||
() => {
|
||||
this.adjustLeftTopValue()
|
||||
},
|
||||
() => {
|
||||
callback(this.root)
|
||||
}
|
||||
]
|
||||
asyncRun(task)
|
||||
}
|
||||
|
||||
// 遍历数据创建节点、计算根节点的位置,计算根节点的子节点的top值
|
||||
computedBaseValue() {
|
||||
walk(
|
||||
this.renderer.renderTree,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex, index) => {
|
||||
// 创建节点
|
||||
let newNode = this.createNode(node, parent, isRoot, layerIndex)
|
||||
// 根节点定位在画布中心位置
|
||||
if (isRoot) {
|
||||
this.setNodeCenter(newNode)
|
||||
} else {
|
||||
// 非根节点
|
||||
// 三级及以下节点以上级方向为准
|
||||
if (parent._node.dir) {
|
||||
newNode.dir = parent._node.dir
|
||||
} else {
|
||||
// 节点生长方向
|
||||
newNode.dir =
|
||||
index % 2 === 0
|
||||
? CONSTANTS.TIMELINE_DIR.TOP
|
||||
: CONSTANTS.TIMELINE_DIR.BOTTOM
|
||||
}
|
||||
// 计算二级节点的top值
|
||||
if (parent._node.isRoot) {
|
||||
if (this.checkIsTop(newNode)) {
|
||||
newNode.top = parent._node.top - newNode.height
|
||||
} else {
|
||||
newNode.top = parent._node.top + parent._node.height
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!node.data.expand) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
null,
|
||||
true,
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
// 遍历节点树计算节点的left、top
|
||||
computedLeftTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (node.isRoot) {
|
||||
let topTotalLeft = node.left + node.width + node.height
|
||||
let bottomTotalLeft = node.left + node.width + node.height
|
||||
node.children.forEach(item => {
|
||||
if (this.checkIsTop(item)) {
|
||||
item.left = topTotalLeft
|
||||
topTotalLeft += item.width
|
||||
} else {
|
||||
item.left = bottomTotalLeft + 20
|
||||
bottomTotalLeft += item.width
|
||||
}
|
||||
})
|
||||
}
|
||||
let params = { layerIndex, node, ctx: this }
|
||||
if (this.checkIsTop(node)) {
|
||||
utils.top.computedLeftTopValue(params)
|
||||
} else {
|
||||
utils.bottom.computedLeftTopValue(params)
|
||||
}
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 调整节点left、top
|
||||
adjustLeftTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (!node.nodeData.data.expand) {
|
||||
return
|
||||
}
|
||||
let params = { node, parent, layerIndex, ctx: this }
|
||||
if (this.checkIsTop(node)) {
|
||||
utils.top.adjustLeftTopValueBefore(params)
|
||||
} else {
|
||||
utils.bottom.adjustLeftTopValueBefore(params)
|
||||
}
|
||||
},
|
||||
(node, parent) => {
|
||||
let params = { parent, node, ctx: this }
|
||||
if (this.checkIsTop(node)) {
|
||||
utils.top.adjustLeftTopValueAfter(params)
|
||||
} else {
|
||||
utils.bottom.adjustLeftTopValueAfter(params)
|
||||
}
|
||||
// 调整二级节点的子节点的left值
|
||||
if (node.isRoot) {
|
||||
let topTotalLeft = 0
|
||||
let bottomTotalLeft = 0
|
||||
node.children.forEach(item => {
|
||||
if (this.checkIsTop(item)) {
|
||||
item.left += topTotalLeft
|
||||
this.updateChildren(item.children, 'left', topTotalLeft)
|
||||
let { left, right } = this.getNodeBoundaries(item, 'h')
|
||||
topTotalLeft += right - left
|
||||
} else {
|
||||
item.left += bottomTotalLeft
|
||||
this.updateChildren(item.children, 'left', bottomTotalLeft)
|
||||
let { left, right } = this.getNodeBoundaries(item, 'h')
|
||||
bottomTotalLeft += right - left
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 递归计算节点的宽度
|
||||
getNodeAreaHeight(node) {
|
||||
let totalHeight = 0
|
||||
let loop = node => {
|
||||
totalHeight +=
|
||||
node.height +
|
||||
(this.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0)
|
||||
if (node.children.length) {
|
||||
node.children.forEach(item => {
|
||||
loop(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
loop(node)
|
||||
return totalHeight
|
||||
}
|
||||
|
||||
// 调整兄弟节点的left
|
||||
updateBrothersLeft(node) {
|
||||
let childrenList = node.children
|
||||
let totalAddWidth = 0
|
||||
childrenList.forEach(item => {
|
||||
item.left += totalAddWidth
|
||||
if (item.children && item.children.length) {
|
||||
this.updateChildren(item.children, 'left', totalAddWidth)
|
||||
}
|
||||
let { left, right } = this.getNodeBoundaries(item, 'h')
|
||||
let areaWidth = right - left
|
||||
let difference = areaWidth - item.width
|
||||
if (difference > 0) {
|
||||
totalAddWidth += difference
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 调整兄弟节点的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)
|
||||
}
|
||||
})
|
||||
// 更新父节点的位置
|
||||
if (this.checkIsTop(node)) {
|
||||
this.updateBrothersTop(node.parent, addHeight)
|
||||
} else {
|
||||
this.updateBrothersTop(
|
||||
node.parent,
|
||||
node.layerIndex === 3 ? 0 : addHeight
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查节点是否是上方节点
|
||||
checkIsTop(node) {
|
||||
return node.dir === CONSTANTS.TIMELINE_DIR.TOP
|
||||
}
|
||||
|
||||
// 绘制连线,连接该节点到其子节点
|
||||
renderLine(node, lines, style) {
|
||||
if (node.layerIndex !== 1 && node.children.length <= 0) {
|
||||
return []
|
||||
}
|
||||
let { top, height, expandBtnSize } = node
|
||||
let len = node.children.length
|
||||
if (node.isRoot) {
|
||||
// 当前节点是根节点
|
||||
// 根节点的子节点是和根节点同一水平线排列
|
||||
let maxx = -Infinity
|
||||
node.children.forEach((item) => {
|
||||
if (item.left > maxx) {
|
||||
maxx = item.left
|
||||
}
|
||||
// 水平线段到二级节点的连线
|
||||
let nodeLineX = item.left + item.width * 0.3
|
||||
let offset = item.height + node.height / 2
|
||||
let offsetX = offset / Math.tan(degToRad(this.mindMap.opt.fishboneDeg))
|
||||
let line = this.draw.path()
|
||||
if (this.checkIsTop(item)) {
|
||||
line.plot(
|
||||
`M ${nodeLineX - offsetX},${item.top + offset} L ${nodeLineX},${
|
||||
item.top
|
||||
}`
|
||||
)
|
||||
} else {
|
||||
line.plot(
|
||||
`M ${nodeLineX - offsetX},${
|
||||
item.top + item.height - offset
|
||||
} L ${nodeLineX},${item.top + item.height}`
|
||||
)
|
||||
}
|
||||
node.style.line(line)
|
||||
node._lines.push(line)
|
||||
style && style(line, node)
|
||||
})
|
||||
// 从根节点出发的水平线
|
||||
let nodeHalfTop = node.top + node.height / 2
|
||||
let offset = node.height / 2
|
||||
let line = this.draw.path()
|
||||
line.plot(
|
||||
`M ${node.left + node.width},${nodeHalfTop} L ${
|
||||
maxx - offset / Math.tan(degToRad(this.mindMap.opt.fishboneDeg))
|
||||
},${nodeHalfTop}`
|
||||
)
|
||||
node.style.line(line)
|
||||
node._lines.push(line)
|
||||
style && style(line, node)
|
||||
} else {
|
||||
// 当前节点为非根节点
|
||||
let maxy = -Infinity
|
||||
let miny = Infinity
|
||||
let maxx = -Infinity
|
||||
let x = node.left + node.width * 0.3
|
||||
node.children.forEach((item, index) => {
|
||||
if (item.left > maxx) {
|
||||
maxx = item.left
|
||||
}
|
||||
let y = item.top + item.height / 2
|
||||
if (y > maxy) {
|
||||
maxy = y
|
||||
}
|
||||
if (y < miny) {
|
||||
miny = y
|
||||
}
|
||||
// 水平线
|
||||
if (node.layerIndex > 1) {
|
||||
let path = `M ${x},${y} L ${item.left},${y}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
}
|
||||
})
|
||||
// 斜线
|
||||
if (len >= 0) {
|
||||
let line = this.draw.path()
|
||||
expandBtnSize = len > 0 ? expandBtnSize : 0
|
||||
let lineLength = maxx - node.left - node.width * 0.3
|
||||
lineLength = Math.max(lineLength, 0)
|
||||
let params = {
|
||||
node,
|
||||
line,
|
||||
top,
|
||||
x,
|
||||
lineLength,
|
||||
height,
|
||||
expandBtnSize,
|
||||
maxy,
|
||||
miny,
|
||||
ctx: this
|
||||
}
|
||||
if (this.checkIsTop(node)) {
|
||||
utils.top.renderLine(params)
|
||||
} else {
|
||||
utils.bottom.renderLine(params)
|
||||
}
|
||||
node.style.line(line)
|
||||
node._lines.push(line)
|
||||
style && style(line, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染按钮
|
||||
renderExpandBtn(node, btn) {
|
||||
let { width, height, expandBtnSize, isRoot } = node
|
||||
if (!isRoot) {
|
||||
let { translateX, translateY } = btn.transform()
|
||||
let params = {
|
||||
node,
|
||||
btn,
|
||||
expandBtnSize,
|
||||
translateX,
|
||||
translateY,
|
||||
width,
|
||||
height
|
||||
}
|
||||
if (this.checkIsTop(node)) {
|
||||
utils.top.renderExpandBtn(params)
|
||||
} else {
|
||||
utils.bottom.renderExpandBtn(params)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建概要节点
|
||||
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 Fishbone
|
||||
369
simple-mind-map/src/layouts/FishboneBottom.js
Normal file
@@ -0,0 +1,369 @@
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun } from '../utils'
|
||||
import { CONSTANTS } from '../utils/constant'
|
||||
|
||||
const degToRad = deg => {
|
||||
return (Math.PI / 180) * deg
|
||||
}
|
||||
|
||||
// 下方鱼骨图
|
||||
class Fishbone extends Base {
|
||||
// 构造函数
|
||||
constructor(opt = {}) {
|
||||
super(opt)
|
||||
}
|
||||
|
||||
// 布局
|
||||
doLayout(callback) {
|
||||
let task = [
|
||||
() => {
|
||||
this.computedBaseValue()
|
||||
},
|
||||
() => {
|
||||
this.computedLeftTopValue()
|
||||
},
|
||||
() => {
|
||||
this.adjustLeftTopValue()
|
||||
},
|
||||
() => {
|
||||
callback(this.root)
|
||||
}
|
||||
]
|
||||
asyncRun(task)
|
||||
}
|
||||
|
||||
// 遍历数据创建节点、计算根节点的位置,计算根节点的子节点的top值
|
||||
computedBaseValue() {
|
||||
walk(
|
||||
this.renderer.renderTree,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex, index) => {
|
||||
// 创建节点
|
||||
let newNode = this.createNode(node, parent, isRoot, layerIndex)
|
||||
// 根节点定位在画布中心位置
|
||||
if (isRoot) {
|
||||
this.setNodeCenter(newNode)
|
||||
} else {
|
||||
// 非根节点
|
||||
// 三级及以下节点以上级方向为准
|
||||
if (parent._node.dir) {
|
||||
newNode.dir = parent._node.dir
|
||||
} else {
|
||||
// 节点生长方向
|
||||
newNode.dir =
|
||||
index % 2 === 0
|
||||
? CONSTANTS.TIMELINE_DIR.TOP
|
||||
: CONSTANTS.TIMELINE_DIR.BOTTOM
|
||||
}
|
||||
// 计算二级节点的top值
|
||||
if (parent._node.isRoot) {
|
||||
newNode.top = parent._node.top + parent._node.height
|
||||
}
|
||||
}
|
||||
if (!node.data.expand) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
null,
|
||||
true,
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
// 遍历节点树计算节点的left、top
|
||||
computedLeftTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex, index) => {
|
||||
if (node.isRoot) {
|
||||
let totalLeft = node.left + node.width
|
||||
node.children.forEach(item => {
|
||||
item.left = totalLeft
|
||||
totalLeft += item.width
|
||||
})
|
||||
}
|
||||
if (layerIndex === 1 && node.children) {
|
||||
// 遍历二级节点的子节点
|
||||
let startLeft = node.left + node.width * 0.5
|
||||
let totalTop =
|
||||
node.top +
|
||||
node.height +
|
||||
(this.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0)
|
||||
|
||||
node.children.forEach(item => {
|
||||
item.left = startLeft
|
||||
item.top =
|
||||
totalTop +
|
||||
(this.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
totalTop +=
|
||||
item.height +
|
||||
(this.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
})
|
||||
}
|
||||
if (layerIndex > 1 && node.children) {
|
||||
// 遍历三级及以下节点的子节点
|
||||
let startLeft = node.left + node.width * 0.5
|
||||
let totalTop =
|
||||
node.top -
|
||||
(this.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0)
|
||||
node.children.forEach(item => {
|
||||
item.left = startLeft
|
||||
item.top = totalTop - item.height
|
||||
totalTop -=
|
||||
item.height +
|
||||
(this.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
})
|
||||
}
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 调整节点left、top
|
||||
adjustLeftTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (!node.nodeData.data.expand) {
|
||||
return
|
||||
}
|
||||
// 调整top
|
||||
let len = node.children.length
|
||||
// 调整三级节点的top
|
||||
// if (layerIndex === 2 && len > 0) {
|
||||
// let totalHeight = node.children.reduce((h, item) => {
|
||||
// return h + item.height
|
||||
// }, 0)
|
||||
// this.updateBrothersTop(node, totalHeight)
|
||||
// }
|
||||
if (layerIndex > 2 && len > 0) {
|
||||
let totalHeight = node.children.reduce((h, item) => {
|
||||
return (
|
||||
h +
|
||||
item.height +
|
||||
(this.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
)
|
||||
}, 0)
|
||||
this.updateBrothersTop(node, -totalHeight)
|
||||
}
|
||||
},
|
||||
(node, parent) => {
|
||||
// 将二级节点的子节点移到上方
|
||||
if (parent && parent.isRoot) {
|
||||
// 遍历二级节点的子节点
|
||||
let totalHeight = 0
|
||||
let totalHeight2 = 0
|
||||
node.children.forEach(item => {
|
||||
// 调整top
|
||||
let hasChildren = this.getNodeActChildrenLength(item) > 0
|
||||
let nodeTotalHeight = this.getNodeAreaHeight(item)
|
||||
let offset =
|
||||
hasChildren > 0
|
||||
? nodeTotalHeight -
|
||||
item.height -
|
||||
(hasChildren ? item.expandBtnSize : 0)
|
||||
: 0
|
||||
let _top = totalHeight + offset
|
||||
item.top += _top
|
||||
// 调整left
|
||||
let offsetLeft =
|
||||
(totalHeight2 + nodeTotalHeight) / Math.tan(degToRad(this.mindMap.opt.fishboneDeg))
|
||||
item.left += offsetLeft
|
||||
totalHeight += offset
|
||||
totalHeight2 += nodeTotalHeight
|
||||
// 同步更新后代节点
|
||||
this.updateChildrenPro(item.children, {
|
||||
top: _top,
|
||||
left: offsetLeft
|
||||
})
|
||||
})
|
||||
}
|
||||
// 调整二级节点的子节点的left值
|
||||
if (node.isRoot) {
|
||||
let totalLeft = 0
|
||||
node.children.forEach(item => {
|
||||
item.left += totalLeft
|
||||
this.updateChildren(item.children, 'left', totalLeft)
|
||||
let { left, right } = this.getNodeBoundaries(item, 'h')
|
||||
totalLeft += right - left
|
||||
})
|
||||
}
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 递归计算节点的宽度
|
||||
getNodeAreaHeight(node) {
|
||||
let totalHeight = 0
|
||||
let loop = node => {
|
||||
totalHeight +=
|
||||
node.height +
|
||||
(this.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0)
|
||||
if (node.children.length) {
|
||||
node.children.forEach(item => {
|
||||
loop(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
loop(node)
|
||||
return totalHeight
|
||||
}
|
||||
|
||||
// 调整兄弟节点的left
|
||||
updateBrothersLeft(node) {
|
||||
let childrenList = node.children
|
||||
let totalAddWidth = 0
|
||||
childrenList.forEach(item => {
|
||||
item.left += totalAddWidth
|
||||
if (item.children && item.children.length) {
|
||||
this.updateChildren(item.children, 'left', totalAddWidth)
|
||||
}
|
||||
// let areaWidth = this.getNodeAreaWidth(item)
|
||||
let { left, right } = this.getNodeBoundaries(item, 'h')
|
||||
let areaWidth = right - left
|
||||
let difference = areaWidth - item.width
|
||||
if (difference > 0) {
|
||||
totalAddWidth += difference
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 调整兄弟节点的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, node.layerIndex === 3 ? 0 : addHeight)
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制连线,连接该节点到其子节点
|
||||
renderLine(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return []
|
||||
}
|
||||
let { left, top, width, height, expandBtnSize } = node
|
||||
let len = node.children.length
|
||||
if (node.isRoot) {
|
||||
// 当前节点是根节点
|
||||
let prevBother = node
|
||||
// 根节点的子节点是和根节点同一水平线排列
|
||||
node.children.forEach((item, index) => {
|
||||
let x1 = prevBother.left + prevBother.width
|
||||
let x2 = item.left
|
||||
let y = node.top + node.height / 2
|
||||
let path = `M ${x1},${y} L ${x2},${y}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
prevBother = item
|
||||
})
|
||||
} else {
|
||||
// 当前节点为非根节点
|
||||
let maxy = -Infinity
|
||||
let miny = Infinity
|
||||
let maxx = -Infinity
|
||||
let x = node.left + node.width * 0.3
|
||||
node.children.forEach((item, index) => {
|
||||
if (item.left > maxx) {
|
||||
maxx = item.left
|
||||
}
|
||||
let y = item.top + item.height / 2
|
||||
if (y > maxy) {
|
||||
maxy = y
|
||||
}
|
||||
if (y < miny) {
|
||||
miny = y
|
||||
}
|
||||
// 水平线
|
||||
if (node.layerIndex > 1) {
|
||||
let path = `M ${x},${y} L ${item.left},${y}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
}
|
||||
})
|
||||
// 竖线
|
||||
if (len > 0) {
|
||||
let line = this.draw.path()
|
||||
expandBtnSize = len > 0 ? expandBtnSize : 0
|
||||
let lineLength = maxx - node.left - node.width * 0.3
|
||||
if (node.parent && node.parent.isRoot) {
|
||||
line.plot(
|
||||
`M ${x},${top + height} L ${x + lineLength},${
|
||||
top + height + Math.tan(degToRad(this.mindMap.opt.fishboneDeg)) * lineLength
|
||||
}`
|
||||
)
|
||||
} else {
|
||||
line.plot(`M ${x},${top} L ${x},${miny}`)
|
||||
}
|
||||
node.style.line(line)
|
||||
node._lines.push(line)
|
||||
style && style(line, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染按钮
|
||||
renderExpandBtn(node, btn) {
|
||||
let { width, height, expandBtnSize, isRoot } = node
|
||||
if (!isRoot) {
|
||||
let { translateX, translateY } = btn.transform()
|
||||
if (node.parent && node.parent.isRoot) {
|
||||
btn.translate(
|
||||
width * 0.3 - expandBtnSize / 2 - translateX,
|
||||
height + expandBtnSize / 2 - translateY
|
||||
)
|
||||
} else {
|
||||
btn.translate(
|
||||
width * 0.3 - expandBtnSize / 2 - translateX,
|
||||
-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 Fishbone
|
||||
350
simple-mind-map/src/layouts/FishboneTop.js
Normal file
@@ -0,0 +1,350 @@
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun } from '../utils'
|
||||
import { CONSTANTS } from '../utils/constant'
|
||||
|
||||
const degToRad = deg => {
|
||||
return (Math.PI / 180) * deg
|
||||
}
|
||||
|
||||
// 上方鱼骨图
|
||||
class Fishbone extends Base {
|
||||
// 构造函数
|
||||
constructor(opt = {}) {
|
||||
super(opt)
|
||||
}
|
||||
|
||||
// 布局
|
||||
doLayout(callback) {
|
||||
let task = [
|
||||
() => {
|
||||
this.computedBaseValue()
|
||||
},
|
||||
() => {
|
||||
this.computedLeftTopValue()
|
||||
},
|
||||
() => {
|
||||
this.adjustLeftTopValue()
|
||||
},
|
||||
() => {
|
||||
callback(this.root)
|
||||
}
|
||||
]
|
||||
asyncRun(task)
|
||||
}
|
||||
|
||||
// 遍历数据创建节点、计算根节点的位置,计算根节点的子节点的top值
|
||||
computedBaseValue() {
|
||||
walk(
|
||||
this.renderer.renderTree,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex, index) => {
|
||||
// 创建节点
|
||||
let newNode = this.createNode(node, parent, isRoot, layerIndex)
|
||||
// 根节点定位在画布中心位置
|
||||
if (isRoot) {
|
||||
this.setNodeCenter(newNode)
|
||||
} else {
|
||||
// 非根节点
|
||||
// 三级及以下节点以上级方向为准
|
||||
if (parent._node.dir) {
|
||||
newNode.dir = parent._node.dir
|
||||
} else {
|
||||
// 节点生长方向
|
||||
newNode.dir =
|
||||
index % 2 === 0
|
||||
? CONSTANTS.TIMELINE_DIR.TOP
|
||||
: CONSTANTS.TIMELINE_DIR.BOTTOM
|
||||
}
|
||||
// 计算二级节点的top值
|
||||
if (parent._node.isRoot) {
|
||||
newNode.top = parent._node.top - newNode.height
|
||||
}
|
||||
}
|
||||
if (!node.data.expand) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
null,
|
||||
true,
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
// 遍历节点树计算节点的left、top
|
||||
computedLeftTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex, index) => {
|
||||
if (node.isRoot) {
|
||||
let totalLeft = node.left + node.width
|
||||
node.children.forEach(item => {
|
||||
item.left = totalLeft
|
||||
totalLeft += item.width
|
||||
})
|
||||
}
|
||||
if (layerIndex >= 1 && node.children) {
|
||||
// 遍历三级及以下节点的子节点
|
||||
let startLeft = node.left + node.width * 0.5
|
||||
let totalTop =
|
||||
node.top +
|
||||
node.height +
|
||||
(this.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0)
|
||||
node.children.forEach(item => {
|
||||
item.left = startLeft
|
||||
item.top += totalTop
|
||||
totalTop +=
|
||||
item.height +
|
||||
(this.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
})
|
||||
}
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 调整节点left、top
|
||||
adjustLeftTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (!node.nodeData.data.expand) {
|
||||
return
|
||||
}
|
||||
// 调整top
|
||||
let len = node.children.length
|
||||
// 调整三级及以下节点的top
|
||||
if (parent && !parent.isRoot && len > 0) {
|
||||
let totalHeight = node.children.reduce((h, item) => {
|
||||
return (
|
||||
h +
|
||||
item.height +
|
||||
(this.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
)
|
||||
}, 0)
|
||||
this.updateBrothersTop(node, totalHeight)
|
||||
}
|
||||
},
|
||||
(node, parent) => {
|
||||
// 将二级节点的子节点移到上方
|
||||
if (parent && parent.isRoot) {
|
||||
// 遍历二级节点的子节点
|
||||
let totalHeight = 0
|
||||
node.children.forEach(item => {
|
||||
// 调整top
|
||||
let nodeTotalHeight = this.getNodeAreaHeight(item)
|
||||
let _top = item.top
|
||||
item.top =
|
||||
node.top - (item.top - node.top) - nodeTotalHeight + node.height
|
||||
// 调整left
|
||||
let offsetLeft =
|
||||
(nodeTotalHeight + totalHeight) / Math.tan(degToRad(this.mindMap.opt.fishboneDeg))
|
||||
item.left += offsetLeft
|
||||
totalHeight += nodeTotalHeight
|
||||
// 同步更新后代节点
|
||||
this.updateChildrenPro(item.children, {
|
||||
top: item.top - _top,
|
||||
left: offsetLeft
|
||||
})
|
||||
})
|
||||
}
|
||||
// 调整二级节点的子节点的left值
|
||||
if (node.isRoot) {
|
||||
let totalLeft = 0
|
||||
node.children.forEach(item => {
|
||||
item.left += totalLeft
|
||||
this.updateChildren(item.children, 'left', totalLeft)
|
||||
let { left, right } = this.getNodeBoundaries(item, 'h')
|
||||
totalLeft += right - left
|
||||
})
|
||||
}
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 递归计算节点的宽度
|
||||
getNodeAreaHeight(node) {
|
||||
let totalHeight = 0
|
||||
let loop = node => {
|
||||
totalHeight +=
|
||||
node.height +
|
||||
(this.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0)
|
||||
if (node.children.length) {
|
||||
node.children.forEach(item => {
|
||||
loop(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
loop(node)
|
||||
return totalHeight
|
||||
}
|
||||
|
||||
// 调整兄弟节点的left
|
||||
updateBrothersLeft(node) {
|
||||
let childrenList = node.children
|
||||
let totalAddWidth = 0
|
||||
childrenList.forEach(item => {
|
||||
item.left += totalAddWidth
|
||||
if (item.children && item.children.length) {
|
||||
this.updateChildren(item.children, 'left', totalAddWidth)
|
||||
}
|
||||
// let areaWidth = this.getNodeAreaWidth(item)
|
||||
let { left, right } = this.getNodeBoundaries(item, 'h')
|
||||
let areaWidth = right - left
|
||||
let difference = areaWidth - item.width
|
||||
if (difference > 0) {
|
||||
totalAddWidth += difference
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 调整兄弟节点的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
|
||||
if (node.isRoot) {
|
||||
// 当前节点是根节点
|
||||
let prevBother = node
|
||||
// 根节点的子节点是和根节点同一水平线排列
|
||||
node.children.forEach((item, index) => {
|
||||
let x1 = prevBother.left + prevBother.width
|
||||
let x2 = item.left
|
||||
let y = node.top + node.height / 2
|
||||
let path = `M ${x1},${y} L ${x2},${y}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
prevBother = item
|
||||
})
|
||||
} else {
|
||||
// 当前节点为非根节点
|
||||
let maxy = -Infinity
|
||||
let miny = Infinity
|
||||
let maxx = -Infinity
|
||||
let x = node.left + node.width * 0.3
|
||||
node.children.forEach((item, index) => {
|
||||
if (item.left > maxx) {
|
||||
maxx = item.left
|
||||
}
|
||||
let y = item.top + item.height / 2
|
||||
if (y > maxy) {
|
||||
maxy = y
|
||||
}
|
||||
if (y < miny) {
|
||||
miny = y
|
||||
}
|
||||
// 水平线
|
||||
if (node.layerIndex > 1) {
|
||||
let path = `M ${x},${y} L ${item.left},${y}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
}
|
||||
})
|
||||
// 竖线
|
||||
if (len > 0) {
|
||||
let line = this.draw.path()
|
||||
expandBtnSize = len > 0 ? expandBtnSize : 0
|
||||
let lineLength = maxx - node.left - node.width * 0.3
|
||||
if (
|
||||
node.parent &&
|
||||
node.parent.isRoot &&
|
||||
node.dir === CONSTANTS.TIMELINE_DIR.TOP
|
||||
) {
|
||||
line.plot(
|
||||
`M ${x},${top} L ${x + lineLength},${
|
||||
top - Math.tan(degToRad(this.mindMap.opt.fishboneDeg)) * lineLength
|
||||
}`
|
||||
)
|
||||
} else {
|
||||
if (node.parent && node.parent.isRoot) {
|
||||
line.plot(
|
||||
`M ${x},${top} L ${x + lineLength},${
|
||||
top - Math.tan(degToRad(this.mindMap.opt.fishboneDeg)) * lineLength
|
||||
}`
|
||||
)
|
||||
} else {
|
||||
line.plot(`M ${x},${top + height + expandBtnSize} L ${x},${maxy}`)
|
||||
}
|
||||
}
|
||||
node.style.line(line)
|
||||
node._lines.push(line)
|
||||
style && style(line, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染按钮
|
||||
renderExpandBtn(node, btn) {
|
||||
let { width, height, expandBtnSize, isRoot } = node
|
||||
if (!isRoot) {
|
||||
let { translateX, translateY } = btn.transform()
|
||||
if (node.parent && node.parent.isRoot) {
|
||||
btn.translate(
|
||||
width * 0.3 - expandBtnSize / 2 - translateX,
|
||||
-expandBtnSize / 2 - translateY
|
||||
)
|
||||
} else {
|
||||
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 Fishbone
|
||||
@@ -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,
|
||||
|
||||
338
simple-mind-map/src/layouts/Timeline.js
Normal file
@@ -0,0 +1,338 @@
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun } from '../utils'
|
||||
import { CONSTANTS } from '../utils/constant'
|
||||
|
||||
// 时间轴
|
||||
class Timeline extends Base {
|
||||
// 构造函数
|
||||
constructor(opt = {}, layout) {
|
||||
super(opt)
|
||||
this.layout = layout
|
||||
}
|
||||
|
||||
// 布局
|
||||
doLayout(callback) {
|
||||
let task = [
|
||||
() => {
|
||||
this.computedBaseValue()
|
||||
},
|
||||
() => {
|
||||
this.computedLeftTopValue()
|
||||
},
|
||||
() => {
|
||||
this.adjustLeftTopValue()
|
||||
},
|
||||
() => {
|
||||
callback(this.root)
|
||||
}
|
||||
]
|
||||
asyncRun(task)
|
||||
}
|
||||
|
||||
// 遍历数据创建节点、计算根节点的位置,计算根节点的子节点的top值
|
||||
computedBaseValue() {
|
||||
walk(
|
||||
this.renderer.renderTree,
|
||||
null,
|
||||
(cur, parent, isRoot, layerIndex, index) => {
|
||||
let newNode = this.createNode(cur, parent, isRoot, layerIndex)
|
||||
// 根节点定位在画布中心位置
|
||||
if (isRoot) {
|
||||
this.setNodeCenter(newNode)
|
||||
} else {
|
||||
// 非根节点
|
||||
// 时间轴2类型需要交替显示
|
||||
if (this.layout === CONSTANTS.LAYOUT.TIMELINE2) {
|
||||
// 三级及以下节点以上级为准
|
||||
if (parent._node.dir) {
|
||||
newNode.dir = parent._node.dir
|
||||
} else {
|
||||
// 节点生长方向
|
||||
newNode.dir =
|
||||
index % 2 === 0
|
||||
? CONSTANTS.TIMELINE_DIR.BOTTOM
|
||||
: CONSTANTS.TIMELINE_DIR.TOP
|
||||
}
|
||||
} else {
|
||||
newNode.dir = ''
|
||||
}
|
||||
if (parent._node.isRoot) {
|
||||
newNode.top =
|
||||
parent._node.top +
|
||||
(cur._node.height > parent._node.height
|
||||
? -(cur._node.height - parent._node.height) / 2
|
||||
: (parent._node.height - cur._node.height) / 2)
|
||||
}
|
||||
}
|
||||
if (!cur.data.expand) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
null,
|
||||
true,
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
// 遍历节点树计算节点的left、top
|
||||
computedLeftTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex, index) => {
|
||||
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
|
||||
let totalLeft = left + marginX
|
||||
node.children.forEach(cur => {
|
||||
cur.left = totalLeft
|
||||
totalLeft += cur.width + marginX
|
||||
})
|
||||
} else {
|
||||
let totalTop =
|
||||
node.top +
|
||||
node.height +
|
||||
marginY +
|
||||
(this.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0)
|
||||
node.children.forEach(cur => {
|
||||
cur.left = node.left + node.width * 0.5
|
||||
cur.top = totalTop
|
||||
totalTop +=
|
||||
cur.height +
|
||||
marginY +
|
||||
(this.getNodeActChildrenLength(cur) > 0 ? cur.expandBtnSize : 0)
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 调整节点left、top
|
||||
adjustLeftTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (!node.nodeData.data.expand) {
|
||||
return
|
||||
}
|
||||
// 调整left
|
||||
if (node.isRoot) {
|
||||
this.updateBrothersLeft(node)
|
||||
}
|
||||
// 调整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 +
|
||||
(this.getNodeActChildrenLength(item) > 0
|
||||
? item.expandBtnSize
|
||||
: 0)
|
||||
)
|
||||
}, 0) +
|
||||
len * marginY
|
||||
this.updateBrothersTop(node, totalHeight)
|
||||
}
|
||||
},
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (
|
||||
parent &&
|
||||
parent.isRoot &&
|
||||
node.dir === CONSTANTS.TIMELINE_DIR.TOP
|
||||
) {
|
||||
// 遍历二级节点的子节点
|
||||
node.children.forEach(item => {
|
||||
let totalHeight = this.getNodeAreaHeight(item)
|
||||
let _top = item.top
|
||||
item.top =
|
||||
node.top - (item.top - node.top) - totalHeight + node.height
|
||||
this.updateChildren(item.children, 'top', item.top - _top)
|
||||
})
|
||||
}
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 递归计算节点的宽度
|
||||
getNodeAreaHeight(node) {
|
||||
let totalHeight = 0
|
||||
let loop = node => {
|
||||
totalHeight +=
|
||||
node.height +
|
||||
(this.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0) +
|
||||
this.getMarginY(node.layerIndex)
|
||||
if (node.children.length) {
|
||||
node.children.forEach(item => {
|
||||
loop(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
loop(node)
|
||||
return totalHeight
|
||||
}
|
||||
|
||||
// 调整兄弟节点的left
|
||||
updateBrothersLeft(node) {
|
||||
let childrenList = node.children
|
||||
let totalAddWidth = 0
|
||||
childrenList.forEach(item => {
|
||||
item.left += totalAddWidth
|
||||
if (item.children && item.children.length) {
|
||||
this.updateChildren(item.children, 'left', totalAddWidth)
|
||||
}
|
||||
// let areaWidth = this.getNodeAreaWidth(item)
|
||||
let { left, right } = this.getNodeBoundaries(item, 'h')
|
||||
let areaWidth = right - left
|
||||
let difference = areaWidth - item.width
|
||||
if (difference > 0) {
|
||||
totalAddWidth += difference
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 调整兄弟节点的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
|
||||
if (node.isRoot) {
|
||||
// 当前节点是根节点
|
||||
let prevBother = node
|
||||
// 根节点的子节点是和根节点同一水平线排列
|
||||
node.children.forEach((item, index) => {
|
||||
let x1 = prevBother.left + prevBother.width
|
||||
let x2 = item.left
|
||||
let y = node.top + node.height / 2
|
||||
let path = `M ${x1},${y} L ${x2},${y}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
prevBother = item
|
||||
})
|
||||
} else {
|
||||
// 当前节点为非根节点
|
||||
let maxy = -Infinity
|
||||
let miny = Infinity
|
||||
let x = node.left + node.width * 0.3
|
||||
node.children.forEach((item, index) => {
|
||||
let y = item.top + item.height / 2
|
||||
if (y > maxy) {
|
||||
maxy = y
|
||||
}
|
||||
if (y < miny) {
|
||||
miny = y
|
||||
}
|
||||
// 水平线
|
||||
let path = `M ${x},${y} L ${item.left},${y}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
})
|
||||
// 竖线
|
||||
if (len > 0) {
|
||||
let line = this.draw.path()
|
||||
expandBtnSize = len > 0 ? expandBtnSize : 0
|
||||
if (
|
||||
node.parent &&
|
||||
node.parent.isRoot &&
|
||||
node.dir === CONSTANTS.TIMELINE_DIR.TOP
|
||||
) {
|
||||
line.plot(`M ${x},${top} L ${x},${miny}`)
|
||||
} else {
|
||||
line.plot(`M ${x},${top + height + expandBtnSize} L ${x},${maxy}`)
|
||||
}
|
||||
node.style.line(line)
|
||||
node._lines.push(line)
|
||||
style && style(line, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染按钮
|
||||
renderExpandBtn(node, btn) {
|
||||
let { width, height, expandBtnSize, isRoot } = node
|
||||
if (!isRoot) {
|
||||
let { translateX, translateY } = btn.transform()
|
||||
if (
|
||||
node.parent &&
|
||||
node.parent.isRoot &&
|
||||
node.dir === CONSTANTS.TIMELINE_DIR.TOP
|
||||
) {
|
||||
btn.translate(
|
||||
width * 0.3 - expandBtnSize / 2 - translateX,
|
||||
-expandBtnSize / 2 - translateY
|
||||
)
|
||||
} else {
|
||||
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 Timeline
|
||||
218
simple-mind-map/src/layouts/fishboneUtils.js
Normal file
@@ -0,0 +1,218 @@
|
||||
import { degToRad } from '../utils/'
|
||||
|
||||
export default {
|
||||
top: {
|
||||
renderExpandBtn({
|
||||
node,
|
||||
btn,
|
||||
expandBtnSize,
|
||||
translateX,
|
||||
translateY,
|
||||
width,
|
||||
height
|
||||
}) {
|
||||
if (node.parent && node.parent.isRoot) {
|
||||
btn.translate(
|
||||
width * 0.3 - expandBtnSize / 2 - translateX,
|
||||
-expandBtnSize / 2 - translateY
|
||||
)
|
||||
} else {
|
||||
btn.translate(
|
||||
width * 0.3 - expandBtnSize / 2 - translateX,
|
||||
height + expandBtnSize / 2 - translateY
|
||||
)
|
||||
}
|
||||
},
|
||||
renderLine({
|
||||
node,
|
||||
line,
|
||||
top,
|
||||
x,
|
||||
lineLength,
|
||||
height,
|
||||
expandBtnSize,
|
||||
maxy,
|
||||
ctx
|
||||
}) {
|
||||
if (node.parent && node.parent.isRoot) {
|
||||
line.plot(
|
||||
`M ${x},${top} L ${x + lineLength},${
|
||||
top - Math.tan(degToRad(ctx.mindMap.opt.fishboneDeg)) * lineLength
|
||||
}`
|
||||
)
|
||||
} else {
|
||||
line.plot(`M ${x},${top + height + expandBtnSize} L ${x},${maxy}`)
|
||||
}
|
||||
},
|
||||
computedLeftTopValue({ layerIndex, node, ctx }) {
|
||||
if (layerIndex >= 1 && node.children) {
|
||||
// 遍历三级及以下节点的子节点
|
||||
let startLeft = node.left + node.width * 0.5
|
||||
let totalTop =
|
||||
node.top +
|
||||
node.height +
|
||||
(ctx.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0)
|
||||
node.children.forEach(item => {
|
||||
item.left = startLeft
|
||||
item.top += totalTop
|
||||
totalTop +=
|
||||
item.height +
|
||||
(ctx.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
})
|
||||
}
|
||||
},
|
||||
adjustLeftTopValueBefore({ node, parent, ctx }) {
|
||||
// 调整top
|
||||
let len = node.children.length
|
||||
// 调整三级及以下节点的top
|
||||
if (parent && !parent.isRoot && len > 0) {
|
||||
let totalHeight = node.children.reduce((h, item) => {
|
||||
return (
|
||||
h +
|
||||
item.height +
|
||||
(ctx.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
)
|
||||
}, 0)
|
||||
ctx.updateBrothersTop(node, totalHeight)
|
||||
}
|
||||
},
|
||||
adjustLeftTopValueAfter({ parent, node, ctx }) {
|
||||
// 将二级节点的子节点移到上方
|
||||
if (parent && parent.isRoot) {
|
||||
// 遍历二级节点的子节点
|
||||
let totalHeight = 0
|
||||
node.children.forEach(item => {
|
||||
// 调整top
|
||||
let nodeTotalHeight = ctx.getNodeAreaHeight(item)
|
||||
let _top = item.top
|
||||
item.top =
|
||||
node.top - (item.top - node.top) - nodeTotalHeight + node.height
|
||||
// 调整left
|
||||
let offsetLeft =
|
||||
(nodeTotalHeight + totalHeight) / Math.tan(degToRad(ctx.mindMap.opt.fishboneDeg))
|
||||
item.left += offsetLeft
|
||||
totalHeight += nodeTotalHeight
|
||||
// 同步更新后代节点
|
||||
ctx.updateChildrenPro(item.children, {
|
||||
top: item.top - _top,
|
||||
left: offsetLeft
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
bottom: {
|
||||
renderExpandBtn({
|
||||
node,
|
||||
btn,
|
||||
expandBtnSize,
|
||||
translateX,
|
||||
translateY,
|
||||
width,
|
||||
height
|
||||
}) {
|
||||
if (node.parent && node.parent.isRoot) {
|
||||
btn.translate(
|
||||
width * 0.3 - expandBtnSize / 2 - translateX,
|
||||
height + expandBtnSize / 2 - translateY
|
||||
)
|
||||
} else {
|
||||
btn.translate(
|
||||
width * 0.3 - expandBtnSize / 2 - translateX,
|
||||
-expandBtnSize / 2 - translateY
|
||||
)
|
||||
}
|
||||
},
|
||||
renderLine({ node, line, top, x, lineLength, height, miny, ctx }) {
|
||||
if (node.parent && node.parent.isRoot) {
|
||||
line.plot(
|
||||
`M ${x},${top + height} L ${x + lineLength},${
|
||||
top + height + Math.tan(degToRad(ctx.mindMap.opt.fishboneDeg)) * lineLength
|
||||
}`
|
||||
)
|
||||
} else {
|
||||
line.plot(`M ${x},${top} L ${x},${miny}`)
|
||||
}
|
||||
},
|
||||
computedLeftTopValue({ layerIndex, node, ctx }) {
|
||||
if (layerIndex === 1 && node.children) {
|
||||
// 遍历二级节点的子节点
|
||||
let startLeft = node.left + node.width * 0.5
|
||||
let totalTop =
|
||||
node.top +
|
||||
node.height +
|
||||
(ctx.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0)
|
||||
|
||||
node.children.forEach(item => {
|
||||
item.left = startLeft
|
||||
item.top =
|
||||
totalTop +
|
||||
(ctx.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
totalTop +=
|
||||
item.height +
|
||||
(ctx.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
})
|
||||
}
|
||||
if (layerIndex > 1 && node.children) {
|
||||
// 遍历三级及以下节点的子节点
|
||||
let startLeft = node.left + node.width * 0.5
|
||||
let totalTop =
|
||||
node.top -
|
||||
(ctx.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0)
|
||||
node.children.forEach(item => {
|
||||
item.left = startLeft
|
||||
item.top = totalTop - item.height
|
||||
totalTop -=
|
||||
item.height +
|
||||
(ctx.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
})
|
||||
}
|
||||
},
|
||||
adjustLeftTopValueBefore({ node, ctx, layerIndex }) {
|
||||
// 调整top
|
||||
let len = node.children.length
|
||||
if (layerIndex > 2 && len > 0) {
|
||||
let totalHeight = node.children.reduce((h, item) => {
|
||||
return (
|
||||
h +
|
||||
item.height +
|
||||
(ctx.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
)
|
||||
}, 0)
|
||||
ctx.updateBrothersTop(node, -totalHeight)
|
||||
}
|
||||
},
|
||||
adjustLeftTopValueAfter({ parent, node, ctx }) {
|
||||
// 将二级节点的子节点移到上方
|
||||
if (parent && parent.isRoot) {
|
||||
// 遍历二级节点的子节点
|
||||
let totalHeight = 0
|
||||
let totalHeight2 = 0
|
||||
node.children.forEach(item => {
|
||||
// 调整top
|
||||
let hasChildren = ctx.getNodeActChildrenLength(item) > 0
|
||||
let nodeTotalHeight = ctx.getNodeAreaHeight(item)
|
||||
let offset =
|
||||
hasChildren > 0
|
||||
? nodeTotalHeight -
|
||||
item.height -
|
||||
(hasChildren ? item.expandBtnSize : 0)
|
||||
: 0
|
||||
let _top = totalHeight + offset
|
||||
item.top += _top
|
||||
// 调整left
|
||||
let offsetLeft =
|
||||
(totalHeight2 + nodeTotalHeight) / Math.tan(degToRad(ctx.mindMap.opt.fishboneDeg))
|
||||
item.left += offsetLeft
|
||||
totalHeight += offset
|
||||
totalHeight2 += nodeTotalHeight
|
||||
// 同步更新后代节点
|
||||
ctx.updateChildrenPro(item.children, {
|
||||
top: _top,
|
||||
left: offsetLeft
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
57
simple-mind-map/src/themes/blackGold.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import defaultTheme from './default'
|
||||
import merge from 'deepmerge'
|
||||
|
||||
// 黑金
|
||||
export default merge(defaultTheme, {
|
||||
// 背景颜色
|
||||
backgroundColor: 'rgb(18, 20, 20)',
|
||||
// 连线的颜色
|
||||
lineColor: 'rgb(205, 186, 156)',
|
||||
lineWidth: 3,
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 3,
|
||||
// 概要连线的颜色
|
||||
generalizationLineColor: 'rgb(245, 224, 191)',
|
||||
// 根节点样式
|
||||
root: {
|
||||
fillColor: 'rgb(255, 208, 124)',
|
||||
color: 'rgb(111, 61, 6)',
|
||||
borderColor: '',
|
||||
borderWidth: 0,
|
||||
fontSize: 24,
|
||||
active: {
|
||||
borderColor: '#fff',
|
||||
borderWidth: 3
|
||||
}
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
fillColor: 'rgb(66, 57, 46)',
|
||||
color: 'rgb(225, 201, 158)',
|
||||
borderColor: 'rgb(245, 224, 191)',
|
||||
borderWidth: 2,
|
||||
fontSize: 18,
|
||||
active: {
|
||||
borderColor: 'rgb(255, 208, 124)'
|
||||
}
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
fontSize: 14,
|
||||
color: 'rgb(231, 203, 155)',
|
||||
active: {
|
||||
borderColor: 'rgb(255, 208, 124)'
|
||||
}
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
fontSize: 14,
|
||||
fillColor: 'rgb(56, 45, 34)',
|
||||
borderColor: 'rgb(104, 84, 61)',
|
||||
borderWidth: 2,
|
||||
color: 'rgb(242, 216, 176)',
|
||||
active: {
|
||||
borderColor: 'rgb(255, 208, 124)'
|
||||
}
|
||||
}
|
||||
})
|
||||
58
simple-mind-map/src/themes/blackHumour.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import defaultTheme from './default'
|
||||
import merge from 'deepmerge'
|
||||
|
||||
// 黑色幽默
|
||||
export default merge(defaultTheme, {
|
||||
// 背景颜色
|
||||
backgroundColor: 'rgb(27, 31, 34)',
|
||||
// 连线的颜色
|
||||
lineColor: 'rgb(75, 81, 78)',
|
||||
lineWidth: 3,
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 3,
|
||||
// 概要连线的颜色
|
||||
generalizationLineColor: 'rgb(255, 119, 34)',
|
||||
// 根节点样式
|
||||
root: {
|
||||
fillColor: 'rgb(36, 179, 96)',
|
||||
color: '#fff',
|
||||
borderColor: '',
|
||||
borderWidth: 0,
|
||||
fontSize: 24,
|
||||
active: {
|
||||
borderColor: 'rgb(254, 199, 13)',
|
||||
borderWidth: 3
|
||||
}
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
fillColor: 'rgb(254, 199, 13)',
|
||||
color: 'rgb(0, 0, 0)',
|
||||
borderColor: '',
|
||||
borderWidth: 0,
|
||||
fontSize: 18,
|
||||
active: {
|
||||
borderColor: 'rgb(36, 179, 96)',
|
||||
borderWidth: 3
|
||||
}
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
fontSize: 14,
|
||||
color: 'rgb(204, 204, 204)',
|
||||
active: {
|
||||
borderColor: 'rgb(254, 199, 13)'
|
||||
}
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
fontSize: 14,
|
||||
fillColor: 'rgb(27, 31, 34)',
|
||||
borderColor: 'rgb(255, 119, 34)',
|
||||
borderWidth: 2,
|
||||
color: 'rgb(204, 204, 204)',
|
||||
active: {
|
||||
borderColor: 'rgb(36, 179, 96)'
|
||||
}
|
||||
}
|
||||
})
|
||||
55
simple-mind-map/src/themes/coffee.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import defaultTheme from './default'
|
||||
import merge from 'deepmerge'
|
||||
|
||||
// 咖啡
|
||||
export default merge(defaultTheme, {
|
||||
// 连线的颜色
|
||||
lineColor: 'rgb(173, 123, 91)',
|
||||
lineWidth: 4,
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 4,
|
||||
// 概要连线的颜色
|
||||
generalizationLineColor: 'rgb(173, 123, 91)',
|
||||
// 根节点样式
|
||||
root: {
|
||||
fillColor: 'rgb(202, 117, 79)',
|
||||
color: '#fff',
|
||||
borderColor: '',
|
||||
borderWidth: 0,
|
||||
fontSize: 24,
|
||||
active: {
|
||||
borderColor: 'rgb(173, 123, 91)',
|
||||
borderWidth: 3
|
||||
}
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
fillColor: 'rgb(245, 231, 216)',
|
||||
color: 'rgb(125, 86, 42)',
|
||||
borderColor: '',
|
||||
borderWidth: 0,
|
||||
fontSize: 18,
|
||||
active: {
|
||||
borderColor: 'rgb(173, 123, 91)'
|
||||
}
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
fontSize: 14,
|
||||
color: 'rgb(96, 71, 47)',
|
||||
active: {
|
||||
borderColor: 'rgb(173, 123, 91)'
|
||||
}
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
fontSize: 14,
|
||||
fillColor: 'rgb(255, 249, 239)',
|
||||
borderColor: 'rgb(173, 123, 91)',
|
||||
borderWidth: 2,
|
||||
color: 'rgb(122, 83, 44)',
|
||||
active: {
|
||||
borderColor: 'rgb(202, 117, 79)'
|
||||
}
|
||||
}
|
||||
})
|
||||
55
simple-mind-map/src/themes/courseGreen.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import defaultTheme from './default'
|
||||
import merge from 'deepmerge'
|
||||
|
||||
// 课程绿
|
||||
export default merge(defaultTheme, {
|
||||
// 连线的颜色
|
||||
lineColor: 'rgb(113, 195, 169)',
|
||||
lineWidth: 3,
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 3,
|
||||
// 概要连线的颜色
|
||||
generalizationLineColor: 'rgb(113, 195, 169)',
|
||||
// 根节点样式
|
||||
root: {
|
||||
fillColor: 'rgb(16, 160, 121)',
|
||||
color: '#fff',
|
||||
borderColor: '',
|
||||
borderWidth: 0,
|
||||
fontSize: 24,
|
||||
active: {
|
||||
borderColor: 'rgb(173, 91, 12)',
|
||||
borderWidth: 3
|
||||
}
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
fillColor: 'rgb(240, 252, 249)',
|
||||
color: 'rgb(50, 113, 96)',
|
||||
borderColor: 'rgb(113, 195, 169)',
|
||||
borderWidth: 2,
|
||||
fontSize: 18,
|
||||
active: {
|
||||
borderColor: 'rgb(173, 91, 12)'
|
||||
}
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
fontSize: 14,
|
||||
color: 'rgb(10, 59, 43)',
|
||||
active: {
|
||||
borderColor: 'rgb(173, 91, 12)'
|
||||
}
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
fontSize: 14,
|
||||
fillColor: 'rgb(246, 238, 211)',
|
||||
borderColor: '',
|
||||
borderWidth: 0,
|
||||
color: 'rgb(173, 91, 12)',
|
||||
active: {
|
||||
borderColor: 'rgb(113, 195, 169)'
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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']
|
||||
|
||||
@@ -20,6 +20,13 @@ import vitalityOrange from './vitalityOrange'
|
||||
import greenLeaf from './greenLeaf'
|
||||
import dark2 from './dark2'
|
||||
import skyGreen from './skyGreen'
|
||||
import simpleBlack from './simpleBlack'
|
||||
import courseGreen from './courseGreen'
|
||||
import coffee from './coffee'
|
||||
import redSpirit from './redSpirit'
|
||||
import blackHumour from './blackHumour'
|
||||
import lateNightOffice from './lateNightOffice'
|
||||
import blackGold from './blackGold'
|
||||
|
||||
export default {
|
||||
default: defaultTheme,
|
||||
@@ -43,5 +50,12 @@ export default {
|
||||
vitalityOrange,
|
||||
greenLeaf,
|
||||
dark2,
|
||||
skyGreen
|
||||
skyGreen,
|
||||
simpleBlack,
|
||||
courseGreen,
|
||||
coffee,
|
||||
redSpirit,
|
||||
blackHumour,
|
||||
lateNightOffice,
|
||||
blackGold
|
||||
}
|
||||
|
||||
58
simple-mind-map/src/themes/lateNightOffice.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import defaultTheme from './default'
|
||||
import merge from 'deepmerge'
|
||||
|
||||
// 深夜办公室
|
||||
export default merge(defaultTheme, {
|
||||
// 背景颜色
|
||||
backgroundColor: 'rgb(32, 37, 49)',
|
||||
// 连线的颜色
|
||||
lineColor: 'rgb(137, 167, 196)',
|
||||
lineWidth: 3,
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 3,
|
||||
// 概要连线的颜色
|
||||
generalizationLineColor: 'rgb(255, 119, 34)',
|
||||
// 根节点样式
|
||||
root: {
|
||||
fillColor: 'rgb(23, 153, 243)',
|
||||
color: 'rgb(255, 255, 255)',
|
||||
borderColor: '',
|
||||
borderWidth: 0,
|
||||
fontSize: 24,
|
||||
active: {
|
||||
borderColor: 'rgb(255, 119, 34)',
|
||||
borderWidth: 3
|
||||
}
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
fillColor: 'rgb(70, 78, 94)',
|
||||
color: 'rgb(209, 210, 210)',
|
||||
borderColor: '',
|
||||
borderWidth: 0,
|
||||
fontSize: 18,
|
||||
active: {
|
||||
borderColor: 'rgb(255, 119, 34)',
|
||||
borderWidth: 3
|
||||
}
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
fontSize: 14,
|
||||
color: 'rgb(204, 204, 204)',
|
||||
active: {
|
||||
borderColor: 'rgb(255, 119, 34)'
|
||||
}
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
fontSize: 14,
|
||||
fillColor: 'rgb(255, 119, 34)',
|
||||
borderColor: '',
|
||||
borderWidth: 2,
|
||||
color: '#fff',
|
||||
active: {
|
||||
borderColor: 'rgb(23, 153, 243)'
|
||||
}
|
||||
}
|
||||
})
|
||||
57
simple-mind-map/src/themes/redSpirit.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import defaultTheme from './default'
|
||||
import merge from 'deepmerge'
|
||||
|
||||
// 红色精神
|
||||
export default merge(defaultTheme, {
|
||||
// 背景颜色
|
||||
backgroundColor: 'rgb(255, 238, 228)',
|
||||
// 连线的颜色
|
||||
lineColor: 'rgb(230, 138, 131)',
|
||||
lineWidth: 3,
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 3,
|
||||
// 概要连线的颜色
|
||||
generalizationLineColor: 'rgb(222, 101, 85)',
|
||||
// 根节点样式
|
||||
root: {
|
||||
fillColor: 'rgb(207, 44, 44)',
|
||||
color: 'rgb(255, 233, 157)',
|
||||
borderColor: '',
|
||||
borderWidth: 0,
|
||||
fontSize: 24,
|
||||
active: {
|
||||
borderColor: 'rgb(255, 233, 157)',
|
||||
borderWidth: 3
|
||||
}
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
fillColor: 'rgb(255, 255, 255)',
|
||||
color: 'rgb(211, 58, 21)',
|
||||
borderColor: 'rgb(222, 101, 85)',
|
||||
borderWidth: 2,
|
||||
fontSize: 18,
|
||||
active: {
|
||||
borderColor: 'rgb(255, 233, 157)'
|
||||
}
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
fontSize: 14,
|
||||
color: 'rgb(144, 71, 43)',
|
||||
active: {
|
||||
borderColor: 'rgb(255, 233, 157)'
|
||||
}
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
fontSize: 14,
|
||||
fillColor: 'rgb(255, 247, 211)',
|
||||
borderColor: 'rgb(255, 202, 162)',
|
||||
borderWidth: 2,
|
||||
color: 'rgb(187, 101, 69)',
|
||||
active: {
|
||||
borderColor: 'rgb(222, 101, 85)'
|
||||
}
|
||||
}
|
||||
})
|
||||
54
simple-mind-map/src/themes/simpleBlack.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import defaultTheme from './default'
|
||||
import merge from 'deepmerge'
|
||||
|
||||
// 简约黑
|
||||
export default merge(defaultTheme, {
|
||||
// 连线的颜色
|
||||
lineColor: 'rgb(34, 34, 34)',
|
||||
lineWidth: 4,
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 4,
|
||||
// 概要连线的颜色
|
||||
generalizationLineColor: 'rgb(34, 34, 34)',
|
||||
// 根节点样式
|
||||
root: {
|
||||
fillColor: '#fff',
|
||||
color: 'rgb(34, 34, 34)',
|
||||
borderColor: 'rgb(34, 34, 34)',
|
||||
borderWidth: 3,
|
||||
fontSize: 24,
|
||||
active: {
|
||||
borderColor: '#a13600'
|
||||
}
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
fillColor: 'rgb(241, 246, 248)',
|
||||
color: 'rgb(34, 34, 34)',
|
||||
borderColor: 'rgb(34, 34, 34)',
|
||||
borderWidth: 3,
|
||||
fontSize: 18,
|
||||
active: {
|
||||
borderColor: '#a13600'
|
||||
}
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
fontSize: 14,
|
||||
color: 'rgb(34, 34, 34)',
|
||||
active: {
|
||||
borderColor: '#a13600'
|
||||
}
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
fontSize: 14,
|
||||
fillColor: 'transparent',
|
||||
borderColor: 'rgb(34, 34, 34)',
|
||||
borderWidth: 2,
|
||||
color: 'rgb(34, 34, 34)',
|
||||
active: {
|
||||
borderColor: '#a13600'
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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 = [
|
||||
{
|
||||
@@ -137,5 +111,139 @@ export const themeList = [
|
||||
{
|
||||
name: '浪漫紫',
|
||||
value: 'romanticPurple',
|
||||
},
|
||||
{
|
||||
name: '简约黑',
|
||||
value: 'simpleBlack',
|
||||
},
|
||||
{
|
||||
name: '课程绿',
|
||||
value: 'courseGreen',
|
||||
},
|
||||
{
|
||||
name: '咖啡',
|
||||
value: 'coffee',
|
||||
},
|
||||
{
|
||||
name: '红色精神',
|
||||
value: 'redSpirit',
|
||||
},
|
||||
{
|
||||
name: '黑色幽默',
|
||||
value: 'blackHumour',
|
||||
},
|
||||
{
|
||||
name: '深夜办公室',
|
||||
value: 'lateNightOffice',
|
||||
},
|
||||
{
|
||||
name: '黑金',
|
||||
value: 'blackGold',
|
||||
}
|
||||
]
|
||||
|
||||
// 常量
|
||||
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',
|
||||
TIMELINE: 'timeline',
|
||||
TIMELINE2: 'timeline2',
|
||||
FISHBONE: 'fishbone'
|
||||
},
|
||||
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'
|
||||
},
|
||||
INIT_ROOT_NODE_POSITION: {
|
||||
LEFT: 'left',
|
||||
TOP: 'top',
|
||||
RIGHT: 'right',
|
||||
BOTTOM: 'bottom',
|
||||
CENTER: 'center'
|
||||
},
|
||||
TIMELINE_DIR: {
|
||||
TOP: 'top',
|
||||
BOTTOM: 'bottom'
|
||||
}
|
||||
}
|
||||
|
||||
export const initRootNodePositionMap = {
|
||||
[CONSTANTS.INIT_ROOT_NODE_POSITION.LEFT]: 0,
|
||||
[CONSTANTS.INIT_ROOT_NODE_POSITION.TOP]: 0,
|
||||
[CONSTANTS.INIT_ROOT_NODE_POSITION.RIGHT]: 1,
|
||||
[CONSTANTS.INIT_ROOT_NODE_POSITION.BOTTOM]: 1,
|
||||
[CONSTANTS.INIT_ROOT_NODE_POSITION.CENTER]: 0.5,
|
||||
}
|
||||
|
||||
// 布局结构列表
|
||||
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,
|
||||
},
|
||||
{
|
||||
name: '时间轴',
|
||||
value: CONSTANTS.LAYOUT.TIMELINE,
|
||||
},
|
||||
{
|
||||
name: '时间轴2',
|
||||
value: CONSTANTS.LAYOUT.TIMELINE2,
|
||||
},
|
||||
{
|
||||
name: '鱼骨图',
|
||||
value: CONSTANTS.LAYOUT.FISHBONE,
|
||||
}
|
||||
]
|
||||
export const layoutValueList = [
|
||||
CONSTANTS.LAYOUT.LOGICAL_STRUCTURE,
|
||||
CONSTANTS.LAYOUT.MIND_MAP,
|
||||
CONSTANTS.LAYOUT.CATALOG_ORGANIZATION,
|
||||
CONSTANTS.LAYOUT.ORGANIZATION_STRUCTURE,
|
||||
CONSTANTS.LAYOUT.TIMELINE,
|
||||
CONSTANTS.LAYOUT.TIMELINE2,
|
||||
CONSTANTS.LAYOUT.FISHBONE
|
||||
]
|
||||
@@ -141,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 &&
|
||||
@@ -155,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
|
||||
|
||||
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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
574
web/package-lock.json
generated
@@ -25,6 +25,7 @@
|
||||
"@vue/cli-service": "^4.5.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"chokidar": "^3.5.3",
|
||||
"esbuild": "^0.17.15",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"less": "^3.12.2",
|
||||
@@ -1717,6 +1718,358 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.15.tgz",
|
||||
"integrity": "sha512-sRSOVlLawAktpMvDyJIkdLI/c/kdRTOqo8t6ImVxg8yT7LQDUYV5Rp2FKeEosLr6ZCja9UjYAzyRSxGteSJPYg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.15.tgz",
|
||||
"integrity": "sha512-0kOB6Y7Br3KDVgHeg8PRcvfLkq+AccreK///B4Z6fNZGr/tNHX0z2VywCc7PTeWp+bPvjA5WMvNXltHw5QjAIA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.15.tgz",
|
||||
"integrity": "sha512-MzDqnNajQZ63YkaUWVl9uuhcWyEyh69HGpMIrf+acR4otMkfLJ4sUCxqwbCyPGicE9dVlrysI3lMcDBjGiBBcQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.15.tgz",
|
||||
"integrity": "sha512-7siLjBc88Z4+6qkMDxPT2juf2e8SJxmsbNVKFY2ifWCDT72v5YJz9arlvBw5oB4W/e61H1+HDB/jnu8nNg0rLA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.15.tgz",
|
||||
"integrity": "sha512-NbImBas2rXwYI52BOKTW342Tm3LTeVlaOQ4QPZ7XuWNKiO226DisFk/RyPk3T0CKZkKMuU69yOvlapJEmax7cg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.15.tgz",
|
||||
"integrity": "sha512-Xk9xMDjBVG6CfgoqlVczHAdJnCs0/oeFOspFap5NkYAmRCT2qTn1vJWA2f419iMtsHSLm+O8B6SLV/HlY5cYKg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.15.tgz",
|
||||
"integrity": "sha512-3TWAnnEOdclvb2pnfsTWtdwthPfOz7qAfcwDLcfZyGJwm1SRZIMOeB5FODVhnM93mFSPsHB9b/PmxNNbSnd0RQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.15.tgz",
|
||||
"integrity": "sha512-MLTgiXWEMAMr8nmS9Gigx43zPRmEfeBfGCwxFQEMgJ5MC53QKajaclW6XDPjwJvhbebv+RzK05TQjvH3/aM4Xw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.15.tgz",
|
||||
"integrity": "sha512-T0MVnYw9KT6b83/SqyznTs/3Jg2ODWrZfNccg11XjDehIved2oQfrX/wVuev9N936BpMRaTR9I1J0tdGgUgpJA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.15.tgz",
|
||||
"integrity": "sha512-wp02sHs015T23zsQtU4Cj57WiteiuASHlD7rXjKUyAGYzlOKDAjqK6bk5dMi2QEl/KVOcsjwL36kD+WW7vJt8Q==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.15.tgz",
|
||||
"integrity": "sha512-k7FsUJjGGSxwnBmMh8d7IbObWu+sF/qbwc+xKZkBe/lTAF16RqxRCnNHA7QTd3oS2AfGBAnHlXL67shV5bBThQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.15.tgz",
|
||||
"integrity": "sha512-ZLWk6czDdog+Q9kE/Jfbilu24vEe/iW/Sj2d8EVsmiixQ1rM2RKH2n36qfxK4e8tVcaXkvuV3mU5zTZviE+NVQ==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.15.tgz",
|
||||
"integrity": "sha512-mY6dPkIRAiFHRsGfOYZC8Q9rmr8vOBZBme0/j15zFUKM99d4ILY4WpOC7i/LqoY+RE7KaMaSfvY8CqjJtuO4xg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.15.tgz",
|
||||
"integrity": "sha512-EcyUtxffdDtWjjwIH8sKzpDRLcVtqANooMNASO59y+xmqqRYBBM7xVLQhqF7nksIbm2yHABptoioS9RAbVMWVA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.15.tgz",
|
||||
"integrity": "sha512-BuS6Jx/ezxFuHxgsfvz7T4g4YlVrmCmg7UAwboeyNNg0OzNzKsIZXpr3Sb/ZREDXWgt48RO4UQRDBxJN3B9Rbg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.15.tgz",
|
||||
"integrity": "sha512-JsdS0EgEViwuKsw5tiJQo9UdQdUJYuB+Mf6HxtJSPN35vez1hlrNb1KajvKWF5Sa35j17+rW1ECEO9iNrIXbNg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.15.tgz",
|
||||
"integrity": "sha512-R6fKjtUysYGym6uXf6qyNephVUQAGtf3n2RCsOST/neIwPqRWcnc3ogcielOd6pT+J0RDR1RGcy0ZY7d3uHVLA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.15.tgz",
|
||||
"integrity": "sha512-mVD4PGc26b8PI60QaPUltYKeSX0wxuy0AltC+WCTFwvKCq2+OgLP4+fFd+hZXzO2xW1HPKcytZBdjqL6FQFa7w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.15.tgz",
|
||||
"integrity": "sha512-U6tYPovOkw3459t2CBwGcFYfFRjivcJJc1WC8Q3funIwX8x4fP+R6xL/QuTPNGOblbq/EUDxj9GU+dWKX0oWlQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.15.tgz",
|
||||
"integrity": "sha512-W+Z5F++wgKAleDABemiyXVnzXgvRFs+GVKThSI+mGgleLWluv0D7Diz4oQpgdpNzh4i2nNDzQtWbjJiqutRp6Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.15.tgz",
|
||||
"integrity": "sha512-Muz/+uGgheShKGqSVS1KsHtCyEzcdOn/W/Xbh6H91Etm+wiIfwZaBn1W58MeGtfI8WA961YMHFYTthBdQs4t+w==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.15.tgz",
|
||||
"integrity": "sha512-DjDa9ywLUUmjhV2Y9wUTIF+1XsmuFGvZoCmOWkli1XcNAh5t25cc7fgsCx4Zi/Uurep3TTLyDiKATgGEg61pkA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@hapi/address": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
|
||||
@@ -6230,6 +6583,43 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.15.tgz",
|
||||
"integrity": "sha512-LBUV2VsUIc/iD9ME75qhT4aJj0r75abCVS0jakhFzOtR7TQsqQA5w0tZ+KTKnwl3kXE0MhskNdHDh/I5aCR1Zw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/android-arm": "0.17.15",
|
||||
"@esbuild/android-arm64": "0.17.15",
|
||||
"@esbuild/android-x64": "0.17.15",
|
||||
"@esbuild/darwin-arm64": "0.17.15",
|
||||
"@esbuild/darwin-x64": "0.17.15",
|
||||
"@esbuild/freebsd-arm64": "0.17.15",
|
||||
"@esbuild/freebsd-x64": "0.17.15",
|
||||
"@esbuild/linux-arm": "0.17.15",
|
||||
"@esbuild/linux-arm64": "0.17.15",
|
||||
"@esbuild/linux-ia32": "0.17.15",
|
||||
"@esbuild/linux-loong64": "0.17.15",
|
||||
"@esbuild/linux-mips64el": "0.17.15",
|
||||
"@esbuild/linux-ppc64": "0.17.15",
|
||||
"@esbuild/linux-riscv64": "0.17.15",
|
||||
"@esbuild/linux-s390x": "0.17.15",
|
||||
"@esbuild/linux-x64": "0.17.15",
|
||||
"@esbuild/netbsd-x64": "0.17.15",
|
||||
"@esbuild/openbsd-x64": "0.17.15",
|
||||
"@esbuild/sunos-x64": "0.17.15",
|
||||
"@esbuild/win32-arm64": "0.17.15",
|
||||
"@esbuild/win32-ia32": "0.17.15",
|
||||
"@esbuild/win32-x64": "0.17.15"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||
@@ -17450,6 +17840,160 @@
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@esbuild/android-arm": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.15.tgz",
|
||||
"integrity": "sha512-sRSOVlLawAktpMvDyJIkdLI/c/kdRTOqo8t6ImVxg8yT7LQDUYV5Rp2FKeEosLr6ZCja9UjYAzyRSxGteSJPYg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/android-arm64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.15.tgz",
|
||||
"integrity": "sha512-0kOB6Y7Br3KDVgHeg8PRcvfLkq+AccreK///B4Z6fNZGr/tNHX0z2VywCc7PTeWp+bPvjA5WMvNXltHw5QjAIA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/android-x64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.15.tgz",
|
||||
"integrity": "sha512-MzDqnNajQZ63YkaUWVl9uuhcWyEyh69HGpMIrf+acR4otMkfLJ4sUCxqwbCyPGicE9dVlrysI3lMcDBjGiBBcQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/darwin-arm64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.15.tgz",
|
||||
"integrity": "sha512-7siLjBc88Z4+6qkMDxPT2juf2e8SJxmsbNVKFY2ifWCDT72v5YJz9arlvBw5oB4W/e61H1+HDB/jnu8nNg0rLA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/darwin-x64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.15.tgz",
|
||||
"integrity": "sha512-NbImBas2rXwYI52BOKTW342Tm3LTeVlaOQ4QPZ7XuWNKiO226DisFk/RyPk3T0CKZkKMuU69yOvlapJEmax7cg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/freebsd-arm64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.15.tgz",
|
||||
"integrity": "sha512-Xk9xMDjBVG6CfgoqlVczHAdJnCs0/oeFOspFap5NkYAmRCT2qTn1vJWA2f419iMtsHSLm+O8B6SLV/HlY5cYKg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/freebsd-x64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.15.tgz",
|
||||
"integrity": "sha512-3TWAnnEOdclvb2pnfsTWtdwthPfOz7qAfcwDLcfZyGJwm1SRZIMOeB5FODVhnM93mFSPsHB9b/PmxNNbSnd0RQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-arm": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.15.tgz",
|
||||
"integrity": "sha512-MLTgiXWEMAMr8nmS9Gigx43zPRmEfeBfGCwxFQEMgJ5MC53QKajaclW6XDPjwJvhbebv+RzK05TQjvH3/aM4Xw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-arm64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.15.tgz",
|
||||
"integrity": "sha512-T0MVnYw9KT6b83/SqyznTs/3Jg2ODWrZfNccg11XjDehIved2oQfrX/wVuev9N936BpMRaTR9I1J0tdGgUgpJA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-ia32": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.15.tgz",
|
||||
"integrity": "sha512-wp02sHs015T23zsQtU4Cj57WiteiuASHlD7rXjKUyAGYzlOKDAjqK6bk5dMi2QEl/KVOcsjwL36kD+WW7vJt8Q==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-loong64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.15.tgz",
|
||||
"integrity": "sha512-k7FsUJjGGSxwnBmMh8d7IbObWu+sF/qbwc+xKZkBe/lTAF16RqxRCnNHA7QTd3oS2AfGBAnHlXL67shV5bBThQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-mips64el": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.15.tgz",
|
||||
"integrity": "sha512-ZLWk6czDdog+Q9kE/Jfbilu24vEe/iW/Sj2d8EVsmiixQ1rM2RKH2n36qfxK4e8tVcaXkvuV3mU5zTZviE+NVQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-ppc64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.15.tgz",
|
||||
"integrity": "sha512-mY6dPkIRAiFHRsGfOYZC8Q9rmr8vOBZBme0/j15zFUKM99d4ILY4WpOC7i/LqoY+RE7KaMaSfvY8CqjJtuO4xg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-riscv64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.15.tgz",
|
||||
"integrity": "sha512-EcyUtxffdDtWjjwIH8sKzpDRLcVtqANooMNASO59y+xmqqRYBBM7xVLQhqF7nksIbm2yHABptoioS9RAbVMWVA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-s390x": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.15.tgz",
|
||||
"integrity": "sha512-BuS6Jx/ezxFuHxgsfvz7T4g4YlVrmCmg7UAwboeyNNg0OzNzKsIZXpr3Sb/ZREDXWgt48RO4UQRDBxJN3B9Rbg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-x64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.15.tgz",
|
||||
"integrity": "sha512-JsdS0EgEViwuKsw5tiJQo9UdQdUJYuB+Mf6HxtJSPN35vez1hlrNb1KajvKWF5Sa35j17+rW1ECEO9iNrIXbNg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/netbsd-x64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.15.tgz",
|
||||
"integrity": "sha512-R6fKjtUysYGym6uXf6qyNephVUQAGtf3n2RCsOST/neIwPqRWcnc3ogcielOd6pT+J0RDR1RGcy0ZY7d3uHVLA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/openbsd-x64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.15.tgz",
|
||||
"integrity": "sha512-mVD4PGc26b8PI60QaPUltYKeSX0wxuy0AltC+WCTFwvKCq2+OgLP4+fFd+hZXzO2xW1HPKcytZBdjqL6FQFa7w==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/sunos-x64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.15.tgz",
|
||||
"integrity": "sha512-U6tYPovOkw3459t2CBwGcFYfFRjivcJJc1WC8Q3funIwX8x4fP+R6xL/QuTPNGOblbq/EUDxj9GU+dWKX0oWlQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/win32-arm64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.15.tgz",
|
||||
"integrity": "sha512-W+Z5F++wgKAleDABemiyXVnzXgvRFs+GVKThSI+mGgleLWluv0D7Diz4oQpgdpNzh4i2nNDzQtWbjJiqutRp6Q==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/win32-ia32": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.15.tgz",
|
||||
"integrity": "sha512-Muz/+uGgheShKGqSVS1KsHtCyEzcdOn/W/Xbh6H91Etm+wiIfwZaBn1W58MeGtfI8WA961YMHFYTthBdQs4t+w==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/win32-x64": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.15.tgz",
|
||||
"integrity": "sha512-DjDa9ywLUUmjhV2Y9wUTIF+1XsmuFGvZoCmOWkli1XcNAh5t25cc7fgsCx4Zi/Uurep3TTLyDiKATgGEg61pkA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@hapi/address": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
|
||||
@@ -21106,6 +21650,36 @@
|
||||
"is-symbol": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"esbuild": {
|
||||
"version": "0.17.15",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.15.tgz",
|
||||
"integrity": "sha512-LBUV2VsUIc/iD9ME75qhT4aJj0r75abCVS0jakhFzOtR7TQsqQA5w0tZ+KTKnwl3kXE0MhskNdHDh/I5aCR1Zw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@esbuild/android-arm": "0.17.15",
|
||||
"@esbuild/android-arm64": "0.17.15",
|
||||
"@esbuild/android-x64": "0.17.15",
|
||||
"@esbuild/darwin-arm64": "0.17.15",
|
||||
"@esbuild/darwin-x64": "0.17.15",
|
||||
"@esbuild/freebsd-arm64": "0.17.15",
|
||||
"@esbuild/freebsd-x64": "0.17.15",
|
||||
"@esbuild/linux-arm": "0.17.15",
|
||||
"@esbuild/linux-arm64": "0.17.15",
|
||||
"@esbuild/linux-ia32": "0.17.15",
|
||||
"@esbuild/linux-loong64": "0.17.15",
|
||||
"@esbuild/linux-mips64el": "0.17.15",
|
||||
"@esbuild/linux-ppc64": "0.17.15",
|
||||
"@esbuild/linux-riscv64": "0.17.15",
|
||||
"@esbuild/linux-s390x": "0.17.15",
|
||||
"@esbuild/linux-x64": "0.17.15",
|
||||
"@esbuild/netbsd-x64": "0.17.15",
|
||||
"@esbuild/openbsd-x64": "0.17.15",
|
||||
"@esbuild/sunos-x64": "0.17.15",
|
||||
"@esbuild/win32-arm64": "0.17.15",
|
||||
"@esbuild/win32-ia32": "0.17.15",
|
||||
"@esbuild/win32-x64": "0.17.15"
|
||||
}
|
||||
},
|
||||
"escalade": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build && node ../copy.js",
|
||||
"lint": "vue-cli-service lint",
|
||||
"buildLibrary": "vue-cli-service build --target lib --name simpleMindMap ../simple-mind-map/full.js --dest ../simple-mind-map/dist",
|
||||
"buildLibrary": "vue-cli-service build --target lib --name simpleMindMap ../simple-mind-map/full.js --dest ../simple-mind-map/dist && esbuild ../simple-mind-map/full.js --bundle --external:buffer --format=esm --outfile=../simple-mind-map/dist/simpleMindMap.esm.js",
|
||||
"format": "prettier --write src/* src/*/* src/*/*/* src/*/*/*/*",
|
||||
"buildDoc": "node ./scripts/buildDoc.js",
|
||||
"autoBuildDoc": "node ./scripts/autoBuildDoc.js"
|
||||
@@ -29,6 +29,7 @@
|
||||
"@vue/cli-service": "^4.5.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"chokidar": "^3.5.3",
|
||||
"esbuild": "^0.17.15",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"less": "^3.12.2",
|
||||
|
||||
@@ -2,6 +2,8 @@ const path = require('path')
|
||||
const fs = require('fs')
|
||||
const hljs = require('highlight.js')
|
||||
const md = require('markdown-it')({
|
||||
html: true,
|
||||
xhtmlOut: true,
|
||||
highlight: function(str, lang) {
|
||||
if (lang && hljs.getLanguage(lang)) {
|
||||
try {
|
||||
|
||||
BIN
web/src/assets/.DS_Store
vendored
BIN
web/src/assets/img/alipay.jpg
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
web/src/assets/img/fishbone.jpg
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
web/src/assets/img/timeline.jpg
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
web/src/assets/img/timeline2.jpg
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
web/src/assets/img/wechat.jpg
Normal file
|
After Width: | Height: | Size: 131 KiB |
@@ -3,7 +3,10 @@ export const layoutImgMap = {
|
||||
logicalStructure: require('../assets/img/logicalStructure.jpg'),
|
||||
mindMap: require('../assets/img/mindMap.jpg'),
|
||||
organizationStructure: require('../assets/img/organizationStructure.jpg'),
|
||||
catalogOrganization: require('../assets/img/catalogOrganization.jpg')
|
||||
catalogOrganization: require('../assets/img/catalogOrganization.jpg'),
|
||||
timeline: require('../assets/img/timeline.jpg'),
|
||||
timeline2: require('../assets/img/timeline2.jpg'),
|
||||
fishbone: require('../assets/img/fishbone.jpg'),
|
||||
}
|
||||
|
||||
// 主题图片映射
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
// 黑金
|
||||
export default {
|
||||
// 背景颜色
|
||||
backgroundColor: 'rgb(18, 20, 20)',
|
||||
// 连线的颜色
|
||||
lineColor: 'rgb(205, 186, 156)',
|
||||
lineWidth: 3,
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 3,
|
||||
// 概要连线的颜色
|
||||
generalizationLineColor: 'rgb(245, 224, 191)',
|
||||
// 根节点样式
|
||||
root: {
|
||||
fillColor: 'rgb(255, 208, 124)',
|
||||
color: 'rgb(111, 61, 6)',
|
||||
borderColor: '',
|
||||
borderWidth: 0,
|
||||
fontSize: 24,
|
||||
active: {
|
||||
borderColor: '#fff',
|
||||
borderWidth: 3,
|
||||
}
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
fillColor: 'rgb(66, 57, 46)',
|
||||
color: 'rgb(225, 201, 158)',
|
||||
borderColor: 'rgb(245, 224, 191)',
|
||||
borderWidth: 2,
|
||||
fontSize: 18,
|
||||
active: {
|
||||
borderColor: 'rgb(255, 208, 124)',
|
||||
}
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
fontSize: 14,
|
||||
color: 'rgb(231, 203, 155)',
|
||||
active: {
|
||||
borderColor: 'rgb(255, 208, 124)'
|
||||
}
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
fontSize: 14,
|
||||
fillColor: 'rgb(56, 45, 34)',
|
||||
borderColor: 'rgb(104, 84, 61)',
|
||||
borderWidth: 2,
|
||||
color: 'rgb(242, 216, 176)',
|
||||
active: {
|
||||
borderColor: 'rgb(255, 208, 124)'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
// 黑色幽默
|
||||
export default {
|
||||
// 背景颜色
|
||||
backgroundColor: 'rgb(27, 31, 34)',
|
||||
// 连线的颜色
|
||||
lineColor: 'rgb(75, 81, 78)',
|
||||
lineWidth: 3,
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 3,
|
||||
// 概要连线的颜色
|
||||
generalizationLineColor: 'rgb(255, 119, 34)',
|
||||
// 根节点样式
|
||||
root: {
|
||||
fillColor: 'rgb(36, 179, 96)',
|
||||
color: '#fff',
|
||||
borderColor: '',
|
||||
borderWidth: 0,
|
||||
fontSize: 24,
|
||||
active: {
|
||||
borderColor: 'rgb(254, 199, 13)',
|
||||
borderWidth: 3,
|
||||
}
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
fillColor: 'rgb(254, 199, 13)',
|
||||
color: 'rgb(0, 0, 0)',
|
||||
borderColor: '',
|
||||
borderWidth: 0,
|
||||
fontSize: 18,
|
||||
active: {
|
||||
borderColor: 'rgb(36, 179, 96)',
|
||||
borderWidth: 3,
|
||||
}
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
fontSize: 14,
|
||||
color: 'rgb(204, 204, 204)',
|
||||
active: {
|
||||
borderColor: 'rgb(254, 199, 13)'
|
||||
}
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
fontSize: 14,
|
||||
fillColor: 'rgb(27, 31, 34)',
|
||||
borderColor: 'rgb(255, 119, 34)',
|
||||
borderWidth: 2,
|
||||
color: 'rgb(204, 204, 204)',
|
||||
active: {
|
||||
borderColor: 'rgb(36, 179, 96)'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
// 咖啡
|
||||
export default {
|
||||
// 连线的颜色
|
||||
lineColor: 'rgb(173, 123, 91)',
|
||||
lineWidth: 4,
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 4,
|
||||
// 概要连线的颜色
|
||||
generalizationLineColor: 'rgb(173, 123, 91)',
|
||||
// 根节点样式
|
||||
root: {
|
||||
fillColor: 'rgb(202, 117, 79)',
|
||||
color: '#fff',
|
||||
borderColor: '',
|
||||
borderWidth: 0,
|
||||
fontSize: 24,
|
||||
active: {
|
||||
borderColor: 'rgb(173, 123, 91)',
|
||||
borderWidth: 3,
|
||||
}
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
fillColor: 'rgb(245, 231, 216)',
|
||||
color: 'rgb(125, 86, 42)',
|
||||
borderColor: '',
|
||||
borderWidth: 0,
|
||||
fontSize: 18,
|
||||
active: {
|
||||
borderColor: 'rgb(173, 123, 91)',
|
||||
}
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
fontSize: 14,
|
||||
color: 'rgb(96, 71, 47)',
|
||||
active: {
|
||||
borderColor: 'rgb(173, 123, 91)'
|
||||
}
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
fontSize: 14,
|
||||
fillColor: 'rgb(255, 249, 239)',
|
||||
borderColor: 'rgb(173, 123, 91)',
|
||||
borderWidth: 2,
|
||||
color: 'rgb(122, 83, 44)',
|
||||
active: {
|
||||
borderColor: 'rgb(202, 117, 79)'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
// 课程绿
|
||||
export default {
|
||||
// 连线的颜色
|
||||
lineColor: 'rgb(113, 195, 169)',
|
||||
lineWidth: 3,
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 3,
|
||||
// 概要连线的颜色
|
||||
generalizationLineColor: 'rgb(113, 195, 169)',
|
||||
// 根节点样式
|
||||
root: {
|
||||
fillColor: 'rgb(16, 160, 121)',
|
||||
color: '#fff',
|
||||
borderColor: '',
|
||||
borderWidth: 0,
|
||||
fontSize: 24,
|
||||
active: {
|
||||
borderColor: 'rgb(173, 91, 12)',
|
||||
borderWidth: 3,
|
||||
}
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
fillColor: 'rgb(240, 252, 249)',
|
||||
color: 'rgb(50, 113, 96)',
|
||||
borderColor: 'rgb(113, 195, 169)',
|
||||
borderWidth: 2,
|
||||
fontSize: 18,
|
||||
active: {
|
||||
borderColor: 'rgb(173, 91, 12)',
|
||||
}
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
fontSize: 14,
|
||||
color: 'rgb(10, 59, 43)',
|
||||
active: {
|
||||
borderColor: 'rgb(173, 91, 12)'
|
||||
}
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
fontSize: 14,
|
||||
fillColor: 'rgb(246, 238, 211)',
|
||||
borderColor: '',
|
||||
borderWidth: 0,
|
||||
color: 'rgb(173, 91, 12)',
|
||||
active: {
|
||||
borderColor: 'rgb(113, 195, 169)'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,9 @@
|
||||
import simpleBlack from './simpleBlack'
|
||||
import courseGreen from './courseGreen'
|
||||
import coffee from './coffee'
|
||||
import redSpirit from './redSpirit'
|
||||
import blackHumour from './blackHumour'
|
||||
import lateNightOffice from './lateNightOffice'
|
||||
import blackGold from './blackGold'
|
||||
|
||||
export default [
|
||||
{
|
||||
name: '简约黑',
|
||||
value: 'simpleBlack',
|
||||
theme: simpleBlack
|
||||
},
|
||||
{
|
||||
name: '课程绿',
|
||||
value: 'courseGreen',
|
||||
theme: courseGreen
|
||||
},
|
||||
{
|
||||
name: '咖啡',
|
||||
value: 'coffee',
|
||||
theme: coffee
|
||||
},
|
||||
{
|
||||
name: '红色精神',
|
||||
value: 'redSpirit',
|
||||
theme: redSpirit
|
||||
},
|
||||
{
|
||||
name: '黑色幽默',
|
||||
value: 'blackHumour',
|
||||
theme: blackHumour
|
||||
},
|
||||
{
|
||||
name: '深夜办公室',
|
||||
value: 'lateNightOffice',
|
||||
theme: lateNightOffice
|
||||
},
|
||||
{
|
||||
name: '黑金',
|
||||
value: 'blackGold',
|
||||
theme: blackGold
|
||||
}
|
||||
]
|
||||
@@ -1,56 +0,0 @@
|
||||
// 深夜办公室
|
||||
export default {
|
||||
// 背景颜色
|
||||
backgroundColor: 'rgb(32, 37, 49)',
|
||||
// 连线的颜色
|
||||
lineColor: 'rgb(137, 167, 196)',
|
||||
lineWidth: 3,
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 3,
|
||||
// 概要连线的颜色
|
||||
generalizationLineColor: 'rgb(255, 119, 34)',
|
||||
// 根节点样式
|
||||
root: {
|
||||
fillColor: 'rgb(23, 153, 243)',
|
||||
color: 'rgb(255, 255, 255)',
|
||||
borderColor: '',
|
||||
borderWidth: 0,
|
||||
fontSize: 24,
|
||||
active: {
|
||||
borderColor: 'rgb(255, 119, 34)',
|
||||
borderWidth: 3,
|
||||
}
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
fillColor: 'rgb(70, 78, 94)',
|
||||
color: 'rgb(209, 210, 210)',
|
||||
borderColor: '',
|
||||
borderWidth: 0,
|
||||
fontSize: 18,
|
||||
active: {
|
||||
borderColor: 'rgb(255, 119, 34)',
|
||||
borderWidth: 3,
|
||||
}
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
fontSize: 14,
|
||||
color: 'rgb(204, 204, 204)',
|
||||
active: {
|
||||
borderColor: 'rgb(255, 119, 34)'
|
||||
}
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
fontSize: 14,
|
||||
fillColor: 'rgb(255, 119, 34)',
|
||||
borderColor: '',
|
||||
borderWidth: 2,
|
||||
color: '#fff',
|
||||
active: {
|
||||
borderColor: 'rgb(23, 153, 243)'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
// 红色精神
|
||||
export default {
|
||||
// 背景颜色
|
||||
backgroundColor: 'rgb(255, 238, 228)',
|
||||
// 连线的颜色
|
||||
lineColor: 'rgb(230, 138, 131)',
|
||||
lineWidth: 3,
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 3,
|
||||
// 概要连线的颜色
|
||||
generalizationLineColor: 'rgb(222, 101, 85)',
|
||||
// 根节点样式
|
||||
root: {
|
||||
fillColor: 'rgb(207, 44, 44)',
|
||||
color: 'rgb(255, 233, 157)',
|
||||
borderColor: '',
|
||||
borderWidth: 0,
|
||||
fontSize: 24,
|
||||
active: {
|
||||
borderColor: 'rgb(255, 233, 157)',
|
||||
borderWidth: 3,
|
||||
}
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
fillColor: 'rgb(255, 255, 255)',
|
||||
color: 'rgb(211, 58, 21)',
|
||||
borderColor: 'rgb(222, 101, 85)',
|
||||
borderWidth: 2,
|
||||
fontSize: 18,
|
||||
active: {
|
||||
borderColor: 'rgb(255, 233, 157)',
|
||||
}
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
fontSize: 14,
|
||||
color: 'rgb(144, 71, 43)',
|
||||
active: {
|
||||
borderColor: 'rgb(255, 233, 157)'
|
||||
}
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
fontSize: 14,
|
||||
fillColor: 'rgb(255, 247, 211)',
|
||||
borderColor: 'rgb(255, 202, 162)',
|
||||
borderWidth: 2,
|
||||
color: 'rgb(187, 101, 69)',
|
||||
active: {
|
||||
borderColor: 'rgb(222, 101, 85)'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ let langList = [
|
||||
}
|
||||
]
|
||||
let StartList = ['introduction', 'start', 'translate', 'changelog']
|
||||
let CourseList = new Array(18).fill(0).map((_, index) => {
|
||||
return 'course' + (index + 1)
|
||||
})
|
||||
let APIList = [
|
||||
'constructor',
|
||||
'node',
|
||||
@@ -59,6 +62,10 @@ export default {
|
||||
groupName: '开始',
|
||||
list: createList('zh', StartList)
|
||||
},
|
||||
{
|
||||
groupName: '教程',
|
||||
list: createList('zh', CourseList)
|
||||
},
|
||||
{
|
||||
groupName: 'API',
|
||||
list: createList('zh', APIList)
|
||||
@@ -69,6 +76,10 @@ export default {
|
||||
groupName: 'Start',
|
||||
list: createList('en', StartList)
|
||||
},
|
||||
{
|
||||
groupName: 'Course',
|
||||
list: createList('zh', CourseList)
|
||||
},
|
||||
{
|
||||
groupName: 'API',
|
||||
list: createList('en', APIList)
|
||||
|
||||
@@ -1,5 +1,67 @@
|
||||
# Changelog
|
||||
|
||||
## 0.5.4
|
||||
|
||||
New: 1.Add new themes. 2.Added timeline and fishbone structure.
|
||||
|
||||
Fix: 1.Fix the conflict issue between node right-click and canvas right-click. 2.Fix the bug that the line segment is not hidden when dragging nodes such as organizational chart and directory organization chart.
|
||||
|
||||
optimization: 1.Optimize the layout of organizational chart. 2.Optimize the layout of the directory organization chart.
|
||||
|
||||
## 0.5.3
|
||||
|
||||
Fix: 1.Fixed the issue of setting the text style when multiple nodes were selected in rich text mode, which would change the text of all selected nodes to the text of the last selected node.
|
||||
|
||||
New: 1.Support setting the position of the initial central node.
|
||||
|
||||
### 0.5.3-fix.1
|
||||
|
||||
Fix: 1.Fix the issue where setting the position of the initial central node does not take effect.
|
||||
|
||||
### 0.5.3-fix.2
|
||||
|
||||
Fix: 1.Fix the issue of not displaying images in nodes when exporting as images.
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -1,6 +1,44 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Changelog</h1>
|
||||
<h2>0.5.4</h2>
|
||||
<p>New: 1.Add new themes. 2.Added timeline and fishbone structure.</p>
|
||||
<p>Fix: 1.Fix the conflict issue between node right-click and canvas right-click. 2.Fix the bug that the line segment is not hidden when dragging nodes such as organizational chart and directory organization chart.</p>
|
||||
<p>optimization: 1.Optimize the layout of organizational chart. 2.Optimize the layout of the directory organization chart.</p>
|
||||
<h2>0.5.3</h2>
|
||||
<p>Fix: 1.Fixed the issue of setting the text style when multiple nodes were selected in rich text mode, which would change the text of all selected nodes to the text of the last selected node.</p>
|
||||
<p>New: 1.Support setting the position of the initial central node.</p>
|
||||
<h3>0.5.3-fix.1</h3>
|
||||
<p>Fix: 1.Fix the issue where setting the position of the initial central node does not take effect.</p>
|
||||
<h3>0.5.3-fix.2</h3>
|
||||
<p>Fix: 1.Fix the issue of not displaying images in nodes when exporting as images.</p>
|
||||
<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>
|
||||
|
||||
@@ -26,8 +26,9 @@ const mindMap = new MindMap({
|
||||
| -------------------------------- | ------- | ---------------- | ------------------------------------------------------------ | -------- |
|
||||
| el | Element | | Container element, must be a DOM element | Yes |
|
||||
| data | Object | {} | Mind map data, refer to: https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js | |
|
||||
| layout | String | logicalStructure | Layout type, options: logicalStructure (logical structure diagram), mindMap (mind map), catalogOrganization (catalog organization diagram), organizationStructure (organization structure diagram) | |
|
||||
| theme | String | default | Theme, options: default, classic, minions, pinkGrape, mint, gold, vitalityOrange, greenLeaf, dark2, skyGreen, classic2, classic3, classic4 (v0.2.0+), classicGreen, classicBlue, blueSky, brainImpairedPink, dark, earthYellow, freshGreen, freshRed, romanticPurple | |
|
||||
| layout | String | logicalStructure | Layout type, options: logicalStructure (logical structure diagram), mindMap (mind map), catalogOrganization (catalog organization diagram), organizationStructure (organization structure diagram)、timeline(v0.5.4+, timeline)、timeline2(v0.5.4+, up down alternating timeline)、fishbone(v0.5.4+, fishbone diagram) | |
|
||||
| fishboneDeg(v0.5.4+) | Number | 45 | Set the diagonal angle of the fishbone structure diagram | |
|
||||
| theme | String | default | Theme, options: default, classic, minions, pinkGrape, mint, gold, vitalityOrange, greenLeaf, dark2, skyGreen, classic2, classic3, classic4(v0.2.0+), classicGreen, classicBlue, blueSky, brainImpairedPink, dark, earthYellow, freshGreen, freshRed, romanticPurple, simpleBlack(v0.5.4+), courseGreen(v0.5.4+), coffee(v0.5.4+), redSpirit(v0.5.4+), blackHumour(v0.5.4+), lateNightOffice(v0.5.4+), blackGold(v0.5.4+) | |
|
||||
| themeConfig | Object | {} | Theme configuration, will be merged with the selected theme, available fields refer to: https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/default.js | |
|
||||
| scaleRatio | Number | 0.1 | The incremental scaling ratio | |
|
||||
| maxTag | Number | 5 | The maximum number of tags displayed in the node, any additional tags will be discarded | |
|
||||
@@ -46,6 +47,12 @@ const mindMap = new MindMap({
|
||||
| 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 | |
|
||||
| initRootNodePosition(v0.5.3+) | Array | null | The position of the initial root node can be passed as an array, default is `['center', 'center']`, Represents the root node at the center of the canvas, In addition to `center`, keywords can also be set to `left`, `top`, `right`, and `bottom`, In addition to passing keywords, each item in the array can also pass a number representing a specific pixel, Can pass a percentage string, such as `['40%', '60%']`, Represents a horizontal position at `40%` of the canvas width, and a vertical position at `60%` of the canvas height | |
|
||||
|
||||
### Watermark config
|
||||
|
||||
@@ -198,6 +205,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)
|
||||
|
||||
|
||||
@@ -46,14 +46,21 @@
|
||||
<td>layout</td>
|
||||
<td>String</td>
|
||||
<td>logicalStructure</td>
|
||||
<td>Layout type, options: logicalStructure (logical structure diagram), mindMap (mind map), catalogOrganization (catalog organization diagram), organizationStructure (organization structure diagram)</td>
|
||||
<td>Layout type, options: logicalStructure (logical structure diagram), mindMap (mind map), catalogOrganization (catalog organization diagram), organizationStructure (organization structure diagram)、timeline(v0.5.4+, timeline)、timeline2(v0.5.4+, up down alternating timeline)、fishbone(v0.5.4+, fishbone diagram)</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>fishboneDeg(v0.5.4+)</td>
|
||||
<td>Number</td>
|
||||
<td>45</td>
|
||||
<td>Set the diagonal angle of the fishbone structure diagram</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>theme</td>
|
||||
<td>String</td>
|
||||
<td>default</td>
|
||||
<td>Theme, options: default, classic, minions, pinkGrape, mint, gold, vitalityOrange, greenLeaf, dark2, skyGreen, classic2, classic3, classic4 (v0.2.0+), classicGreen, classicBlue, blueSky, brainImpairedPink, dark, earthYellow, freshGreen, freshRed, romanticPurple</td>
|
||||
<td>Theme, options: default, classic, minions, pinkGrape, mint, gold, vitalityOrange, greenLeaf, dark2, skyGreen, classic2, classic3, classic4(v0.2.0+), classicGreen, classicBlue, blueSky, brainImpairedPink, dark, earthYellow, freshGreen, freshRed, romanticPurple, simpleBlack(v0.5.4+), courseGreen(v0.5.4+), coffee(v0.5.4+), redSpirit(v0.5.4+), blackHumour(v0.5.4+), lateNightOffice(v0.5.4+), blackGold(v0.5.4+)</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -182,6 +189,48 @@
|
||||
<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>
|
||||
<tr>
|
||||
<td>initRootNodePosition(v0.5.3+)</td>
|
||||
<td>Array</td>
|
||||
<td>null</td>
|
||||
<td>The position of the initial root node can be passed as an array, default is <code>['center', 'center']</code>, Represents the root node at the center of the canvas, In addition to <code>center</code>, keywords can also be set to <code>left</code>, <code>top</code>, <code>right</code>, and <code>bottom</code>, In addition to passing keywords, each item in the array can also pass a number representing a specific pixel, Can pass a percentage string, such as <code>['40%', '60%']</code>, Represents a horizontal position at <code>40%</code> of the canvas width, and a vertical position at <code>60%</code> of the canvas height</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Watermark config</h3>
|
||||
@@ -476,6 +525,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>
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
# 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.
|
||||
|
||||
> If you just want to use mind mapping, you can also use the demo of this project as a regular online mind mapping tool. Click on the 【Online Demo】 in the upper right corner to start using it.
|
||||
|
||||
## Features
|
||||
|
||||
- [x] Plugin architecture. In addition to core functions, other functions are provided as plugins, which can be used as needed to reduce the overall volume
|
||||
- [x] Supports four types of structures: logical structure diagrams, mind maps,
|
||||
organizational structure diagrams, and directory organization diagrams
|
||||
- [x] Supports six types of structures: logical structure diagrams, mind maps,
|
||||
organizational structure diagrams, directory organization diagrams, timeline, and fishbone diagrams
|
||||
- [x] Built-in multiple themes and allows for highly customized styles, and support register new themes
|
||||
- [x] Supports shortcuts
|
||||
- [x] Node content supports images, icons, hyperlinks, notes, tags, and
|
||||
@@ -16,15 +18,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 +65,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:
|
||||
|
||||
@@ -75,6 +79,34 @@ The built-in themes and icons in the project come from:
|
||||
|
||||
Respect the copyright, and do not use the theme and icons directly for commercial projects.
|
||||
|
||||
## Why not?
|
||||
|
||||
1.[Zhixi](https://www.zhixi.com/)
|
||||
|
||||
Zhixi is a free mind mapping product that supports multi end synchronization. The UI design is beautiful and the features are complete, but it is not open source, so it can only be used as a user and cannot be used in your project.
|
||||
|
||||
There are many other online mind mapping products similar to Zhixi, such as [GitMind](https://gitmind.cn/)、[MindLine](http://www.mindline.cn/)、[MinMeister](https://www.mindmeister.com/zh)、[Mubu](https://mubu.com/) and so on, There are many searches on search engines, but these products either require fees or are developed by small companies, and their stability and sustainability cannot be guaranteed. Of course, the most crucial thing is that they are not open-source.
|
||||
|
||||
2.[kityminder-core](https://github.com/fex-team/kityminder-core)
|
||||
|
||||
`kityminder-core` is an open source brain mapping tool developed by Baidu. It has powerful functions and good performance, but it is no longer maintained. Therefore, the code is relatively old, and the interface beauty is relatively ordinary. In addition, bugs can only be fixed by yourself, and the functions can only be developed by yourself. It has high requirements for front-end development capabilities.
|
||||
|
||||
3.[jsmind](https://github.com/hizzgdev/jsmind)、[Mind-elixir](https://github.com/ssshooter/mind-elixir-core)、[my-mind](https://github.com/ondras/my-mind)、[blink-mind](https://github.com/awehook/blink-mind)、[remind](https://github.com/luvsic3/remind)、[vue3-mindmap](https://github.com/hellowuxin/vue3-mindmap)、[ZMindMap](https://github.com/zyascend/ZMindMap)...
|
||||
|
||||
These open-source mind maps are also good, each with its own characteristics, but they also have certain drawbacks, such as stopping updates, average interface aesthetics, less functionality, relying on a certain framework, and so on.
|
||||
|
||||
In summary, in open-source mind maps, it is difficult to find a better choice than `simple-mind-map`. Of course, `simple-mind-map` is far from being the best, and it also has many shortcomings, as you saw in the previous [special note]. However, `simple-mind-map` has always been in a fast iteration process, and we welcome you to join and improve it together.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://opensource.org/licenses/MIT)
|
||||
[MIT](https://opensource.org/licenses/MIT)
|
||||
|
||||
## Invite the author to a cup of coffee
|
||||
|
||||
Open source is not easy. If this project is helpful to you, you can invite the author to have a cup of coffee~
|
||||
|
||||
> Please note the 【mind map】 for transfer. Your avatar and name will appear below.
|
||||
|
||||
<img src="../../../../assets/img/alipay.jpg" style="width: 300px" />
|
||||
|
||||
<img src="../../../../assets/img/wechat.jpg" style="width: 300px" />
|
||||
@@ -1,43 +1,46 @@
|
||||
<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>
|
||||
<blockquote>
|
||||
<p>If you just want to use mind mapping, you can also use the demo of this project as a regular online mind mapping tool. Click on the 【Online Demo】 in the upper right corner to start using it.</p>
|
||||
</blockquote>
|
||||
<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>
|
||||
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="checkbox18" checked="true" /><label for="checkbox18">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="checkbox19" checked="true" /><label for="checkbox19">Supports six types of structures: logical structure diagrams, mind maps,</label>
|
||||
organizational structure diagrams, directory organization diagrams, timeline, and fishbone diagrams</li>
|
||||
<li><input type="checkbox" id="checkbox20" checked="true" /><label for="checkbox20">Built-in multiple themes and allows for highly customized styles, and support register new themes</label></li>
|
||||
<li><input type="checkbox" id="checkbox21" checked="true" /><label for="checkbox21">Supports shortcuts</label></li>
|
||||
<li><input type="checkbox" id="checkbox22" checked="true" /><label for="checkbox22">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="checkbox23" checked="true" /><label for="checkbox23">Supports forward and backward navigation</label></li>
|
||||
<li><input type="checkbox" id="checkbox24" checked="true" /><label for="checkbox24">Supports dragging and scaling</label></li>
|
||||
<li><input type="checkbox" id="checkbox25" checked="true" /><label for="checkbox25">Supports right-click and Ctrl + left-click to select multiple items</label></li>
|
||||
<li><input type="checkbox" id="checkbox26" checked="true" /><label for="checkbox26">Supports free dragging and dragging to adjust nodes</label></li>
|
||||
<li><input type="checkbox" id="checkbox27" checked="true" /><label for="checkbox27">Supports various node shapes</label></li>
|
||||
<li><input type="checkbox" id="checkbox28" checked="true" /><label for="checkbox28">Supports export to json, png, svg, pdf markdown, and import from json, xmind, markdown</label></li>
|
||||
<li><input type="checkbox" id="checkbox29" checked="true" /><label for="checkbox29">Supports mini map、support watermark</label></li>
|
||||
<li><input type="checkbox" id="checkbox30" checked="true" /><label for="checkbox30">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="checkbox31" checked="true" /><label for="checkbox31">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="checkbox32" checked="true" /><label for="checkbox32">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="checkbox33" checked="true" /><label for="checkbox33">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="checkbox34" checked="true" /><label for="checkbox34">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="checkbox35" checked="true" /><label for="checkbox35">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,16 +53,31 @@ 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>
|
||||
<p>Respect the copyright, and do not use the theme and icons directly for commercial projects.</p>
|
||||
<h2>Why not?</h2>
|
||||
<p>1.<a href="https://www.zhixi.com/">Zhixi</a></p>
|
||||
<p>Zhixi is a free mind mapping product that supports multi end synchronization. The UI design is beautiful and the features are complete, but it is not open source, so it can only be used as a user and cannot be used in your project.</p>
|
||||
<p>There are many other online mind mapping products similar to Zhixi, such as <a href="https://gitmind.cn/">GitMind</a>、<a href="http://www.mindline.cn/">MindLine</a>、<a href="https://www.mindmeister.com/zh">MinMeister</a>、<a href="https://mubu.com/">Mubu</a> and so on, There are many searches on search engines, but these products either require fees or are developed by small companies, and their stability and sustainability cannot be guaranteed. Of course, the most crucial thing is that they are not open-source.</p>
|
||||
<p>2.<a href="https://github.com/fex-team/kityminder-core">kityminder-core</a></p>
|
||||
<p><code>kityminder-core</code> is an open source brain mapping tool developed by Baidu. It has powerful functions and good performance, but it is no longer maintained. Therefore, the code is relatively old, and the interface beauty is relatively ordinary. In addition, bugs can only be fixed by yourself, and the functions can only be developed by yourself. It has high requirements for front-end development capabilities.</p>
|
||||
<p>3.<a href="https://github.com/hizzgdev/jsmind">jsmind</a>、<a href="https://github.com/ssshooter/mind-elixir-core">Mind-elixir</a>、<a href="https://github.com/ondras/my-mind">my-mind</a>、<a href="https://github.com/awehook/blink-mind">blink-mind</a>、<a href="https://github.com/luvsic3/remind">remind</a>、<a href="https://github.com/hellowuxin/vue3-mindmap">vue3-mindmap</a>、<a href="https://github.com/zyascend/ZMindMap">ZMindMap</a>...</p>
|
||||
<p>These open-source mind maps are also good, each with its own characteristics, but they also have certain drawbacks, such as stopping updates, average interface aesthetics, less functionality, relying on a certain framework, and so on.</p>
|
||||
<p>In summary, in open-source mind maps, it is difficult to find a better choice than <code>simple-mind-map</code>. Of course, <code>simple-mind-map</code> is far from being the best, and it also has many shortcomings, as you saw in the previous [special note]. However, <code>simple-mind-map</code> has always been in a fast iteration process, and we welcome you to join and improve it together.</p>
|
||||
<h2>License</h2>
|
||||
<p><a href="https://opensource.org/licenses/MIT">MIT</a></p>
|
||||
|
||||
<h2>Invite the author to a cup of coffee</h2>
|
||||
<p>Open source is not easy. If this project is helpful to you, you can invite the author to have a cup of coffee~</p>
|
||||
<blockquote>
|
||||
<p>Please note the 【mind map】 for transfer. Your avatar and name will appear below.</p>
|
||||
</blockquote>
|
||||
<img src="../../../../assets/img/alipay.jpg" style="width: 300px" />
|
||||
<img src="../../../../assets/img/wechat.jpg" style="width: 300px" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -54,13 +54,13 @@ positioning, set border style, transition property (optional)
|
||||
|
||||
4.Listen for `data_change` and `view_data_change` events, and in this event call
|
||||
the `calculationMiniMap` method to get calculation data, then render `svgHTML`
|
||||
to the `miniMapContainer` element and set its style:
|
||||
to the `miniMapContainer` element and set `miniMapContainer` element style:
|
||||
|
||||
```js
|
||||
:style="{
|
||||
transform: `scale(${svgBoxScale})`,
|
||||
left: svgBoxLeft + 'px',
|
||||
top: svgBoxTop + 'px',
|
||||
transform: `scale(${miniMapBoxScale})`,
|
||||
left: miniMapBoxLeft + 'px',
|
||||
top: miniMapBoxTop + 'px',
|
||||
}"
|
||||
```
|
||||
|
||||
|
||||
@@ -39,11 +39,11 @@ absolute positioning</p>
|
||||
positioning, set border style, transition property (optional)</p>
|
||||
<p>4.Listen for <code>data_change</code> and <code>view_data_change</code> events, and in this event call
|
||||
the <code>calculationMiniMap</code> method to get calculation data, then render <code>svgHTML</code>
|
||||
to the <code>miniMapContainer</code> element and set its style:</p>
|
||||
to the <code>miniMapContainer</code> element and set <code>miniMapContainer</code> element style:</p>
|
||||
<pre class="hljs"><code>:style=<span class="hljs-string">"{
|
||||
transform: `scale(${svgBoxScale})`,
|
||||
left: svgBoxLeft + 'px',
|
||||
top: svgBoxTop + 'px',
|
||||
transform: `scale(${miniMapBoxScale})`,
|
||||
left: miniMapBoxLeft + 'px',
|
||||
top: miniMapBoxTop + 'px',
|
||||
}"</span>
|
||||
</code></pre>
|
||||
<p>5.Set the <code>viewBoxStyle</code> object as the style of the <code>viewBoxContainer</code> element</p>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,7 +1,92 @@
|
||||
# Start
|
||||
|
||||
## Installation
|
||||
|
||||
> Things to note before version 0.2.0:
|
||||
|
||||
```bash
|
||||
npm i simple-mind-map
|
||||
```
|
||||
|
||||
`0.2.0` Notes for previous versions:
|
||||
|
||||
> Note: This project is directly published in source code form and has not been
|
||||
> packaged. If compilation fails, a Vue CLI-created project can add the
|
||||
> following configuration to the vue.config.js file to allow babel-loader to
|
||||
> compile this dependency:
|
||||
>
|
||||
> ```js
|
||||
> module.exports = {
|
||||
> transpileDependencies: ["simple-mind-map"],
|
||||
> };
|
||||
> ```
|
||||
>
|
||||
> Other projects should modify the packaging configuration as needed.
|
||||
|
||||
## 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.
|
||||
|
||||
> To learn about its use in other frameworks, you can refer to the following unofficial implementations:
|
||||
>
|
||||
> 1.[https://github.com/huangyuanyin/hyy-vue3-mindMap](https://github.com/huangyuanyin/hyy-vue3-mindMap): A mind map based on Vue3.2+ElementPlus.
|
||||
|
||||
Firstly, provide a container element with a width and height not equal to 0:
|
||||
|
||||
```html
|
||||
<div id="mindMapContainer"></div>
|
||||
```
|
||||
|
||||
Also, set the `CSS` style again:
|
||||
|
||||
```css
|
||||
#mindMapContainer * {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
```
|
||||
|
||||
Then introduce the `simple-mind-map` library and create an instance:
|
||||
|
||||
```js
|
||||
import MindMap from "simple-mind-map";
|
||||
|
||||
const mindMap = new MindMap({
|
||||
el: document.getElementById('mindMapContainer'),
|
||||
data: {
|
||||
"data": {
|
||||
"text": "Root Node"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
This will result in a mind map.
|
||||
|
||||
If you want to implement a complete mind map, you usually need to develop some UI interfaces to achieve more functions through the interfaces provided by the `simple-mind-map` library.
|
||||
|
||||
`simple-mind-map` supports rich configurations, events, commands, and some additional plugin extensions. Read the subsequent documentation to learn more.
|
||||
|
||||
The non-packaged 'ES' module is introduced by default, and only contains core functions, not unregistered plugin content, which can effectively reduce the size. However, you need to configure the `babel` compilation `simple mind-map` in your project to prevent some newer `js` syntax some browsers not supporting it.
|
||||
|
||||
If you need a file in the format of `umd` module, such as `CDN` in the browser, Then you can find the `simpleMindMap.umd.min.js` file and `simpleMindMap.css` file in the `/simple-mind-map/dist/` directory, copy it to your project, and then import it into the page:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="simpleMindMap.css">
|
||||
<script scr="simpleMindMap.umd.min.js"></script>
|
||||
```
|
||||
|
||||
A global variable `window.simpleMindMap` will be created.
|
||||
|
||||
The disadvantage of this method is that it will contain all the content, including the plugins you have not registered, so the overall volume will be relatively large.
|
||||
|
||||
(v0.5.4+)If you want to use the `ES` module directly on the browser side, you can find the `simpleMindMap.esm.js` and `simpleMindMap.esm.css` files in the `/simple-mind-map/dist/` directory.
|
||||
|
||||
## Development
|
||||
|
||||
If you only use library, you don't need to read this section.
|
||||
|
||||
### Local Development
|
||||
|
||||
```bash
|
||||
@@ -56,61 +141,6 @@ npm run build
|
||||
|
||||
The `index.html` file will be automatically moved to the root directory.
|
||||
|
||||
## Installation
|
||||
|
||||
> Things to note before version 0.2.0:
|
||||
|
||||
```bash
|
||||
npm i simple-mind-map
|
||||
```
|
||||
|
||||
`0.2.0` Notes for previous versions:
|
||||
|
||||
> Note: This project is directly published in source code form and has not been
|
||||
> packaged. If compilation fails, a Vue CLI-created project can add the
|
||||
> following configuration to the vue.config.js file to allow babel-loader to
|
||||
> compile this dependency:
|
||||
>
|
||||
> ```js
|
||||
> module.exports = {
|
||||
> transpileDependencies: ["simple-mind-map"],
|
||||
> };
|
||||
> ```
|
||||
>
|
||||
> Other projects should modify the packaging configuration as needed.
|
||||
|
||||
## Usage
|
||||
|
||||
```html
|
||||
<div id="mindMapContainer"></div>
|
||||
```
|
||||
|
||||
```js
|
||||
import MindMap from "simple-mind-map";
|
||||
|
||||
const mindMap = new MindMap({
|
||||
el: document.getElementById('mindMapContainer'),
|
||||
data: {
|
||||
"data": {
|
||||
"text": "Root Node"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
The non-packaged 'ES' module is introduced by default, and only contains core functions, not unregistered plugin content, which can effectively reduce the size. However, you need to configure the `babel` compilation `simple mind-map` in your project to prevent some newer `js` syntax some browsers not supporting it.
|
||||
|
||||
If you need a file in the format of `umd` module, such as `CDN` in the browser, Then you can find the `simpleMindMap.umd.min.js` file in the `/simple-mind-map/dist/` directory, copy it to your project, and then import it into the page:
|
||||
|
||||
```html
|
||||
<script scr="simpleMindMap.umd.min.js"></script>
|
||||
```
|
||||
|
||||
A global variable `window.simpleMindMap` will be created.
|
||||
|
||||
The disadvantage of this method is that it will contain all the content, including the plugins you have not registered, so the overall volume will be relatively large.
|
||||
|
||||
## Problems
|
||||
|
||||
### Error when using in Vite, indicating xml-js dependency error
|
||||
|
||||
@@ -1,7 +1,67 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Start</h1>
|
||||
<h2>Installation</h2>
|
||||
<blockquote>
|
||||
<p>Things to note before version 0.2.0:</p>
|
||||
</blockquote>
|
||||
<pre class="hljs"><code>npm i simple-mind-map
|
||||
</code></pre>
|
||||
<p><code>0.2.0</code> Notes for previous versions:</p>
|
||||
<blockquote>
|
||||
<p>Note: This project is directly published in source code form and has not been
|
||||
packaged. If compilation fails, a Vue CLI-created project can add the
|
||||
following configuration to the vue.config.js file to allow babel-loader to
|
||||
compile this dependency:</p>
|
||||
<pre class="hljs"><code><span class="hljs-built_in">module</span>.exports = {
|
||||
<span class="hljs-attr">transpileDependencies</span>: [<span class="hljs-string">"simple-mind-map"</span>],
|
||||
};
|
||||
</code></pre>
|
||||
<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>
|
||||
<blockquote>
|
||||
<p>To learn about its use in other frameworks, you can refer to the following unofficial implementations:</p>
|
||||
<p>1.<a href="https://github.com/huangyuanyin/hyy-vue3-mindMap">https://github.com/huangyuanyin/hyy-vue3-mindMap</a>: A mind map based on Vue3.2+ElementPlus.</p>
|
||||
</blockquote>
|
||||
<p>Firstly, provide a container element with a width and height not equal to 0:</p>
|
||||
<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>
|
||||
<p>Also, set the <code>CSS</code> style again:</p>
|
||||
<pre class="hljs"><code><span class="hljs-selector-id">#mindMapContainer</span> * {
|
||||
<span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
|
||||
<span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;
|
||||
}
|
||||
</code></pre>
|
||||
<p>Then introduce the <code>simple-mind-map</code> library and create an instance:</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"</span>;
|
||||
|
||||
<span class="hljs-keyword">const</span> mindMap = <span class="hljs-keyword">new</span> MindMap({
|
||||
<span class="hljs-attr">el</span>: <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'mindMapContainer'</span>),
|
||||
<span class="hljs-attr">data</span>: {
|
||||
<span class="hljs-string">"data"</span>: {
|
||||
<span class="hljs-string">"text"</span>: <span class="hljs-string">"Root Node"</span>
|
||||
},
|
||||
<span class="hljs-string">"children"</span>: []
|
||||
}
|
||||
});
|
||||
</code></pre>
|
||||
<p>This will result in a mind map.</p>
|
||||
<p>If you want to implement a complete mind map, you usually need to develop some UI interfaces to achieve more functions through the interfaces provided by the <code>simple-mind-map</code> library.</p>
|
||||
<p><code>simple-mind-map</code> supports rich configurations, events, commands, and some additional plugin extensions. Read the subsequent documentation to learn more.</p>
|
||||
<p>The non-packaged 'ES' module is introduced by default, and only contains core functions, not unregistered plugin content, which can effectively reduce the size. However, you need to configure the <code>babel</code> compilation <code>simple mind-map</code> in your project to prevent some newer <code>js</code> syntax some browsers not supporting it.</p>
|
||||
<p>If you need a file in the format of <code>umd</code> module, such as <code>CDN</code> in the browser, Then you can find the <code>simpleMindMap.umd.min.js</code> file and <code>simpleMindMap.css</code> file in the <code>/simple-mind-map/dist/</code> directory, copy it to your project, and then import it into the page:</p>
|
||||
<pre class="hljs"><code><span class="hljs-tag"><<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"simpleMindMap.css"</span>></span>
|
||||
<span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">scr</span>=<span class="hljs-string">"simpleMindMap.umd.min.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
|
||||
</code></pre>
|
||||
<p>A global variable <code>window.simpleMindMap</code> will be created.</p>
|
||||
<p>The disadvantage of this method is that it will contain all the content, including the plugins you have not registered, so the overall volume will be relatively large.</p>
|
||||
<p>(v0.5.4+)If you want to use the <code>ES</code> module directly on the browser side, you can find the <code>simpleMindMap.esm.js</code> and <code>simpleMindMap.esm.css</code> files in the <code>/simple-mind-map/dist/</code> directory.</p>
|
||||
<h2>Development</h2>
|
||||
<p>If you only use library, you don't need to read this section.</p>
|
||||
<h3>Local Development</h3>
|
||||
<pre class="hljs"><code>git <span class="hljs-built_in">clone</span> https://github.com/wanglin2/mind-map.git
|
||||
<span class="hljs-built_in">cd</span> simple-mind-map
|
||||
@@ -38,45 +98,6 @@ npm run buildDoc
|
||||
npm run build
|
||||
</code></pre>
|
||||
<p>The <code>index.html</code> file will be automatically moved to the root directory.</p>
|
||||
<h2>Installation</h2>
|
||||
<blockquote>
|
||||
<p>Things to note before version 0.2.0:</p>
|
||||
</blockquote>
|
||||
<pre class="hljs"><code>npm i simple-mind-map
|
||||
</code></pre>
|
||||
<p><code>0.2.0</code> Notes for previous versions:</p>
|
||||
<blockquote>
|
||||
<p>Note: This project is directly published in source code form and has not been
|
||||
packaged. If compilation fails, a Vue CLI-created project can add the
|
||||
following configuration to the vue.config.js file to allow babel-loader to
|
||||
compile this dependency:</p>
|
||||
<pre class="hljs"><code><span class="hljs-built_in">module</span>.exports = {
|
||||
<span class="hljs-attr">transpileDependencies</span>: [<span class="hljs-string">"simple-mind-map"</span>],
|
||||
};
|
||||
</code></pre>
|
||||
<p>Other projects should modify the packaging configuration as needed.</p>
|
||||
</blockquote>
|
||||
<h2>Usage</h2>
|
||||
<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>;
|
||||
|
||||
<span class="hljs-keyword">const</span> mindMap = <span class="hljs-keyword">new</span> MindMap({
|
||||
<span class="hljs-attr">el</span>: <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'mindMapContainer'</span>),
|
||||
<span class="hljs-attr">data</span>: {
|
||||
<span class="hljs-string">"data"</span>: {
|
||||
<span class="hljs-string">"text"</span>: <span class="hljs-string">"Root Node"</span>
|
||||
},
|
||||
<span class="hljs-string">"children"</span>: []
|
||||
}
|
||||
});
|
||||
</code></pre>
|
||||
<p>The non-packaged 'ES' module is introduced by default, and only contains core functions, not unregistered plugin content, which can effectively reduce the size. However, you need to configure the <code>babel</code> compilation <code>simple mind-map</code> in your project to prevent some newer <code>js</code> syntax some browsers not supporting it.</p>
|
||||
<p>If you need a file in the format of <code>umd</code> module, such as <code>CDN</code> in the browser, Then you can find the <code>simpleMindMap.umd.min.js</code> file in the <code>/simple-mind-map/dist/</code> directory, copy it to your project, and then import it into the page:</p>
|
||||
<pre class="hljs"><code><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">scr</span>=<span class="hljs-string">"simpleMindMap.umd.min.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
|
||||
</code></pre>
|
||||
<p>A global variable <code>window.simpleMindMap</code> will be created.</p>
|
||||
<p>The disadvantage of this method is that it will contain all the content, including the plugins you have not registered, so the overall volume will be relatively large.</p>
|
||||
<h2>Problems</h2>
|
||||
<h3>Error when using in Vite, indicating xml-js dependency error</h3>
|
||||
<p>Solution: use the following import method:</p>
|
||||
|
||||
@@ -1,3 +1,74 @@
|
||||
|
||||
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"}]}]
|
||||
|
||||
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: 'course1', title: '基本使用' },
|
||||
{ path: 'course2', title: '操作节点内容' },
|
||||
{ path: 'course3', title: '插入/删除节点、前进回退' },
|
||||
{ path: 'course4', title: '设置节点样式' },
|
||||
{ path: 'course5', title: '设置基础样式' },
|
||||
{ path: 'course6', title: '显示水印' },
|
||||
{ path: 'course7', title: '开启节点自由拖拽' },
|
||||
{ path: 'course8', title: '开启节点富文本编辑' },
|
||||
{ path: 'course9', title: '修改鼠标滚轮的行为' },
|
||||
{ path: 'course10', title: '主题' },
|
||||
{ path: 'course11', title: '结构' },
|
||||
{ path: 'course12', title: '如何渲染一个大纲' },
|
||||
{ path: 'course13', title: '快捷键' },
|
||||
{ path: 'course14', title: '如何渲染一个小地图' },
|
||||
{ path: 'course15', title: '如何渲染一个右键菜单' },
|
||||
{ path: 'course16', title: '如何渲染富文本的悬浮工具栏' },
|
||||
{ path: 'course17', title: '导入和导出' },
|
||||
{ path: 'course18', title: '如何持久化数据' },
|
||||
{ path: 'doExport', title: 'Export 插件' },
|
||||
{ path: 'drag', title: 'Drag插件' },
|
||||
{ path: 'introduction', title: '简介' },
|
||||
{ path: 'keyCommand', title: 'KeyCommand实例' },
|
||||
{ path: 'keyboardNavigation', title: 'KeyboardNavigation插件' },
|
||||
{ path: 'markdown', title: 'Markdown解析' },
|
||||
{ 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: 'keyCommand', title: 'KeyCommand instance' },
|
||||
{ path: 'keyboardNavigation', title: 'KeyboardNavigation plugin' },
|
||||
{ path: 'markdown', title: 'Markdown parse' },
|
||||
{ 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' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,5 +1,67 @@
|
||||
# Changelog
|
||||
|
||||
## 0.5.4
|
||||
|
||||
新增:1.添加新主题。 2.新增时间轴和鱼骨结构。
|
||||
|
||||
修复:1.修复节点右键和画布右键的冲突问题。 2.修复组织结构图、目录组织图等节点拖拽时存在线段未隐藏的bug。
|
||||
|
||||
优化:1.优化组织结构图布局。2.优化目录组织图布局。
|
||||
|
||||
## 0.5.3
|
||||
|
||||
修复:1.修复富文本模式下,如果选择了多个节点时设置文本样式,会将所有多选节点的文本改成最后一个多选节点的文本的问题。
|
||||
|
||||
新增:1.支持设置初始中心节点的位置。
|
||||
|
||||
### 0.5.3-fix.1
|
||||
|
||||
修复:1.修复设置初始中心节点的位置不生效的问题。
|
||||
|
||||
### 0.5.3-fix.2
|
||||
|
||||
修复:1.修复导出为图片时,节点中的图片显示不出来的问题。
|
||||
|
||||
## 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,6 +1,44 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Changelog</h1>
|
||||
<h2>0.5.4</h2>
|
||||
<p>新增:1.添加新主题。 2.新增时间轴和鱼骨结构。</p>
|
||||
<p>修复:1.修复节点右键和画布右键的冲突问题。 2.修复组织结构图、目录组织图等节点拖拽时存在线段未隐藏的bug。</p>
|
||||
<p>优化:1.优化组织结构图布局。2.优化目录组织图布局。</p>
|
||||
<h2>0.5.3</h2>
|
||||
<p>修复:1.修复富文本模式下,如果选择了多个节点时设置文本样式,会将所有多选节点的文本改成最后一个多选节点的文本的问题。</p>
|
||||
<p>新增:1.支持设置初始中心节点的位置。</p>
|
||||
<h3>0.5.3-fix.1</h3>
|
||||
<p>修复:1.修复设置初始中心节点的位置不生效的问题。</p>
|
||||
<h3>0.5.3-fix.2</h3>
|
||||
<p>修复:1.修复导出为图片时,节点中的图片显示不出来的问题。</p>
|
||||
<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>
|
||||
|
||||
@@ -26,8 +26,9 @@ const mindMap = new MindMap({
|
||||
| -------------------------------- | ------- | ---------------- | ------------------------------------------------------------ | -------- |
|
||||
| el | Element | | 容器元素,必须为DOM元素 | 是 |
|
||||
| data | Object | {} | 思维导图数据,可参考:[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) | |
|
||||
| layout | String | logicalStructure | 布局类型,可选列表:logicalStructure(逻辑结构图)、mindMap(思维导图)、catalogOrganization(目录组织图)、organizationStructure(组织结构图) | |
|
||||
| theme | String | default | 主题,可选列表:default(默认)、classic(脑图经典)、minions(小黄人)、pinkGrape(粉红葡萄)、mint(薄荷)、gold(金色vip)、vitalityOrange(活力橙)、greenLeaf(绿叶)、dark2(暗色2)、skyGreen(天清绿)、classic2(脑图经典2)、classic3(脑图经典3)、classic4(脑图经典4,v0.2.0+)、classicGreen(经典绿)、classicBlue(经典蓝)、blueSky(天空蓝)、brainImpairedPink(脑残粉)、dark(暗色)、earthYellow(泥土黄)、freshGreen(清新绿)、freshRed(清新红)、romanticPurple(浪漫紫) | |
|
||||
| layout | String | logicalStructure | 布局类型,可选列表:logicalStructure(逻辑结构图)、mindMap(思维导图)、catalogOrganization(目录组织图)、organizationStructure(组织结构图)、timeline(v0.5.4+,时间轴)、timeline2(v0.5.4+,上下交替型时间轴)、fishbone(v0.5.4+,鱼骨图) | |
|
||||
| fishboneDeg(v0.5.4+) | Number | 45 | 设置鱼骨结构图的斜线角度 | |
|
||||
| theme | String | default | 主题,可选列表:default(默认)、classic(脑图经典)、minions(小黄人)、pinkGrape(粉红葡萄)、mint(薄荷)、gold(金色vip)、vitalityOrange(活力橙)、greenLeaf(绿叶)、dark2(暗色2)、skyGreen(天清绿)、classic2(脑图经典2)、classic3(脑图经典3)、classic4(脑图经典4,v0.2.0+)、classicGreen(经典绿)、classicBlue(经典蓝)、blueSky(天空蓝)、brainImpairedPink(脑残粉)、dark(暗色)、earthYellow(泥土黄)、freshGreen(清新绿)、freshRed(清新红)、romanticPurple(浪漫紫)、simpleBlack(v0.5.4+简约黑)、courseGreen(v0.5.4+课程绿)、coffee(v0.5.4+咖啡)、redSpirit(v0.5.4+红色精神)、blackHumour(v0.5.4+黑色幽默)、lateNightOffice(v0.5.4+深夜办公室)、blackGold(v0.5.4+黑金) | |
|
||||
| themeConfig | Object | {} | 主题配置,会和所选择的主题进行合并,可用字段可参考:[https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/default.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/default.js) | |
|
||||
| scaleRatio | Number | 0.1 | 放大缩小的增量比例 | |
|
||||
| maxTag | Number | 5 | 节点里最多显示的标签数量,多余的会被丢弃 | |
|
||||
@@ -46,6 +47,12 @@ const mindMap = new MindMap({
|
||||
| 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 | |
|
||||
| initRootNodePosition(v0.5.3+) | Array | null | 初始根节点的位置,可传一个数组,默认为`['center', 'center']`,代表根节点处于画布中心位置,除了`center`,关键词还可以设置`left`、`top`、`right`、`bottom`,除了可以传关键词,数组的每项还可以传递一个数字,代表具体的像素,可以传递一个百分比字符串,比如`['40%', '60%']`,代表水平位置在画布宽度的`40%`的位置,垂直位置在画布高度的`60%`的位置 | |
|
||||
|
||||
### 水印配置
|
||||
|
||||
@@ -195,6 +202,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)
|
||||
|
||||
|
||||
@@ -46,14 +46,21 @@
|
||||
<td>layout</td>
|
||||
<td>String</td>
|
||||
<td>logicalStructure</td>
|
||||
<td>布局类型,可选列表:logicalStructure(逻辑结构图)、mindMap(思维导图)、catalogOrganization(目录组织图)、organizationStructure(组织结构图)</td>
|
||||
<td>布局类型,可选列表:logicalStructure(逻辑结构图)、mindMap(思维导图)、catalogOrganization(目录组织图)、organizationStructure(组织结构图)、timeline(v0.5.4+,时间轴)、timeline2(v0.5.4+,上下交替型时间轴)、fishbone(v0.5.4+,鱼骨图)</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>fishboneDeg(v0.5.4+)</td>
|
||||
<td>Number</td>
|
||||
<td>45</td>
|
||||
<td>设置鱼骨结构图的斜线角度</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>theme</td>
|
||||
<td>String</td>
|
||||
<td>default</td>
|
||||
<td>主题,可选列表:default(默认)、classic(脑图经典)、minions(小黄人)、pinkGrape(粉红葡萄)、mint(薄荷)、gold(金色vip)、vitalityOrange(活力橙)、greenLeaf(绿叶)、dark2(暗色2)、skyGreen(天清绿)、classic2(脑图经典2)、classic3(脑图经典3)、classic4(脑图经典4,v0.2.0+)、classicGreen(经典绿)、classicBlue(经典蓝)、blueSky(天空蓝)、brainImpairedPink(脑残粉)、dark(暗色)、earthYellow(泥土黄)、freshGreen(清新绿)、freshRed(清新红)、romanticPurple(浪漫紫)</td>
|
||||
<td>主题,可选列表:default(默认)、classic(脑图经典)、minions(小黄人)、pinkGrape(粉红葡萄)、mint(薄荷)、gold(金色vip)、vitalityOrange(活力橙)、greenLeaf(绿叶)、dark2(暗色2)、skyGreen(天清绿)、classic2(脑图经典2)、classic3(脑图经典3)、classic4(脑图经典4,v0.2.0+)、classicGreen(经典绿)、classicBlue(经典蓝)、blueSky(天空蓝)、brainImpairedPink(脑残粉)、dark(暗色)、earthYellow(泥土黄)、freshGreen(清新绿)、freshRed(清新红)、romanticPurple(浪漫紫)、simpleBlack(v0.5.4+简约黑)、courseGreen(v0.5.4+课程绿)、coffee(v0.5.4+咖啡)、redSpirit(v0.5.4+红色精神)、blackHumour(v0.5.4+黑色幽默)、lateNightOffice(v0.5.4+深夜办公室)、blackGold(v0.5.4+黑金)</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -182,6 +189,48 @@
|
||||
<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>
|
||||
<tr>
|
||||
<td>initRootNodePosition(v0.5.3+)</td>
|
||||
<td>Array</td>
|
||||
<td>null</td>
|
||||
<td>初始根节点的位置,可传一个数组,默认为<code>['center', 'center']</code>,代表根节点处于画布中心位置,除了<code>center</code>,关键词还可以设置<code>left</code>、<code>top</code>、<code>right</code>、<code>bottom</code>,除了可以传关键词,数组的每项还可以传递一个数字,代表具体的像素,可以传递一个百分比字符串,比如<code>['40%', '60%']</code>,代表水平位置在画布宽度的<code>40%</code>的位置,垂直位置在画布高度的<code>60%</code>的位置</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>水印配置</h3>
|
||||
@@ -469,6 +518,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>
|
||||
|
||||
64
web/src/pages/Doc/zh/course1/index.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# 基本使用
|
||||
|
||||
> 重要说明:
|
||||
>
|
||||
> 1.本教程中的在线编辑器中使用的是完整版的simple-mind-map,即包含所有插件,如果你是通过`npm`方式使用的话,需要自己手动注册相关插件。
|
||||
>
|
||||
> 2.本教程的代码示例基于Vue3.x,但是您不必担心,因为simple-mind-map本身是框架无关的,所以即使某些Vue3的语法您看不懂也不会影响对于逻辑的理解。
|
||||
|
||||
`simple-mind-map`的使用非常简单,提供一个宽高不为0的元素,然后创建一个实例即可:
|
||||
|
||||
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNptUktu2zAQvcqARWG7sCUV6EqVjX7QRRc+QacLVZzYLKQRQdJxAkObLHuKXiMXanOMDEXZCJIIECS+mff4+Dgn9dna7PpAqlSVb5yxATyFg90gm872LsAJHF0toedtf+BAGga4cn0HM2HNLl1bw3pb21RC5QVuadUJuupqiwoZ+SIxny9gvYETMkDTsw8QGyN9DUzHs9h8bACgtgTdN4eOOGQ7Ct9air9fbr/r+Wxifu051IbJzRbLxNJ1qMu0R3xQRQDVEyjBgW5ChFH9+3v/8Ofu/919tJvKwyQWG5u9abUjjs0/fiZ8iJ9h8RF5WCBXeYpQwpNFIMmgDiQrgEqbazB6jeq5YVSbKpeqtFX5E44sfbhtE/3TFDOqLE/ZThll5Lus8R6VeBCX8r55vsP5xEejw76E90XxdmwG2JPZ7UMJH4rC3oyYHOh1jXdnla52O8MlFJOGrbU2vDsDIiAxjMbVUiXbcQSy375nmbJRBaeC2L7cByoZqHQRWS6/mZNZMR3FE65+uf7oyYkIqulKXhmyxH2ZT2RN3gY1PAJQKfo9" />
|
||||
|
||||
注意,我们还给容器元素设置了样式:
|
||||
|
||||
```css
|
||||
#mindMapContainer * {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
```
|
||||
|
||||
这是为了避免节点内的文字因为默认样式而出现偏移。
|
||||
|
||||
一个节点的基本数据结构如下所示:
|
||||
|
||||
```js
|
||||
{
|
||||
data: {
|
||||
// 节点文本
|
||||
text: '根节点',
|
||||
// 图片
|
||||
image: 'xxx.jpg',
|
||||
imageTitle: '图片名称',
|
||||
imageSize: {
|
||||
width: 1152,
|
||||
height: 1152
|
||||
},
|
||||
// 图标
|
||||
icon: ['priority_1'],
|
||||
// 标签
|
||||
tag: ['标签1', '标签2'],
|
||||
// 链接
|
||||
hyperlink: 'http://lxqnsys.com/',
|
||||
hyperlinkTitle: '理想青年实验室',
|
||||
// 备注内容
|
||||
note: '理想青年实验室\n一个有意思的角落',
|
||||
// 概要
|
||||
generalization: {
|
||||
text: '概要的内容'
|
||||
},
|
||||
// 节点是否展开
|
||||
expand: true,
|
||||
},
|
||||
children: []// 子节点
|
||||
}
|
||||
```
|
||||
|
||||
`icon`目前只能使用内置的图标,完整图标可以在[icons.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js)文件中查看。
|
||||
|
||||
创建实例时还支持传递其他很多选项参数,完整选项列表可以在[实例化选项](https://wanglin2.github.io/mind-map/#/doc/zh/constructor/%E5%AE%9E%E4%BE%8B%E5%8C%96%E9%80%89%E9%A1%B9)查看。
|
||||
|
||||
这样得到的思维导图可以通过鼠标和快捷键进行操作,比如单击某个节点可以激活它,双击某个节点可以编辑节点文本,按下`Tab`键会给当前激活的节点添加一个子节点,按下`Enter`键会给当前激活的节点添加一个兄弟节点等等,完整的快捷键列表可以参考[快捷键列表](https://github.com/wanglin2/mind-map/blob/main/web/src/config/zh.js#L246)。
|
||||
|
||||
当然有些功能还是需要UI界面的,比如图标列表、编辑超链接等等,需要注意的是`simple-mind-map`库并不包含UI界面,所以需要你自己开发,然后通过`simple-mind-map`提供的相关API来操作,本教程的其他章节会向你介绍如何使用。
|
||||
65
web/src/pages/Doc/zh/course1/index.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>基本使用</h1>
|
||||
<blockquote>
|
||||
<p>重要说明:</p>
|
||||
<p>1.本教程中的在线编辑器中使用的是完整版的simple-mind-map,即包含所有插件,如果你是通过<code>npm</code>方式使用的话,需要自己手动注册相关插件。</p>
|
||||
<p>2.本教程的代码示例基于Vue3.x,但是您不必担心,因为simple-mind-map本身是框架无关的,所以即使某些Vue3的语法您看不懂也不会影响对于逻辑的理解。</p>
|
||||
</blockquote>
|
||||
<p><code>simple-mind-map</code>的使用非常简单,提供一个宽高不为0的元素,然后创建一个实例即可:</p>
|
||||
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNptUktu2zAQvcqARWG7sCUV6EqVjX7QRRc+QacLVZzYLKQRQdJxAkObLHuKXiMXanOMDEXZCJIIECS+mff4+Dgn9dna7PpAqlSVb5yxATyFg90gm872LsAJHF0toedtf+BAGga4cn0HM2HNLl1bw3pb21RC5QVuadUJuupqiwoZ+SIxny9gvYETMkDTsw8QGyN9DUzHs9h8bACgtgTdN4eOOGQ7Ct9air9fbr/r+Wxifu051IbJzRbLxNJ1qMu0R3xQRQDVEyjBgW5ChFH9+3v/8Ofu/919tJvKwyQWG5u9abUjjs0/fiZ8iJ9h8RF5WCBXeYpQwpNFIMmgDiQrgEqbazB6jeq5YVSbKpeqtFX5E44sfbhtE/3TFDOqLE/ZThll5Lus8R6VeBCX8r55vsP5xEejw76E90XxdmwG2JPZ7UMJH4rC3oyYHOh1jXdnla52O8MlFJOGrbU2vDsDIiAxjMbVUiXbcQSy375nmbJRBaeC2L7cByoZqHQRWS6/mZNZMR3FE65+uf7oyYkIqulKXhmyxH2ZT2RN3gY1PAJQKfo9" />
|
||||
<p>注意,我们还给容器元素设置了样式:</p>
|
||||
<pre class="hljs"><code><span class="hljs-selector-id">#mindMapContainer</span> * {
|
||||
<span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
|
||||
<span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;
|
||||
}
|
||||
</code></pre>
|
||||
<p>这是为了避免节点内的文字因为默认样式而出现偏移。</p>
|
||||
<p>一个节点的基本数据结构如下所示:</p>
|
||||
<pre class="hljs"><code>{
|
||||
<span class="hljs-attr">data</span>: {
|
||||
<span class="hljs-comment">// 节点文本</span>
|
||||
<span class="hljs-attr">text</span>: <span class="hljs-string">'根节点'</span>,
|
||||
<span class="hljs-comment">// 图片</span>
|
||||
<span class="hljs-attr">image</span>: <span class="hljs-string">'xxx.jpg'</span>,
|
||||
<span class="hljs-attr">imageTitle</span>: <span class="hljs-string">'图片名称'</span>,
|
||||
<span class="hljs-attr">imageSize</span>: {
|
||||
<span class="hljs-attr">width</span>: <span class="hljs-number">1152</span>,
|
||||
<span class="hljs-attr">height</span>: <span class="hljs-number">1152</span>
|
||||
},
|
||||
<span class="hljs-comment">// 图标</span>
|
||||
<span class="hljs-attr">icon</span>: [<span class="hljs-string">'priority_1'</span>],
|
||||
<span class="hljs-comment">// 标签</span>
|
||||
<span class="hljs-attr">tag</span>: [<span class="hljs-string">'标签1'</span>, <span class="hljs-string">'标签2'</span>],
|
||||
<span class="hljs-comment">// 链接</span>
|
||||
<span class="hljs-attr">hyperlink</span>: <span class="hljs-string">'http://lxqnsys.com/'</span>,
|
||||
<span class="hljs-attr">hyperlinkTitle</span>: <span class="hljs-string">'理想青年实验室'</span>,
|
||||
<span class="hljs-comment">// 备注内容</span>
|
||||
<span class="hljs-attr">note</span>: <span class="hljs-string">'理想青年实验室\n一个有意思的角落'</span>,
|
||||
<span class="hljs-comment">// 概要</span>
|
||||
<span class="hljs-attr">generalization</span>: {
|
||||
<span class="hljs-attr">text</span>: <span class="hljs-string">'概要的内容'</span>
|
||||
},
|
||||
<span class="hljs-comment">// 节点是否展开</span>
|
||||
<span class="hljs-attr">expand</span>: <span class="hljs-literal">true</span>,
|
||||
},
|
||||
<span class="hljs-attr">children</span>: []<span class="hljs-comment">// 子节点</span>
|
||||
}
|
||||
</code></pre>
|
||||
<p><code>icon</code>目前只能使用内置的图标,完整图标可以在<a href="https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js">icons.js</a>文件中查看。</p>
|
||||
<p>创建实例时还支持传递其他很多选项参数,完整选项列表可以在<a href="https://wanglin2.github.io/mind-map/#/doc/zh/constructor/%E5%AE%9E%E4%BE%8B%E5%8C%96%E9%80%89%E9%A1%B9">实例化选项</a>查看。</p>
|
||||
<p>这样得到的思维导图可以通过鼠标和快捷键进行操作,比如单击某个节点可以激活它,双击某个节点可以编辑节点文本,按下<code>Tab</code>键会给当前激活的节点添加一个子节点,按下<code>Enter</code>键会给当前激活的节点添加一个兄弟节点等等,完整的快捷键列表可以参考<a href="https://github.com/wanglin2/mind-map/blob/main/web/src/config/zh.js#L246">快捷键列表</a>。</p>
|
||||
<p>当然有些功能还是需要UI界面的,比如图标列表、编辑超链接等等,需要注意的是<code>simple-mind-map</code>库并不包含UI界面,所以需要你自己开发,然后通过<code>simple-mind-map</code>提供的相关API来操作,本教程的其他章节会向你介绍如何使用。</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
114
web/src/pages/Doc/zh/course10/index.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# 主题
|
||||
|
||||
## 使用和切换主题
|
||||
|
||||
`simple-mind-map`内置了很多主题,可以通过如下方式获取到所有的内置主题列表:
|
||||
|
||||
```js
|
||||
import { themeList } from 'simple-mind-map/src/utils/constant'
|
||||
```
|
||||
|
||||
可以在实例化`simple-mind-map`时指定使用的主题:
|
||||
|
||||
```js
|
||||
new MindMap({
|
||||
theme: 'minions'
|
||||
})
|
||||
```
|
||||
|
||||
如果想动态切换主题也很简单:
|
||||
|
||||
```js
|
||||
mindMap.setTheme('classic')
|
||||
```
|
||||
|
||||
如果要获取当前使用的主题名称可以使用:
|
||||
|
||||
```js
|
||||
const theme = mindMap.getTheme()
|
||||
```
|
||||
|
||||
## 定义新主题
|
||||
|
||||
除了可以使用内置的主题外,你也可以自定义新主题:
|
||||
|
||||
```js
|
||||
import MindMap from 'simple-mind-map'
|
||||
|
||||
// 注册新主题
|
||||
MindMap.defineTheme('主题名称', {
|
||||
// 主题配置
|
||||
})
|
||||
|
||||
// 1.实例化时使用新注册的主题
|
||||
const mindMap = new MindMap({
|
||||
theme: '主题名称'
|
||||
})
|
||||
|
||||
// 2.动态切换新主题
|
||||
mindMap.setTheme('主题名称')
|
||||
```
|
||||
|
||||
最好在实例化之前进行注册,这样在实例化时可以直接使用新注册的主题。
|
||||
|
||||
一个主题其实就是一个普通的对象,完整配置可以参考[默认主题](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/default.js),`defineTheme`方法会把你传入的配置和默认配置做合并。大部分主题其实需要自定义的部分不是很多,一个典型的自定义主题配置可以参考[blueSky](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/blueSky.js)。
|
||||
|
||||
```js
|
||||
MindMap.defineTheme('redSpirit', {
|
||||
// 背景颜色
|
||||
backgroundColor: 'rgb(255, 238, 228)',
|
||||
// 连线的颜色
|
||||
lineColor: 'rgb(230, 138, 131)',
|
||||
lineWidth: 3,
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 3,
|
||||
// 概要连线的颜色
|
||||
generalizationLineColor: 'rgb(222, 101, 85)',
|
||||
// 根节点样式
|
||||
root: {
|
||||
fillColor: 'rgb(207, 44, 44)',
|
||||
color: 'rgb(255, 233, 157)',
|
||||
borderColor: '',
|
||||
borderWidth: 0,
|
||||
fontSize: 24,
|
||||
active: {
|
||||
borderColor: 'rgb(255, 233, 157)',
|
||||
borderWidth: 3,
|
||||
}
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
fillColor: 'rgb(255, 255, 255)',
|
||||
color: 'rgb(211, 58, 21)',
|
||||
borderColor: 'rgb(222, 101, 85)',
|
||||
borderWidth: 2,
|
||||
fontSize: 18,
|
||||
active: {
|
||||
borderColor: 'rgb(255, 233, 157)',
|
||||
}
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
fontSize: 14,
|
||||
color: 'rgb(144, 71, 43)',
|
||||
active: {
|
||||
borderColor: 'rgb(255, 233, 157)'
|
||||
}
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
fontSize: 14,
|
||||
fillColor: 'rgb(255, 247, 211)',
|
||||
borderColor: 'rgb(255, 202, 162)',
|
||||
borderWidth: 2,
|
||||
color: 'rgb(187, 101, 69)',
|
||||
active: {
|
||||
borderColor: 'rgb(222, 101, 85)'
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## 完整示例
|
||||
|
||||
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFV+tvG0UQ/1dWh9A5yDk/0wTjVIXCB6QGoRaJD7kIne/W9rbn3eN2nUcjSxBQadpUBYEoL6ESiZIPSKCCSh5U+Wf8SP8LZm/vZfuSlqpSP/ixszO/mf3N7Nzcpvam5xmrXazVtDq3feIJxLHoeudNSjoe8wXaRD5u5hGjS6xLBXbyiLct12Vrl3ET9VDTZx2kA4IeWywR6ixZntoyNQ5iF892QDrbsTxTMylCJnWxQFImNRcR7bqukhcKaPD468H2neHxJ8O/j0Y/fH5ya2u0dTC4ee/klz2T2oxygSxbkFX8HnMwB+skotzyyoxJFcrNL4Z3dvv7R092v4vM7LZFW/iDNu5gMMvNoMXzaFP6DSMx4PDBbk63XYtzYusA1wsRh3/tDW7sDA4e9R8fj77ZG3775zi6g5uEZqGHjBgphZzuY+eKR3wi9LzSQgh8nHy2M/z+jye7P51sP1TChmVfa/lAvnORucyvId1vNXLlubk8KlcW4Ku8MKPnE4Djn0eHx0BbGsMFt2PWlWIelaR1qVKKraXWh8QR7RqqJIDDB1snDz6NYUcP742ObqjdFqbYt1xy3RKE0UvPYp6Oatp8LMZyGcIrlvJoYS59wOH9A1URw/v/DP69q+Q+Y6IW0YhQk7juGFZxPo+qVfmJoRCyp/msgMu5+ZROg/kO9iOsSXl43GIsbjIqrpDruIbK1VioijUV3iTsWQFMuIqYRQiqMvhJmOkf7owOf5smh2OoT+csegLf4ddpBJUgE3Oy3pKCyTxHVtomDlHO4Ku08GL4yqBlfxtoGdy91T/6tb9/e5ofCm0kzU4SU5LDNBUlWUnzcMJqJeX4eaI+Pejw2kzFOn5nnhZ1dqKrcBkgnWenMdAsylyeKz89j2P0LAB+UAHnXv/f/IxVzyQ90IdFDxpyVr9O2mnUseNHVm660ctHDl6LGnMujAm7NeQwu9vBVBgtLN5xAZqKtzbedXJ6aHkReLagVfn6THg0xxJW6limJgWmNnZSKRZ4XUixqcUtTD0M06mXinabuI6PqVReTjAm4DK9THpKt4TE2bjDDKcryV5a7yVFEP0NZZEdoURchr4vp4D3GSfqQizrLm7KZ6puQ+ogTSuhupB1AmUGeQRFHhRXb+YNNXUEd27041eDL38P71wwffQPb/ePHqULjtGcLvvFR6qcwU9OLvNBE7lEuEhKLSr5YEoxVi23K8eCSE+5h1KFT72gZi+YumAhMAxMlsCwQqjukFUUzCKLphYG8TbuMFMLtkMF4iS7cYGCSr0Au2nFCEkw5jYsqaI2TVFvdIVgFF2wXWJfA5XUrARq6WmqXlC6oS1AT9qmBh2wPW1umsBJgo3+1QspLmDJxYaraLkQzpqmZhTUgBnNWJh3DJtzU4sza6RoizKzphpYqVh8NdBDyIsryMfgEfIWbATFJz+vTNIbQSWGVoMztyuUIYxTUIcwG4QrwbxkMe2+jUmrDerVYtFbjzxn+30t8tyx/BYBvxGqZzkOoa1IEIduhNl+xohLUQRh0PEaAKFSgxxoeU1lQI70xlXOKLxBBPBmuAEZiDuDqcELgmoHRgH+Gj70ZdLBMlmzDZ+tcewDiKmFVzXjpUHatoXweK1QcNc/pnyDG4zzWZvONjC5Csc24Km40aU2N2zWKUCZY8EzakO6CQ/T03r/AbfdVt0=" />
|
||||
105
web/src/pages/Doc/zh/course10/index.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>主题</h1>
|
||||
<h2>使用和切换主题</h2>
|
||||
<p><code>simple-mind-map</code>内置了很多主题,可以通过如下方式获取到所有的内置主题列表:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> { themeList } <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/utils/constant'</span>
|
||||
</code></pre>
|
||||
<p>可以在实例化<code>simple-mind-map</code>时指定使用的主题:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">new</span> MindMap({
|
||||
<span class="hljs-attr">theme</span>: <span class="hljs-string">'minions'</span>
|
||||
})
|
||||
</code></pre>
|
||||
<p>如果想动态切换主题也很简单:</p>
|
||||
<pre class="hljs"><code>mindMap.setTheme(<span class="hljs-string">'classic'</span>)
|
||||
</code></pre>
|
||||
<p>如果要获取当前使用的主题名称可以使用:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">const</span> theme = mindMap.getTheme()
|
||||
</code></pre>
|
||||
<h2>定义新主题</h2>
|
||||
<p>除了可以使用内置的主题外,你也可以自定义新主题:</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'</span>
|
||||
|
||||
<span class="hljs-comment">// 注册新主题</span>
|
||||
MindMap.defineTheme(<span class="hljs-string">'主题名称'</span>, {
|
||||
<span class="hljs-comment">// 主题配置</span>
|
||||
})
|
||||
|
||||
<span class="hljs-comment">// 1.实例化时使用新注册的主题</span>
|
||||
<span class="hljs-keyword">const</span> mindMap = <span class="hljs-keyword">new</span> MindMap({
|
||||
<span class="hljs-attr">theme</span>: <span class="hljs-string">'主题名称'</span>
|
||||
})
|
||||
|
||||
<span class="hljs-comment">// 2.动态切换新主题</span>
|
||||
mindMap.setTheme(<span class="hljs-string">'主题名称'</span>)
|
||||
</code></pre>
|
||||
<p>最好在实例化之前进行注册,这样在实例化时可以直接使用新注册的主题。</p>
|
||||
<p>一个主题其实就是一个普通的对象,完整配置可以参考<a href="https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/default.js">默认主题</a>,<code>defineTheme</code>方法会把你传入的配置和默认配置做合并。大部分主题其实需要自定义的部分不是很多,一个典型的自定义主题配置可以参考<a href="https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/blueSky.js">blueSky</a>。</p>
|
||||
<pre class="hljs"><code>MindMap.defineTheme(<span class="hljs-string">'redSpirit'</span>, {
|
||||
<span class="hljs-comment">// 背景颜色</span>
|
||||
<span class="hljs-attr">backgroundColor</span>: <span class="hljs-string">'rgb(255, 238, 228)'</span>,
|
||||
<span class="hljs-comment">// 连线的颜色</span>
|
||||
<span class="hljs-attr">lineColor</span>: <span class="hljs-string">'rgb(230, 138, 131)'</span>,
|
||||
<span class="hljs-attr">lineWidth</span>: <span class="hljs-number">3</span>,
|
||||
<span class="hljs-comment">// 概要连线的粗细</span>
|
||||
<span class="hljs-attr">generalizationLineWidth</span>: <span class="hljs-number">3</span>,
|
||||
<span class="hljs-comment">// 概要连线的颜色</span>
|
||||
<span class="hljs-attr">generalizationLineColor</span>: <span class="hljs-string">'rgb(222, 101, 85)'</span>,
|
||||
<span class="hljs-comment">// 根节点样式</span>
|
||||
<span class="hljs-attr">root</span>: {
|
||||
<span class="hljs-attr">fillColor</span>: <span class="hljs-string">'rgb(207, 44, 44)'</span>,
|
||||
<span class="hljs-attr">color</span>: <span class="hljs-string">'rgb(255, 233, 157)'</span>,
|
||||
<span class="hljs-attr">borderColor</span>: <span class="hljs-string">''</span>,
|
||||
<span class="hljs-attr">borderWidth</span>: <span class="hljs-number">0</span>,
|
||||
<span class="hljs-attr">fontSize</span>: <span class="hljs-number">24</span>,
|
||||
<span class="hljs-attr">active</span>: {
|
||||
<span class="hljs-attr">borderColor</span>: <span class="hljs-string">'rgb(255, 233, 157)'</span>,
|
||||
<span class="hljs-attr">borderWidth</span>: <span class="hljs-number">3</span>,
|
||||
}
|
||||
},
|
||||
<span class="hljs-comment">// 二级节点样式</span>
|
||||
<span class="hljs-attr">second</span>: {
|
||||
<span class="hljs-attr">fillColor</span>: <span class="hljs-string">'rgb(255, 255, 255)'</span>,
|
||||
<span class="hljs-attr">color</span>: <span class="hljs-string">'rgb(211, 58, 21)'</span>,
|
||||
<span class="hljs-attr">borderColor</span>: <span class="hljs-string">'rgb(222, 101, 85)'</span>,
|
||||
<span class="hljs-attr">borderWidth</span>: <span class="hljs-number">2</span>,
|
||||
<span class="hljs-attr">fontSize</span>: <span class="hljs-number">18</span>,
|
||||
<span class="hljs-attr">active</span>: {
|
||||
<span class="hljs-attr">borderColor</span>: <span class="hljs-string">'rgb(255, 233, 157)'</span>,
|
||||
}
|
||||
},
|
||||
<span class="hljs-comment">// 三级及以下节点样式</span>
|
||||
<span class="hljs-attr">node</span>: {
|
||||
<span class="hljs-attr">fontSize</span>: <span class="hljs-number">14</span>,
|
||||
<span class="hljs-attr">color</span>: <span class="hljs-string">'rgb(144, 71, 43)'</span>,
|
||||
<span class="hljs-attr">active</span>: {
|
||||
<span class="hljs-attr">borderColor</span>: <span class="hljs-string">'rgb(255, 233, 157)'</span>
|
||||
}
|
||||
},
|
||||
<span class="hljs-comment">// 概要节点样式</span>
|
||||
<span class="hljs-attr">generalization</span>: {
|
||||
<span class="hljs-attr">fontSize</span>: <span class="hljs-number">14</span>,
|
||||
<span class="hljs-attr">fillColor</span>: <span class="hljs-string">'rgb(255, 247, 211)'</span>,
|
||||
<span class="hljs-attr">borderColor</span>: <span class="hljs-string">'rgb(255, 202, 162)'</span>,
|
||||
<span class="hljs-attr">borderWidth</span>: <span class="hljs-number">2</span>,
|
||||
<span class="hljs-attr">color</span>: <span class="hljs-string">'rgb(187, 101, 69)'</span>,
|
||||
<span class="hljs-attr">active</span>: {
|
||||
<span class="hljs-attr">borderColor</span>: <span class="hljs-string">'rgb(222, 101, 85)'</span>
|
||||
}
|
||||
}
|
||||
})
|
||||
</code></pre>
|
||||
<h2>完整示例</h2>
|
||||
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFV+tvG0UQ/1dWh9A5yDk/0wTjVIXCB6QGoRaJD7kIne/W9rbn3eN2nUcjSxBQadpUBYEoL6ESiZIPSKCCSh5U+Wf8SP8LZm/vZfuSlqpSP/ixszO/mf3N7Nzcpvam5xmrXazVtDq3feIJxLHoeudNSjoe8wXaRD5u5hGjS6xLBXbyiLct12Vrl3ET9VDTZx2kA4IeWywR6ixZntoyNQ5iF892QDrbsTxTMylCJnWxQFImNRcR7bqukhcKaPD468H2neHxJ8O/j0Y/fH5ya2u0dTC4ee/klz2T2oxygSxbkFX8HnMwB+skotzyyoxJFcrNL4Z3dvv7R092v4vM7LZFW/iDNu5gMMvNoMXzaFP6DSMx4PDBbk63XYtzYusA1wsRh3/tDW7sDA4e9R8fj77ZG3775zi6g5uEZqGHjBgphZzuY+eKR3wi9LzSQgh8nHy2M/z+jye7P51sP1TChmVfa/lAvnORucyvId1vNXLlubk8KlcW4Ku8MKPnE4Djn0eHx0BbGsMFt2PWlWIelaR1qVKKraXWh8QR7RqqJIDDB1snDz6NYUcP742ObqjdFqbYt1xy3RKE0UvPYp6Oatp8LMZyGcIrlvJoYS59wOH9A1URw/v/DP69q+Q+Y6IW0YhQk7juGFZxPo+qVfmJoRCyp/msgMu5+ZROg/kO9iOsSXl43GIsbjIqrpDruIbK1VioijUV3iTsWQFMuIqYRQiqMvhJmOkf7owOf5smh2OoT+csegLf4ddpBJUgE3Oy3pKCyTxHVtomDlHO4Ku08GL4yqBlfxtoGdy91T/6tb9/e5ofCm0kzU4SU5LDNBUlWUnzcMJqJeX4eaI+Pejw2kzFOn5nnhZ1dqKrcBkgnWenMdAsylyeKz89j2P0LAB+UAHnXv/f/IxVzyQ90IdFDxpyVr9O2mnUseNHVm660ctHDl6LGnMujAm7NeQwu9vBVBgtLN5xAZqKtzbedXJ6aHkReLagVfn6THg0xxJW6limJgWmNnZSKRZ4XUixqcUtTD0M06mXinabuI6PqVReTjAm4DK9THpKt4TE2bjDDKcryV5a7yVFEP0NZZEdoURchr4vp4D3GSfqQizrLm7KZ6puQ+ogTSuhupB1AmUGeQRFHhRXb+YNNXUEd27041eDL38P71wwffQPb/ePHqULjtGcLvvFR6qcwU9OLvNBE7lEuEhKLSr5YEoxVi23K8eCSE+5h1KFT72gZi+YumAhMAxMlsCwQqjukFUUzCKLphYG8TbuMFMLtkMF4iS7cYGCSr0Au2nFCEkw5jYsqaI2TVFvdIVgFF2wXWJfA5XUrARq6WmqXlC6oS1AT9qmBh2wPW1umsBJgo3+1QspLmDJxYaraLkQzpqmZhTUgBnNWJh3DJtzU4sza6RoizKzphpYqVh8NdBDyIsryMfgEfIWbATFJz+vTNIbQSWGVoMztyuUIYxTUIcwG4QrwbxkMe2+jUmrDerVYtFbjzxn+30t8tyx/BYBvxGqZzkOoa1IEIduhNl+xohLUQRh0PEaAKFSgxxoeU1lQI70xlXOKLxBBPBmuAEZiDuDqcELgmoHRgH+Gj70ZdLBMlmzDZ+tcewDiKmFVzXjpUHatoXweK1QcNc/pnyDG4zzWZvONjC5Csc24Km40aU2N2zWKUCZY8EzakO6CQ/T03r/AbfdVt0=" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
28
web/src/pages/Doc/zh/course11/index.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# 结构
|
||||
|
||||
`simple-mind-map`目前支持四种结构:logicalStructure(逻辑结构图)、mindMap(思维导图)、organizationStructure(组织结构图)、catalogOrganization(目录组织图)。
|
||||
|
||||
可以在实例化`simple-mind-map`时通过选项指定使用的结构:
|
||||
|
||||
```js
|
||||
new MindMap({
|
||||
// ...
|
||||
layout: 'logicalStructure'
|
||||
})
|
||||
```
|
||||
|
||||
也可以动态切换结构:
|
||||
|
||||
```js
|
||||
mindMap.setLayout('organizationStructure')
|
||||
```
|
||||
|
||||
获取当前使用的结构可以使用`getLayout`方法:
|
||||
|
||||
```js
|
||||
const layout = mindMap.getLayout()
|
||||
```
|
||||
|
||||
## 完整示例
|
||||
|
||||
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFVVtrFDEU/iuHiMxWtrNb8Gndlnp7EFqR+tgUSWfS3WgmGSaZXiwLUgrWS0Hpg6Ag6os++ChCW8Q/0+72Z3iymZkd2z745sIsk3P5vpOc72S2yc00DddzTjqka6JMpBYMt3k6R5VIUp1Z2IaMrzVBq0WdK8vjJpg+k1JvLPE1GMBaphMIECGoMhaFihdZ6l2UGDRLPp2gdTphKSVUAVAluQVnc5GzoHIpvb3VgtNfB6cv9oe/nw1/HI/e75693BntHJ7uvTv7/I2qSCtjgUVWrPP7OuYGsycVNZZXpqjyKHvPh/tfRscHw4+7ZVrUZ6rHF7aYzi3mNaZgdg62HXFRSoi7X2Bb6G4EOusxJZ4yK7R6aLM8snnGA4QfOIbqQBoXUdyG+EZ5EI2xC4DLDsQ6yhOubNjj9q7k7vXW1r24ERSZt7WyTCieBVNNnxUzyzoe3f0ocQZKaiZvtnzTOjMlw0+H/sj8UbvfoABzgVFfyDjjygUvTzDOwV3Kcp7p5Oj16OjrebK/CS8hXZn46nH/qYLytbCVeUIJu6S1dRp7oI1wKsDMQPI1GzQhiLB12KaVIlyORdOBQOqeiJicCMb5B1M3vLoBUJmjD29P33z3RXuVnxy9Ojn+WdehVo1AIfUjr3RkbLhlE9z/gjB2IjqoT0O4zmTOnQCLOE+PosWn2/IzjtONC8txMJnluALoxmIdIsmMmaWkKOIOTzQlY3cRIOKJt5IqhnRb6K0HlkhWa7nKXIh3Uttdza3VCuYjKaInGFKfSYyrj2235YNL5IqlfOu2apvApbFb0u9nvriMKAlb/gYqpjHkJgkjYyipWhLW9lse6YaIbb8DM+321XEcQFqJIOPIiAc+doz1454r58+lhJokslWjZW59ImoGpdSBdrGyOp0sLtL3uej1Mfx6u51ulsyX814rmROW9QTylqgpi2OheqWhKj0s2vSPFc+UFRRFV2sERImNe0CaxHfA3fnhY6MVfmLG8LRwYAeq4aYEvyB+osMWvoYZXq0i4a5Z06uZ3jA8QxBKimm75Kvicy+22mUVtQ3I4A+xm1Zg" />
|
||||
30
web/src/pages/Doc/zh/course11/index.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>结构</h1>
|
||||
<p><code>simple-mind-map</code>目前支持四种结构:logicalStructure(逻辑结构图)、mindMap(思维导图)、organizationStructure(组织结构图)、catalogOrganization(目录组织图)。</p>
|
||||
<p>可以在实例化<code>simple-mind-map</code>时通过选项指定使用的结构:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">new</span> MindMap({
|
||||
<span class="hljs-comment">// ...</span>
|
||||
<span class="hljs-attr">layout</span>: <span class="hljs-string">'logicalStructure'</span>
|
||||
})
|
||||
</code></pre>
|
||||
<p>也可以动态切换结构:</p>
|
||||
<pre class="hljs"><code>mindMap.setLayout(<span class="hljs-string">'organizationStructure'</span>)
|
||||
</code></pre>
|
||||
<p>获取当前使用的结构可以使用<code>getLayout</code>方法:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">const</span> layout = mindMap.getLayout()
|
||||
</code></pre>
|
||||
<h2>完整示例</h2>
|
||||
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFVVtrFDEU/iuHiMxWtrNb8Gndlnp7EFqR+tgUSWfS3WgmGSaZXiwLUgrWS0Hpg6Ag6os++ChCW8Q/0+72Z3iymZkd2z745sIsk3P5vpOc72S2yc00DddzTjqka6JMpBYMt3k6R5VIUp1Z2IaMrzVBq0WdK8vjJpg+k1JvLPE1GMBaphMIECGoMhaFihdZ6l2UGDRLPp2gdTphKSVUAVAluQVnc5GzoHIpvb3VgtNfB6cv9oe/nw1/HI/e75693BntHJ7uvTv7/I2qSCtjgUVWrPP7OuYGsycVNZZXpqjyKHvPh/tfRscHw4+7ZVrUZ6rHF7aYzi3mNaZgdg62HXFRSoi7X2Bb6G4EOusxJZ4yK7R6aLM8snnGA4QfOIbqQBoXUdyG+EZ5EI2xC4DLDsQ6yhOubNjj9q7k7vXW1r24ERSZt7WyTCieBVNNnxUzyzoe3f0ocQZKaiZvtnzTOjMlw0+H/sj8UbvfoABzgVFfyDjjygUvTzDOwV3Kcp7p5Oj16OjrebK/CS8hXZn46nH/qYLytbCVeUIJu6S1dRp7oI1wKsDMQPI1GzQhiLB12KaVIlyORdOBQOqeiJicCMb5B1M3vLoBUJmjD29P33z3RXuVnxy9Ojn+WdehVo1AIfUjr3RkbLhlE9z/gjB2IjqoT0O4zmTOnQCLOE+PosWn2/IzjtONC8txMJnluALoxmIdIsmMmaWkKOIOTzQlY3cRIOKJt5IqhnRb6K0HlkhWa7nKXIh3Uttdza3VCuYjKaInGFKfSYyrj2235YNL5IqlfOu2apvApbFb0u9nvriMKAlb/gYqpjHkJgkjYyipWhLW9lse6YaIbb8DM+321XEcQFqJIOPIiAc+doz1454r58+lhJokslWjZW59ImoGpdSBdrGyOp0sLtL3uej1Mfx6u51ulsyX814rmROW9QTylqgpi2OheqWhKj0s2vSPFc+UFRRFV2sERImNe0CaxHfA3fnhY6MVfmLG8LRwYAeq4aYEvyB+osMWvoYZXq0i4a5Z06uZ3jA8QxBKimm75Kvicy+22mUVtQ3I4A+xm1Zg" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
34
web/src/pages/Doc/zh/course12/index.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# 如何渲染一个大纲
|
||||
|
||||
思维导图本质就是一颗树,所以你可以使用树组件来完成大纲的显示。
|
||||
|
||||
可以监听`data_change`事件来获取当前最新的思维导图数据:
|
||||
|
||||
```js
|
||||
mindMap.on('data_change', (data) => {
|
||||
// data数据是不带节点对象的纯数据
|
||||
// 如果你需要操作节点对象,可以使用mindMap.renderer.renderTree
|
||||
console.log(data, mindMap.renderer.renderTree)
|
||||
})
|
||||
```
|
||||
|
||||
通常点击了大纲的某个节点,会将画布定位到该节点并激活该节点,这可以这么做:
|
||||
|
||||
```js
|
||||
const node = data._node
|
||||
mindMap.renderer.moveNodeToCenter(node)
|
||||
node.active()
|
||||
```
|
||||
|
||||
当在大纲树上编辑了某个节点的内容,需要同步到思维导图树上:
|
||||
|
||||
```js
|
||||
data._node.setText('xxx')
|
||||
```
|
||||
|
||||
要插入兄弟节点或子节点可以这么操作:
|
||||
|
||||
```js
|
||||
mindMap.execCommand('INSERT_NODE', false)
|
||||
mindMap.execCommand('INSERT_CHILD_NODE', false)
|
||||
```
|
||||
36
web/src/pages/Doc/zh/course12/index.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>如何渲染一个大纲</h1>
|
||||
<p>思维导图本质就是一颗树,所以你可以使用树组件来完成大纲的显示。</p>
|
||||
<p>可以监听<code>data_change</code>事件来获取当前最新的思维导图数据:</p>
|
||||
<pre class="hljs"><code>mindMap.on(<span class="hljs-string">'data_change'</span>, <span class="hljs-function">(<span class="hljs-params">data</span>) =></span> {
|
||||
<span class="hljs-comment">// data数据是不带节点对象的纯数据</span>
|
||||
<span class="hljs-comment">// 如果你需要操作节点对象,可以使用mindMap.renderer.renderTree</span>
|
||||
<span class="hljs-built_in">console</span>.log(data, mindMap.renderer.renderTree)
|
||||
})
|
||||
</code></pre>
|
||||
<p>通常点击了大纲的某个节点,会将画布定位到该节点并激活该节点,这可以这么做:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">const</span> node = data._node
|
||||
mindMap.renderer.moveNodeToCenter(node)
|
||||
node.active()
|
||||
</code></pre>
|
||||
<p>当在大纲树上编辑了某个节点的内容,需要同步到思维导图树上:</p>
|
||||
<pre class="hljs"><code>data._node.setText(<span class="hljs-string">'xxx'</span>)
|
||||
</code></pre>
|
||||
<p>要插入兄弟节点或子节点可以这么操作:</p>
|
||||
<pre class="hljs"><code>mindMap.execCommand(<span class="hljs-string">'INSERT_NODE'</span>, <span class="hljs-literal">false</span>)
|
||||
mindMap.execCommand(<span class="hljs-string">'INSERT_CHILD_NODE'</span>, <span class="hljs-literal">false</span>)
|
||||
</code></pre>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
131
web/src/pages/Doc/zh/course13/index.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# 快捷键
|
||||
|
||||
`simple-mind-map`常用操作都支持快捷键方式使用,目前所有的快捷键列表如下:
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
type: '节点操作',
|
||||
list: [
|
||||
{
|
||||
name: '插入下级节点',
|
||||
value: 'Tab'
|
||||
},
|
||||
{
|
||||
name: '插入同级节点',
|
||||
value: 'Enter'
|
||||
},
|
||||
{
|
||||
name: '上移节点',
|
||||
value: 'Ctrl + ↑'
|
||||
},
|
||||
{
|
||||
name: '下移节点',
|
||||
value: 'Ctrl + ↓'
|
||||
},
|
||||
{
|
||||
name: '插入概要',
|
||||
value: 'Ctrl + S'
|
||||
},
|
||||
{
|
||||
name: '展开/收起节点',
|
||||
value: '/'
|
||||
},
|
||||
{
|
||||
name: '删除节点',
|
||||
value: 'Delete | Backspace'
|
||||
},
|
||||
{
|
||||
name: '复制节点',
|
||||
value: 'Ctrl + C'
|
||||
},
|
||||
{
|
||||
name: '剪切节点',
|
||||
value: 'Ctrl + X'
|
||||
},
|
||||
{
|
||||
name: '粘贴节点',
|
||||
value: 'Ctrl + V'
|
||||
},
|
||||
{
|
||||
name: '编辑节点',
|
||||
value: 'F2'
|
||||
},
|
||||
{
|
||||
name: '文本换行',
|
||||
value: 'Shift + Enter'
|
||||
},
|
||||
{
|
||||
name: '回退',
|
||||
value: 'Ctrl + Z'
|
||||
},
|
||||
{
|
||||
name: '前进',
|
||||
value: 'Ctrl + Y'
|
||||
},
|
||||
{
|
||||
name: '全选',
|
||||
value: 'Ctrl + A'
|
||||
},
|
||||
{
|
||||
name: '多选',
|
||||
value: '右键 / Ctrl + 左键'
|
||||
},
|
||||
{
|
||||
name: '一键整理布局',
|
||||
value: 'Ctrl + L'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: '画布操作',
|
||||
list: [
|
||||
{
|
||||
name: '放大',
|
||||
value: 'Ctrl + +'
|
||||
},
|
||||
{
|
||||
name: '缩小',
|
||||
value: 'Ctrl + -'
|
||||
},
|
||||
{
|
||||
name: '恢复默认',
|
||||
value: 'Ctrl + Enter'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
默认当鼠标滑入画布范围内才会响应快捷键操作,如果你想去掉这个限制可以在实例化`simple-mind-map`时通过选项指定:
|
||||
|
||||
```js
|
||||
new MindMap({
|
||||
// ...
|
||||
enableShortcutOnlyWhenMouseInSvg: false
|
||||
})
|
||||
```
|
||||
|
||||
你也可以添加新的快捷键:
|
||||
|
||||
```js
|
||||
mindMap.keyCommand.addShortcut('key', () => {
|
||||
// 执行一些操作
|
||||
})
|
||||
```
|
||||
|
||||
`key`支持三种方式:
|
||||
|
||||
```
|
||||
Enter // 单个按键
|
||||
Tab | Insert // 或
|
||||
Shift + a // 与
|
||||
```
|
||||
|
||||
要获取所有的按键值对应的名称,可以:
|
||||
|
||||
```js
|
||||
import { keyMap } from 'simple-mind-map/src/utils/keyMap'
|
||||
```
|
||||
|
||||
可以添加当然也可以移除,详细文档可以参考[keyCommand](https://wanglin2.github.io/mind-map/#/doc/zh/keyCommand)。
|
||||
130
web/src/pages/Doc/zh/course13/index.vue
Normal file
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>快捷键</h1>
|
||||
<p><code>simple-mind-map</code>常用操作都支持快捷键方式使用,目前所有的快捷键列表如下:</p>
|
||||
<pre class="hljs"><code>[
|
||||
{
|
||||
<span class="hljs-attr">type</span>: <span class="hljs-string">'节点操作'</span>,
|
||||
<span class="hljs-attr">list</span>: [
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'插入下级节点'</span>,
|
||||
<span class="hljs-attr">value</span>: <span class="hljs-string">'Tab'</span>
|
||||
},
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'插入同级节点'</span>,
|
||||
<span class="hljs-attr">value</span>: <span class="hljs-string">'Enter'</span>
|
||||
},
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'上移节点'</span>,
|
||||
<span class="hljs-attr">value</span>: <span class="hljs-string">'Ctrl + ↑'</span>
|
||||
},
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'下移节点'</span>,
|
||||
<span class="hljs-attr">value</span>: <span class="hljs-string">'Ctrl + ↓'</span>
|
||||
},
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'插入概要'</span>,
|
||||
<span class="hljs-attr">value</span>: <span class="hljs-string">'Ctrl + S'</span>
|
||||
},
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'展开/收起节点'</span>,
|
||||
<span class="hljs-attr">value</span>: <span class="hljs-string">'/'</span>
|
||||
},
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'删除节点'</span>,
|
||||
<span class="hljs-attr">value</span>: <span class="hljs-string">'Delete | Backspace'</span>
|
||||
},
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'复制节点'</span>,
|
||||
<span class="hljs-attr">value</span>: <span class="hljs-string">'Ctrl + C'</span>
|
||||
},
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'剪切节点'</span>,
|
||||
<span class="hljs-attr">value</span>: <span class="hljs-string">'Ctrl + X'</span>
|
||||
},
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'粘贴节点'</span>,
|
||||
<span class="hljs-attr">value</span>: <span class="hljs-string">'Ctrl + V'</span>
|
||||
},
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'编辑节点'</span>,
|
||||
<span class="hljs-attr">value</span>: <span class="hljs-string">'F2'</span>
|
||||
},
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'文本换行'</span>,
|
||||
<span class="hljs-attr">value</span>: <span class="hljs-string">'Shift + Enter'</span>
|
||||
},
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'回退'</span>,
|
||||
<span class="hljs-attr">value</span>: <span class="hljs-string">'Ctrl + Z'</span>
|
||||
},
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'前进'</span>,
|
||||
<span class="hljs-attr">value</span>: <span class="hljs-string">'Ctrl + Y'</span>
|
||||
},
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'全选'</span>,
|
||||
<span class="hljs-attr">value</span>: <span class="hljs-string">'Ctrl + A'</span>
|
||||
},
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'多选'</span>,
|
||||
<span class="hljs-attr">value</span>: <span class="hljs-string">'右键 / Ctrl + 左键'</span>
|
||||
},
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'一键整理布局'</span>,
|
||||
<span class="hljs-attr">value</span>: <span class="hljs-string">'Ctrl + L'</span>
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
<span class="hljs-attr">type</span>: <span class="hljs-string">'画布操作'</span>,
|
||||
<span class="hljs-attr">list</span>: [
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'放大'</span>,
|
||||
<span class="hljs-attr">value</span>: <span class="hljs-string">'Ctrl + +'</span>
|
||||
},
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'缩小'</span>,
|
||||
<span class="hljs-attr">value</span>: <span class="hljs-string">'Ctrl + -'</span>
|
||||
},
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'恢复默认'</span>,
|
||||
<span class="hljs-attr">value</span>: <span class="hljs-string">'Ctrl + Enter'</span>
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
</code></pre>
|
||||
<p>默认当鼠标滑入画布范围内才会响应快捷键操作,如果你想去掉这个限制可以在实例化<code>simple-mind-map</code>时通过选项指定:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">new</span> MindMap({
|
||||
<span class="hljs-comment">// ...</span>
|
||||
<span class="hljs-attr">enableShortcutOnlyWhenMouseInSvg</span>: <span class="hljs-literal">false</span>
|
||||
})
|
||||
</code></pre>
|
||||
<p>你也可以添加新的快捷键:</p>
|
||||
<pre class="hljs"><code>mindMap.keyCommand.addShortcut(<span class="hljs-string">'key'</span>, <span class="hljs-function">() =></span> {
|
||||
<span class="hljs-comment">// 执行一些操作</span>
|
||||
})
|
||||
</code></pre>
|
||||
<p><code>key</code>支持三种方式:</p>
|
||||
<pre class="hljs"><code>Enter // 单个按键
|
||||
Tab | Insert // 或
|
||||
Shift + a // 与
|
||||
</code></pre>
|
||||
<p>要获取所有的按键值对应的名称,可以:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> { keyMap } <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/utils/keyMap'</span>
|
||||
</code></pre>
|
||||
<p>可以添加当然也可以移除,详细文档可以参考<a href="https://wanglin2.github.io/mind-map/#/doc/zh/keyCommand">keyCommand</a>。</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
54
web/src/pages/Doc/zh/course14/index.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# 如何渲染一个小地图
|
||||
|
||||
> 要使用小地图需要注册小地图插件
|
||||
|
||||
小地图可以方便看到当前画布可视区域在思维导图树的哪个部分。
|
||||
|
||||
虽然小地图需要你自行开发,不过`simple-mind-map`提供了一些方法来帮你快速的完成这一工作。
|
||||
|
||||
小地图由两部分组成,一个是当前的画布内容,一个是视口框,当缩放、移动、元素过多时画布上可能只显示了思维导图的部分内容,可以通过视口框来查看当前视口所在位置,以及可以通过在小地图上拖动来快速定位。
|
||||
|
||||
当注册了小地图插件后可以通过`mindMap.miniMap`获取到插件实例,然后通过`mindMap.miniMap.calculationMiniMap`方法即可获取小地图渲染需要的数据,返回的数据结构如下:
|
||||
|
||||
```js
|
||||
{
|
||||
svgHTML, // 小地图html
|
||||
viewBoxStyle, // 视图框的位置信息
|
||||
miniMapBoxScale, // 视图框的缩放值
|
||||
miniMapBoxLeft, // 视图框的left值
|
||||
miniMapBoxTop, // 视图框的top值
|
||||
}
|
||||
```
|
||||
|
||||
完整实现思路如下:
|
||||
|
||||
1.准备一个宽高不为0的容器元素container,定位不为static
|
||||
|
||||
2.在container内创建一个小地图容器元素miniMapContainer,绝对定位,设置变换中心点为`left top`:
|
||||
|
||||
```css
|
||||
transform-origin: left top;
|
||||
```
|
||||
|
||||
3.在container内创建一个视口框元素viewBoxContainer,绝对定位,设置边框样式,过渡属性(可选)
|
||||
|
||||
4.监听data_change和view_data_change事件,最好也监听一下node_tree_render_end事件,防止初次渲染完毕后小地图没有刷新,在该事件内调用calculationMiniMap方法获取计算数据,然后将返回数据中的svgHTML渲染到miniMapContainer元素内,并且给miniMapContainer元素设置或更新如下样式:
|
||||
|
||||
```js
|
||||
{
|
||||
transform: `scale(${miniMapBoxScale})`,
|
||||
left: miniMapBoxLeft + 'px',
|
||||
top: miniMapBoxTop + 'px',
|
||||
}
|
||||
```
|
||||
5.将viewBoxStyle对象设置为viewBoxContainer元素的样式
|
||||
|
||||
到这一步,当画布上的思维导图变化了,小地图也会实时更新,并且视口框元素会实时反映视口在思维导图图形上的位置
|
||||
|
||||
6.监听container元素的mousedown、mousemove、mouseup事件,分别调用小地图插件实例的三个方法即可实现鼠标拖动时画布上的思维导图也随之拖动的效果
|
||||
|
||||
插件的完整信息可以参考[miniMap](https://wanglin2.github.io/mind-map/#/doc/zh/miniMap)。
|
||||
|
||||
## 完整示例
|
||||
|
||||
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFVt2O20QUfpWRASULWSfbUgQhW5UCEkgNQqUSF0yVeu1J4sqesTzjJFWUm4oLhEBcIH6LBFyBhBStEBdsJXgaki1vwTn2jD12stpy1ZU28sz3nZ85c35m6byRJO4sY07fGUg/DRNFJFNZcp3yME5EqsiSpGzcIYIPRcYVCzpETr0oEvPbbExWZJyKmLRAQ6uUGIY8GHpJAVFHwnbEDmPYPYy9hDqUE0J5xBTBPWQeE55FEeWUd7tkc/rF5ofTzaO/N+uzzXe/nn//8Wb917+/fUu5L7hUBH6VF3KWfhgGagqyR73eDvYOCydTBeA1wHK120d/bL8+LZUbCXAhBBfeNIIgAudtoz8HhjML2fymWHygHkRM48tViWoNSPC9knC0B7/FxugSwr098B2BkaijWRJ4ikFEwyGC7QNyfJ0sMYJwpCfrn8/X35RH2n51uv18jRjGFuQ8kNAhdrUZF1z0s8hTIVxosdWuB7TTDCL4klvb/vn79scvN5/YMSQ78XNnXpQxN+Qof2d4C1xAT1w5m+ASRexwFnRDshFLuQltndwA63wM9UV0xOpsiPxFZIAoX2EOFReS14FkgZhzvA9WXUgz0hYTaPuUxGKGFp9CCTIvUJLleXG5iiypFJTF3LYSyipGNjdF3M4hQljUJ4Hws5hx5U6Yejti+HnzwbtBu6UlyxxoHXQKKYxlv9COf9TBDepYW8W2YguF29TZ/nT25NOH5w/PijaBfyutDIn+NIyClHEkf1TpaKjba6Vp6Z/Hn50//qVprG5wj9G7FWbznpEH5lPvGTm4eXVbCPWeCNj7QoZY7SDZiiDzWx3S8uHq4Jru5vTVwetFR65SR/B2C48w8qcenzAQsftQ3hBsKpbt6H/wObg1UiljIzhTwNIR/O7KYIcddIuhBOMIForBJAEKrAgZBOGM+JEn5TF1tPa3oFCok8OaEAYVWuYnUAZdQG2iiaTRWDbB6mZuxKaeAbeq2zCoKhhYrBUDV01GllR4piciIdodqmoOWT41ey11bBYMjr0ci9KX2FmB1chLlXpcjkUa98k9ic20/fyy0V5XB/dqSYlDZqz6zen2Emkli1aDqURiE3HOFbyysBVVq8pTHYgc0V/2fTXuXg8NOyrlOWsDpXHr5bf5GnStBINlriRn3NAvG+q43eI5o9ujy2Ts+lJSp6wh7LsmF039z3Gw9vGh8kLOIyQpqzJlOI1nLAfygsb/55o5a1RVgt6JFFGmCkFzHT29ykNuFrvmp/lk75OXe71kYSzvt/uisRx76SQEu0Zr4gVByCdmo3TdLUvnKX0+Mj5ot6115Xm5ZVy/ZnleRb7+lrvMfpn3hyIN88OhR+hFQ3Mzxy7VfCJSaGx9ciVZEADCgKSTk/aVq692yCuv4X/RdLULRkcUkZ57VRrj0P3yFHQ6TpGA+H5270vB4bmee0A1AAlYDhvI+gySHSeM24VPN4VRH8YMc/XwJBVzCa+0+yChq3TPC72Q3c10lNK+rZzVf9RSHZw=" />
|
||||
49
web/src/pages/Doc/zh/course14/index.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>如何渲染一个小地图</h1>
|
||||
<blockquote>
|
||||
<p>要使用小地图需要注册小地图插件</p>
|
||||
</blockquote>
|
||||
<p>小地图可以方便看到当前画布可视区域在思维导图树的哪个部分。</p>
|
||||
<p>虽然小地图需要你自行开发,不过<code>simple-mind-map</code>提供了一些方法来帮你快速的完成这一工作。</p>
|
||||
<p>小地图由两部分组成,一个是当前的画布内容,一个是视口框,当缩放、移动、元素过多时画布上可能只显示了思维导图的部分内容,可以通过视口框来查看当前视口所在位置,以及可以通过在小地图上拖动来快速定位。</p>
|
||||
<p>当注册了小地图插件后可以通过<code>mindMap.miniMap</code>获取到插件实例,然后通过<code>mindMap.miniMap.calculationMiniMap</code>方法即可获取小地图渲染需要的数据,返回的数据结构如下:</p>
|
||||
<pre class="hljs"><code>{
|
||||
svgHTML, <span class="hljs-comment">// 小地图html</span>
|
||||
viewBoxStyle, <span class="hljs-comment">// 视图框的位置信息</span>
|
||||
miniMapBoxScale, <span class="hljs-comment">// 视图框的缩放值</span>
|
||||
miniMapBoxLeft, <span class="hljs-comment">// 视图框的left值</span>
|
||||
miniMapBoxTop, <span class="hljs-comment">// 视图框的top值</span>
|
||||
}
|
||||
</code></pre>
|
||||
<p>完整实现思路如下:</p>
|
||||
<p>1.准备一个宽高不为0的容器元素container,定位不为static</p>
|
||||
<p>2.在container内创建一个小地图容器元素miniMapContainer,绝对定位,设置变换中心点为<code>left top</code>:</p>
|
||||
<pre class="hljs"><code><span class="hljs-attribute">transform-origin</span>: left top;
|
||||
</code></pre>
|
||||
<p>3.在container内创建一个视口框元素viewBoxContainer,绝对定位,设置边框样式,过渡属性(可选)</p>
|
||||
<p>4.监听data_change和view_data_change事件,最好也监听一下node_tree_render_end事件,防止初次渲染完毕后小地图没有刷新,在该事件内调用calculationMiniMap方法获取计算数据,然后将返回数据中的svgHTML渲染到miniMapContainer元素内,并且给miniMapContainer元素设置或更新如下样式:</p>
|
||||
<pre class="hljs"><code>{
|
||||
<span class="hljs-attr">transform</span>: <span class="hljs-string">`scale(<span class="hljs-subst">${miniMapBoxScale}</span>)`</span>,
|
||||
<span class="hljs-attr">left</span>: miniMapBoxLeft + <span class="hljs-string">'px'</span>,
|
||||
<span class="hljs-attr">top</span>: miniMapBoxTop + <span class="hljs-string">'px'</span>,
|
||||
}
|
||||
</code></pre>
|
||||
<p>5.将viewBoxStyle对象设置为viewBoxContainer元素的样式</p>
|
||||
<p>到这一步,当画布上的思维导图变化了,小地图也会实时更新,并且视口框元素会实时反映视口在思维导图图形上的位置</p>
|
||||
<p>6.监听container元素的mousedown、mousemove、mouseup事件,分别调用小地图插件实例的三个方法即可实现鼠标拖动时画布上的思维导图也随之拖动的效果</p>
|
||||
<p>插件的完整信息可以参考<a href="https://wanglin2.github.io/mind-map/#/doc/zh/miniMap">miniMap</a>。</p>
|
||||
<h2>完整示例</h2>
|
||||
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFVt2O20QUfpWRASULWSfbUgQhW5UCEkgNQqUSF0yVeu1J4sqesTzjJFWUm4oLhEBcIH6LBFyBhBStEBdsJXgaki1vwTn2jD12stpy1ZU28sz3nZ85c35m6byRJO4sY07fGUg/DRNFJFNZcp3yME5EqsiSpGzcIYIPRcYVCzpETr0oEvPbbExWZJyKmLRAQ6uUGIY8GHpJAVFHwnbEDmPYPYy9hDqUE0J5xBTBPWQeE55FEeWUd7tkc/rF5ofTzaO/N+uzzXe/nn//8Wb917+/fUu5L7hUBH6VF3KWfhgGagqyR73eDvYOCydTBeA1wHK120d/bL8+LZUbCXAhBBfeNIIgAudtoz8HhjML2fymWHygHkRM48tViWoNSPC9knC0B7/FxugSwr098B2BkaijWRJ4ikFEwyGC7QNyfJ0sMYJwpCfrn8/X35RH2n51uv18jRjGFuQ8kNAhdrUZF1z0s8hTIVxosdWuB7TTDCL4klvb/vn79scvN5/YMSQ78XNnXpQxN+Qof2d4C1xAT1w5m+ASRexwFnRDshFLuQltndwA63wM9UV0xOpsiPxFZIAoX2EOFReS14FkgZhzvA9WXUgz0hYTaPuUxGKGFp9CCTIvUJLleXG5iiypFJTF3LYSyipGNjdF3M4hQljUJ4Hws5hx5U6Yejti+HnzwbtBu6UlyxxoHXQKKYxlv9COf9TBDepYW8W2YguF29TZ/nT25NOH5w/PijaBfyutDIn+NIyClHEkf1TpaKjba6Vp6Z/Hn50//qVprG5wj9G7FWbznpEH5lPvGTm4eXVbCPWeCNj7QoZY7SDZiiDzWx3S8uHq4Jru5vTVwetFR65SR/B2C48w8qcenzAQsftQ3hBsKpbt6H/wObg1UiljIzhTwNIR/O7KYIcddIuhBOMIForBJAEKrAgZBOGM+JEn5TF1tPa3oFCok8OaEAYVWuYnUAZdQG2iiaTRWDbB6mZuxKaeAbeq2zCoKhhYrBUDV01GllR4piciIdodqmoOWT41ey11bBYMjr0ci9KX2FmB1chLlXpcjkUa98k9ic20/fyy0V5XB/dqSYlDZqz6zen2Emkli1aDqURiE3HOFbyysBVVq8pTHYgc0V/2fTXuXg8NOyrlOWsDpXHr5bf5GnStBINlriRn3NAvG+q43eI5o9ujy2Ts+lJSp6wh7LsmF039z3Gw9vGh8kLOIyQpqzJlOI1nLAfygsb/55o5a1RVgt6JFFGmCkFzHT29ykNuFrvmp/lk75OXe71kYSzvt/uisRx76SQEu0Zr4gVByCdmo3TdLUvnKX0+Mj5ot6115Xm5ZVy/ZnleRb7+lrvMfpn3hyIN88OhR+hFQ3Mzxy7VfCJSaGx9ciVZEADCgKSTk/aVq692yCuv4X/RdLULRkcUkZ57VRrj0P3yFHQ6TpGA+H5270vB4bmee0A1AAlYDhvI+gySHSeM24VPN4VRH8YMc/XwJBVzCa+0+yChq3TPC72Q3c10lNK+rZzVf9RSHZw=" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
124
web/src/pages/Doc/zh/course15/index.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# 如何渲染一个右键菜单
|
||||
|
||||
右键菜单可以方便的完成一些功能,大体上分两种,一是在画布上点击右键,二是在节点上点击右键,两者的功能肯定是不一样的,甚至根节点和其他节点功能上也有些不同,比如根节点不能添加同级节点,也不能被删除等等。
|
||||
|
||||
右键菜单的UI界面需要你自行开发,可以设置成绝对定位或固定定位,然后让它显示在鼠标右键点击的位置即可。
|
||||
|
||||
## 右键菜单的显示和隐藏
|
||||
|
||||
首先监听`node_contextmenu`事件在右键点击节点时显示菜单:
|
||||
|
||||
```js
|
||||
// 当前右键点击的类型
|
||||
const type = ref('')
|
||||
// 如果点击的节点,那么代表被点击的节点
|
||||
const currentNode = shallowRef(null)
|
||||
// 菜单显示的位置
|
||||
const left = ref(0)
|
||||
const top = ref(0)
|
||||
// 是否显示菜单
|
||||
const show = ref(false)
|
||||
|
||||
mindMap.on('node_contextmenu', (e, node) => {
|
||||
type.value = 'node'
|
||||
left.value = e.clientX + 10
|
||||
top.value = e.clientY + 10
|
||||
show.value = true
|
||||
currentNode.value = node
|
||||
})
|
||||
```
|
||||
|
||||
你可以根据当前点击的节点来判断一些操作是否可用。比如根节点不能删除,不能插入同级节点,又比如同级第一个节点不能再被往上移,同级最后一个节点不能被往下移。
|
||||
|
||||
对于画布的处理会比较麻烦,不能直接监听`contextmenu`事件,因为会和右键多选节点冲突,所以需要结合`mousedown`事件和`mouseup`事件来处理。
|
||||
|
||||
```js
|
||||
// 记录鼠标右键按下的位置
|
||||
const mousedownX = ref(0)
|
||||
const mousedownY = ref(0)
|
||||
const isMousedown = ref(false)
|
||||
|
||||
mindMap.on('svg_mousedown', (e) => {
|
||||
// 如果不是右键点击直接返回
|
||||
if (e.which !== 3) {
|
||||
return
|
||||
}
|
||||
mousedownX.value = e.clientX
|
||||
mousedownY.value = e.clientY
|
||||
isMousedown.value = true
|
||||
})
|
||||
|
||||
mindMap.on('mouseup', (e) => {
|
||||
if (!isMousedown.value) {
|
||||
return
|
||||
}
|
||||
isMousedown.value = false
|
||||
// 如果鼠标松开和按下的距离大于3,则不认为是点击事件
|
||||
if (
|
||||
Math.abs(mousedownX.value - e.clientX) > 3 ||
|
||||
Math.abs(mousedownX.value - e.clientY) > 3
|
||||
) {
|
||||
hide()
|
||||
return
|
||||
}
|
||||
type.value = 'svg'
|
||||
left.value = e.clientX + 10
|
||||
top.value = e.clientY + 10
|
||||
show.value = true
|
||||
})
|
||||
```
|
||||
|
||||
很简单,其实就是判断鼠标按下和松开的距离是否很小,是的话就认为是点击事件,否则应该是鼠标拖动。
|
||||
|
||||
右键菜单显示了,肯定就需要隐藏,当左键点击了画布、左键点击了节点、左键点击了展开收起按钮时都需要隐藏右键菜单。
|
||||
|
||||
```js
|
||||
const hide = () => {
|
||||
show.value = false
|
||||
left.value = 0
|
||||
top.value = 0
|
||||
type.value = ''
|
||||
}
|
||||
mindMap.on('node_click', hide)
|
||||
mindMap.on('draw_click', hide)
|
||||
mindMap.on('expand_btn_click', hide)
|
||||
```
|
||||
|
||||
## 复制、剪切、粘贴的实现
|
||||
|
||||
接下来介绍一下复制、剪切、粘贴的实现。
|
||||
|
||||
一般来说你的右键菜单中肯定也会添加这三个按钮,另外快捷键操作也是必不可少的,但是这三个快捷键是没有内置的,所以你需要自己注册一下:
|
||||
|
||||
```js
|
||||
mindMap.keyCommand.addShortcut('Control+c', copy)
|
||||
mindMap.keyCommand.addShortcut('Control+v', paste)
|
||||
mindMap.keyCommand.addShortcut('Control+x', cut)
|
||||
```
|
||||
|
||||
如需删除调用`removeShortcut`方法即可。
|
||||
|
||||
接下来实现一下这三个方法:
|
||||
|
||||
```js
|
||||
// 保存复制/剪切的节点的数据,后续可以原来粘贴
|
||||
let copyData = null
|
||||
|
||||
const copy = () => {
|
||||
copyData = mindMap.renderer.copyNode()
|
||||
}
|
||||
|
||||
const cut = () => {
|
||||
mindMap.execCommand('CUT_NODE', _copyData => {
|
||||
copyData = _copyData
|
||||
})
|
||||
}
|
||||
|
||||
const paste = () => {
|
||||
mindMap.execCommand('PASTE_NODE', copyData)
|
||||
}
|
||||
```
|
||||
|
||||
## 完整示例
|
||||
|
||||
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFV1tv1EYU/itTo2o3JfEG0Re2SQQFHngIRUAlohpFjj2bNXg9lj3ObhoioagSSUkCakFKC6G0BKhaEWhLuWQJ/Jm1d3nqX+iZi2/rkNBKVR9WWp/bd+Zc5pyZU464rjoTYKWqjPiGZ7kU+ZgG7pjmWA2XeBTNIQ/XBhFxxkngUGwOIr+u2zZpnsY1NI9qHmmgElgoJRrjlmOO665gaYoPZBsPNYA61NBdTdEchDTHxhQxGpMcRU5g25qjOZUKCre/DZdWwmt/vL2x2V14GV5pd7//qvtbO7xzVXMM4vgU0VkXgxI4Vi6VBoTWg4Xozu1Evvf1Avz/69Xy24V7nZeLnfa93o8/9376tU8gNmgEnocdepKYzG56wDLzSwD0rt0OV25Ga6+7G1ug39le6W5vxvo2rlHp0DCISy8JO1lMAxPR2uPw+gNhQpiLRf06aUrZmm77WEJuPgm3b759dTe6e0XEI1pe6ry4WoBvkMDHJmk65wpOJKyJAsvyIaWCWcTuvFkPH62FGyvh4rNKuPRLuHgliRr8iW4+iVY2Ib7h9dVu+1F47XGnfT9c/SFav9/9fa339KlIsEHc2WM61TMZlgEHBhDLA2h0DM2xikBZYVkYKiTFxB72VMZj2SmDd/MZMwELe85KrIpb2DhKGg3dMculo5+fnTz52bHjpUE0mcIkOn3oiYjgzucxXd2nrEr2Rj115MzZ4zFubDNvrG7xisvZYsWgzuh2wDg8JYLOiiyhDwsa1FiBBM2R0ErQlhwu6d9yBivTf7gZ921ZuoHtKjKJETSgMdRpTI/bmP39dPYEHE1qHiUO1S0He6WBQaFlwhGraVw1hRE0JUMSZIpblJE1Jbr7UjYjvxl4xKUxJmjULduEOmDCX6Q2+sztiNKP1Nla7m497AfLA+4Aej7lZeX+Jw/iv5IW61mORU8Twq+wU8S3qEUc0CyxooHqKxmQOkjTeS4+P/CJuITTwr2IZ2XdqrppnqnDRQ7NBZ0DGfaIvd+QJQzlq9H3VZoBJd4u/0irxaACynQyDhKnXHLgcNCccJIWhVoMQLCMBxEjZxso3wGMC11Q6CCsGrYFUTmH9qMDO7RTzJ/I8HOtSb1AdmZmfCRcBstOzW6PfKjZSfyZ6cnkaubHyB4gmWedFytsaGRn4a2n0er93psb4a07MvM10Fabdcuoow9GR9HBgWwNejDNPR7IpHbSaVGMRZ/ERDEaEjSdHX3xECfO5pudl1sM3MJJmfMfFIztdYKd0DMXZRI+MTqj9dfhq8vhN8vJ9Ow9X+8+aIcbDztbqwfZEFv8DiLd29zovNiCeItId7audtrPUjdTj8Z1Wlf1Kb9ciORQGskBNIYOokuXdlOLw5uqTXA1oZOLAhsUbPjtEpV82UOF/YdV/666Fh1qW8ZFSDXzOdf5TML09ObuErjlwt0wOUWdfjmGOVIReypsqPBBMSyXOsXwhdCIac0gw9Z9f1RTpMVjuEE0hbOlgGWm3GR+gchIBbhZwdiSvHDG4cLRFDQzZNWAyGICX1WfztoYvud4mKtiE9yPSi67xSCwVb4GCgIkKvYEIGLXY4tiqYUGljdWKqrRd/lzAoyAF4d5nDjLnQVFsbZlD7TboQpGAphWY2Ln+7c2+K0PVsQymLcyUsllbY9g8Dp+/1iMRTee9f58Hi1djm4v7YGbsuN/ORH45NnlEofl40ZT1Ip40ch1ScV+QzV8X1OSmapmai/u4aZl0noVWmr4Qy6HkJtMaQ8DojWDOYM3M/vt66/R2FSqCJcJsQMqFEWjV9Gw/OLFF38U4evYmq6D+MfDw24rRt4Z96Nky9W9aQtwY6suzG/LmY4Jieuwridp2dPrL4cAD7eq6NChQ5I0pRsXpz3YV80q2ler1frhDmRcLiKyQkjeFIHnE68K6Bbbf2IluER4ZpVBReSVvUzVCz5x4CHMVTXJgLwmO52mwDtXLHJqBf6qHmzUVgOzEhia8kjTh5fKBdCQO9kOb1+hWywgpiV9m1fm/wbZvoo9" />
|
||||
113
web/src/pages/Doc/zh/course15/index.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>如何渲染一个右键菜单</h1>
|
||||
<p>右键菜单可以方便的完成一些功能,大体上分两种,一是在画布上点击右键,二是在节点上点击右键,两者的功能肯定是不一样的,甚至根节点和其他节点功能上也有些不同,比如根节点不能添加同级节点,也不能被删除等等。</p>
|
||||
<p>右键菜单的UI界面需要你自行开发,可以设置成绝对定位或固定定位,然后让它显示在鼠标右键点击的位置即可。</p>
|
||||
<h2>右键菜单的显示和隐藏</h2>
|
||||
<p>首先监听<code>node_contextmenu</code>事件在右键点击节点时显示菜单:</p>
|
||||
<pre class="hljs"><code><span class="hljs-comment">// 当前右键点击的类型</span>
|
||||
<span class="hljs-keyword">const</span> type = ref(<span class="hljs-string">''</span>)
|
||||
<span class="hljs-comment">// 如果点击的节点,那么代表被点击的节点</span>
|
||||
<span class="hljs-keyword">const</span> currentNode = shallowRef(<span class="hljs-literal">null</span>)
|
||||
<span class="hljs-comment">// 菜单显示的位置</span>
|
||||
<span class="hljs-keyword">const</span> left = ref(<span class="hljs-number">0</span>)
|
||||
<span class="hljs-keyword">const</span> top = ref(<span class="hljs-number">0</span>)
|
||||
<span class="hljs-comment">// 是否显示菜单</span>
|
||||
<span class="hljs-keyword">const</span> show = ref(<span class="hljs-literal">false</span>)
|
||||
|
||||
mindMap.on(<span class="hljs-string">'node_contextmenu'</span>, <span class="hljs-function">(<span class="hljs-params">e, node</span>) =></span> {
|
||||
type.value = <span class="hljs-string">'node'</span>
|
||||
left.value = e.clientX + <span class="hljs-number">10</span>
|
||||
top.value = e.clientY + <span class="hljs-number">10</span>
|
||||
show.value = <span class="hljs-literal">true</span>
|
||||
currentNode.value = node
|
||||
})
|
||||
</code></pre>
|
||||
<p>你可以根据当前点击的节点来判断一些操作是否可用。比如根节点不能删除,不能插入同级节点,又比如同级第一个节点不能再被往上移,同级最后一个节点不能被往下移。</p>
|
||||
<p>对于画布的处理会比较麻烦,不能直接监听<code>contextmenu</code>事件,因为会和右键多选节点冲突,所以需要结合<code>mousedown</code>事件和<code>mouseup</code>事件来处理。</p>
|
||||
<pre class="hljs"><code><span class="hljs-comment">// 记录鼠标右键按下的位置</span>
|
||||
<span class="hljs-keyword">const</span> mousedownX = ref(<span class="hljs-number">0</span>)
|
||||
<span class="hljs-keyword">const</span> mousedownY = ref(<span class="hljs-number">0</span>)
|
||||
<span class="hljs-keyword">const</span> isMousedown = ref(<span class="hljs-literal">false</span>)
|
||||
|
||||
mindMap.on(<span class="hljs-string">'svg_mousedown'</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =></span> {
|
||||
<span class="hljs-comment">// 如果不是右键点击直接返回</span>
|
||||
<span class="hljs-keyword">if</span> (e.which !== <span class="hljs-number">3</span>) {
|
||||
<span class="hljs-keyword">return</span>
|
||||
}
|
||||
mousedownX.value = e.clientX
|
||||
mousedownY.value = e.clientY
|
||||
isMousedown.value = <span class="hljs-literal">true</span>
|
||||
})
|
||||
|
||||
mindMap.on(<span class="hljs-string">'mouseup'</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =></span> {
|
||||
<span class="hljs-keyword">if</span> (!isMousedown.value) {
|
||||
<span class="hljs-keyword">return</span>
|
||||
}
|
||||
isMousedown.value = <span class="hljs-literal">false</span>
|
||||
<span class="hljs-comment">// 如果鼠标松开和按下的距离大于3,则不认为是点击事件</span>
|
||||
<span class="hljs-keyword">if</span> (
|
||||
<span class="hljs-built_in">Math</span>.abs(mousedownX.value - e.clientX) > <span class="hljs-number">3</span> ||
|
||||
<span class="hljs-built_in">Math</span>.abs(mousedownX.value - e.clientY) > <span class="hljs-number">3</span>
|
||||
) {
|
||||
hide()
|
||||
<span class="hljs-keyword">return</span>
|
||||
}
|
||||
type.value = <span class="hljs-string">'svg'</span>
|
||||
left.value = e.clientX + <span class="hljs-number">10</span>
|
||||
top.value = e.clientY + <span class="hljs-number">10</span>
|
||||
show.value = <span class="hljs-literal">true</span>
|
||||
})
|
||||
</code></pre>
|
||||
<p>很简单,其实就是判断鼠标按下和松开的距离是否很小,是的话就认为是点击事件,否则应该是鼠标拖动。</p>
|
||||
<p>右键菜单显示了,肯定就需要隐藏,当左键点击了画布、左键点击了节点、左键点击了展开收起按钮时都需要隐藏右键菜单。</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">const</span> hide = <span class="hljs-function">() =></span> {
|
||||
show.value = <span class="hljs-literal">false</span>
|
||||
left.value = <span class="hljs-number">0</span>
|
||||
top.value = <span class="hljs-number">0</span>
|
||||
type.value = <span class="hljs-string">''</span>
|
||||
}
|
||||
mindMap.on(<span class="hljs-string">'node_click'</span>, hide)
|
||||
mindMap.on(<span class="hljs-string">'draw_click'</span>, hide)
|
||||
mindMap.on(<span class="hljs-string">'expand_btn_click'</span>, hide)
|
||||
</code></pre>
|
||||
<h2>复制、剪切、粘贴的实现</h2>
|
||||
<p>接下来介绍一下复制、剪切、粘贴的实现。</p>
|
||||
<p>一般来说你的右键菜单中肯定也会添加这三个按钮,另外快捷键操作也是必不可少的,但是这三个快捷键是没有内置的,所以你需要自己注册一下:</p>
|
||||
<pre class="hljs"><code>mindMap.keyCommand.addShortcut(<span class="hljs-string">'Control+c'</span>, copy)
|
||||
mindMap.keyCommand.addShortcut(<span class="hljs-string">'Control+v'</span>, paste)
|
||||
mindMap.keyCommand.addShortcut(<span class="hljs-string">'Control+x'</span>, cut)
|
||||
</code></pre>
|
||||
<p>如需删除调用<code>removeShortcut</code>方法即可。</p>
|
||||
<p>接下来实现一下这三个方法:</p>
|
||||
<pre class="hljs"><code><span class="hljs-comment">// 保存复制/剪切的节点的数据,后续可以原来粘贴</span>
|
||||
<span class="hljs-keyword">let</span> copyData = <span class="hljs-literal">null</span>
|
||||
|
||||
<span class="hljs-keyword">const</span> copy = <span class="hljs-function">() =></span> {
|
||||
copyData = mindMap.renderer.copyNode()
|
||||
}
|
||||
|
||||
<span class="hljs-keyword">const</span> cut = <span class="hljs-function">() =></span> {
|
||||
mindMap.execCommand(<span class="hljs-string">'CUT_NODE'</span>, <span class="hljs-function"><span class="hljs-params">_copyData</span> =></span> {
|
||||
copyData = _copyData
|
||||
})
|
||||
}
|
||||
|
||||
<span class="hljs-keyword">const</span> paste = <span class="hljs-function">() =></span> {
|
||||
mindMap.execCommand(<span class="hljs-string">'PASTE_NODE'</span>, copyData)
|
||||
}
|
||||
</code></pre>
|
||||
<h2>完整示例</h2>
|
||||
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFV1tv1EYU/itTo2o3JfEG0Re2SQQFHngIRUAlohpFjj2bNXg9lj3ObhoioagSSUkCakFKC6G0BKhaEWhLuWQJ/Jm1d3nqX+iZi2/rkNBKVR9WWp/bd+Zc5pyZU464rjoTYKWqjPiGZ7kU+ZgG7pjmWA2XeBTNIQ/XBhFxxkngUGwOIr+u2zZpnsY1NI9qHmmgElgoJRrjlmOO665gaYoPZBsPNYA61NBdTdEchDTHxhQxGpMcRU5g25qjOZUKCre/DZdWwmt/vL2x2V14GV5pd7//qvtbO7xzVXMM4vgU0VkXgxI4Vi6VBoTWg4Xozu1Evvf1Avz/69Xy24V7nZeLnfa93o8/9376tU8gNmgEnocdepKYzG56wDLzSwD0rt0OV25Ga6+7G1ug39le6W5vxvo2rlHp0DCISy8JO1lMAxPR2uPw+gNhQpiLRf06aUrZmm77WEJuPgm3b759dTe6e0XEI1pe6ry4WoBvkMDHJmk65wpOJKyJAsvyIaWCWcTuvFkPH62FGyvh4rNKuPRLuHgliRr8iW4+iVY2Ib7h9dVu+1F47XGnfT9c/SFav9/9fa339KlIsEHc2WM61TMZlgEHBhDLA2h0DM2xikBZYVkYKiTFxB72VMZj2SmDd/MZMwELe85KrIpb2DhKGg3dMculo5+fnTz52bHjpUE0mcIkOn3oiYjgzucxXd2nrEr2Rj115MzZ4zFubDNvrG7xisvZYsWgzuh2wDg8JYLOiiyhDwsa1FiBBM2R0ErQlhwu6d9yBivTf7gZ921ZuoHtKjKJETSgMdRpTI/bmP39dPYEHE1qHiUO1S0He6WBQaFlwhGraVw1hRE0JUMSZIpblJE1Jbr7UjYjvxl4xKUxJmjULduEOmDCX6Q2+sztiNKP1Nla7m497AfLA+4Aej7lZeX+Jw/iv5IW61mORU8Twq+wU8S3qEUc0CyxooHqKxmQOkjTeS4+P/CJuITTwr2IZ2XdqrppnqnDRQ7NBZ0DGfaIvd+QJQzlq9H3VZoBJd4u/0irxaACynQyDhKnXHLgcNCccJIWhVoMQLCMBxEjZxso3wGMC11Q6CCsGrYFUTmH9qMDO7RTzJ/I8HOtSb1AdmZmfCRcBstOzW6PfKjZSfyZ6cnkaubHyB4gmWedFytsaGRn4a2n0er93psb4a07MvM10Fabdcuoow9GR9HBgWwNejDNPR7IpHbSaVGMRZ/ERDEaEjSdHX3xECfO5pudl1sM3MJJmfMfFIztdYKd0DMXZRI+MTqj9dfhq8vhN8vJ9Ow9X+8+aIcbDztbqwfZEFv8DiLd29zovNiCeItId7audtrPUjdTj8Z1Wlf1Kb9ciORQGskBNIYOokuXdlOLw5uqTXA1oZOLAhsUbPjtEpV82UOF/YdV/666Fh1qW8ZFSDXzOdf5TML09ObuErjlwt0wOUWdfjmGOVIReypsqPBBMSyXOsXwhdCIac0gw9Z9f1RTpMVjuEE0hbOlgGWm3GR+gchIBbhZwdiSvHDG4cLRFDQzZNWAyGICX1WfztoYvud4mKtiE9yPSi67xSCwVb4GCgIkKvYEIGLXY4tiqYUGljdWKqrRd/lzAoyAF4d5nDjLnQVFsbZlD7TboQpGAphWY2Ln+7c2+K0PVsQymLcyUsllbY9g8Dp+/1iMRTee9f58Hi1djm4v7YGbsuN/ORH45NnlEofl40ZT1Ip40ch1ScV+QzV8X1OSmapmai/u4aZl0noVWmr4Qy6HkJtMaQ8DojWDOYM3M/vt66/R2FSqCJcJsQMqFEWjV9Gw/OLFF38U4evYmq6D+MfDw24rRt4Z96Nky9W9aQtwY6suzG/LmY4Jieuwridp2dPrL4cAD7eq6NChQ5I0pRsXpz3YV80q2ler1frhDmRcLiKyQkjeFIHnE68K6Bbbf2IluER4ZpVBReSVvUzVCz5x4CHMVTXJgLwmO52mwDtXLHJqBf6qHmzUVgOzEhia8kjTh5fKBdCQO9kOb1+hWywgpiV9m1fm/wbZvoo9" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
3
web/src/pages/Doc/zh/course16/index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# 如何渲染富文本的悬浮工具栏
|
||||
|
||||
> 要支持节点富文本编辑需要使用富文本插件
|
||||
19
web/src/pages/Doc/zh/course16/index.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>如何渲染富文本的悬浮工具栏</h1>
|
||||
<blockquote>
|
||||
<p>要支持节点富文本编辑需要使用富文本插件</p>
|
||||
</blockquote>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
3
web/src/pages/Doc/zh/course17/index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# 导入和导出
|
||||
|
||||
编写中。。。
|
||||
17
web/src/pages/Doc/zh/course17/index.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>导入和导出</h1>
|
||||
<p>编写中。。。</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
3
web/src/pages/Doc/zh/course18/index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# 如何持久化数据
|
||||
|
||||
编写中。。。
|
||||
17
web/src/pages/Doc/zh/course18/index.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>如何持久化数据</h1>
|
||||
<p>编写中。。。</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
183
web/src/pages/Doc/zh/course2/index.md
Normal file
@@ -0,0 +1,183 @@
|
||||
# 操作节点内容
|
||||
|
||||
目前支持在节点中插入`图片`、`图标`、`超链接`、`备注`、`标签`、`概要`、`关联线`,本节教程会介绍如何通过UI界面来完成这些内容的插入。
|
||||
|
||||
## 监听节点激活事件
|
||||
|
||||
首先我们要监听节点的激活事件,如果当前没有激活节点,那么对应的UI界面肯定是禁用状态。可以通过`node_active`事件来监听节点激活事件:
|
||||
|
||||
```js
|
||||
const activeNodes = shallowRef([])
|
||||
mindMap.on('node_active', (node, activeNodeList) => {
|
||||
activeNodes.value = activeNodeList
|
||||
})
|
||||
```
|
||||
|
||||
你还可以进一步判断当前激活的节点中是否操作根节点,是否存在概要节点,因为根节点肯定不能添加兄弟节点,概要节点则子节点、兄弟节点、概要节点、关联线都不能添加。
|
||||
|
||||
```js
|
||||
const hasRoot = computed(() => {
|
||||
return activeNodes.value.findIndex(node => {
|
||||
return node.isRoot
|
||||
}) !== -1
|
||||
})
|
||||
|
||||
|
||||
const hasGeneralization = computed(() => {
|
||||
return activeNodes.value.findIndex(node => {
|
||||
return node.isGeneralization
|
||||
}) !== -1
|
||||
})
|
||||
```
|
||||
|
||||
有了这些判断以后,我们就可以对工具按钮进行控制了,接下来就是实现按钮的相关功能。
|
||||
|
||||
## 插入图片
|
||||
|
||||
选择图片和上传图片的功能需要你自行开发,假设我们已经上传了一张图片,我们就可以遍历当前激活的节点,挨个调用节点的`setImage`方法设置图片:
|
||||
|
||||
```js
|
||||
activeNodes.value.forEach((node) => {
|
||||
node.setImage({
|
||||
url: '图片url',
|
||||
title: '图片的标题或描述',
|
||||
width: 100,// 图片的宽高也不能少
|
||||
height: 100
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
图片回显也很简单,可以使用节点的`getData`方法获取节点的图片:
|
||||
|
||||
```js
|
||||
const img = activeNode.getData('image')
|
||||
const imgTitle = activeNode.getData('imageTitle')
|
||||
```
|
||||
|
||||
## 插入图标
|
||||
|
||||
整体逻辑和插入图片是一样的,不过你需要显示所有可插入的图标,目前只能使用库里自带的一些图标,然后将选择的图标插入到节点中:
|
||||
|
||||
```js
|
||||
const iconList = ['priority_1', '...']// 选择的图标
|
||||
activeNodes.value.forEach(node => {
|
||||
node.setIcon(iconList)
|
||||
})
|
||||
```
|
||||
|
||||
获取节点的图标数据:
|
||||
|
||||
```js
|
||||
const iconList = activeNode.getData('icon') || []
|
||||
```
|
||||
|
||||
## 插入超链接
|
||||
|
||||
对于超链接,你需要实现两个输入框,分别输入`url`和名称,然后插入到节点:
|
||||
|
||||
```js
|
||||
activeNodes.value.forEach(node => {
|
||||
node.setHyperlink('url', '名称')
|
||||
})
|
||||
```
|
||||
|
||||
获取节点超链接数据:
|
||||
|
||||
```js
|
||||
const url = activeNode.getData('hyperlink')
|
||||
const name = activeNode.getData('hyperlinkTitle')
|
||||
```
|
||||
|
||||
## 插入备注
|
||||
|
||||
备注会稍微复杂一点,因为支持两种方式。
|
||||
|
||||
### 使用内置逻辑
|
||||
|
||||
使用内置的逻辑,只能插入纯文本备注,所以你只需要显示一个输入框用来输入文本,然后插入到节点:
|
||||
|
||||
```js
|
||||
activeNodes.value.forEach(node => {
|
||||
node.setNote('备注内容')
|
||||
})
|
||||
```
|
||||
|
||||
这样在鼠标移入节点内的备注图标时会显示你插入的备注内容。
|
||||
|
||||
### 自定义显示备注
|
||||
|
||||
如果你想显示更丰富的备注内容,比如支持`markdown`,那么你可以在实例化`simple-mind-map`时插入`customNoteContentShow`选项。
|
||||
|
||||
设置备注内容还是使用`setNote`方法,只不过现在插入的是`markdown`字符串,因为内置的备注弹窗只能处理纯文本内容,所以你需要自己来渲染`markdown`:
|
||||
|
||||
```js
|
||||
const mindMap = new MindMap({
|
||||
// ...
|
||||
customNoteContentShow: {
|
||||
show: (content, left, top) => {
|
||||
// 在这里显示你的自定义弹窗
|
||||
// content表示你插入的备注的内容,left和top时弹窗应该显示的位置,你需要将你的弹窗元素设置成fixed定位
|
||||
},
|
||||
hide: () => {
|
||||
// 在这里隐藏你的自定义弹窗
|
||||
// 你也可以选择不在鼠标移出备注图标时隐藏弹窗,比如可以在画布被点击时隐藏
|
||||
}
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
获取节点的备注内容可以通过`getData`方法:
|
||||
|
||||
```js
|
||||
const note = activeNode.getData('note')
|
||||
```
|
||||
|
||||
## 插入标签
|
||||
|
||||
标签其实就是一个个带背景颜色的文本块,你需要实现的就是一个可以输入多个文本的输入框,然后插入到节点:
|
||||
|
||||
```js
|
||||
activeNodes.value.forEach(node => {
|
||||
node.setTag(['标签1', '...'])
|
||||
})
|
||||
```
|
||||
|
||||
获取节点标签数据:
|
||||
|
||||
```js
|
||||
const tagArr = activeNode.getData('tag') || []
|
||||
```
|
||||
|
||||
## 插入概要
|
||||
|
||||
插入概要可以不需要实现UI,直接调用插入概要的命令即可:
|
||||
|
||||
```js
|
||||
mindMap.execCommand('ADD_GENERALIZATION', data)
|
||||
```
|
||||
|
||||
会给当前激活的节点插入一个概要节点,第二个参数`data`可以不传,默认为:
|
||||
|
||||
```js
|
||||
{
|
||||
text: '概要'
|
||||
}
|
||||
```
|
||||
|
||||
概要节点本质上也是一个节点,所以普通节点支持的内容它也支持。
|
||||
|
||||
## 插入关联线
|
||||
|
||||
> 要支持关联线需要使用关联线插件
|
||||
|
||||
关联线也不需要UI,调用一个方法即可:
|
||||
|
||||
```js
|
||||
mindMap.associativeLine.createLineFromActiveNode()
|
||||
```
|
||||
|
||||
然后会从激活的节点(如果有多个激活节点,默认是第一个)到鼠标的实时位置生成一条曲线,点击某个目标节点后就会创建一条从激活节点到目标节点的关联线。
|
||||
|
||||
## 完整示例
|
||||
|
||||
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFV/9PHEUU/1cma8we5tiDRmKCBykFRBJAQ5poZRuy7M7dDezObHfn4AghsVRK0Vb6xWjTRq2mraYqaULTCA3wz7B3x3/hm539dsdhzvhDSQ5m3rzPe+/el88Ma8qI62rLVawMKkXf9IjLkY951R3WKXFc5nG0hjxcyiNGp1mVcmzlkV8xbJutzOISWkcljzlIBQtqgpgm1Jo2XHmkKz6IbdzrgLTXMVxd0SlCOrUxR0ImNIcQrdq2lBcKKDh8EGzfqR9/WX/1pvHoq+bXG42N/eDWD81fftepyajPkWFysoxnmIV9QKcR5eau9ug0tFLfuR9sPgseHzW2t2IYoT72Jh2jjAGV60FDw2hNuM2Y05YNu4q1EvPGDbOSy1EQpooIib0GOQqt5CKh+Kl69iBSK5y7/mChYNeuUX/V15jv95q0dwGTRULLmmGT1So1fc1kTuGaXSvUKhdGPl+6VLviDCxO4IHS2Adjn13xRgcWNJeW1XxqnhNuY3AgvxCkpf5k6/TXh/Vb39d3dprHL7O6K8TilUHU39eXF+mMEcHu4ekfD0/2fz75+07zxmHw8l4KqWBSrvAQI4XrIo9c/F5vTyh4bkkon4RNa0KjU/gzRWAxhOZU1yPMI3x1vl/No3R3Qb367xUQCe+Uf7Cdix2cE2vz9ebpg6P6t89aw50idKnb+p/j/eNVF3s22MmFFc8UXBRWfMHG3Zv1G3unP94P9l8Fuz+dvrgd7D5Vz0vq0636XtLcMsoZxrvu0nOiFCZyqjQe3NwMdvfPCwBK2vjrqDWAy0b5f/oHC7k5VdoOqy6XUPLzwni+0Xx+vTWMCaM1iog0NFzD5ihzHINaOXVkbGx+YnxmfHZkavKLkcuTn8yAu1Bd5xzXoLHV5taLYPfRyf62dBLlQ6h0qMfmXvP6d42D4zON01aSOBjD95lJDJEfoaSZHjZ4uPwIWHAkSVwudpXwae6sNcGHeCXm0ZhjMNCLxcyqgynXypiP21gsL61OwvePkKOMcgOcempPRAeWwY3BuC6QDUUIdCUjkmKRJCHWlfqTfcm4kqlDLki4RVfMCrEtD1OhPJfaaDPX0Uu7p5OD242D39qdtTrs4DTki7N6bymCeBnJYhyhhM8yBiNo4U+ZTzhhFJCqjUtcTIIJpYMyhaj1ng/l3YcQdGDj8b3g7p8yJnkHnhx8c/LmdbbdgPpUMWTzciTBYDiC+XDyQkLMTOOZsRX9Fekl7Q+fYkG+AODuhw3HcG1DD8MOoaJFlpFpQ5cP6UoUxBh2mK6Ex5ECsdLTpBNBpViA06xibIkzZi8YoIKWe0kJBNlQbUzLvIKGUV/iRefFhSrncNlcNG1iLgEivdFBK3vlFwtSNYKC547Q8B7JQoGiuoWKeySBJndNt2hBzqnjkKO7hQKvJkhJqd0iJ2AmYmBIg//hu2aijemxDZ0WOl4VC5k+gq3PV23ZUhej16KuaAX5RIz4TsO+o5m+ryvJVGiZlou7On3jvBvqIeQmY+Zh8AiNFB6EEyo+77S3ZmwqBRoLPrOrXAIREsM6iPqiHWduujnrPn5Bvd/X59Ziz539vhd7dgyvTMBvbNU1LAueibEgCV2LJqXLiPvjCKKgkz0YhCkPa6DkFVkB8SjXFn1G4X8AeWNGB1CBhD51BZ74kjO1Aiw1Dy4v4mBRrN4Fj61Aj4ARXYnor8OzX2LPllqgotjWlfV/AEgelAo=" />
|
||||