Compare commits
88 Commits
0.9.12
...
0.10.2-fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2dbfb41d5 | ||
|
|
5867649429 | ||
|
|
de29ec59c5 | ||
|
|
bc1cf71eaa | ||
|
|
d929792157 | ||
|
|
658b47b72e | ||
|
|
591e6a5b2a | ||
|
|
58baf4c0aa | ||
|
|
00f86fe167 | ||
|
|
5c1e5f072c | ||
|
|
b45674cf8f | ||
|
|
c23a16e65a | ||
|
|
b7722987b1 | ||
|
|
1fc5b951a0 | ||
|
|
53eb608007 | ||
|
|
876afb2504 | ||
|
|
4d1608e8c4 | ||
|
|
80f45e5e7d | ||
|
|
f80317a449 | ||
|
|
9bce6d3ded | ||
|
|
16e40b4342 | ||
|
|
624203ea84 | ||
|
|
6ffa4570d4 | ||
|
|
d99a4dcc33 | ||
|
|
e8c4aad690 | ||
|
|
35c8e129f0 | ||
|
|
dc096fd535 | ||
|
|
a047dabbd0 | ||
|
|
1ec723db0e | ||
|
|
d9fc209dac | ||
|
|
8647cb5893 | ||
|
|
264875e14e | ||
|
|
de423f3bd3 | ||
|
|
fcdcda929c | ||
|
|
1cfd50a42b | ||
|
|
f794df4e6f | ||
|
|
eea1109e43 | ||
|
|
65004d08cd | ||
|
|
44d75ef7f4 | ||
|
|
1f473b79e9 | ||
|
|
8ad26da5d7 | ||
|
|
e7f1608605 | ||
|
|
d14fb0b666 | ||
|
|
a55401de23 | ||
|
|
a9ad38e39b | ||
|
|
4f5746680d | ||
|
|
b7d906d1ea | ||
|
|
1181871772 | ||
|
|
93df2d7950 | ||
|
|
09633dda58 | ||
|
|
9ef789110b | ||
|
|
9294fc4e7b | ||
|
|
abacff6ede | ||
|
|
79755e80b9 | ||
|
|
b8a23beba4 | ||
|
|
f996ec9bae | ||
|
|
8152fab185 | ||
|
|
21b404a322 | ||
|
|
c5ed48ad99 | ||
|
|
25f0668a44 | ||
|
|
67fec82c72 | ||
|
|
0760500ceb | ||
|
|
3355900bd3 | ||
|
|
417376dcb6 | ||
|
|
6a45ff2221 | ||
|
|
c967be2bc2 | ||
|
|
12dae210ef | ||
|
|
a75eb5f195 | ||
|
|
9ad71c6627 | ||
|
|
2218e7bf12 | ||
|
|
c4777fb17a | ||
|
|
edc2097d14 | ||
|
|
53bcabe3d0 | ||
|
|
27477e39de | ||
|
|
f4800746a3 | ||
|
|
7c82d16d66 | ||
|
|
dccd1c9459 | ||
|
|
1f8fad8fc5 | ||
|
|
a3d5588cd6 | ||
|
|
7c96daf6d0 | ||
|
|
db03e74f0d | ||
|
|
459044beb9 | ||
|
|
2537fb858f | ||
|
|
5c6d460455 | ||
|
|
6c3790e20e | ||
|
|
40de891695 | ||
|
|
bc9d118efd | ||
|
|
950b7ad57b |
58
README.md
@@ -28,7 +28,7 @@ Github:[releases](https://github.com/wanglin2/mind-map/releases)。百度云
|
||||
# 特性
|
||||
|
||||
- [x] 插件化架构,除核心功能外,其他功能作为插件提供,按需使用,减小打包体积
|
||||
- [x] 支持逻辑结构图、思维导图、组织结构图、目录组织图、时间轴(横向、竖向)、鱼骨图等结构
|
||||
- [x] 支持逻辑结构图(向左、向右逻辑结构图)、思维导图、组织结构图、目录组织图、时间轴(横向、竖向)、鱼骨图等结构
|
||||
- [x] 内置多种主题,允许高度自定义样式,支持注册新主题
|
||||
- [x] 节点内容支持文本(普通文本、富文本)、图片、图标、超链接、备注、标签、概要、数学公式
|
||||
- [x] 节点支持拖拽(拖拽移动、自由调整)、多种节点形状;支持扩展节点内容、支持使用 DDM 完全自定义节点内容
|
||||
@@ -42,7 +42,7 @@ Github:[releases](https://github.com/wanglin2/mind-map/releases)。百度云
|
||||
|
||||
官方提供了如下插件,可根据需求按需引入(某个功能不生效大概率是因为你没有引入对应的插件),具体使用方式请查看文档:
|
||||
|
||||
> RichText(节点富文本插件)、Select(鼠标多选节点插件)、Drag(节点拖拽插件)、AssociativeLine(关联线插件)、Export(导出插件)、KeyboardNavigation(键盘导航插件)、MiniMap(小地图插件)、Watermark(水印插件)、TouchEvent(移动端触摸事件支持插件)、NodeImgAdjust(拖拽调整节点图片大小插件)、Search(搜索插件)、Painter(节点格式刷插件)、Scrollbar(滚动条插件)、Formula(数学公式插件)、Cooperate(协同编辑插件)、RainbowLines(彩虹线条插件)、Demonstrate(演示模式插件)、HandDrawnLikeStyle(手绘风格插件)[收费]
|
||||
> RichText(节点富文本插件)、Select(鼠标多选节点插件)、Drag(节点拖拽插件)、AssociativeLine(关联线插件)、Export(导出插件)、KeyboardNavigation(键盘导航插件)、MiniMap(小地图插件)、Watermark(水印插件)、TouchEvent(移动端触摸事件支持插件)、NodeImgAdjust(拖拽调整节点图片大小插件)、Search(搜索插件)、Painter(节点格式刷插件)、Scrollbar(滚动条插件)、Formula(数学公式插件)、Cooperate(协同编辑插件)、RainbowLines(彩虹线条插件)、Demonstrate(演示模式插件)、OuterFrame(外框插件)、HandDrawnLikeStyle(手绘风格插件)[收费]、Notation(节点标记插件)[收费]
|
||||
|
||||
本项目不会实现的特性:
|
||||
|
||||
@@ -109,9 +109,13 @@ const mindMap = new MindMap({
|
||||
|
||||
[](https://star-history.com/#wanglin2/mind-map&Date)
|
||||
|
||||
# 关于定制
|
||||
|
||||
如果你有个性化的商用定制需求,可以联系我们,我们提供付费开发服务,无论前端、后端、还是部署,都可以帮你一站式搞定。
|
||||
|
||||
# 请作者喝杯咖啡
|
||||
|
||||
开源不易,如果本项目有帮助到你的话,可以考虑请作者喝杯咖啡~
|
||||
开源不易,如果本项目有帮助到你的话,可以考虑请作者喝杯咖啡,你的支持是开发者持续维护的最大动力~
|
||||
|
||||
> 推荐使用支付宝,微信获取不到头像。转账请备注【思维导图】。
|
||||
|
||||
@@ -354,8 +358,8 @@ const mindMap = new MindMap({
|
||||
<span>庆国</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/孟照星.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>孟照星</span>
|
||||
<img src="./web/src/assets/avatar/Alex.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>Alex</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/子豪.jpg" style="width: 50px;height: 50px;" />
|
||||
@@ -365,4 +369,48 @@ const mindMap = new MindMap({
|
||||
<img src="./web/src/assets/avatar/宏涛.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>宏涛</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/最多5个字.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>最多5个字</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/雨馨.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>雨馨</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/ZX.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>ZX</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/峰.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>峰</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
|
||||
<span>协成</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/木木.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>木木</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/好名字.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>好名字</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/Kyle.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>Kyle</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/lsytyrt.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>lsytyrt</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/秀树因馨雨.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>秀树因馨雨</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/buddy.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>buddy</span>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
2
copy.js
@@ -13,4 +13,4 @@ if (fs.existsSync(src)) {
|
||||
fs.unlinkSync(src)
|
||||
}
|
||||
|
||||
console.warn('请检查手绘风格选项是否开启!!!')
|
||||
console.warn('请检查手绘风格、标记插件是否启用!!!')
|
||||
2
dist/css/app.css
vendored
BIN
dist/fonts/iconfont.ttf
vendored
BIN
dist/fonts/iconfont.woff
vendored
BIN
dist/fonts/iconfont.woff2
vendored
0
dist/img/孟照星.jpg → dist/img/Alex.jpg
vendored
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
BIN
dist/img/Kyle.jpg
vendored
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
dist/img/ZX.jpg
vendored
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
dist/img/buddy.jpg
vendored
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
dist/img/logicalStructureLeft.jpg
vendored
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
dist/img/好名字.jpg
vendored
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
dist/img/峰.jpg
vendored
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
dist/img/最多5个字.jpg
vendored
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
dist/img/木木.jpg
vendored
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
dist/img/标记.jpg
vendored
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
dist/img/秀树因馨雨.jpg
vendored
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
dist/img/错误.jpg
vendored
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
dist/img/雨馨.jpg
vendored
Normal file
|
After Width: | Height: | Size: 32 KiB |
2
dist/js/app.js
vendored
1
dist/js/chunk-02087b0a.js
vendored
Normal file
1
dist/js/chunk-16bd07ee.js
vendored
1
dist/js/chunk-1ba6eabc.js
vendored
2
dist/js/chunk-2d0a3179.js
vendored
2
dist/js/chunk-2d0ab10b.js
vendored
2
dist/js/chunk-2d0abe0f.js
vendored
@@ -1 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0abe0f"],{"16c7":function(e,t,o){"use strict";o.r(t);var a=function(){var e=this;e._self._c;return e._m(0)},n=[function(){var e=this,t=e._self._c;return t("div",[t("h1",[e._v("View instance")]),t("p",[e._v("The "),t("code",[e._v("view")]),e._v(" instance is responsible for view operations, and can be obtained through "),t("code",[e._v("mindMap.view")])]),t("h2",[e._v("Methods")]),t("h3",[e._v("fit()")]),t("blockquote",[t("p",[e._v("v0.6.0+")])]),t("p",[e._v("Zoom the mind map to fit the canvas.")]),t("p",[e._v("Note that this method cannot be called immediately after calling the 'setData' and 'setFullData' methods, and needs to listen to the 'node_tree_render_end' event calls 'fit'.")]),t("h3",[e._v("translateX(step)")]),t("p",[e._v("Translate in the "),t("code",[e._v("x")]),e._v(" direction, "),t("code",[e._v("step")]),e._v(": number of pixels to translate")]),t("h3",[e._v("translateY(step)")]),t("p",[e._v("Translate in the "),t("code",[e._v("y")]),e._v(" direction, "),t("code",[e._v("step")]),e._v(": number of pixels to translate")]),t("h3",[e._v("translateXTo(x)")]),t("blockquote",[t("p",[e._v("v0.2.11+")])]),t("p",[e._v("Translate the "),t("code",[e._v("x")]),e._v(" direction to a specific position")]),t("h3",[e._v("translateYTo(y)")]),t("blockquote",[t("p",[e._v("v0.2.11+")])]),t("p",[e._v("Translate the "),t("code",[e._v("y")]),e._v(" direction to a specific position")]),t("h3",[e._v("reset()")]),t("p",[e._v("Revert to the default transformation")]),t("h3",[e._v("narrow(cx, cy)")]),t("ul",[t("li",[t("p",[t("code",[e._v("cx")]),e._v(":(v0.6.4+)Zoom to the specified position on the canvas, default to the center point of the canvas")])]),t("li",[t("p",[t("code",[e._v("cy")]),e._v(":(v0.6.4+)Zoom to the specified position on the canvas, default to the center point of the canvas")])])]),t("p",[e._v("Zoom out")]),t("h3",[e._v("enlarge(cx, cy)")]),t("ul",[t("li",[t("p",[t("code",[e._v("cx")]),e._v(":(v0.6.4+)Zoom to the specified position on the canvas, default to the center point of the canvas")])]),t("li",[t("p",[t("code",[e._v("cy")]),e._v(":(v0.6.4+)Zoom to the specified position on the canvas, default to the center point of the canvas")])])]),t("p",[e._v("Zoom in")]),t("h3",[e._v("getTransformData()")]),t("blockquote",[t("p",[e._v("v0.1.1+")])]),t("p",[e._v("Get the current transform data, can be used for display")]),t("h3",[e._v("setTransformData(data)")]),t("blockquote",[t("p",[e._v("v0.1.1+")])]),t("p",[e._v('Dynamically set transform data, transform data can be obtained through the getTransformData method"')]),t("h3",[e._v("setScale(scale, cx, cy)")]),t("blockquote",[t("p",[e._v("v0.2.17+")])]),t("ul",[t("li",[t("p",[t("code",[e._v("cx")]),e._v(":(v0.6.4+)Zoom to the specified position on the canvas, default to the center point of the canvas")])]),t("li",[t("p",[t("code",[e._v("cy")]),e._v(":(v0.6.4+)Zoom to the specified position on the canvas, default to the center point of the canvas")])])]),t("p",[e._v("Setting Zoom")])])}],v={},i=v,c=o("2877"),s=Object(c["a"])(i,a,n,!1,null,null,null);t["default"]=s.exports}}]);
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0abe0f"],{"16c7":function(e,t,o){"use strict";o.r(t);var a=function(){var e=this;e._self._c;return e._m(0)},n=[function(){var e=this,t=e._self._c;return t("div",[t("h1",[e._v("View instance")]),t("p",[e._v("The "),t("code",[e._v("view")]),e._v(" instance is responsible for view operations, and can be obtained through "),t("code",[e._v("mindMap.view")])]),t("h2",[e._v("Methods")]),t("h3",[e._v("fit()")]),t("blockquote",[t("p",[e._v("v0.6.0+")])]),t("p",[e._v("Zoom the mind map to fit the canvas.")]),t("p",[e._v("Note that this method cannot be called immediately after calling the 'setData' and 'setFullData' methods, and needs to listen to the 'node_tree_render_end' event calls 'fit'.")]),t("h3",[e._v("translateX(step)")]),t("p",[e._v("Translate in the "),t("code",[e._v("x")]),e._v(" direction, "),t("code",[e._v("step")]),e._v(": number of pixels to translate")]),t("h3",[e._v("translateY(step)")]),t("p",[e._v("Translate in the "),t("code",[e._v("y")]),e._v(" direction, "),t("code",[e._v("step")]),e._v(": number of pixels to translate")]),t("h3",[e._v("translateXTo(x)")]),t("blockquote",[t("p",[e._v("v0.2.11+")])]),t("p",[e._v("Translate the "),t("code",[e._v("x")]),e._v(" direction to a specific position")]),t("h3",[e._v("translateYTo(y)")]),t("blockquote",[t("p",[e._v("v0.2.11+")])]),t("p",[e._v("Translate the "),t("code",[e._v("y")]),e._v(" direction to a specific position")]),t("h3",[e._v("reset()")]),t("p",[e._v("Revert to the default transformation")]),t("h3",[e._v("narrow(cx, cy)")]),t("ul",[t("li",[t("p",[t("code",[e._v("cx")]),e._v(":(v0.6.4+)Zoom to the specified position on the canvas, default to the center point of the canvas")])]),t("li",[t("p",[t("code",[e._v("cy")]),e._v(":(v0.6.4+)Zoom to the specified position on the canvas, default to the center point of the canvas")])])]),t("p",[e._v("Zoom out")]),t("h3",[e._v("enlarge(cx, cy)")]),t("ul",[t("li",[t("p",[t("code",[e._v("cx")]),e._v(":(v0.6.4+)Zoom to the specified position on the canvas, default to the center point of the canvas")])]),t("li",[t("p",[t("code",[e._v("cy")]),e._v(":(v0.6.4+)Zoom to the specified position on the canvas, default to the center point of the canvas")])])]),t("p",[e._v("Zoom in")]),t("h3",[e._v("getTransformData()")]),t("blockquote",[t("p",[e._v("v0.1.1+")])]),t("p",[e._v("Get the current transform data, can be used for display")]),t("h3",[e._v("setTransformData(data)")]),t("blockquote",[t("p",[e._v("v0.1.1+")])]),t("p",[e._v('Dynamically set transform data, transform data can be obtained through the getTransformData method"')]),t("h3",[e._v("setScale(scale, cx, cy)")]),t("blockquote",[t("p",[e._v("v0.2.17+")])]),t("ul",[t("li",[t("p",[t("code",[e._v("scale")]),e._v(": Scaling values, not scaled to '1', scaled to '1' for values less than, scaled to '1' for values greater than, scaled to '1' for values greater than")])]),t("li",[t("p",[t("code",[e._v("cx")]),e._v(":(v0.6.4+)Zoom to the specified position on the canvas, default to the center point of the canvas")])]),t("li",[t("p",[t("code",[e._v("cy")]),e._v(":(v0.6.4+)Zoom to the specified position on the canvas, default to the center point of the canvas")])])]),t("p",[e._v("Setting Zoom")])])}],v={},c=v,s=o("2877"),i=Object(s["a"])(c,a,n,!1,null,null,null);t["default"]=i.exports}}]);
|
||||
1
dist/js/chunk-2d0ae956.js
vendored
Normal file
2
dist/js/chunk-2d0c191e.js
vendored
2
dist/js/chunk-2d0d9fbc.js
vendored
2
dist/js/chunk-2d0da701.js
vendored
2
dist/js/chunk-2d0dad5f.js
vendored
2
dist/js/chunk-2d0db0f2.js
vendored
@@ -1 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0db0f2"],{"6df4":function(v,_,e){"use strict";e.r(_);var n=function(){var v=this;v._self._c;return v._m(0)},o=[function(){var v=this,_=v._self._c;return _("div",[_("h1",[v._v("Command 实例")]),_("p",[_("code",[v._v("command")]),v._v("实例负责命令的添加及执行,内置了很多命令,也可以自行添加,命令指需要在历史堆栈数据里添加副本的操作。可通过"),_("code",[v._v("mindMap.command")]),v._v("获取到该实例")]),_("h2",[v._v("方法")]),_("h3",[v._v("pause()")]),_("blockquote",[_("p",[v._v("v0.9.11+")])]),_("p",[v._v("暂停收集历史数据。")]),_("h3",[v._v("recovery()")]),_("blockquote",[_("p",[v._v("v0.9.11+")])]),_("p",[v._v("恢复收集历史数据。")]),_("h3",[v._v("add(name, fn)")]),_("p",[v._v("添加命令。")]),_("p",[_("code",[v._v("name")]),v._v(":命令名称")]),_("p",[_("code",[v._v("fn")]),v._v(":命令要执行的方法")]),_("h3",[v._v("remove(name, fn)")]),_("p",[v._v("移除命令。")]),_("p",[_("code",[v._v("name")]),v._v(":要移除的命令名称")]),_("p",[_("code",[v._v("fn")]),v._v(":要移除的方法,不传的话移除该命令所有的方法")]),_("h3",[v._v("getCopyData()")]),_("p",[v._v("获取渲染树数据副本")]),_("h3",[v._v("clearHistory()")]),_("p",[v._v("清空历史堆栈数据")])])}],c={},p=c,a=e("2877"),d=Object(a["a"])(p,n,o,!1,null,null,null);_["default"]=d.exports}}]);
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0db0f2"],{"6df4":function(v,_,e){"use strict";e.r(_);var n=function(){var v=this;v._self._c;return v._m(0)},o=[function(){var v=this,_=v._self._c;return _("div",[_("h1",[v._v("Command 实例")]),_("p",[_("code",[v._v("command")]),v._v("实例负责命令的添加及执行,内置了很多命令,也可以自行添加,命令指需要在历史堆栈数据里添加副本的操作。可通过"),_("code",[v._v("mindMap.command")]),v._v("获取到该实例")]),_("h2",[v._v("属性")]),_("h3",[v._v("history")]),_("p",[v._v("当前所有的历史数据列表。不要手动修改该数组。")]),_("h3",[v._v("activeHistoryIndex")]),_("p",[v._v("当前所在的历史数据索引。不要手动修改该属性。")]),_("h2",[v._v("方法")]),_("p",[v._v("前进后退请使用命令"),_("code",[v._v("BACK")]),v._v("或"),_("code",[v._v("FORWARD")]),v._v("。")]),_("h3",[v._v("pause()")]),_("blockquote",[_("p",[v._v("v0.9.11+")])]),_("p",[v._v("暂停收集历史数据。")]),_("h3",[v._v("recovery()")]),_("blockquote",[_("p",[v._v("v0.9.11+")])]),_("p",[v._v("恢复收集历史数据。")]),_("h3",[v._v("add(name, fn)")]),_("p",[v._v("添加命令。")]),_("p",[_("code",[v._v("name")]),v._v(":命令名称")]),_("p",[_("code",[v._v("fn")]),v._v(":命令要执行的方法")]),_("h3",[v._v("remove(name, fn)")]),_("p",[v._v("移除命令。")]),_("p",[_("code",[v._v("name")]),v._v(":要移除的命令名称")]),_("p",[_("code",[v._v("fn")]),v._v(":要移除的方法,不传的话移除该命令所有的方法")]),_("h3",[v._v("getCopyData()")]),_("p",[v._v("获取渲染树数据副本。即当前画布的数据。")]),_("h3",[v._v("clearHistory()")]),_("p",[v._v("清空历史堆栈数据")])])}],c={},p=c,d=e("2877"),a=Object(d["a"])(p,n,o,!1,null,null,null);_["default"]=a.exports}}]);
|
||||
2
dist/js/chunk-2d0dddce.js
vendored
@@ -1 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0dddce"],{"82ca":function(v,_,e){"use strict";e.r(_);var c=function(){var v=this;v._self._c;return v._m(0)},t=[function(){var v=this,_=v._self._c;return _("div",[_("h1",[v._v("View实例")]),_("p",[_("code",[v._v("view")]),v._v("实例负责视图操作,可通过"),_("code",[v._v("mindMap.view")]),v._v("获取到该实例")]),_("h2",[v._v("方法")]),_("h3",[v._v("fit()")]),_("blockquote",[_("p",[v._v("v0.6.0+")])]),_("p",[v._v("缩放思维导图至适应画布。")]),_("p",[v._v("注意该方法不能在"),_("code",[v._v("setData")]),v._v("、"),_("code",[v._v("setFullData")]),v._v("方法调用后立即调用,需要监听"),_("code",[v._v("node_tree_render_end")]),v._v("事件调用"),_("code",[v._v("fit")]),v._v("。")]),_("h3",[v._v("translateX(step)")]),_("p",[_("code",[v._v("x")]),v._v("方向进行平移,"),_("code",[v._v("step")]),v._v(":要平移的像素")]),_("h3",[v._v("translateY(step)")]),_("p",[_("code",[v._v("y")]),v._v("方向进行平移,"),_("code",[v._v("step")]),v._v(":要平移的像素")]),_("h3",[v._v("translateXTo(x)")]),_("blockquote",[_("p",[v._v("v0.2.11+")])]),_("p",[v._v("平移"),_("code",[v._v("x")]),v._v("方向到指定位置")]),_("h3",[v._v("translateYTo(y)")]),_("blockquote",[_("p",[v._v("v0.2.11+")])]),_("p",[v._v("平移"),_("code",[v._v("y")]),v._v("方向到指定位置")]),_("h3",[v._v("reset()")]),_("p",[v._v("恢复到默认的变换")]),_("h3",[v._v("narrow(cx, cy)")]),_("ul",[_("li",[_("p",[_("code",[v._v("cx")]),v._v(":(v0.6.4+)以画布指定位置进行缩放,默认为画布中心点")])]),_("li",[_("p",[_("code",[v._v("cy")]),v._v(":(v0.6.4+)以画布指定位置进行缩放,默认为画布中心点")])])]),_("p",[v._v("缩小")]),_("h3",[v._v("enlarge(cx, cy)")]),_("ul",[_("li",[_("p",[_("code",[v._v("cx")]),v._v(":(v0.6.4+)以画布指定位置进行缩放,默认为画布中心点")])]),_("li",[_("p",[_("code",[v._v("cy")]),v._v(":(v0.6.4+)以画布指定位置进行缩放,默认为画布中心点")])])]),_("p",[v._v("放大")]),_("h3",[v._v("getTransformData()")]),_("blockquote",[_("p",[v._v("v0.1.1+")])]),_("p",[v._v("获取当前变换数据,可用于回显")]),_("h3",[v._v("setTransformData(data)")]),_("blockquote",[_("p",[v._v("v0.1.1+")])]),_("p",[v._v("动态设置变换数据,可以通过getTransformData方法获取变换数据")]),_("h3",[v._v("setScale(scale, cx, cy)")]),_("blockquote",[_("p",[v._v("v0.2.17+")])]),_("ul",[_("li",[_("p",[_("code",[v._v("cx")]),v._v(":(v0.6.4+)以画布指定位置进行缩放,默认为画布中心点")])]),_("li",[_("p",[_("code",[v._v("cy")]),v._v(":(v0.6.4+)以画布指定位置进行缩放,默认为画布中心点")])])]),_("p",[v._v("设置缩放")])])}],o={},a=o,p=e("2877"),l=Object(p["a"])(a,c,t,!1,null,null,null);_["default"]=l.exports}}]);
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0dddce"],{"82ca":function(v,_,e){"use strict";e.r(_);var c=function(){var v=this;v._self._c;return v._m(0)},o=[function(){var v=this,_=v._self._c;return _("div",[_("h1",[v._v("View实例")]),_("p",[_("code",[v._v("view")]),v._v("实例负责视图操作,可通过"),_("code",[v._v("mindMap.view")]),v._v("获取到该实例")]),_("h2",[v._v("方法")]),_("h3",[v._v("fit()")]),_("blockquote",[_("p",[v._v("v0.6.0+")])]),_("p",[v._v("缩放思维导图至适应画布。")]),_("p",[v._v("注意该方法不能在"),_("code",[v._v("setData")]),v._v("、"),_("code",[v._v("setFullData")]),v._v("方法调用后立即调用,需要监听"),_("code",[v._v("node_tree_render_end")]),v._v("事件调用"),_("code",[v._v("fit")]),v._v("。")]),_("h3",[v._v("translateX(step)")]),_("p",[_("code",[v._v("x")]),v._v("方向进行平移,"),_("code",[v._v("step")]),v._v(":要平移的像素")]),_("h3",[v._v("translateY(step)")]),_("p",[_("code",[v._v("y")]),v._v("方向进行平移,"),_("code",[v._v("step")]),v._v(":要平移的像素")]),_("h3",[v._v("translateXTo(x)")]),_("blockquote",[_("p",[v._v("v0.2.11+")])]),_("p",[v._v("平移"),_("code",[v._v("x")]),v._v("方向到指定位置")]),_("h3",[v._v("translateYTo(y)")]),_("blockquote",[_("p",[v._v("v0.2.11+")])]),_("p",[v._v("平移"),_("code",[v._v("y")]),v._v("方向到指定位置")]),_("h3",[v._v("reset()")]),_("p",[v._v("恢复到默认的变换")]),_("h3",[v._v("narrow(cx, cy)")]),_("ul",[_("li",[_("p",[_("code",[v._v("cx")]),v._v(":(v0.6.4+)以画布指定位置进行缩放,默认为画布中心点")])]),_("li",[_("p",[_("code",[v._v("cy")]),v._v(":(v0.6.4+)以画布指定位置进行缩放,默认为画布中心点")])])]),_("p",[v._v("缩小")]),_("h3",[v._v("enlarge(cx, cy)")]),_("ul",[_("li",[_("p",[_("code",[v._v("cx")]),v._v(":(v0.6.4+)以画布指定位置进行缩放,默认为画布中心点")])]),_("li",[_("p",[_("code",[v._v("cy")]),v._v(":(v0.6.4+)以画布指定位置进行缩放,默认为画布中心点")])])]),_("p",[v._v("放大")]),_("h3",[v._v("getTransformData()")]),_("blockquote",[_("p",[v._v("v0.1.1+")])]),_("p",[v._v("获取当前变换数据,可用于回显")]),_("h3",[v._v("setTransformData(data)")]),_("blockquote",[_("p",[v._v("v0.1.1+")])]),_("p",[v._v("动态设置变换数据,可以通过getTransformData方法获取变换数据")]),_("h3",[v._v("setScale(scale, cx, cy)")]),_("blockquote",[_("p",[v._v("v0.2.17+")])]),_("ul",[_("li",[_("p",[_("code",[v._v("scale")]),v._v(": 缩放数值,未缩放为"),_("code",[v._v("1")]),v._v(",小于为"),_("code",[v._v("1")]),v._v("缩小,大于"),_("code",[v._v("1")]),v._v("为放大")])]),_("li",[_("p",[_("code",[v._v("cx")]),v._v(":(v0.6.4+)以画布指定位置进行缩放,默认为画布中心点")])]),_("li",[_("p",[_("code",[v._v("cy")]),v._v(":(v0.6.4+)以画布指定位置进行缩放,默认为画布中心点")])])]),_("p",[v._v("设置缩放")])])}],t={},a=t,p=e("2877"),d=Object(p["a"])(a,c,o,!1,null,null,null);_["default"]=d.exports}}]);
|
||||
2
dist/js/chunk-2d0ddf37.js
vendored
@@ -1 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0ddf37"],{8427:function(v,_,s){"use strict";s.r(_);var n=function(){var v=this;v._self._c;return v._m(0)},e=[function(){var v=this,_=v._self._c;return _("div",[_("h1",[v._v("XMind解析")]),_("blockquote",[_("p",[v._v("v0.2.7+")])]),_("p",[v._v("提供导入和导出"),_("code",[v._v("XMind")]),v._v("文件的方法。")]),_("h2",[v._v("引入")]),_("pre",{staticClass:"hljs"},[_("code",[_("span",{staticClass:"hljs-keyword"},[v._v("import")]),v._v(" xmind "),_("span",{staticClass:"hljs-keyword"},[v._v("from")]),v._v(" "),_("span",{staticClass:"hljs-string"},[v._v("'simple-mind-map/src/parse/xmind.js'")]),v._v("\n")])]),_("p",[v._v("如果使用的是"),_("code",[v._v("umd")]),v._v("格式的文件,那么可以通过如下方式获取:")]),_("pre",{staticClass:"hljs"},[_("code",[_("span",{staticClass:"hljs-tag"},[v._v("<"),_("span",{staticClass:"hljs-name"},[v._v("script")]),v._v(" "),_("span",{staticClass:"hljs-attr"},[v._v("src")]),v._v("="),_("span",{staticClass:"hljs-string"},[v._v('"simple-mind-map/dist/simpleMindMap.umd.min.js"')]),v._v(">")]),_("span",{staticClass:"hljs-tag"},[v._v("</"),_("span",{staticClass:"hljs-name"},[v._v("script")]),v._v(">")]),v._v("\n")])]),_("pre",{staticClass:"hljs"},[_("code",[v._v("simpleMindMap.xmind\n")])]),_("h2",[v._v("方法")]),_("h3",[v._v("xmind.parseXmindFile(file)")]),_("p",[v._v("解析"),_("code",[v._v(".xmind")]),v._v("文件,返回解析后的数据,可以使用"),_("code",[v._v("mindMap.setData(data)")]),v._v("来将返回的数据渲染到画布上")]),_("p",[_("code",[v._v("file")]),v._v(":"),_("code",[v._v("File")]),v._v("对象")]),_("h3",[v._v("xmind.transformXmind(content)")]),_("blockquote",[_("p",[v._v("v0.6.6+版本该方法改为异步方法,返回一个Promise实例")])]),_("p",[v._v("转换"),_("code",[v._v("xmind")]),v._v("数据,"),_("code",[v._v(".xmind")]),v._v("文件本质上是一个压缩包,改成"),_("code",[v._v("zip")]),v._v("后缀可以解压缩,里面存在一个"),_("code",[v._v("content.json")]),v._v("文件,如果你自己解析出了这个文件,那么可以把这个文件内容传递给这个方法进行转换,转换后的数据,可以使用"),_("code",[v._v("mindMap.setData(data)")]),v._v("来将返回的数据渲染到画布上")]),_("p",[_("code",[v._v("content")]),v._v(":"),_("code",[v._v(".xmind")]),v._v("压缩包内的"),_("code",[v._v("content.json")]),v._v("文件内容")]),_("h3",[v._v("xmind.transformOldXmind(content)")]),_("blockquote",[_("p",[v._v("v0.2.8+")])]),_("p",[v._v("针对"),_("code",[v._v("xmind8")]),v._v("版本的数据解析,因为该版本的"),_("code",[v._v(".xmind")]),v._v("文件内没有"),_("code",[v._v("content.json")]),v._v(",对应的是"),_("code",[v._v("content.xml")]),v._v("。")]),_("p",[_("code",[v._v("content")]),v._v(":"),_("code",[v._v(".xmind")]),v._v("压缩包内的"),_("code",[v._v("content.xml")]),v._v("文件内容")]),_("h3",[v._v("transformToXmind(data, name)")]),_("blockquote",[_("p",[v._v("v0.6.6+")])]),_("ul",[_("li",[_("p",[_("code",[v._v("data")]),v._v(":"),_("code",[v._v("simple-mind-map")]),v._v("思维导图数据,可以通过"),_("code",[v._v("mindMap.getData()")]),v._v("方法获取。")])]),_("li",[_("p",[_("code",[v._v("name")]),v._v(":要导出的文件名。")])])]),_("p",[v._v("将"),_("code",[v._v("simple-mind-map")]),v._v("数据转为"),_("code",[v._v("xmind")]),v._v("文件。该方法为异步方法,返回一个"),_("code",[v._v("Promise")]),v._v("实例,返回的数据是一个"),_("code",[v._v("blob")]),v._v("类型的"),_("code",[v._v("zip")]),v._v("压缩包数据,你可以自行下载为文件。")])])}],t={},d=t,a=s("2877"),i=Object(a["a"])(d,n,e,!1,null,null,null);_["default"]=i.exports}}]);
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0ddf37"],{8427:function(v,_,n){"use strict";n.r(_);var s=function(){var v=this;v._self._c;return v._m(0)},e=[function(){var v=this,_=v._self._c;return _("div",[_("h1",[v._v("XMind解析")]),_("blockquote",[_("p",[v._v("v0.2.7+")])]),_("p",[v._v("提供导入和导出"),_("code",[v._v("XMind")]),v._v("文件的方法。")]),_("h2",[v._v("引入")]),_("pre",{staticClass:"hljs"},[_("code",[_("span",{staticClass:"hljs-keyword"},[v._v("import")]),v._v(" xmind "),_("span",{staticClass:"hljs-keyword"},[v._v("from")]),v._v(" "),_("span",{staticClass:"hljs-string"},[v._v("'simple-mind-map/src/parse/xmind.js'")]),v._v("\n")])]),_("p",[v._v("如果使用的是"),_("code",[v._v("umd")]),v._v("格式的文件,那么可以通过如下方式获取:")]),_("pre",{staticClass:"hljs"},[_("code",[_("span",{staticClass:"hljs-tag"},[v._v("<"),_("span",{staticClass:"hljs-name"},[v._v("script")]),v._v(" "),_("span",{staticClass:"hljs-attr"},[v._v("src")]),v._v("="),_("span",{staticClass:"hljs-string"},[v._v('"simple-mind-map/dist/simpleMindMap.umd.min.js"')]),v._v(">")]),_("span",{staticClass:"hljs-tag"},[v._v("</"),_("span",{staticClass:"hljs-name"},[v._v("script")]),v._v(">")]),v._v("\n")])]),_("pre",{staticClass:"hljs"},[_("code",[v._v("simpleMindMap.xmind\n")])]),_("h2",[v._v("方法")]),_("h3",[v._v("xmind.parseXmindFile(file, handleMultiCanvas)")]),_("p",[v._v("解析"),_("code",[v._v(".xmind")]),v._v("文件,返回解析后的数据,可以使用"),_("code",[v._v("mindMap.setData(data)")]),v._v("来将返回的数据渲染到画布上")]),_("p",[_("code",[v._v("file")]),v._v(":"),_("code",[v._v("File")]),v._v("对象")]),_("p",[_("code",[v._v("handleMultiCanvas")]),v._v(":v0.10.0+,可选,可传递一个函数,如果导入的xmind文件存在多个画布,那么会调用该函数,函数接收xmind画布列表数据为参数,需要返回其中一个画布的数据,比如接收的参数为"),_("code",[v._v("content")]),v._v(",要导入第二个画布的数据则返回"),_("code",[v._v("content[1]")]),v._v("。函数可以是异步函数,返回一个Promise实例。")]),_("h3",[v._v("xmind.transformXmind(content)")]),_("blockquote",[_("p",[v._v("v0.6.6+版本该方法改为异步方法,返回一个Promise实例")])]),_("p",[v._v("转换"),_("code",[v._v("xmind")]),v._v("数据,"),_("code",[v._v(".xmind")]),v._v("文件本质上是一个压缩包,改成"),_("code",[v._v("zip")]),v._v("后缀可以解压缩,里面存在一个"),_("code",[v._v("content.json")]),v._v("文件,如果你自己解析出了这个文件,那么可以把这个文件内容传递给这个方法进行转换,转换后的数据,可以使用"),_("code",[v._v("mindMap.setData(data)")]),v._v("来将返回的数据渲染到画布上")]),_("p",[_("code",[v._v("content")]),v._v(":"),_("code",[v._v(".xmind")]),v._v("压缩包内的"),_("code",[v._v("content.json")]),v._v("文件内容")]),_("h3",[v._v("xmind.transformOldXmind(content)")]),_("blockquote",[_("p",[v._v("v0.2.8+")])]),_("p",[v._v("针对"),_("code",[v._v("xmind8")]),v._v("版本的数据解析,因为该版本的"),_("code",[v._v(".xmind")]),v._v("文件内没有"),_("code",[v._v("content.json")]),v._v(",对应的是"),_("code",[v._v("content.xml")]),v._v("。")]),_("p",[_("code",[v._v("content")]),v._v(":"),_("code",[v._v(".xmind")]),v._v("压缩包内的"),_("code",[v._v("content.xml")]),v._v("文件内容")]),_("h3",[v._v("transformToXmind(data, name)")]),_("blockquote",[_("p",[v._v("v0.6.6+")])]),_("ul",[_("li",[_("p",[_("code",[v._v("data")]),v._v(":"),_("code",[v._v("simple-mind-map")]),v._v("思维导图数据,可以通过"),_("code",[v._v("mindMap.getData()")]),v._v("方法获取。")])]),_("li",[_("p",[_("code",[v._v("name")]),v._v(":要导出的文件名。")])])]),_("p",[v._v("将"),_("code",[v._v("simple-mind-map")]),v._v("数据转为"),_("code",[v._v("xmind")]),v._v("文件。该方法为异步方法,返回一个"),_("code",[v._v("Promise")]),v._v("实例,返回的数据是一个"),_("code",[v._v("blob")]),v._v("类型的"),_("code",[v._v("zip")]),v._v("压缩包数据,你可以自行下载为文件。")])])}],t={},d=t,a=n("2877"),i=Object(a["a"])(d,s,e,!1,null,null,null);_["default"]=i.exports}}]);
|
||||
2
dist/js/chunk-2d0e5089.js
vendored
@@ -1 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0e5089"],{9381:function(e,o,a){"use strict";a.r(o);var n=function(){var e=this;e._self._c;return e._m(0)},t=[function(){var e=this,o=e._self._c;return o("div",[o("h1",[e._v("command instance")]),o("p",[e._v("The "),o("code",[e._v("command")]),e._v(" instance is responsible for adding and executing commands. It includes many built-in commands and can also be added manually. A command refers to an operation that needs to add a copy to the history stack data. The "),o("code",[e._v("mindMap.command")]),e._v(' instance can be obtained through this."')]),o("h2",[e._v("Methods")]),o("h3",[e._v("pause()")]),o("blockquote",[o("p",[e._v("v0.9.11+")])]),o("p",[e._v("Pause collecting historical data.")]),o("h3",[e._v("recovery()")]),o("blockquote",[o("p",[e._v("v0.9.11+")])]),o("p",[e._v("Restore the collection of historical data.")]),o("h3",[e._v("add(name, fn)")]),o("p",[e._v("Add a command.")]),o("p",[o("code",[e._v("name")]),e._v(": Command name")]),o("p",[o("code",[e._v("fn")]),e._v(": Method to be executed by the command")]),o("h3",[e._v("remove(name, fn)")]),o("p",[e._v("Remove a command.")]),o("p",[o("code",[e._v("name")]),e._v(": Name of the command to be removed")]),o("p",[o("code",[e._v("fn")]),e._v(": Method to be removed, if not provided all methods for the command will be removed")]),o("h3",[e._v("getCopyData()")]),o("p",[e._v("Get a copy of the rendering tree data")]),o("h3",[e._v("clearHistory()")]),o("p",[e._v("Clear the history stack data")])])}],d={},c=d,v=a("2877"),m=Object(v["a"])(c,n,t,!1,null,null,null);o["default"]=m.exports}}]);
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0e5089"],{9381:function(e,a,o){"use strict";o.r(a);var t=function(){var e=this;e._self._c;return e._m(0)},n=[function(){var e=this,a=e._self._c;return a("div",[a("h1",[e._v("command instance")]),a("p",[e._v("The "),a("code",[e._v("command")]),e._v(" instance is responsible for adding and executing commands. It includes many built-in commands and can also be added manually. A command refers to an operation that needs to add a copy to the history stack data. The "),a("code",[e._v("mindMap.command")]),e._v(' instance can be obtained through this."')]),a("h2",[e._v("Props")]),a("h3",[e._v("history")]),a("p",[e._v("The current list of all historical data. Do not manually modify the array.")]),a("h3",[e._v("activeHistoryIndex")]),a("p",[e._v("The current historical data index. Do not manually modify this property.")]),a("h2",[e._v("Methods")]),a("p",[e._v("Please use the command 'Back' or 'FORWARD' to move forward or backward.")]),a("h3",[e._v("pause()")]),a("blockquote",[a("p",[e._v("v0.9.11+")])]),a("p",[e._v("Pause collecting historical data.")]),a("h3",[e._v("recovery()")]),a("blockquote",[a("p",[e._v("v0.9.11+")])]),a("p",[e._v("Restore the collection of historical data.")]),a("h3",[e._v("add(name, fn)")]),a("p",[e._v("Add a command.")]),a("p",[a("code",[e._v("name")]),e._v(": Command name")]),a("p",[a("code",[e._v("fn")]),e._v(": Method to be executed by the command")]),a("h3",[e._v("remove(name, fn)")]),a("p",[e._v("Remove a command.")]),a("p",[a("code",[e._v("name")]),e._v(": Name of the command to be removed")]),a("p",[a("code",[e._v("fn")]),e._v(": Method to be removed, if not provided all methods for the command will be removed")]),a("h3",[e._v("getCopyData()")]),a("p",[e._v("Get a copy of the rendering tree data. That is, the data of the current canvas.")]),a("h3",[e._v("clearHistory()")]),a("p",[e._v("Clear the history stack data")])])}],d={},c=d,r=o("2877"),v=Object(r["a"])(c,t,n,!1,null,null,null);a["default"]=v.exports}}]);
|
||||
2
dist/js/chunk-2d0e9742.js
vendored
2
dist/js/chunk-2d0f026c.js
vendored
2
dist/js/chunk-2d208ffa.js
vendored
2
dist/js/chunk-2d20f68f.js
vendored
2
dist/js/chunk-2d210a7a.js
vendored
1
dist/js/chunk-2d21e7f1.js
vendored
Normal file
2
dist/js/chunk-2d2254a4.js
vendored
1
dist/js/chunk-2d22a194.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d22a194"],{dfea:function(p,v,_){"use strict";_.r(v);var e=function(){var p=this;p._self._c;return p._m(0)},t=[function(){var p=this,v=p._self._c;return v("div",[v("h1",[p._v("局域网docker部署解决HTTPS问题的一种方法")]),v("blockquote",[v("p",[p._v("本文来自:"),v("a",{attrs:{href:"https://github.com/Brzjomo"}},[p._v("Brzjomo")]),p._v("的"),v("a",{attrs:{href:"https://github.com/wanglin2/mind-map/issues/658"}},[p._v("issue")]),p._v("。")])]),v("p",[p._v("受Api的限制,MindMap以HTTP访问时,目录、新建和打开功能不能正常工作。因此在局域网架设时,需要给它进行配置证书等操作,使其正常工作。")]),v("p",[p._v("假设先前已经基于Github源码,架设了MindMap的docker服务。没有的先看这个"),v("a",{attrs:{href:"https://github.com/wanglin2/mind-map/issues/309"}},[p._v("Issue")])]),v("p",[p._v("事前准备: 需要准备一个域名。")]),v("p",[p._v("需要安装Linux 服务器运维管理面板"),v("a",{attrs:{href:"https://github.com/1Panel-dev/1Panel"}},[p._v("1panel")])]),v("p",[p._v("设置域名解析: 以阿里云为例,登录后进入"),v("a",{attrs:{href:"https://dns.console.aliyun.com/#/dns/domainList"}},[p._v("域名解析页面")])]),v("p",[p._v("点击对应域名的解析设置。")]),v("p",[p._v("添加或编辑对应的@和www记录,将IP记录值修改为局域网IP,比如192.168.2.36。")]),v("p",[p._v("保存后退出。")]),v("p",[p._v("获取AccessKey: 进入账号下面的AccessKey管理。")]),v("p",[p._v("创建或者使用已经记录的AccessKey。")]),v("p",[p._v("1panel设置: 进入应用商店,安装OpenResty(稍后用于申请证书和设置反代)。")]),v("p",[p._v("进入网站-网站,点击创建网站。")]),v("p",[p._v("点击反向代理。")]),v("p",[p._v("设置主域名为自己的域名。")]),v("p",[p._v("代理地址为http和127.0.0.1:MindMap容器端口。")]),v("p",[p._v("点击确认。")]),v("p",[p._v("创建证书申请账户: 进入1panel的网站-证书,点击Acme 账户。")]),v("p",[p._v("点击创建账户。")]),v("p",[p._v("输入邮箱后确认。")]),v("p",[p._v("回到刚才的证书页面,点击DNS 账户。")]),v("p",[p._v("点击创建账户。")]),v("p",[p._v("填写名称后,选择类型为阿里云DNS。")]),v("p",[p._v("再填入刚才准备好的Access Key和Secret Key。")]),v("p",[p._v("点击确认。")]),v("p",[p._v("申请证书: 回到刚才的证书页面,点击申请证书。")]),v("p",[p._v("填写主域名,其他按实际情况填写。一般会自动设置。")]),v("p",[p._v("点击确认,等待其成功。")]),v("p",[p._v("启用HTTPS访问: 回到1panel的网站管理页面。")]),v("p",[p._v("找到刚才建立的反向代理,点击配置。")]),v("p",[p._v("点击HTTPS。")]),v("p",[p._v("点击启用HTTPS。")]),v("p",[p._v("SSL 选项设置为选择已有证书。")]),v("p",[p._v("选择好刚才创建的Acme账户和证书。")]),v("p",[p._v("点击保存。")]),v("p",[p._v("此时,在局域网内访问该域名,应当能正确以Https访问MindMap了。")]),v("p",[p._v("如果不能,输入host 域名,查看返回的DNS解析是否为局域网IP。")])])}],s={},n=s,a=_("2877"),c=Object(a["a"])(n,e,t,!1,null,null,null);v["default"]=c.exports}}]);
|
||||
2
dist/js/chunk-2d22bd06.js
vendored
@@ -1 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d22bd06"],{f127:function(e,t,n){"use strict";n.r(t);var s=function(){var e=this;e._self._c;return e._m(0)},a=[function(){var e=this,t=e._self._c;return t("div",[t("h1",[e._v("XMind parse")]),t("blockquote",[t("p",[e._v("v0.2.7+")])]),t("p",[e._v("Provides methods for importing and export "),t("code",[e._v("XMind")]),e._v(" files.")]),t("h2",[e._v("Import")]),t("pre",{staticClass:"hljs"},[t("code",[t("span",{staticClass:"hljs-keyword"},[e._v("import")]),e._v(" xmind "),t("span",{staticClass:"hljs-keyword"},[e._v("from")]),e._v(" "),t("span",{staticClass:"hljs-string"},[e._v("'simple-mind-map/src/parse/xmind.js'")]),e._v("\n")])]),t("p",[e._v("If you are using the file in the format of "),t("code",[e._v("umd")]),e._v(", you can obtain it in the following way:")]),t("pre",{staticClass:"hljs"},[t("code",[t("span",{staticClass:"hljs-tag"},[e._v("<"),t("span",{staticClass:"hljs-name"},[e._v("script")]),e._v(" "),t("span",{staticClass:"hljs-attr"},[e._v("src")]),e._v("="),t("span",{staticClass:"hljs-string"},[e._v('"simple-mind-map/dist/simpleMindMap.umd.min.js"')]),e._v(">")]),t("span",{staticClass:"hljs-tag"},[e._v("</"),t("span",{staticClass:"hljs-name"},[e._v("script")]),e._v(">")]),e._v("\n")])]),t("pre",{staticClass:"hljs"},[t("code",[e._v("simpleMindMap.xmind\n")])]),t("h2",[e._v("Methods")]),t("h3",[e._v("xmind.parseXmindFile(file)")]),t("p",[e._v("Parsing the "),t("code",[e._v(".xmind")]),e._v(" file and returning the parsed data. You can use "),t("code",[e._v("mindMap.setData(data)")]),e._v(" to render the returned data to the canvas.")]),t("p",[t("code",[e._v("file")]),e._v(": "),t("code",[e._v("File")]),e._v(" object")]),t("h3",[e._v("xmind.transformXmind(content)")]),t("blockquote",[t("p",[e._v("V0.6.6+version changes the method to asynchronous and returns a Promise instance")])]),t("p",[e._v("Convert "),t("code",[e._v("xmind")]),e._v(" data. The "),t("code",[e._v(".xmind")]),e._v(" file is essentially a "),t("code",[e._v("zip")]),e._v(" file that can be decompressed by changing the suffix to zip. Inside, there is a "),t("code",[e._v("content.json")]),e._v(" file. If you have parsed this file yourself, you can pass the contents of this file to this method for conversion. You can use "),t("code",[e._v("mindMap.setData(data)")]),e._v(" to render the returned data to the canvas.")]),t("p",[t("code",[e._v("content")]),e._v(": the contents of the "),t("code",[e._v("content.json")]),e._v(" file within the "),t("code",[e._v(".xmind")]),e._v(" zip package")]),t("h3",[e._v("xmind.transformOldXmind(content)")]),t("blockquote",[t("p",[e._v("v0.2.8+")])]),t("p",[e._v("For data parsing of the "),t("code",[e._v("xmind8")]),e._v(" version, because the "),t("code",[e._v(".xmind")]),e._v(" file in this version does not have a "),t("code",[e._v("content.json")]),e._v(", it corresponds to "),t("code",[e._v("content.xml")]),e._v(".")]),t("p",[t("code",[e._v("content")]),e._v(": the contents of the "),t("code",[e._v("content.xml")]),e._v(" file within the "),t("code",[e._v(".xmind")]),e._v(" zip package")]),t("h3",[e._v("transformToXmind(data, name)")]),t("blockquote",[t("p",[e._v("v0.6.6+")])]),t("ul",[t("li",[t("p",[t("code",[e._v("data")]),e._v(": "),t("code",[e._v("simple-mind-map")]),e._v(" data, you can get it by "),t("code",[e._v("mindMap.getData()")]),e._v(" method.")])]),t("li",[t("p",[t("code",[e._v("name")]),e._v(": The file name to export.")])])]),t("p",[e._v("Convert the "),t("code",[e._v("simple mind map")]),e._v(" data to an "),t("code",[e._v("xmind")]),e._v(" file. This method is asynchronous and returns an instance of "),t("code",[e._v("Promise")]),e._v(". The returned data is a "),t("code",[e._v("blob")]),e._v(" type "),t("code",[e._v("zip")]),e._v(" compressed package data, which you can download as a file yourself.")])])}],o={},i=o,d=n("2877"),v=Object(d["a"])(i,s,a,!1,null,null,null);t["default"]=v.exports}}]);
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d22bd06"],{f127:function(e,t,n){"use strict";n.r(t);var a=function(){var e=this;e._self._c;return e._m(0)},s=[function(){var e=this,t=e._self._c;return t("div",[t("h1",[e._v("XMind parse")]),t("blockquote",[t("p",[e._v("v0.2.7+")])]),t("p",[e._v("Provides methods for importing and export "),t("code",[e._v("XMind")]),e._v(" files.")]),t("h2",[e._v("Import")]),t("pre",{staticClass:"hljs"},[t("code",[t("span",{staticClass:"hljs-keyword"},[e._v("import")]),e._v(" xmind "),t("span",{staticClass:"hljs-keyword"},[e._v("from")]),e._v(" "),t("span",{staticClass:"hljs-string"},[e._v("'simple-mind-map/src/parse/xmind.js'")]),e._v("\n")])]),t("p",[e._v("If you are using the file in the format of "),t("code",[e._v("umd")]),e._v(", you can obtain it in the following way:")]),t("pre",{staticClass:"hljs"},[t("code",[t("span",{staticClass:"hljs-tag"},[e._v("<"),t("span",{staticClass:"hljs-name"},[e._v("script")]),e._v(" "),t("span",{staticClass:"hljs-attr"},[e._v("src")]),e._v("="),t("span",{staticClass:"hljs-string"},[e._v('"simple-mind-map/dist/simpleMindMap.umd.min.js"')]),e._v(">")]),t("span",{staticClass:"hljs-tag"},[e._v("</"),t("span",{staticClass:"hljs-name"},[e._v("script")]),e._v(">")]),e._v("\n")])]),t("pre",{staticClass:"hljs"},[t("code",[e._v("simpleMindMap.xmind\n")])]),t("h2",[e._v("Methods")]),t("h3",[e._v("xmind.parseXmindFile(file, handleMultiCanvas)")]),t("p",[e._v("Parsing the "),t("code",[e._v(".xmind")]),e._v(" file and returning the parsed data. You can use "),t("code",[e._v("mindMap.setData(data)")]),e._v(" to render the returned data to the canvas.")]),t("p",[t("code",[e._v("file")]),e._v(": "),t("code",[e._v("File")]),e._v(" object")]),t("p",[t("code",[e._v("handleMultiCanvas")]),e._v(":v0.10.0+,Optional, a function can be passed. If there are multiple canvases in the imported xmind file, this function will be called. The function takes the xmind canvas list data as a parameter and needs to return the data of one of the canvases, For example, if the received parameter is 'content', if you want to import data from the second canvas, you will return 'content[1]'. A function can be an asynchronous function that returns a Promise instance.")]),t("h3",[e._v("xmind.transformXmind(content)")]),t("blockquote",[t("p",[e._v("V0.6.6+version changes the method to asynchronous and returns a Promise instance")])]),t("p",[e._v("Convert "),t("code",[e._v("xmind")]),e._v(" data. The "),t("code",[e._v(".xmind")]),e._v(" file is essentially a "),t("code",[e._v("zip")]),e._v(" file that can be decompressed by changing the suffix to zip. Inside, there is a "),t("code",[e._v("content.json")]),e._v(" file. If you have parsed this file yourself, you can pass the contents of this file to this method for conversion. You can use "),t("code",[e._v("mindMap.setData(data)")]),e._v(" to render the returned data to the canvas.")]),t("p",[t("code",[e._v("content")]),e._v(": the contents of the "),t("code",[e._v("content.json")]),e._v(" file within the "),t("code",[e._v(".xmind")]),e._v(" zip package")]),t("h3",[e._v("xmind.transformOldXmind(content)")]),t("blockquote",[t("p",[e._v("v0.2.8+")])]),t("p",[e._v("For data parsing of the "),t("code",[e._v("xmind8")]),e._v(" version, because the "),t("code",[e._v(".xmind")]),e._v(" file in this version does not have a "),t("code",[e._v("content.json")]),e._v(", it corresponds to "),t("code",[e._v("content.xml")]),e._v(".")]),t("p",[t("code",[e._v("content")]),e._v(": the contents of the "),t("code",[e._v("content.xml")]),e._v(" file within the "),t("code",[e._v(".xmind")]),e._v(" zip package")]),t("h3",[e._v("transformToXmind(data, name)")]),t("blockquote",[t("p",[e._v("v0.6.6+")])]),t("ul",[t("li",[t("p",[t("code",[e._v("data")]),e._v(": "),t("code",[e._v("simple-mind-map")]),e._v(" data, you can get it by "),t("code",[e._v("mindMap.getData()")]),e._v(" method.")])]),t("li",[t("p",[t("code",[e._v("name")]),e._v(": The file name to export.")])])]),t("p",[e._v("Convert the "),t("code",[e._v("simple mind map")]),e._v(" data to an "),t("code",[e._v("xmind")]),e._v(" file. This method is asynchronous and returns an instance of "),t("code",[e._v("Promise")]),e._v(". The returned data is a "),t("code",[e._v("blob")]),e._v(" type "),t("code",[e._v("zip")]),e._v(" compressed package data, which you can download as a file yourself.")])])}],o={},i=o,d=n("2877"),v=Object(d["a"])(i,a,s,!1,null,null,null);t["default"]=v.exports}}]);
|
||||
1
dist/js/chunk-2d22dd95.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d22dd95"],{f8f3:function(n,t,u){"use strict";u.r(t);var _=function(){var n=this;n._self._c;return n._m(0)},c=[function(){var n=this,t=n._self._c;return t("div",[t("h1",[n._v("如何编辑数学公式")]),t("p",[n._v("数学公式只在开启了【富文本】编辑模式下才可使用。")]),t("p",[n._v("首先可以激活节点,然后点击上方工具栏中的【公式】打开右侧的格式侧边栏,然后再输入框中输入公式后点击【完成】即可将公式输入节点。")]),t("p",[n._v("当你再次双击节点时,公式会转换成源码,你可以直接修改,回车完成后即可渲染。")]),t("p",[n._v("所以你也可以不通过侧边栏,直接在文本编辑框中输入公式,不过公式的源码前后必须通过"),t("code",[n._v("$")]),n._v("符号包裹,否则不会解析为格式。")])])}],e={},v=e,p=u("2877"),r=Object(p["a"])(v,_,c,!1,null,null,null);t["default"]=r.exports}}]);
|
||||
1
dist/js/chunk-428b560e.js
vendored
Normal file
76
dist/js/chunk-5b7dab2e.js
vendored
Normal file
1
dist/js/chunk-6fd88c2d.js
vendored
Normal file
76
dist/js/chunk-8bd0d5d4.js
vendored
38
index.html
@@ -1,24 +1,28 @@
|
||||
<!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,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1"><link rel="icon" href="dist/logo.ico"><title>思绪思维导图</title><script>// 自定义静态资源的路径
|
||||
window.externalPublicPath = './dist/'
|
||||
// 接管应用
|
||||
window.takeOverApp = false</script><link href="dist/css/chunk-vendors.css?78d97b280dca5d10b8bc" rel="stylesheet"><link href="dist/css/app.css?78d97b280dca5d10b8bc" 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>const getDataFromBackend = () => {
|
||||
window.takeOverApp = false</script><script charset="UTF-8" id="LA_COLLECT" src="//sdk.51.la/js-sdk-pro.min.js"></script><script>LA.init({
|
||||
id: 'KRO0WxK8GT66tYCQ',
|
||||
ck: 'KRO0WxK8GT66tYCQ',
|
||||
autoTrack: false
|
||||
})</script><link href="dist/css/chunk-vendors.css?2446f7fb257c977cc9e7" rel="stylesheet"><link href="dist/css/app.css?2446f7fb257c977cc9e7" 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>const getDataFromBackend = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
mindMapData: {
|
||||
root:{
|
||||
"data": {
|
||||
"text": "根节点"
|
||||
root: {
|
||||
data: {
|
||||
text: '根节点'
|
||||
},
|
||||
"children": []
|
||||
children: []
|
||||
},
|
||||
theme:{
|
||||
"template":"avocado",
|
||||
"config":{}
|
||||
theme: {
|
||||
template: 'avocado',
|
||||
config: {}
|
||||
},
|
||||
layout:"logicalStructure",
|
||||
layout: 'logicalStructure',
|
||||
config: {},
|
||||
view: null,
|
||||
view: null
|
||||
},
|
||||
lang: 'zh',
|
||||
localConfig: null
|
||||
@@ -26,14 +30,14 @@
|
||||
}, 200)
|
||||
})
|
||||
}
|
||||
const setTakeOverAppMethods = (data) => {
|
||||
const setTakeOverAppMethods = data => {
|
||||
window.takeOverAppMethods = {}
|
||||
// 获取思维导图数据的函数
|
||||
window.takeOverAppMethods.getMindMapData = () => {
|
||||
return data.mindMapData
|
||||
}
|
||||
}
|
||||
// 保存思维导图数据的函数
|
||||
window.takeOverAppMethods.saveMindMapData = (data) => {
|
||||
window.takeOverAppMethods.saveMindMapData = data => {
|
||||
console.log(data)
|
||||
}
|
||||
// 获取语言的函数
|
||||
@@ -41,7 +45,7 @@
|
||||
return data.lang
|
||||
}
|
||||
// 保存语言的函数
|
||||
window.takeOverAppMethods.saveLanguage = (lang) => {
|
||||
window.takeOverAppMethods.saveLanguage = lang => {
|
||||
console.log(lang)
|
||||
}
|
||||
// 获取本地配置的函数
|
||||
@@ -49,7 +53,7 @@
|
||||
return data.localConfig
|
||||
}
|
||||
// 保存本地配置的函数
|
||||
window.takeOverAppMethods.saveLocalConfig = (config) => {
|
||||
window.takeOverAppMethods.saveLocalConfig = config => {
|
||||
console.log(config)
|
||||
}
|
||||
}
|
||||
@@ -60,10 +64,10 @@
|
||||
// 设置全局的方法
|
||||
setTakeOverAppMethods(data)
|
||||
// 思维导图实例创建完成事件
|
||||
window.$bus.$on('app_inited', (mindMap) => {
|
||||
window.$bus.$on('app_inited', mindMap => {
|
||||
console.log(mindMap)
|
||||
})
|
||||
// 可以通过window.$bus.$on()来监听应用的一些事件
|
||||
// 实例化页面
|
||||
window.initApp()
|
||||
}</script><script src="dist/js/chunk-vendors.js?78d97b280dca5d10b8bc"></script><script src="dist/js/app.js?78d97b280dca5d10b8bc"></script></body></html>
|
||||
}</script><script src="dist/js/chunk-vendors.js?2446f7fb257c977cc9e7"></script><script src="dist/js/app.js?2446f7fb257c977cc9e7"></script></body></html>
|
||||
@@ -16,6 +16,8 @@ import Painter from './src/plugins/Painter.js'
|
||||
import Scrollbar from './src/plugins/Scrollbar.js'
|
||||
import Formula from './src/plugins/Formula.js'
|
||||
import RainbowLines from './src/plugins/RainbowLines.js'
|
||||
import Demonstrate from './src/plugins/Demonstrate.js'
|
||||
import OuterFrame from './src/plugins/OuterFrame.js'
|
||||
import xmind from './src/parse/xmind.js'
|
||||
import markdown from './src/parse/markdown.js'
|
||||
import icons from './src/svg/icons.js'
|
||||
@@ -29,7 +31,7 @@ MindMap.iconList = icons.nodeIconList
|
||||
MindMap.constants = constants
|
||||
MindMap.themes = themes
|
||||
MindMap.defaultTheme = defaultTheme
|
||||
MindMap.version = '0.9.12'
|
||||
MindMap.version = '0.10.2-fix.1'
|
||||
|
||||
MindMap.usePlugin(MiniMap)
|
||||
.usePlugin(Watermark)
|
||||
@@ -48,5 +50,7 @@ MindMap.usePlugin(MiniMap)
|
||||
.usePlugin(Scrollbar)
|
||||
.usePlugin(Formula)
|
||||
.usePlugin(RainbowLines)
|
||||
.usePlugin(Demonstrate)
|
||||
.usePlugin(OuterFrame)
|
||||
|
||||
export default MindMap
|
||||
|
||||
@@ -136,10 +136,8 @@ class MindMap {
|
||||
this.associativeLineDraw.addClass('smm-associative-line-container')
|
||||
}
|
||||
// 画布
|
||||
this.svg = SVG()
|
||||
.addTo(this.el)
|
||||
.size(this.width, this.height)
|
||||
|
||||
this.svg = SVG().addTo(this.el).size(this.width, this.height)
|
||||
|
||||
// 容器
|
||||
this.draw = this.svg.group()
|
||||
this.draw.addClass('smm-container')
|
||||
@@ -520,7 +518,6 @@ class MindMap {
|
||||
// 恢复原先的大小和变换信息
|
||||
svg.size(origWidth, origHeight)
|
||||
draw.transform(origTransform)
|
||||
|
||||
return {
|
||||
svg: clone, // 思维导图图形的整体svg元素,包括:svg(画布容器)、g(实际的思维导图组)
|
||||
svgHTML: clone.svg(), // svg字符串
|
||||
|
||||
4
simple-mind-map/package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.9.8",
|
||||
"version": "0.10.0-fix.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "0.9.8",
|
||||
"version": "0.10.0-fix.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@svgdotjs/svg.js": "^3.0.16",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.9.12",
|
||||
"version": "0.10.2-fix.1",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
|
||||
@@ -174,6 +174,7 @@ export const CONSTANTS = {
|
||||
},
|
||||
LAYOUT: {
|
||||
LOGICAL_STRUCTURE: 'logicalStructure',
|
||||
LOGICAL_STRUCTURE_LEFT: 'logicalStructureLeft',
|
||||
MIND_MAP: 'mindMap',
|
||||
ORGANIZATION_STRUCTURE: 'organizationStructure',
|
||||
CATALOG_ORGANIZATION: 'catalogOrganization',
|
||||
@@ -251,6 +252,10 @@ export const layoutList = [
|
||||
name: '逻辑结构图',
|
||||
value: CONSTANTS.LAYOUT.LOGICAL_STRUCTURE
|
||||
},
|
||||
{
|
||||
name: '向左逻辑结构图',
|
||||
value: CONSTANTS.LAYOUT.LOGICAL_STRUCTURE_LEFT
|
||||
},
|
||||
{
|
||||
name: '思维导图',
|
||||
value: CONSTANTS.LAYOUT.MIND_MAP
|
||||
@@ -282,6 +287,7 @@ export const layoutList = [
|
||||
]
|
||||
export const layoutValueList = [
|
||||
CONSTANTS.LAYOUT.LOGICAL_STRUCTURE,
|
||||
CONSTANTS.LAYOUT.LOGICAL_STRUCTURE_LEFT,
|
||||
CONSTANTS.LAYOUT.MIND_MAP,
|
||||
CONSTANTS.LAYOUT.CATALOG_ORGANIZATION,
|
||||
CONSTANTS.LAYOUT.ORGANIZATION_STRUCTURE,
|
||||
@@ -314,7 +320,9 @@ export const nodeDataNoStylePropList = [
|
||||
'associativeLinePoint',
|
||||
'associativeLineText',
|
||||
'attachmentUrl',
|
||||
'attachmentName'
|
||||
'attachmentName',
|
||||
'notation',
|
||||
'outerFrame'
|
||||
]
|
||||
|
||||
// 错误类型
|
||||
|
||||
@@ -2,6 +2,11 @@ import { CONSTANTS } from './constant'
|
||||
|
||||
// 默认选项配置
|
||||
export const defaultOpt = {
|
||||
// 【基本】
|
||||
// 容器元素,必传,必须为DOM元素
|
||||
el: null,
|
||||
// 思维导图回显数据
|
||||
data: null,
|
||||
// 是否只读
|
||||
readonly: false,
|
||||
// 布局
|
||||
@@ -24,10 +29,6 @@ export const defaultOpt = {
|
||||
imgTextMargin: 5,
|
||||
// 节点里各种文字信息的间距,如图标和文字的间距
|
||||
textContentMargin: 2,
|
||||
// 多选节点时鼠标移动到边缘时的画布移动偏移量
|
||||
selectTranslateStep: 3,
|
||||
// 多选节点时鼠标移动距边缘多少距离时开始偏移
|
||||
selectTranslateLimit: 20,
|
||||
// 自定义节点备注内容显示
|
||||
customNoteContentShow: null,
|
||||
/*
|
||||
@@ -36,21 +37,6 @@ export const defaultOpt = {
|
||||
hide(){}
|
||||
}
|
||||
*/
|
||||
// 是否开启节点自由拖拽
|
||||
enableFreeDrag: false,
|
||||
// 水印配置
|
||||
watermarkConfig: {
|
||||
onlyExport: false, // 是否仅在导出时添加水印
|
||||
text: '',
|
||||
lineSpacing: 100,
|
||||
textSpacing: 100,
|
||||
angle: 30,
|
||||
textStyle: {
|
||||
color: '#999',
|
||||
opacity: 0.5,
|
||||
fontSize: 14
|
||||
}
|
||||
},
|
||||
// 达到该宽度文本自动换行
|
||||
textAutoWrapWidth: 500,
|
||||
// 自定义鼠标滚轮事件处理
|
||||
@@ -88,9 +74,6 @@ export const defaultOpt = {
|
||||
enableShortcutOnlyWhenMouseInSvg: true,
|
||||
// 初始根节点的位置
|
||||
initRootNodePosition: null,
|
||||
// 导出png、svg、pdf时的图形内边距,注意是单侧内边距
|
||||
exportPaddingX: 10,
|
||||
exportPaddingY: 10,
|
||||
// 节点文本编辑框的z-index
|
||||
nodeTextEditZIndex: 3000,
|
||||
// 节点备注浮层的z-index
|
||||
@@ -116,8 +99,6 @@ export const defaultOpt = {
|
||||
],
|
||||
// 节点最大缓存数量
|
||||
maxNodeCacheCount: 1000,
|
||||
// 关联线默认文字
|
||||
defaultAssociativeLineText: '关联',
|
||||
// 思维导图适应画布大小时的内边距
|
||||
fitPadding: 50,
|
||||
// 是否开启按住ctrl键多选节点功能
|
||||
@@ -132,14 +113,9 @@ export const defaultOpt = {
|
||||
customCreateNodeContent: null,
|
||||
// 指定内部一些元素(节点文本编辑元素、节点备注显示元素、关联线文本编辑元素、节点图片调整按钮元素)添加到的位置,默认添加到document.body下
|
||||
customInnerElsAppendTo: null,
|
||||
// 拖拽元素时,指示元素新位置的块的最大高度
|
||||
nodeDragPlaceholderMaxSize: 20,
|
||||
// 是否在存在一个激活节点时,当按下中文、英文、数字按键时自动进入文本编辑模式
|
||||
// 开启该特性后,需要给你的输入框绑定keydown事件,并禁止冒泡
|
||||
enableAutoEnterTextEditWhenKeydown: false,
|
||||
// 设置富文本节点编辑框和节点大小一致,形成伪原地编辑的效果
|
||||
// 需要注意的是,只有当节点内只有文本、且形状是矩形才会有比较好的效果
|
||||
richTextEditFakeInPlace: false,
|
||||
// 自定义对剪贴板文本的处理。当按ctrl+v粘贴时会读取用户剪贴板中的文本和图片,默认只会判断文本是否是普通文本和simple-mind-map格式的节点数据,如果你想处理其他思维导图的数据,比如processon、zhixi等,那么可以传递一个函数,接受当前剪贴板中的文本为参数,返回处理后的数据,可以返回两种类型:
|
||||
/*
|
||||
1.返回一个纯文本,那么会直接以该文本创建一个子节点
|
||||
@@ -161,26 +137,12 @@ export const defaultOpt = {
|
||||
customHandleClipboardText: null,
|
||||
// 禁止鼠标滚轮缩放,你仍旧可以使用api进行缩放
|
||||
disableMouseWheelZoom: false,
|
||||
// 禁止双指缩放,你仍旧可以使用api进行缩放
|
||||
// 需要注册TouchEvent插件后生效
|
||||
disableTouchZoom: false,
|
||||
// 错误处理函数
|
||||
errorHandler: (code, error) => {
|
||||
console.error(code, error)
|
||||
},
|
||||
// 设置导出图片和svg时,针对富文本节点内容,也就是嵌入到svg中的html节点的默认样式覆盖
|
||||
// 如果不覆盖,会发生偏移问题
|
||||
resetCss: `
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`,
|
||||
// 是否在鼠标双击时回到根节点,也就是让根节点居中显示
|
||||
enableDblclickBackToRootNode: false,
|
||||
// 导出图片时canvas的缩放倍数,该配置会和window.devicePixelRatio值取最大值
|
||||
minExportImgCanvasScale: 2,
|
||||
// 节点鼠标hover和激活时显示的矩形边框的颜色
|
||||
hoverRectColor: 'rgb(94, 200, 248)',
|
||||
// 节点鼠标hover和激活时显示的矩形边框距节点内容的距离
|
||||
@@ -189,23 +151,8 @@ export const defaultOpt = {
|
||||
selectTextOnEnterEditText: false,
|
||||
// 删除节点后激活相邻节点
|
||||
deleteNodeActive: true,
|
||||
// 拖拽节点时鼠标移动到画布边缘是否开启画布自动移动
|
||||
autoMoveWhenMouseInEdgeOnDrag: true,
|
||||
// 是否首次加载fit view
|
||||
fit: false,
|
||||
// 拖拽多个节点时随鼠标移动的示意矩形的样式配置
|
||||
dragMultiNodeRectConfig: {
|
||||
width: 40,
|
||||
height: 20,
|
||||
fill: '' // 填充颜色,如果不传默认使用连线的颜色
|
||||
},
|
||||
// 节点拖拽时新位置的示意矩形的填充颜色,如果不传默认使用连线的颜色
|
||||
dragPlaceholderRectFill: '',
|
||||
// 节点拖拽时的透明度配置
|
||||
dragOpacityConfig: {
|
||||
cloneNodeOpacity: 0.5, // 跟随鼠标移动的克隆节点或矩形的透明度
|
||||
beingDragNodeOpacity: 0.3 // 被拖拽节点的透明度
|
||||
},
|
||||
// 自定义标签的颜色
|
||||
// {pass: 'green, unpass: 'red'}
|
||||
tagsColorMap: {},
|
||||
@@ -214,9 +161,8 @@ export const defaultOpt = {
|
||||
avatarSize: 22, // 头像大小
|
||||
fontSize: 12 // 如果是文字头像,那么文字的大小
|
||||
},
|
||||
// 关联线是否始终显示在节点上层
|
||||
// false:即创建关联线和激活关联线时处于最顶层,其他情况下处于节点下方
|
||||
associativeLineIsAlwaysAboveNode: true,
|
||||
// 协同编辑时,同一个节点不能同时被多人选中
|
||||
onlyOneEnableActiveNodeOnCooperate: false,
|
||||
// 插入概要的默认文本
|
||||
defaultGeneralizationText: '概要',
|
||||
// 粘贴文本的方式创建新节点时,控制是否按换行自动分割节点,即如果存在换行,那么会根据换行创建多个节点,否则只会创建一个节点
|
||||
@@ -243,8 +189,6 @@ export const defaultOpt = {
|
||||
// 是否将思维导图限制在画布内
|
||||
// 比如向右拖动时,思维导图图形的最左侧到达画布中心时将无法继续向右拖动,其他同理
|
||||
isLimitMindMapInCanvas: false,
|
||||
// 当注册了滚动条插件(Scrollbar)时,是否将思维导图限制在画布内,isLimitMindMapInCanvas不再起作用
|
||||
isLimitMindMapInCanvasWhenHasScrollbar: true,
|
||||
// 在节点上粘贴剪贴板中的图片的处理方法,默认是转换为data:url数据插入到节点中,你可以通过该方法来将图片数据上传到服务器,实现保存图片的url
|
||||
// 可以传递一个异步方法,接收Blob类型的图片数据,需要返回如下结构:
|
||||
/*
|
||||
@@ -257,15 +201,6 @@ export const defaultOpt = {
|
||||
}
|
||||
*/
|
||||
handleNodePasteImg: null,
|
||||
// 默认情况下,新创建的关联线两个端点的位置是根据两个节点中心点的相对位置来计算的,如果你想固定位置,可以通过这个属性来配置
|
||||
// from和to都不传,则都自动计算,如果只传一个,另一个则会自动计算
|
||||
associativeLineInitPointsPosition: {
|
||||
// from和to可选值:left、top、bottom、right
|
||||
from: '', // 关联线起始节点上端点的位置
|
||||
to: '' // 关联线目标节点上端点的位置
|
||||
},
|
||||
// 是否允许调整关联线两个端点的位置
|
||||
enableAdjustAssociativeLinePoints: true,
|
||||
// 自定义创建节点形状的方法,可以传一个函数,均接收一个参数
|
||||
// 矩形、圆角矩形、椭圆、圆等形状会调用该方法
|
||||
// 接收svg path字符串,返回svg节点
|
||||
@@ -276,10 +211,139 @@ export const defaultOpt = {
|
||||
// 自定义转换节点连线路径的方法
|
||||
// 接收svg path字符串,返回转换后的svg path字符串
|
||||
customTransformNodeLinePath: null,
|
||||
// 快捷键操作即将执行前的生命周期函数,返回true可以阻止操作执行
|
||||
// 函数接收两个参数:key(快捷键)、activeNodeList(当前激活的节点列表)
|
||||
beforeShortcutRun: null,
|
||||
// 移动节点到画布中心、回到根节点等操作时是否将缩放层级复位为100%
|
||||
// 该选项实际影响的是render.moveNodeToCenter方法,moveNodeToCenter方法本身也存在第二个参数resetScale来设置是否复位,如果resetScale参数没有传递,那么使用resetScaleOnMoveNodeToCenter配置,否则使用resetScale配置
|
||||
resetScaleOnMoveNodeToCenter: false,
|
||||
// 添加附加的节点前置内容,前置内容指和文本同一行的区域中的前置内容,不包括节点图片部分
|
||||
createNodePrefixContent: null,
|
||||
// 添加附加的节点后置内容,后置内容指和文本同一行的区域中的后置内容,不包括节点图片部分
|
||||
createNodePostfixContent: null,
|
||||
// 禁止粘贴用户剪贴板中的数据,禁止将复制的数据写入用户的剪贴板中
|
||||
disabledClipboard: false,
|
||||
// 自定义超链接的跳转
|
||||
// 如果不传,默认会以新窗口的方式打开超链接,可以传递一个函数,函数接收两个参数:link(超链接的url)、node(所属节点实例),只要传递了函数,就会阻止默认的跳转
|
||||
customHyperlinkJump: null,
|
||||
|
||||
// 【Select插件】
|
||||
// 多选节点时鼠标移动到边缘时的画布移动偏移量
|
||||
selectTranslateStep: 3,
|
||||
// 多选节点时鼠标移动距边缘多少距离时开始偏移
|
||||
selectTranslateLimit: 20,
|
||||
|
||||
// 【Drag插件】
|
||||
// 是否开启节点自由拖拽
|
||||
enableFreeDrag: false,
|
||||
// 拖拽节点时鼠标移动到画布边缘是否开启画布自动移动
|
||||
autoMoveWhenMouseInEdgeOnDrag: true,
|
||||
// 拖拽多个节点时随鼠标移动的示意矩形的样式配置
|
||||
dragMultiNodeRectConfig: {
|
||||
width: 40,
|
||||
height: 20,
|
||||
fill: 'rgb(94, 200, 248)' // 填充颜色
|
||||
},
|
||||
// 节点拖拽时新位置的示意矩形的填充颜色
|
||||
dragPlaceholderRectFill: 'rgb(94, 200, 248)',
|
||||
// 节点拖拽时新位置的示意连线的样式配置
|
||||
dragPlaceholderLineConfig: {
|
||||
color: 'rgb(94, 200, 248)',
|
||||
width: 2
|
||||
},
|
||||
// 节点拖拽时的透明度配置
|
||||
dragOpacityConfig: {
|
||||
cloneNodeOpacity: 0.5, // 跟随鼠标移动的克隆节点或矩形的透明度
|
||||
beingDragNodeOpacity: 0.3 // 被拖拽节点的透明度
|
||||
},
|
||||
// 拖拽单个节点时会克隆被拖拽节点,如果想修改该克隆节点,那么可以通过该选项提供一个处理函数,函数接收克隆节点对象
|
||||
// 需要注意的是节点对象指的是@svgdotjs/svg.js库的元素对象,所以你需要阅读该库的文档来操作该对象
|
||||
handleDragCloneNode: null,
|
||||
// 即将拖拽完成前调用该函数,函数接收一个对象作为参数:{overlapNodeUid,prevNodeUid,nextNodeUid},代表拖拽信息,如果要阻止本次拖拽,那么可以返回true,此时node_dragend事件不会再触发。函数可以是异步函数,返回Promise实例
|
||||
beforeDragEnd: null,
|
||||
// 即将开始调整节点前调用该函数,函数接收当前即将被拖拽的节点实例列表作为参数,如果要阻止本次拖拽,那么可以返回true
|
||||
beforeDragStart: null,
|
||||
|
||||
// 【Watermark插件】
|
||||
// 水印配置
|
||||
watermarkConfig: {
|
||||
onlyExport: false, // 是否仅在导出时添加水印
|
||||
text: '',
|
||||
lineSpacing: 100,
|
||||
textSpacing: 100,
|
||||
angle: 30,
|
||||
textStyle: {
|
||||
color: '#999',
|
||||
opacity: 0.5,
|
||||
fontSize: 14
|
||||
},
|
||||
belowNode: false
|
||||
},
|
||||
|
||||
// 【Export插件】
|
||||
// 导出png、svg、pdf时的图形内边距,注意是单侧内边距
|
||||
exportPaddingX: 10,
|
||||
exportPaddingY: 10,
|
||||
// 设置导出图片和svg时,针对富文本节点内容,也就是嵌入到svg中的html节点的默认样式覆盖
|
||||
// 如果不覆盖,会发生偏移问题
|
||||
resetCss: `
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`,
|
||||
// 导出图片时canvas的缩放倍数,该配置会和window.devicePixelRatio值取最大值
|
||||
minExportImgCanvasScale: 2,
|
||||
// 导出png、svg、pdf时在头部和尾部添加自定义内容
|
||||
// 可传递一个函数,这个函数可以返回null代表不添加内容,也可以返回如下数据:
|
||||
/*
|
||||
{
|
||||
el,// 要追加的自定义DOM节点,样式可内联
|
||||
cssText,// 可选,如果样式不想内联,可以传递该值,一个css字符串
|
||||
height: 50// 返回的DOM节点的高度,必须传递
|
||||
}
|
||||
*/
|
||||
addContentToHeader: null,
|
||||
addContentToFooter: null,
|
||||
// 导出png、svg、pdf时会获取画布上的svg数据进行克隆,然后通过该克隆的元素进行导出,如果你想对该克隆元素做一些处理,比如新增、替换、修改其中的一些元素,那么可以通过该参数传递一个处理函数,接收svg元素对象,处理后,需要返回原svg元素对象。
|
||||
// 需要注意的是svg对象指的是@svgdotjs/svg.js库的元素对象,所以你需要阅读该库的文档来操作该对象
|
||||
handleBeingExportSvg: null,
|
||||
|
||||
// 【AssociativeLine插件】
|
||||
// 关联线默认文字
|
||||
defaultAssociativeLineText: '关联',
|
||||
// 关联线是否始终显示在节点上层
|
||||
// false:即创建关联线和激活关联线时处于最顶层,其他情况下处于节点下方
|
||||
associativeLineIsAlwaysAboveNode: true,
|
||||
// 默认情况下,新创建的关联线两个端点的位置是根据两个节点中心点的相对位置来计算的,如果你想固定位置,可以通过这个属性来配置
|
||||
// from和to都不传,则都自动计算,如果只传一个,另一个则会自动计算
|
||||
associativeLineInitPointsPosition: {
|
||||
// from和to可选值:left、top、bottom、right
|
||||
from: '', // 关联线起始节点上端点的位置
|
||||
to: '' // 关联线目标节点上端点的位置
|
||||
},
|
||||
// 是否允许调整关联线两个端点的位置
|
||||
enableAdjustAssociativeLinePoints: true,
|
||||
|
||||
// 【TouchEvent插件】
|
||||
// 禁止双指缩放,你仍旧可以使用api进行缩放
|
||||
// 需要注册TouchEvent插件后生效
|
||||
disableTouchZoom: false,
|
||||
// 允许最大和最小的缩放值,百分数
|
||||
// 传-1代表不限制
|
||||
minTouchZoomScale: 20,
|
||||
maxTouchZoomScale: -1,
|
||||
|
||||
// 【Scrollbar插件】
|
||||
// 当注册了滚动条插件(Scrollbar)时,是否将思维导图限制在画布内,isLimitMindMapInCanvas不再起作用
|
||||
isLimitMindMapInCanvasWhenHasScrollbar: true,
|
||||
|
||||
// 【Search插件】
|
||||
// 是否仅搜索当前渲染的节点,被收起的节点不会被搜索到
|
||||
isOnlySearchCurrentRenderNodes: false,
|
||||
// 协同编辑时,同一个节点不能同时被多人选中
|
||||
onlyOneEnableActiveNodeOnCooperate: false,
|
||||
|
||||
// 【Cooperate插件】
|
||||
// 协同编辑时,节点操作即将更新到其他客户端前的生命周期函数
|
||||
// 函数接收一个对象作为参数:
|
||||
/*
|
||||
@@ -289,9 +353,8 @@ export const defaultOpt = {
|
||||
}
|
||||
*/
|
||||
beforeCooperateUpdate: null,
|
||||
// 快捷键操作即将执行前的生命周期函数,返回true可以阻止操作执行
|
||||
// 函数接收两个参数:key(快捷键)、activeNodeList(当前激活的节点列表)
|
||||
beforeShortcutRun: null,
|
||||
|
||||
// 【RainbowLines插件】
|
||||
// 彩虹线条配置,需要先注册RainbowLines插件
|
||||
rainbowLinesConfig: {
|
||||
open: false, // 是否开启彩虹线条
|
||||
@@ -308,23 +371,21 @@ export const defaultOpt = {
|
||||
]
|
||||
*/
|
||||
},
|
||||
// 导出png、svg、pdf时在头部和尾部添加自定义内容
|
||||
// 可传递一个函数,这个函数可以返回null代表不添加内容,也可以返回如下数据:
|
||||
/*
|
||||
{
|
||||
el,// 要追加的自定义DOM节点,样式可内联
|
||||
cssText,// 可选,如果样式不想内联,可以传递该值,一个css字符串
|
||||
height: 50// 返回的DOM节点的高度,必须传递
|
||||
}
|
||||
*/
|
||||
addContentToHeader: null,
|
||||
addContentToFooter: null,
|
||||
|
||||
// 【Demonstrate插件】
|
||||
// 演示插件配置
|
||||
demonstrateConfig: null,
|
||||
// 移动节点到画布中心、回到根节点等操作时是否将缩放层级复位为100%
|
||||
resetScaleOnMoveNodeToCenter: false,
|
||||
// 添加附加的节点前置内容,前置内容指和文本同一行的区域中的前置内容,不包括节点图片部分
|
||||
createNodePrefixContent: null,
|
||||
// 添加附加的节点后置内容,后置内容指和文本同一行的区域中的后置内容,不包括节点图片部分
|
||||
createNodePostfixContent: null
|
||||
|
||||
// 【Formula插件】
|
||||
// 是否开启在富文本编辑框中直接编辑数学公式
|
||||
enableEditFormulaInRichTextEdit: true,
|
||||
|
||||
// 【RichText插件】
|
||||
// 转换富文本内容,当进入富文本编辑时,可以通过该参数传递一个函数,函数接收文本内容,需要返回你处理后的文本内容
|
||||
transformRichTextOnEnterEdit: null,
|
||||
// 可以传递一个函数,即将结束富文本编辑前会执行该函数,函数接收richText实例,所以你可以在此时机更新quill文档数据
|
||||
beforeHideRichTextEdit: null,
|
||||
// 设置富文本节点编辑框和节点大小一致,形成伪原地编辑的效果
|
||||
// 需要注意的是,只有当节点内只有文本、且形状是矩形才会有比较好的效果
|
||||
richTextEditFakeInPlace: false
|
||||
}
|
||||
|
||||
@@ -99,6 +99,7 @@ class Event extends EventEmitter {
|
||||
|
||||
// 鼠标按下事件
|
||||
onMousedown(e) {
|
||||
e.preventDefault()
|
||||
// 鼠标左键
|
||||
if (e.which === 1) {
|
||||
this.isLeftMousedown = true
|
||||
@@ -114,6 +115,7 @@ class Event extends EventEmitter {
|
||||
|
||||
// 鼠标移动事件
|
||||
onMousemove(e) {
|
||||
e.preventDefault()
|
||||
let { useLeftKeySelectionRightKeyDrag } = this.mindMap.opt
|
||||
this.mousemovePos.x = e.clientX
|
||||
this.mousemovePos.y = e.clientY
|
||||
|
||||
@@ -42,6 +42,8 @@ import { Polygon } from '@svgdotjs/svg.js'
|
||||
const layouts = {
|
||||
// 逻辑结构图
|
||||
[CONSTANTS.LAYOUT.LOGICAL_STRUCTURE]: LogicalStructure,
|
||||
// 向左逻辑结构图
|
||||
[CONSTANTS.LAYOUT.LOGICAL_STRUCTURE_LEFT]: LogicalStructure,
|
||||
// 思维导图
|
||||
[CONSTANTS.LAYOUT.MIND_MAP]: MindMap,
|
||||
// 目录组织图
|
||||
@@ -448,6 +450,7 @@ class Render {
|
||||
this.mindMap.emit('node_tree_render_end')
|
||||
return
|
||||
}
|
||||
this.mindMap.emit('node_tree_render_start')
|
||||
// 计算布局
|
||||
this.layout.doLayout(root => {
|
||||
// 删除本次渲染时不再需要的节点
|
||||
@@ -465,7 +468,6 @@ class Render {
|
||||
// 渲染节点
|
||||
this.root.render(() => {
|
||||
this.isRendering = false
|
||||
this.mindMap.emit('node_tree_render_end')
|
||||
callback && callback()
|
||||
if (this.hasWaitRendering) {
|
||||
const params = this.waitRenderingParams
|
||||
@@ -485,6 +487,7 @@ class Render {
|
||||
this.mindMap.command.addHistory()
|
||||
}
|
||||
}
|
||||
this.mindMap.emit('node_tree_render_end')
|
||||
})
|
||||
})
|
||||
this.emitNodeActiveEvent()
|
||||
@@ -1012,14 +1015,18 @@ class Render {
|
||||
copy() {
|
||||
this.beingCopyData = this.copyNode()
|
||||
if (!this.beingCopyData) return
|
||||
setDataToClipboard(createSmmFormatData(this.beingCopyData))
|
||||
if (!this.mindMap.opt.disabledClipboard) {
|
||||
setDataToClipboard(createSmmFormatData(this.beingCopyData))
|
||||
}
|
||||
}
|
||||
|
||||
// 剪切节点
|
||||
cut() {
|
||||
this.mindMap.execCommand('CUT_NODE', copyData => {
|
||||
this.beingCopyData = copyData
|
||||
setDataToClipboard(createSmmFormatData(copyData))
|
||||
if (!this.mindMap.opt.disabledClipboard) {
|
||||
setDataToClipboard(createSmmFormatData(copyData))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1028,17 +1035,20 @@ class Render {
|
||||
const {
|
||||
errorHandler,
|
||||
handleIsSplitByWrapOnPasteCreateNewNode,
|
||||
handleNodePasteImg
|
||||
handleNodePasteImg,
|
||||
disabledClipboard
|
||||
} = this.mindMap.opt
|
||||
// 读取剪贴板的文字和图片
|
||||
let text = null
|
||||
let text = ''
|
||||
let img = null
|
||||
try {
|
||||
const res = await getDataFromClipboard()
|
||||
text = res.text
|
||||
img = res.img
|
||||
} catch (error) {
|
||||
errorHandler(ERROR_TYPES.READ_CLIPBOARD_ERROR, error)
|
||||
if (!disabledClipboard) {
|
||||
try {
|
||||
const res = await getDataFromClipboard()
|
||||
text = res.text || ''
|
||||
img = res.img || null
|
||||
} catch (error) {
|
||||
errorHandler(ERROR_TYPES.READ_CLIPBOARD_ERROR, error)
|
||||
}
|
||||
}
|
||||
// 检查剪切板数据是否有变化
|
||||
// 通过图片大小来判断图片是否发生变化,可能是不准确的,但是目前没有其他好方法
|
||||
@@ -1552,7 +1562,7 @@ class Render {
|
||||
// 切换激活节点的展开状态
|
||||
toggleActiveExpand() {
|
||||
this.activeNodeList.forEach(node => {
|
||||
if (node.nodeData.children.length <= 0) {
|
||||
if (node.nodeData.children.length <= 0 || node.isRoot) {
|
||||
return
|
||||
}
|
||||
this.toggleNodeExpand(node)
|
||||
@@ -1798,9 +1808,13 @@ class Render {
|
||||
}
|
||||
}
|
||||
|
||||
// 移动节点到画布中心
|
||||
moveNodeToCenter(node) {
|
||||
const { resetScaleOnMoveNodeToCenter } = this.mindMap.opt
|
||||
// 移动节点到画布中心
|
||||
// resetScale参数指定是否要将画布缩放值复位为100%,当你没有显式传递时,默认值为undefined,因为实例化选项的resetScaleOnMoveNodeToCenter配置也会决定是否复位缩放,所以当你没有显式传递时使用resetScaleOnMoveNodeToCenter配置,否则使用resetScale配置
|
||||
moveNodeToCenter(node, resetScale) {
|
||||
let { resetScaleOnMoveNodeToCenter } = this.mindMap.opt
|
||||
if (resetScale !== undefined) {
|
||||
resetScaleOnMoveNodeToCenter = resetScale
|
||||
}
|
||||
let { transform, state } = this.mindMap.view.getTransformData()
|
||||
let { left, top, width, height } = node
|
||||
if (!resetScaleOnMoveNodeToCenter) {
|
||||
|
||||
@@ -286,7 +286,6 @@ export default class TextEdit {
|
||||
|
||||
// 隐藏文本编辑框
|
||||
hideEditTextBox() {
|
||||
this.currentNode = null
|
||||
if (this.mindMap.richText) {
|
||||
return this.mindMap.richText.hideEditText()
|
||||
}
|
||||
@@ -305,8 +304,10 @@ export default class TextEdit {
|
||||
this.mindMap.emit(
|
||||
'hide_text_edit',
|
||||
this.textEditNode,
|
||||
this.renderer.activeNodeList
|
||||
this.renderer.activeNodeList,
|
||||
this.currentNode
|
||||
)
|
||||
this.currentNode = null
|
||||
this.textEditNode.style.display = 'none'
|
||||
this.textEditNode.innerHTML = ''
|
||||
this.textEditNode.style.fontFamily = 'inherit'
|
||||
|
||||
@@ -8,12 +8,18 @@ import nodeCreateContentsMethods from './nodeCreateContents'
|
||||
import nodeExpandBtnPlaceholderRectMethods from './nodeExpandBtnPlaceholderRect'
|
||||
import nodeCooperateMethods from './nodeCooperate'
|
||||
import { CONSTANTS } from '../../../constants/constant'
|
||||
import { copyNodeTree, createForeignObjectNode } from '../../../utils/index'
|
||||
import {
|
||||
copyNodeTree,
|
||||
createForeignObjectNode,
|
||||
createUid,
|
||||
addXmlns
|
||||
} from '../../../utils/index'
|
||||
|
||||
// 节点类
|
||||
class Node {
|
||||
// 构造函数
|
||||
constructor(opt = {}) {
|
||||
this.opt = opt
|
||||
// 节点数据
|
||||
this.nodeData = this.handleData(opt.data || {})
|
||||
// uid
|
||||
@@ -111,31 +117,35 @@ class Node {
|
||||
this.needLayout = false
|
||||
// 当前是否是隐藏状态
|
||||
this.isHide = false
|
||||
// 概要相关方法
|
||||
Object.keys(nodeGeneralizationMethods).forEach(item => {
|
||||
this[item] = nodeGeneralizationMethods[item].bind(this)
|
||||
})
|
||||
// 展开收起按钮相关方法
|
||||
Object.keys(nodeExpandBtnMethods).forEach(item => {
|
||||
this[item] = nodeExpandBtnMethods[item].bind(this)
|
||||
})
|
||||
// 展开收起按钮占位元素相关方法
|
||||
Object.keys(nodeExpandBtnPlaceholderRectMethods).forEach(item => {
|
||||
this[item] = nodeExpandBtnPlaceholderRectMethods[item].bind(this)
|
||||
})
|
||||
// 命令的相关方法
|
||||
Object.keys(nodeCommandWrapsMethods).forEach(item => {
|
||||
this[item] = nodeCommandWrapsMethods[item].bind(this)
|
||||
})
|
||||
// 创建节点内容的相关方法
|
||||
Object.keys(nodeCreateContentsMethods).forEach(item => {
|
||||
this[item] = nodeCreateContentsMethods[item].bind(this)
|
||||
})
|
||||
// 协同相关
|
||||
if (this.mindMap.cooperate) {
|
||||
Object.keys(nodeCooperateMethods).forEach(item => {
|
||||
this[item] = nodeCooperateMethods[item].bind(this)
|
||||
const proto = Object.getPrototypeOf(this)
|
||||
if (!proto.bindEvent) {
|
||||
// 概要相关方法
|
||||
Object.keys(nodeGeneralizationMethods).forEach(item => {
|
||||
proto[item] = nodeGeneralizationMethods[item]
|
||||
})
|
||||
// 展开收起按钮相关方法
|
||||
Object.keys(nodeExpandBtnMethods).forEach(item => {
|
||||
proto[item] = nodeExpandBtnMethods[item]
|
||||
})
|
||||
// 展开收起按钮占位元素相关方法
|
||||
Object.keys(nodeExpandBtnPlaceholderRectMethods).forEach(item => {
|
||||
proto[item] = nodeExpandBtnPlaceholderRectMethods[item]
|
||||
})
|
||||
// 命令的相关方法
|
||||
Object.keys(nodeCommandWrapsMethods).forEach(item => {
|
||||
proto[item] = nodeCommandWrapsMethods[item]
|
||||
})
|
||||
// 创建节点内容的相关方法
|
||||
Object.keys(nodeCreateContentsMethods).forEach(item => {
|
||||
proto[item] = nodeCreateContentsMethods[item]
|
||||
})
|
||||
// 协同相关
|
||||
if (this.mindMap.cooperate) {
|
||||
Object.keys(nodeCooperateMethods).forEach(item => {
|
||||
proto[item] = nodeCooperateMethods[item]
|
||||
})
|
||||
}
|
||||
proto.bindEvent = true
|
||||
}
|
||||
// 初始化
|
||||
this.getSize()
|
||||
@@ -195,10 +205,7 @@ class Node {
|
||||
}
|
||||
// 如果没有返回内容,那么还是使用内置的节点内容
|
||||
if (this._customNodeContent) {
|
||||
this._customNodeContent.setAttribute(
|
||||
'xmlns',
|
||||
'http://www.w3.org/1999/xhtml'
|
||||
)
|
||||
addXmlns(this._customNodeContent)
|
||||
return
|
||||
}
|
||||
this._imgData = this.createImgNode()
|
||||
@@ -211,9 +218,15 @@ class Node {
|
||||
this._prefixData = createNodePrefixContent
|
||||
? createNodePrefixContent(this)
|
||||
: null
|
||||
if (this._prefixData && this._prefixData.el) {
|
||||
addXmlns(this._prefixData.el)
|
||||
}
|
||||
this._postfixData = createNodePostfixContent
|
||||
? createNodePostfixContent(this)
|
||||
: null
|
||||
if (this._postfixData && this._postfixData.el) {
|
||||
addXmlns(this._postfixData.el)
|
||||
}
|
||||
}
|
||||
|
||||
// 计算节点的宽高
|
||||
@@ -414,7 +427,7 @@ class Node {
|
||||
;(this._textData.nodeContent || this._textData.node)
|
||||
.x(-oldX) // 修复非富文本模式下同时存在图标和换行的文本时,被收起和展开时图标与文字距离会逐渐拉大的问题
|
||||
.x(textContentOffsetX)
|
||||
.y(0)
|
||||
.y((this._rectInfo.textContentHeight - this._textData.height) / 2)
|
||||
textContentNested.add(this._textData.node)
|
||||
textContentOffsetX += this._textData.width + textContentItemMargin
|
||||
}
|
||||
@@ -480,6 +493,7 @@ class Node {
|
||||
)
|
||||
this.group.add(textContentNested)
|
||||
addHoverNode()
|
||||
this.mindMap.emit('node_layout_end', this)
|
||||
}
|
||||
|
||||
// 给节点绑定事件
|
||||
@@ -501,6 +515,7 @@ class Node {
|
||||
this.active(e)
|
||||
})
|
||||
this.group.on('mousedown', e => {
|
||||
e.preventDefault()
|
||||
const {
|
||||
readonly,
|
||||
enableCtrlKeyNodeSelection,
|
||||
@@ -521,7 +536,7 @@ class Node {
|
||||
}
|
||||
}
|
||||
// 多选和取消多选
|
||||
if ((e.ctrlKey || e.metaKey) && enableCtrlKeyNodeSelection) {
|
||||
if (!readonly && (e.ctrlKey || e.metaKey) && enableCtrlKeyNodeSelection) {
|
||||
this.isMultipleChoice = true
|
||||
let isActive = this.getData('isActive')
|
||||
if (!isActive)
|
||||
@@ -743,7 +758,9 @@ class Node {
|
||||
this.layout()
|
||||
this.update()
|
||||
} else {
|
||||
this.nodeDraw.add(this.group)
|
||||
if (!this.nodeDraw.has(this.group)) {
|
||||
this.nodeDraw.add(this.group)
|
||||
}
|
||||
if (this.needLayout) {
|
||||
this.needLayout = false
|
||||
this.layout()
|
||||
@@ -773,9 +790,9 @@ class Node {
|
||||
if (this.nodeData.inserting) {
|
||||
delete this.nodeData.inserting
|
||||
this.active()
|
||||
setTimeout(() => {
|
||||
this.mindMap.emit('node_dblclick', this, null, true)
|
||||
}, 0)
|
||||
// setTimeout(() => {
|
||||
this.mindMap.emit('node_dblclick', this, null, true)
|
||||
// }, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1175,6 +1192,19 @@ class Node {
|
||||
closeHighlight() {
|
||||
if (this.group) this.group.removeClass('smm-node-highlight')
|
||||
}
|
||||
|
||||
// 伪克隆节点
|
||||
// 克隆出的节点并不能真正当做一个节点使用
|
||||
fakeClone() {
|
||||
const newNode = new Node({
|
||||
...this.opt,
|
||||
uid: createUid()
|
||||
})
|
||||
Object.keys(this).forEach(item => {
|
||||
newNode[item] = this[item]
|
||||
})
|
||||
return newNode
|
||||
}
|
||||
}
|
||||
|
||||
export default Node
|
||||
|
||||
@@ -5,12 +5,12 @@ import {
|
||||
addHtmlStyle,
|
||||
checkIsRichText,
|
||||
isUndef,
|
||||
createForeignObjectNode
|
||||
createForeignObjectNode,
|
||||
addXmlns
|
||||
} from '../../../utils'
|
||||
import { Image as SVGImage, SVG, A, G, Rect, Text } from '@svgdotjs/svg.js'
|
||||
import iconsSvg from '../../../svg/icons'
|
||||
import { CONSTANTS } from '../../../constants/constant'
|
||||
import { defenseXSS } from '../../../utils/xss'
|
||||
|
||||
// 创建图片节点
|
||||
function createImgNode() {
|
||||
@@ -142,7 +142,7 @@ function createRichTextNode() {
|
||||
text: text
|
||||
})
|
||||
}
|
||||
let html = `<div>${defenseXSS(this.getData('text'))}</div>`
|
||||
let html = `<div>${this.getData('text')}</div>`
|
||||
if (!this.mindMap.commonCaches.measureRichtextNodeTextSizeEl) {
|
||||
this.mindMap.commonCaches.measureRichtextNodeTextSizeEl =
|
||||
document.createElement('div')
|
||||
@@ -158,7 +158,7 @@ function createRichTextNode() {
|
||||
div.innerHTML = html
|
||||
let el = div.children[0]
|
||||
el.classList.add('smm-richtext-node-wrap')
|
||||
el.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml')
|
||||
addXmlns(el)
|
||||
el.style.maxWidth = textAutoWrapWidth + 'px'
|
||||
let { width, height } = el.getBoundingClientRect()
|
||||
// 如果文本为空,那么需要计算一个默认高度
|
||||
@@ -254,12 +254,16 @@ function createHyperlinkNode() {
|
||||
if (!hyperlink) {
|
||||
return
|
||||
}
|
||||
const { customHyperlinkJump } = this.mindMap.opt
|
||||
let iconSize = this.mindMap.themeConfig.iconSize
|
||||
let node = new SVG().size(iconSize, iconSize)
|
||||
// 超链接节点
|
||||
let a = new A().to(hyperlink).target('_blank')
|
||||
a.node.addEventListener('click', e => {
|
||||
e.stopPropagation()
|
||||
if (typeof customHyperlinkJump === 'function') {
|
||||
e.preventDefault()
|
||||
customHyperlinkJump(hyperlink, this)
|
||||
}
|
||||
})
|
||||
if (hyperlinkTitle) {
|
||||
node.add(SVG(`<title>${hyperlinkTitle}</title>`))
|
||||
|
||||
@@ -29,8 +29,9 @@ class View {
|
||||
this.fit()
|
||||
})
|
||||
// 拖动视图
|
||||
this.mindMap.event.on('mousedown', () => {
|
||||
this.mindMap.event.on('mousedown', e => {
|
||||
if (this.mindMap.opt.isDisableDrag) return
|
||||
e.preventDefault()
|
||||
this.sx = this.x
|
||||
this.sy = this.y
|
||||
})
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun, getNodeIndexInNodeList } from '../utils'
|
||||
import { CONSTANTS } from '../constants/constant'
|
||||
|
||||
// 逻辑结构图
|
||||
class LogicalStructure extends Base {
|
||||
// 构造函数
|
||||
constructor(opt = {}) {
|
||||
constructor(opt = {}, layout) {
|
||||
super(opt)
|
||||
this.isUseLeft = layout === CONSTANTS.LAYOUT.LOGICAL_STRUCTURE_LEFT
|
||||
}
|
||||
|
||||
// 布局
|
||||
@@ -40,8 +42,15 @@ class LogicalStructure extends Base {
|
||||
} else {
|
||||
// 非根节点
|
||||
// 定位到父节点右侧
|
||||
newNode.left =
|
||||
parent._node.left + parent._node.width + this.getMarginX(layerIndex)
|
||||
if (this.isUseLeft) {
|
||||
newNode.left =
|
||||
parent._node.left - newNode.width - this.getMarginX(layerIndex)
|
||||
} else {
|
||||
newNode.left =
|
||||
parent._node.left +
|
||||
parent._node.width +
|
||||
this.getMarginX(layerIndex)
|
||||
}
|
||||
}
|
||||
if (!cur.data.expand) {
|
||||
return true
|
||||
@@ -167,15 +176,24 @@ class LogicalStructure extends Base {
|
||||
}
|
||||
let marginX = this.getMarginX(node.layerIndex + 1)
|
||||
let s1 = (marginX - expandBtnSize) * 0.6
|
||||
if (this.isUseLeft) {
|
||||
s1 *= -1
|
||||
}
|
||||
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
|
||||
node.children.forEach((item, index) => {
|
||||
let x1 =
|
||||
node.layerIndex === 0 ? left + width : left + width + expandBtnSize
|
||||
let x1
|
||||
if (this.isUseLeft) {
|
||||
x1 = node.layerIndex === 0 ? left : left - expandBtnSize
|
||||
} else {
|
||||
x1 = node.layerIndex === 0 ? left + width : left + width + expandBtnSize
|
||||
}
|
||||
let y1 = top + height / 2
|
||||
let x2 = item.left
|
||||
let x2 = this.isUseLeft ? item.left + item.width : item.left
|
||||
let y2 = item.top + item.height / 2
|
||||
// 节点使用横线风格,需要额外渲染横线
|
||||
let nodeUseLineStyleOffset = nodeUseLineStyle ? item.width : 0
|
||||
let nodeUseLineStyleOffset = nodeUseLineStyle
|
||||
? item.width * (this.isUseLeft ? -1 : 1)
|
||||
: 0
|
||||
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
|
||||
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
|
||||
let path = this.createFoldLine([
|
||||
@@ -202,15 +220,17 @@ class LogicalStructure extends Base {
|
||||
if (node.layerIndex === 0) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
let x1 = left + width + expandBtnSize
|
||||
let x1 = this.isUseLeft
|
||||
? left - expandBtnSize
|
||||
: left + width + expandBtnSize
|
||||
let y1 = top + height / 2
|
||||
let x2 = item.left
|
||||
let x2 = this.isUseLeft ? item.left + item.width : item.left
|
||||
let y2 = item.top + item.height / 2
|
||||
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
|
||||
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
|
||||
// 节点使用横线风格,需要额外渲染横线
|
||||
let nodeUseLineStylePath = nodeUseLineStyle
|
||||
? ` L ${item.left + item.width},${y2}`
|
||||
? ` L ${this.isUseLeft ? item.left : item.left + item.width},${y2}`
|
||||
: ''
|
||||
let path = `M ${x1},${y1} L ${x2},${y2}` + nodeUseLineStylePath
|
||||
this.setLineStyle(style, lines[index], path, item)
|
||||
@@ -235,20 +255,33 @@ class LogicalStructure extends Base {
|
||||
if (node.layerIndex === 0) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
let x1 =
|
||||
node.layerIndex === 0 && !rootLineStartPositionKeepSameInCurve
|
||||
? left + width / 2
|
||||
: left + width + expandBtnSize
|
||||
let x1
|
||||
if (this.isUseLeft) {
|
||||
x1 =
|
||||
node.layerIndex === 0 && !rootLineStartPositionKeepSameInCurve
|
||||
? left + width / 2
|
||||
: left - expandBtnSize
|
||||
} else {
|
||||
x1 =
|
||||
node.layerIndex === 0 && !rootLineStartPositionKeepSameInCurve
|
||||
? left + width / 2
|
||||
: left + width + expandBtnSize
|
||||
}
|
||||
let y1 = top + height / 2
|
||||
let x2 = item.left
|
||||
let x2 = this.isUseLeft ? item.left + item.width : item.left
|
||||
let y2 = item.top + item.height / 2
|
||||
let path = ''
|
||||
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
|
||||
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
|
||||
// 节点使用横线风格,需要额外渲染横线
|
||||
let nodeUseLineStylePath = nodeUseLineStyle
|
||||
? ` L ${item.left + item.width},${y2}`
|
||||
: ''
|
||||
let nodeUseLineStylePath
|
||||
if (this.isUseLeft) {
|
||||
nodeUseLineStylePath = nodeUseLineStyle ? ` L ${item.left},${y2}` : ''
|
||||
} else {
|
||||
nodeUseLineStylePath = nodeUseLineStyle
|
||||
? ` L ${item.left + item.width},${y2}`
|
||||
: ''
|
||||
}
|
||||
if (node.isRoot && !rootLineKeepSameInCurve) {
|
||||
path = this.quadraticCurvePath(x1, y1, x2, y2) + nodeUseLineStylePath
|
||||
} else {
|
||||
@@ -260,14 +293,17 @@ class LogicalStructure extends Base {
|
||||
|
||||
// 渲染按钮
|
||||
renderExpandBtn(node, btn) {
|
||||
let { width, height } = node
|
||||
let { width, height, expandBtnSize, layerIndex } = node
|
||||
if (layerIndex === 0) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
let { translateX, translateY } = btn.transform()
|
||||
// 节点使用横线风格,需要调整展开收起按钮位置
|
||||
let nodeUseLineStyleOffset = this.mindMap.themeConfig.nodeUseLineStyle
|
||||
? height / 2
|
||||
: 0
|
||||
// 位置没有变化则返回
|
||||
let _x = width
|
||||
let _x = this.isUseLeft ? 0 - expandBtnSize : width
|
||||
let _y = height / 2 + nodeUseLineStyleOffset
|
||||
if (_x === translateX && _y === translateY) {
|
||||
return
|
||||
@@ -279,29 +315,42 @@ class LogicalStructure extends Base {
|
||||
renderGeneralization(list) {
|
||||
list.forEach(item => {
|
||||
let {
|
||||
left,
|
||||
top,
|
||||
bottom,
|
||||
right,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
} = this.getNodeGeneralizationRenderBoundaries(item, 'h')
|
||||
let x1 = right + generalizationLineMargin
|
||||
let x = this.isUseLeft
|
||||
? left - generalizationLineMargin
|
||||
: right + generalizationLineMargin
|
||||
let x1 = x
|
||||
let y1 = top
|
||||
let x2 = right + generalizationLineMargin
|
||||
let x2 = x
|
||||
let y2 = bottom
|
||||
let cx = x1 + 20
|
||||
let cx = x1 + (this.isUseLeft ? -20 : 20)
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
item.generalizationLine.plot(path)
|
||||
item.generalizationNode.left = right + generalizationNodeMargin
|
||||
item.generalizationNode.left =
|
||||
x +
|
||||
(this.isUseLeft
|
||||
? -generalizationNodeMargin
|
||||
: generalizationNodeMargin) -
|
||||
(this.isUseLeft ? item.generalizationNode.width : 0)
|
||||
item.generalizationNode.top =
|
||||
top + (bottom - top - item.generalizationNode.height) / 2
|
||||
})
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
renderExpandBtnRect(rect, expandBtnSize, width, height, node) {
|
||||
rect.size(expandBtnSize, height).x(width).y(0)
|
||||
renderExpandBtnRect(rect, expandBtnSize, width, height) {
|
||||
if (this.isUseLeft) {
|
||||
rect.size(expandBtnSize, height).x(-expandBtnSize).y(0)
|
||||
} else {
|
||||
rect.size(expandBtnSize, height).x(width).y(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
} from '../utils/xmind'
|
||||
|
||||
// 解析.xmind文件
|
||||
const parseXmindFile = file => {
|
||||
const parseXmindFile = (file, handleMultiCanvas) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
JSZip.loadAsync(file).then(
|
||||
async zip => {
|
||||
@@ -25,7 +25,7 @@ const parseXmindFile = file => {
|
||||
let xmlFile = zip.files['content.xml'] || zip.files['/content.xml']
|
||||
if (jsonFile) {
|
||||
let json = await jsonFile.async('string')
|
||||
content = await transformXmind(json, zip.files)
|
||||
content = await transformXmind(json, zip.files, handleMultiCanvas)
|
||||
} else if (xmlFile) {
|
||||
let xml = await xmlFile.async('string')
|
||||
let json = xmlConvert.xml2json(xml)
|
||||
@@ -48,8 +48,15 @@ const parseXmindFile = file => {
|
||||
}
|
||||
|
||||
// 转换xmind数据
|
||||
const transformXmind = async (content, files) => {
|
||||
const data = JSON.parse(content)[0]
|
||||
const transformXmind = async (content, files, handleMultiCanvas) => {
|
||||
content = JSON.parse(content)
|
||||
let data = null
|
||||
if (content.length > 1 && typeof handleMultiCanvas === 'function') {
|
||||
data = await handleMultiCanvas(content)
|
||||
}
|
||||
if (!data) {
|
||||
data = content[0]
|
||||
}
|
||||
const nodeTree = data.rootTopic
|
||||
const newTree = {}
|
||||
const waitLoadImageList = []
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
getNodeIndexInNodeList
|
||||
} from '../utils'
|
||||
import Base from '../layouts/Base'
|
||||
import { CONSTANTS } from '../constants/constant'
|
||||
import AutoMove from '../utils/AutoMove'
|
||||
|
||||
// 节点拖动插件
|
||||
class Drag extends Base {
|
||||
@@ -12,13 +14,14 @@ class Drag extends Base {
|
||||
constructor({ mindMap }) {
|
||||
super(mindMap.renderer)
|
||||
this.mindMap = mindMap
|
||||
this.autoMove = new AutoMove(mindMap)
|
||||
this.reset()
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
// 复位
|
||||
reset() {
|
||||
// 是否正在跳转中
|
||||
// 是否正在拖拽中
|
||||
this.isDragging = false
|
||||
// 鼠标按下的节点
|
||||
this.mousedownNode = null
|
||||
@@ -38,6 +41,10 @@ class Drag extends Base {
|
||||
this.clone = null
|
||||
// 同级位置占位符
|
||||
this.placeholder = null
|
||||
this.placeholderWidth = 50
|
||||
this.placeholderHeight = 10
|
||||
this.placeHolderLine = null
|
||||
this.placeHolderExtraLines = []
|
||||
// 鼠标按下位置和节点左上角的偏移量
|
||||
this.offsetX = 0
|
||||
this.offsetY = 0
|
||||
@@ -118,13 +125,15 @@ class Drag extends Base {
|
||||
}
|
||||
|
||||
// 鼠标松开事件
|
||||
onMouseup(e) {
|
||||
async onMouseup(e) {
|
||||
if (!this.isMousedown) {
|
||||
return
|
||||
}
|
||||
const { autoMoveWhenMouseInEdgeOnDrag, enableFreeDrag, beforeDragEnd } =
|
||||
this.mindMap.opt
|
||||
// 停止自动移动
|
||||
if (this.mindMap.opt.autoMoveWhenMouseInEdgeOnDrag && this.mindMap.select) {
|
||||
this.mindMap.select.clearAutoMoveTimer()
|
||||
if (autoMoveWhenMouseInEdgeOnDrag && this.mindMap.select) {
|
||||
this.autoMove.clearAutoMoveTimer()
|
||||
}
|
||||
this.isMousedown = false
|
||||
// 恢复被拖拽节点的临时设置
|
||||
@@ -137,9 +146,21 @@ class Drag extends Base {
|
||||
let overlapNodeUid = this.overlapNode ? this.overlapNode.getData('uid') : ''
|
||||
let prevNodeUid = this.prevNode ? this.prevNode.getData('uid') : ''
|
||||
let nextNodeUid = this.nextNode ? this.nextNode.getData('uid') : ''
|
||||
if (this.isDragging && typeof beforeDragEnd === 'function') {
|
||||
const isCancel = await beforeDragEnd({
|
||||
overlapNodeUid,
|
||||
prevNodeUid,
|
||||
nextNodeUid,
|
||||
beingDragNodeList: [...this.beingDragNodeList]
|
||||
})
|
||||
if (isCancel) {
|
||||
this.reset()
|
||||
return
|
||||
}
|
||||
}
|
||||
// 存在重叠子节点,则移动作为其子节点
|
||||
if (this.overlapNode) {
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', this.overlapNode, false)
|
||||
this.removeNodeActive(this.overlapNode)
|
||||
this.mindMap.execCommand(
|
||||
'MOVE_NODE_TO',
|
||||
this.beingDragNodeList,
|
||||
@@ -147,7 +168,7 @@ class Drag extends Base {
|
||||
)
|
||||
} else if (this.prevNode) {
|
||||
// 存在前一个相邻节点,作为其下一个兄弟节点
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', this.prevNode, false)
|
||||
this.removeNodeActive(this.prevNode)
|
||||
this.mindMap.execCommand(
|
||||
'INSERT_AFTER',
|
||||
this.beingDragNodeList,
|
||||
@@ -155,7 +176,7 @@ class Drag extends Base {
|
||||
)
|
||||
} else if (this.nextNode) {
|
||||
// 存在下一个相邻节点,作为其前一个兄弟节点
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', this.nextNode, false)
|
||||
this.removeNodeActive(this.nextNode)
|
||||
this.mindMap.execCommand(
|
||||
'INSERT_BEFORE',
|
||||
this.beingDragNodeList,
|
||||
@@ -163,7 +184,7 @@ class Drag extends Base {
|
||||
)
|
||||
} else if (
|
||||
this.clone &&
|
||||
this.mindMap.opt.enableFreeDrag &&
|
||||
enableFreeDrag &&
|
||||
this.beingDragNodeList.length === 1
|
||||
) {
|
||||
// 如果只拖拽了一个节点,那么设置自定义位置
|
||||
@@ -196,9 +217,16 @@ class Drag extends Base {
|
||||
this.reset()
|
||||
}
|
||||
|
||||
// 移除节点的激活状态
|
||||
removeNodeActive(node) {
|
||||
if (node.getData('isActive')) {
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', node, false)
|
||||
}
|
||||
}
|
||||
|
||||
// 拖动中
|
||||
onMove(x, y, e) {
|
||||
if (!this.isMousedown) {
|
||||
if (!this.isMousedown || !this.isDragging) {
|
||||
return
|
||||
}
|
||||
// 更新克隆节点的位置
|
||||
@@ -211,18 +239,15 @@ class Drag extends Base {
|
||||
this.clone.translate(x - t.translateX, y - t.translateY)
|
||||
// 检测新位置
|
||||
this.checkOverlapNode()
|
||||
// 如果注册了多选节点插件,那么复用它的边缘自动移动画布功能
|
||||
if (this.mindMap.opt.autoMoveWhenMouseInEdgeOnDrag && this.mindMap.select) {
|
||||
this.drawTransform = this.mindMap.draw.transform()
|
||||
this.mindMap.select.clearAutoMoveTimer()
|
||||
this.mindMap.select.onMove(e.clientX, e.clientY)
|
||||
}
|
||||
// 边缘自动移动画布
|
||||
this.drawTransform = this.mindMap.draw.transform()
|
||||
this.autoMove.clearAutoMoveTimer()
|
||||
this.autoMove.onMove(e.clientX, e.clientY)
|
||||
}
|
||||
|
||||
// 开始拖拽时初始化一些数据
|
||||
handleStartMove() {
|
||||
async handleStartMove() {
|
||||
if (!this.isDragging) {
|
||||
this.isDragging = true
|
||||
// 鼠标按下的节点
|
||||
let node = this.mousedownNode
|
||||
// 计算鼠标按下的位置距离节点左上角的距离
|
||||
@@ -243,12 +268,19 @@ class Drag extends Base {
|
||||
// 否则只拖拽按下的节点
|
||||
this.beingDragNodeList = [node]
|
||||
}
|
||||
// 拦截拖拽
|
||||
const { beforeDragStart } = this.mindMap.opt
|
||||
if (typeof beforeDragStart === 'function') {
|
||||
const stop = await beforeDragStart([...this.beingDragNodeList])
|
||||
if (stop) return
|
||||
}
|
||||
// 将节点树转为节点数组
|
||||
this.nodeTreeToList()
|
||||
// 创建克隆节点
|
||||
this.createCloneNode()
|
||||
// 清除当前所有激活的节点
|
||||
this.mindMap.execCommand('CLEAR_ACTIVE_NODE')
|
||||
this.isDragging = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +308,9 @@ class Drag extends Base {
|
||||
const {
|
||||
dragMultiNodeRectConfig,
|
||||
dragPlaceholderRectFill,
|
||||
dragOpacityConfig
|
||||
dragPlaceholderLineConfig,
|
||||
dragOpacityConfig,
|
||||
handleDragCloneNode
|
||||
} = this.mindMap.opt
|
||||
const {
|
||||
width: rectWidth,
|
||||
@@ -305,13 +339,26 @@ class Drag extends Base {
|
||||
expandEl.remove()
|
||||
}
|
||||
this.mindMap.otherDraw.add(this.clone)
|
||||
if (typeof handleDragCloneNode === 'function') {
|
||||
handleDragCloneNode(this.clone)
|
||||
}
|
||||
}
|
||||
this.clone.opacity(dragOpacityConfig.cloneNodeOpacity)
|
||||
this.clone.css('z-index', 99999)
|
||||
// 同级位置提示元素
|
||||
this.placeholder = this.mindMap.otherDraw.rect().fill({
|
||||
color: dragPlaceholderRectFill || lineColor
|
||||
})
|
||||
this.placeholder = this.mindMap.otherDraw
|
||||
.rect()
|
||||
.fill({
|
||||
color: dragPlaceholderRectFill || lineColor
|
||||
})
|
||||
.radius(5)
|
||||
this.placeHolderLine = this.mindMap.otherDraw
|
||||
.path()
|
||||
.stroke({
|
||||
color: dragPlaceholderLineConfig.color || lineColor,
|
||||
width: dragPlaceholderLineConfig.width
|
||||
})
|
||||
.fill({ color: 'none' })
|
||||
// 当前被拖拽的节点的临时设置
|
||||
this.beingDragNodeList.forEach(node => {
|
||||
// 降低透明度
|
||||
@@ -331,6 +378,16 @@ class Drag extends Base {
|
||||
}
|
||||
this.clone.remove()
|
||||
this.placeholder.remove()
|
||||
this.placeHolderLine.remove()
|
||||
this.removeExtraLines()
|
||||
}
|
||||
|
||||
// 移除额外创建的连线
|
||||
removeExtraLines() {
|
||||
this.placeHolderExtraLines.forEach(item => {
|
||||
item.remove()
|
||||
})
|
||||
this.placeHolderExtraLines = []
|
||||
}
|
||||
|
||||
// 检测重叠节点
|
||||
@@ -338,10 +395,23 @@ class Drag extends Base {
|
||||
if (!this.drawTransform || !this.placeholder) {
|
||||
return
|
||||
}
|
||||
const {
|
||||
LOGICAL_STRUCTURE,
|
||||
LOGICAL_STRUCTURE_LEFT,
|
||||
MIND_MAP,
|
||||
ORGANIZATION_STRUCTURE,
|
||||
CATALOG_ORGANIZATION,
|
||||
TIMELINE,
|
||||
TIMELINE2,
|
||||
VERTICAL_TIMELINE,
|
||||
FISHBONE
|
||||
} = CONSTANTS.LAYOUT
|
||||
this.overlapNode = null
|
||||
this.prevNode = null
|
||||
this.nextNode = null
|
||||
this.placeholder.size(0, 0)
|
||||
this.placeHolderLine.hide()
|
||||
this.removeExtraLines()
|
||||
this.nodeList.forEach(node => {
|
||||
if (node.getData('isActive')) {
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', node, false)
|
||||
@@ -350,52 +420,325 @@ class Drag extends Base {
|
||||
return
|
||||
}
|
||||
switch (this.mindMap.opt.layout) {
|
||||
case 'logicalStructure':
|
||||
case LOGICAL_STRUCTURE:
|
||||
case LOGICAL_STRUCTURE_LEFT:
|
||||
this.handleLogicalStructure(node)
|
||||
break
|
||||
case 'mindMap':
|
||||
case MIND_MAP:
|
||||
this.handleMindMap(node)
|
||||
break
|
||||
case 'organizationStructure':
|
||||
case ORGANIZATION_STRUCTURE:
|
||||
this.handleOrganizationStructure(node)
|
||||
break
|
||||
case 'catalogOrganization':
|
||||
case CATALOG_ORGANIZATION:
|
||||
this.handleCatalogOrganization(node)
|
||||
break
|
||||
case 'timeline':
|
||||
case TIMELINE:
|
||||
this.handleTimeLine(node)
|
||||
break
|
||||
case 'timeline2':
|
||||
case TIMELINE2:
|
||||
this.handleTimeLine2(node)
|
||||
break
|
||||
case 'verticalTimeline':
|
||||
case VERTICAL_TIMELINE:
|
||||
this.handleLogicalStructure(node)
|
||||
break
|
||||
case 'fishbone':
|
||||
case FISHBONE:
|
||||
this.handleFishbone(node)
|
||||
break
|
||||
default:
|
||||
this.handleLogicalStructure(node)
|
||||
}
|
||||
})
|
||||
// 重叠节点,也就是添加为子节点
|
||||
if (this.overlapNode) {
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', this.overlapNode, true)
|
||||
this.handleOverlapNode()
|
||||
}
|
||||
}
|
||||
|
||||
// 处理作为子节点的情况
|
||||
handleOverlapNode() {
|
||||
const {
|
||||
LOGICAL_STRUCTURE,
|
||||
LOGICAL_STRUCTURE_LEFT,
|
||||
MIND_MAP,
|
||||
ORGANIZATION_STRUCTURE,
|
||||
CATALOG_ORGANIZATION,
|
||||
TIMELINE,
|
||||
TIMELINE2,
|
||||
VERTICAL_TIMELINE,
|
||||
FISHBONE
|
||||
} = CONSTANTS.LAYOUT
|
||||
const { LEFT, TOP, RIGHT, BOTTOM } = CONSTANTS.LAYOUT_GROW_DIR
|
||||
const layerIndex = this.overlapNode.layerIndex
|
||||
const children = this.overlapNode.children
|
||||
const marginX = this.mindMap.renderer.layout.getMarginX(layerIndex + 1)
|
||||
const marginY = this.mindMap.renderer.layout.getMarginY(layerIndex + 1)
|
||||
const halfPlaceholderWidth = this.placeholderWidth / 2
|
||||
const halfPlaceholderHeight = this.placeholderHeight / 2
|
||||
let dir = ''
|
||||
let x = ''
|
||||
let y = ''
|
||||
let rotate = false
|
||||
let notRenderPlaceholder = false
|
||||
// 目标节点存在子节点,那么基于最后一个子节点定位
|
||||
if (children.length > 0) {
|
||||
const lastChild = children[children.length - 1]
|
||||
const lastNodeRect = this.getNodeRect(lastChild)
|
||||
dir = this.getNewChildNodeDir(lastChild)
|
||||
switch (this.mindMap.opt.layout) {
|
||||
case LOGICAL_STRUCTURE:
|
||||
case MIND_MAP:
|
||||
x =
|
||||
dir === LEFT
|
||||
? lastNodeRect.originRight - this.placeholderWidth
|
||||
: lastNodeRect.originLeft
|
||||
y = lastNodeRect.originBottom + this.minOffset - halfPlaceholderHeight
|
||||
break
|
||||
case LOGICAL_STRUCTURE_LEFT:
|
||||
x = lastNodeRect.originRight - this.placeholderWidth
|
||||
y = lastNodeRect.originBottom + this.minOffset - halfPlaceholderHeight
|
||||
break
|
||||
case ORGANIZATION_STRUCTURE:
|
||||
rotate = true
|
||||
x = lastNodeRect.originRight + this.minOffset - halfPlaceholderHeight
|
||||
y = lastNodeRect.originTop
|
||||
break
|
||||
case CATALOG_ORGANIZATION:
|
||||
if (layerIndex === 0) {
|
||||
rotate = true
|
||||
x =
|
||||
lastNodeRect.originRight + this.minOffset - halfPlaceholderHeight
|
||||
y = lastNodeRect.originTop
|
||||
} else {
|
||||
x = lastNodeRect.originLeft
|
||||
y =
|
||||
lastNodeRect.originBottom + this.minOffset - halfPlaceholderHeight
|
||||
}
|
||||
break
|
||||
case TIMELINE:
|
||||
if (layerIndex === 0) {
|
||||
rotate = true
|
||||
x =
|
||||
lastNodeRect.originRight + this.minOffset - halfPlaceholderHeight
|
||||
y =
|
||||
lastNodeRect.originTop +
|
||||
lastNodeRect.originHeight / 2 -
|
||||
halfPlaceholderWidth
|
||||
} else {
|
||||
x = lastNodeRect.originLeft
|
||||
y =
|
||||
lastNodeRect.originBottom + this.minOffset - halfPlaceholderHeight
|
||||
}
|
||||
break
|
||||
case TIMELINE2:
|
||||
if (layerIndex === 0) {
|
||||
rotate = true
|
||||
x =
|
||||
lastNodeRect.originRight + this.minOffset - halfPlaceholderHeight
|
||||
y =
|
||||
lastNodeRect.originTop +
|
||||
lastNodeRect.originHeight / 2 -
|
||||
halfPlaceholderWidth
|
||||
} else {
|
||||
x = lastNodeRect.originLeft
|
||||
if (layerIndex === 1) {
|
||||
y =
|
||||
dir === TOP
|
||||
? lastNodeRect.originTop -
|
||||
this.placeholderHeight -
|
||||
this.minOffset +
|
||||
halfPlaceholderHeight
|
||||
: lastNodeRect.originBottom +
|
||||
this.minOffset -
|
||||
halfPlaceholderHeight
|
||||
} else {
|
||||
y =
|
||||
lastNodeRect.originBottom +
|
||||
this.minOffset -
|
||||
halfPlaceholderHeight
|
||||
}
|
||||
}
|
||||
break
|
||||
case VERTICAL_TIMELINE:
|
||||
if (layerIndex === 0) {
|
||||
x =
|
||||
lastNodeRect.originLeft +
|
||||
lastNodeRect.originWidth / 2 -
|
||||
halfPlaceholderWidth
|
||||
y =
|
||||
lastNodeRect.originBottom + this.minOffset - halfPlaceholderHeight
|
||||
} else {
|
||||
x =
|
||||
dir === RIGHT
|
||||
? lastNodeRect.originLeft
|
||||
: lastNodeRect.originRight - this.placeholderWidth
|
||||
y =
|
||||
lastNodeRect.originBottom + this.minOffset - halfPlaceholderHeight
|
||||
}
|
||||
break
|
||||
case FISHBONE:
|
||||
if (layerIndex <= 1) {
|
||||
notRenderPlaceholder = true
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', this.overlapNode, true)
|
||||
} else {
|
||||
x = lastNodeRect.originLeft
|
||||
y =
|
||||
dir === TOP
|
||||
? lastNodeRect.originBottom +
|
||||
this.minOffset -
|
||||
halfPlaceholderHeight
|
||||
: lastNodeRect.originTop -
|
||||
this.placeholderHeight -
|
||||
this.minOffset +
|
||||
halfPlaceholderHeight
|
||||
}
|
||||
break
|
||||
default:
|
||||
}
|
||||
} else {
|
||||
// 目标节点不存在子节点,那么基于目标节点定位
|
||||
const nodeRect = this.getNodeRect(this.overlapNode)
|
||||
dir = this.getNewChildNodeDir(this.overlapNode)
|
||||
switch (this.mindMap.opt.layout) {
|
||||
case LOGICAL_STRUCTURE:
|
||||
case MIND_MAP:
|
||||
x =
|
||||
dir === RIGHT
|
||||
? nodeRect.originRight + marginX
|
||||
: nodeRect.originLeft - this.placeholderWidth - marginX
|
||||
y =
|
||||
nodeRect.originTop +
|
||||
(nodeRect.originHeight - this.placeholderHeight) / 2
|
||||
break
|
||||
case LOGICAL_STRUCTURE_LEFT:
|
||||
x = nodeRect.originLeft - this.placeholderWidth - marginX
|
||||
y =
|
||||
nodeRect.originTop +
|
||||
(nodeRect.originHeight - this.placeholderHeight) / 2
|
||||
break
|
||||
case ORGANIZATION_STRUCTURE:
|
||||
rotate = true
|
||||
x =
|
||||
nodeRect.originLeft +
|
||||
(nodeRect.originWidth - this.placeholderHeight) / 2
|
||||
y = nodeRect.originBottom + marginX
|
||||
break
|
||||
case CATALOG_ORGANIZATION:
|
||||
if (layerIndex === 0) {
|
||||
rotate = true
|
||||
}
|
||||
x = nodeRect.originLeft + nodeRect.originWidth * 0.5
|
||||
y = nodeRect.originBottom + marginX
|
||||
break
|
||||
case TIMELINE:
|
||||
if (layerIndex === 0) {
|
||||
rotate = true
|
||||
}
|
||||
x = nodeRect.originLeft + nodeRect.originWidth * 0.5
|
||||
y = nodeRect.originBottom + marginY
|
||||
break
|
||||
case TIMELINE2:
|
||||
if (layerIndex === 0) {
|
||||
rotate = true
|
||||
}
|
||||
x = nodeRect.originLeft + nodeRect.originWidth * 0.5
|
||||
if (layerIndex === 1) {
|
||||
y =
|
||||
dir === TOP
|
||||
? nodeRect.originTop - this.placeholderHeight - marginX
|
||||
: nodeRect.originBottom + marginX
|
||||
} else {
|
||||
y = nodeRect.originBottom + marginX
|
||||
}
|
||||
break
|
||||
case VERTICAL_TIMELINE:
|
||||
if (layerIndex === 0) {
|
||||
rotate = true
|
||||
}
|
||||
x =
|
||||
dir === RIGHT
|
||||
? nodeRect.originRight + marginX
|
||||
: nodeRect.originLeft - this.placeholderWidth - marginX
|
||||
y =
|
||||
nodeRect.originTop +
|
||||
nodeRect.originHeight / 2 -
|
||||
halfPlaceholderHeight
|
||||
break
|
||||
case FISHBONE:
|
||||
if (layerIndex <= 1) {
|
||||
notRenderPlaceholder = true
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', this.overlapNode, true)
|
||||
} else {
|
||||
x = nodeRect.originLeft + nodeRect.originWidth * 0.5
|
||||
y =
|
||||
dir === BOTTOM
|
||||
? nodeRect.originTop -
|
||||
this.placeholderHeight -
|
||||
this.minOffset +
|
||||
halfPlaceholderHeight
|
||||
: nodeRect.originBottom + this.minOffset - halfPlaceholderHeight
|
||||
}
|
||||
break
|
||||
default:
|
||||
}
|
||||
}
|
||||
if (!notRenderPlaceholder) {
|
||||
this.setPlaceholderRect({
|
||||
x,
|
||||
y,
|
||||
dir,
|
||||
rotate
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 获取节点的生长方向
|
||||
getNewChildNodeDir(node) {
|
||||
const {
|
||||
LOGICAL_STRUCTURE,
|
||||
LOGICAL_STRUCTURE_LEFT,
|
||||
MIND_MAP,
|
||||
TIMELINE2,
|
||||
VERTICAL_TIMELINE,
|
||||
FISHBONE
|
||||
} = CONSTANTS.LAYOUT
|
||||
switch (this.mindMap.opt.layout) {
|
||||
case LOGICAL_STRUCTURE:
|
||||
return CONSTANTS.LAYOUT_GROW_DIR.RIGHT
|
||||
case LOGICAL_STRUCTURE_LEFT:
|
||||
return CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
case MIND_MAP:
|
||||
case TIMELINE2:
|
||||
case VERTICAL_TIMELINE:
|
||||
case FISHBONE:
|
||||
return node.dir
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
// 垂直方向比较
|
||||
// isReverse:是否反向
|
||||
handleVerticalCheck(node, checkList, isReverse = false) {
|
||||
let x = this.mouseMoveX
|
||||
let y = this.mouseMoveY
|
||||
let nodeRect = this.getNodeRect(node)
|
||||
if (isReverse) {
|
||||
const { layout } = this.mindMap.opt
|
||||
const { LAYOUT, LAYOUT_GROW_DIR } = CONSTANTS
|
||||
const { VERTICAL_TIMELINE, FISHBONE } = LAYOUT
|
||||
const { BOTTOM, LEFT } = LAYOUT_GROW_DIR
|
||||
const mouseMoveX = this.mouseMoveX
|
||||
const mouseMoveY = this.mouseMoveY
|
||||
const nodeRect = this.getNodeRect(node)
|
||||
const dir = this.getNewChildNodeDir(node)
|
||||
const layerIndex = node.layerIndex
|
||||
if (
|
||||
isReverse ||
|
||||
(layout === FISHBONE && dir === BOTTOM && layerIndex >= 3)
|
||||
) {
|
||||
checkList = checkList.reverse()
|
||||
}
|
||||
let oneFourthHeight = nodeRect.height / 4
|
||||
let oneFourthHeight = nodeRect.originHeight / 4
|
||||
let { prevBrotherOffset, nextBrotherOffset } =
|
||||
this.getNodeDistanceToSiblingNode(checkList, node, nodeRect, 'v')
|
||||
if (nodeRect.left <= x && nodeRect.right >= x) {
|
||||
if (nodeRect.left <= mouseMoveX && nodeRect.right >= mouseMoveX) {
|
||||
// 检测兄弟节点位置
|
||||
if (
|
||||
!this.overlapNode &&
|
||||
@@ -405,38 +748,92 @@ class Drag extends Base {
|
||||
) {
|
||||
let checkIsPrevNode =
|
||||
nextBrotherOffset > 0 // 距离下一个兄弟节点的距离大于0
|
||||
? y > nodeRect.bottom && y <= nodeRect.bottom + nextBrotherOffset // 那么在当前节点外底部判断
|
||||
: y >= nodeRect.bottom - oneFourthHeight && y <= nodeRect.bottom // 否则在当前节点内底部1/4区间判断
|
||||
? mouseMoveY > nodeRect.bottom &&
|
||||
mouseMoveY <= nodeRect.bottom + nextBrotherOffset // 那么在当前节点外底部判断
|
||||
: mouseMoveY >= nodeRect.bottom - oneFourthHeight &&
|
||||
mouseMoveY <= nodeRect.bottom // 否则在当前节点内底部1/4区间判断
|
||||
let checkIsNextNode =
|
||||
prevBrotherOffset > 0 // 距离上一个兄弟节点的距离大于0
|
||||
? y < nodeRect.top && y >= nodeRect.top - prevBrotherOffset // 那么在当前节点外底部判断
|
||||
: y >= nodeRect.top && y <= nodeRect.top + oneFourthHeight
|
||||
? mouseMoveY < nodeRect.top &&
|
||||
mouseMoveY >= nodeRect.top - prevBrotherOffset // 那么在当前节点外底部判断
|
||||
: mouseMoveY >= nodeRect.top &&
|
||||
mouseMoveY <= nodeRect.top + oneFourthHeight
|
||||
|
||||
const { scaleY } = this.drawTransform
|
||||
let x =
|
||||
dir === LEFT
|
||||
? nodeRect.originRight - this.placeholderWidth
|
||||
: nodeRect.originLeft
|
||||
let notRenderLine = false
|
||||
switch (layout) {
|
||||
case VERTICAL_TIMELINE:
|
||||
if (layerIndex === 1) {
|
||||
x =
|
||||
nodeRect.originLeft +
|
||||
nodeRect.originWidth / 2 -
|
||||
this.placeholderWidth / 2
|
||||
}
|
||||
break
|
||||
default:
|
||||
}
|
||||
if (checkIsPrevNode) {
|
||||
if (isReverse) {
|
||||
this.nextNode = node
|
||||
} else {
|
||||
this.prevNode = node
|
||||
}
|
||||
let size = this.formatPlaceholderSize(nextBrotherOffset)
|
||||
this.setPlaceholderRect(
|
||||
node.width,
|
||||
size,
|
||||
nodeRect.originLeft,
|
||||
nodeRect.originBottom
|
||||
)
|
||||
let y =
|
||||
nodeRect.originBottom +
|
||||
nextBrotherOffset / scaleY - //nextBrotherOffset已经是实际间距的一半了
|
||||
this.placeholderHeight / 2
|
||||
switch (layout) {
|
||||
case FISHBONE:
|
||||
if (layerIndex === 2) {
|
||||
notRenderLine = true
|
||||
y =
|
||||
nodeRect.originBottom +
|
||||
this.minOffset -
|
||||
this.placeholderHeight / 2
|
||||
}
|
||||
break
|
||||
default:
|
||||
}
|
||||
this.setPlaceholderRect({
|
||||
x,
|
||||
y,
|
||||
dir,
|
||||
notRenderLine
|
||||
})
|
||||
} else if (checkIsNextNode) {
|
||||
if (isReverse) {
|
||||
this.prevNode = node
|
||||
} else {
|
||||
this.nextNode = node
|
||||
}
|
||||
let size = this.formatPlaceholderSize(prevBrotherOffset)
|
||||
this.setPlaceholderRect(
|
||||
node.width,
|
||||
size,
|
||||
nodeRect.originLeft,
|
||||
nodeRect.originTop - size
|
||||
)
|
||||
let y =
|
||||
nodeRect.originTop -
|
||||
this.placeholderHeight -
|
||||
prevBrotherOffset / scaleY +
|
||||
this.placeholderHeight / 2
|
||||
switch (layout) {
|
||||
case FISHBONE:
|
||||
if (layerIndex === 2) {
|
||||
notRenderLine = true
|
||||
y =
|
||||
nodeRect.originTop -
|
||||
this.placeholderHeight -
|
||||
this.minOffset +
|
||||
this.placeholderHeight / 2
|
||||
}
|
||||
break
|
||||
default:
|
||||
}
|
||||
this.setPlaceholderRect({
|
||||
x,
|
||||
y,
|
||||
dir,
|
||||
notRenderLine
|
||||
})
|
||||
}
|
||||
}
|
||||
// 检测是否重叠
|
||||
@@ -446,7 +843,7 @@ class Drag extends Base {
|
||||
prevBrotherOffset,
|
||||
nextBrotherOffset,
|
||||
size: oneFourthHeight,
|
||||
pos: y,
|
||||
pos: mouseMoveY,
|
||||
nodeRect
|
||||
})
|
||||
}
|
||||
@@ -454,13 +851,16 @@ class Drag extends Base {
|
||||
|
||||
// 水平方向比较
|
||||
handleHorizontalCheck(node, checkList) {
|
||||
let x = this.mouseMoveX
|
||||
let y = this.mouseMoveY
|
||||
const { layout } = this.mindMap.opt
|
||||
const { LAYOUT } = CONSTANTS
|
||||
const { FISHBONE, TIMELINE, TIMELINE2 } = LAYOUT
|
||||
let mouseMoveX = this.mouseMoveX
|
||||
let mouseMoveY = this.mouseMoveY
|
||||
let nodeRect = this.getNodeRect(node)
|
||||
let oneFourthWidth = nodeRect.width / 4
|
||||
let oneFourthWidth = nodeRect.originWidth / 4
|
||||
let { prevBrotherOffset, nextBrotherOffset } =
|
||||
this.getNodeDistanceToSiblingNode(checkList, node, nodeRect, 'h')
|
||||
if (nodeRect.top <= y && nodeRect.bottom >= y) {
|
||||
if (nodeRect.top <= mouseMoveY && nodeRect.bottom >= mouseMoveY) {
|
||||
// 检测兄弟节点位置
|
||||
if (
|
||||
!this.overlapNode &&
|
||||
@@ -470,30 +870,62 @@ class Drag extends Base {
|
||||
) {
|
||||
let checkIsPrevNode =
|
||||
nextBrotherOffset > 0 // 距离下一个兄弟节点的距离大于0
|
||||
? x < nodeRect.right + nextBrotherOffset && x >= nodeRect.right // 那么在当前节点外底部判断
|
||||
: x <= nodeRect.right && x >= nodeRect.right - oneFourthWidth // 否则在当前节点内底部1/4区间判断
|
||||
? mouseMoveX < nodeRect.right + nextBrotherOffset &&
|
||||
mouseMoveX >= nodeRect.right // 那么在当前节点外底部判断
|
||||
: mouseMoveX <= nodeRect.right &&
|
||||
mouseMoveX >= nodeRect.right - oneFourthWidth // 否则在当前节点内底部1/4区间判断
|
||||
let checkIsNextNode =
|
||||
prevBrotherOffset > 0 // 距离上一个兄弟节点的距离大于0
|
||||
? x > nodeRect.left - prevBrotherOffset && x <= nodeRect.left // 那么在当前节点外底部判断
|
||||
: x <= nodeRect.left + oneFourthWidth && x >= nodeRect.left
|
||||
? mouseMoveX > nodeRect.left - prevBrotherOffset &&
|
||||
mouseMoveX <= nodeRect.left // 那么在当前节点外底部判断
|
||||
: mouseMoveX <= nodeRect.left + oneFourthWidth &&
|
||||
mouseMoveX >= nodeRect.left
|
||||
const { scaleX } = this.drawTransform
|
||||
const layerIndex = node.layerIndex
|
||||
let y = nodeRect.originTop
|
||||
let notRenderLine = false
|
||||
switch (layout) {
|
||||
case TIMELINE:
|
||||
case TIMELINE2:
|
||||
y =
|
||||
nodeRect.originTop +
|
||||
nodeRect.originHeight / 2 -
|
||||
this.placeholderWidth / 2
|
||||
break
|
||||
case FISHBONE:
|
||||
if (layerIndex === 1) {
|
||||
notRenderLine = true
|
||||
y =
|
||||
nodeRect.originTop +
|
||||
nodeRect.originHeight / 2 -
|
||||
this.placeholderWidth / 2
|
||||
}
|
||||
break
|
||||
default:
|
||||
}
|
||||
if (checkIsPrevNode) {
|
||||
this.prevNode = node
|
||||
let size = this.formatPlaceholderSize(nextBrotherOffset)
|
||||
this.setPlaceholderRect(
|
||||
size,
|
||||
node.height,
|
||||
nodeRect.originRight,
|
||||
nodeRect.originTop
|
||||
)
|
||||
this.setPlaceholderRect({
|
||||
x:
|
||||
nodeRect.originRight +
|
||||
nextBrotherOffset / scaleX - //nextBrotherOffset已经是实际间距的一半了
|
||||
this.placeholderHeight / 2,
|
||||
y,
|
||||
rotate: true,
|
||||
notRenderLine
|
||||
})
|
||||
} else if (checkIsNextNode) {
|
||||
this.nextNode = node
|
||||
let size = this.formatPlaceholderSize(prevBrotherOffset)
|
||||
this.setPlaceholderRect(
|
||||
size,
|
||||
node.height,
|
||||
nodeRect.originLeft - size,
|
||||
nodeRect.originTop
|
||||
)
|
||||
this.setPlaceholderRect({
|
||||
x:
|
||||
nodeRect.originLeft -
|
||||
this.placeholderHeight -
|
||||
prevBrotherOffset / scaleX +
|
||||
this.placeholderHeight / 2,
|
||||
y,
|
||||
rotate: true,
|
||||
notRenderLine
|
||||
})
|
||||
}
|
||||
}
|
||||
// 检测是否重叠
|
||||
@@ -503,7 +935,7 @@ class Drag extends Base {
|
||||
prevBrotherOffset,
|
||||
nextBrotherOffset,
|
||||
size: oneFourthWidth,
|
||||
pos: x,
|
||||
pos: mouseMoveX,
|
||||
nodeRect
|
||||
})
|
||||
}
|
||||
@@ -511,8 +943,12 @@ class Drag extends Base {
|
||||
|
||||
// 获取节点距前一个和后一个节点的距离
|
||||
getNodeDistanceToSiblingNode(checkList, node, nodeRect, dir) {
|
||||
let dir1 = dir === 'v' ? 'top' : 'left'
|
||||
let dir2 = dir === 'v' ? 'bottom' : 'right'
|
||||
const { TOP, LEFT, BOTTOM, RIGHT } = CONSTANTS.LAYOUT_GROW_DIR
|
||||
let { scaleX, scaleY } = this.drawTransform
|
||||
let dir1 = dir === 'v' ? TOP : LEFT
|
||||
let dir2 = dir === 'v' ? BOTTOM : RIGHT
|
||||
let scale = dir === 'v' ? scaleY : scaleX
|
||||
let minOffset = this.minOffset * scale
|
||||
let index = getNodeIndexInNodeList(node, checkList)
|
||||
let prevBrother = null
|
||||
let nextBrother = null
|
||||
@@ -531,10 +967,10 @@ class Drag extends Base {
|
||||
prevBrotherOffset = nodeRect[dir1] - prevNodeRect[dir2]
|
||||
// 间距小于10就当它不存在
|
||||
prevBrotherOffset =
|
||||
prevBrotherOffset >= this.minOffset ? prevBrotherOffset / 2 : 0
|
||||
prevBrotherOffset >= minOffset ? prevBrotherOffset / 2 : 0
|
||||
} else {
|
||||
// 没有前一个兄弟节点,那么假设和前一个节点的距离为20
|
||||
prevBrotherOffset = this.minOffset
|
||||
prevBrotherOffset = minOffset
|
||||
}
|
||||
// 和后一个兄弟节点的距离
|
||||
let nextBrotherOffset = 0
|
||||
@@ -542,25 +978,70 @@ class Drag extends Base {
|
||||
let nextNodeRect = this.getNodeRect(nextBrother)
|
||||
nextBrotherOffset = nextNodeRect[dir1] - nodeRect[dir2]
|
||||
nextBrotherOffset =
|
||||
nextBrotherOffset >= this.minOffset ? nextBrotherOffset / 2 : 0
|
||||
nextBrotherOffset >= minOffset ? nextBrotherOffset / 2 : 0
|
||||
} else {
|
||||
nextBrotherOffset = this.minOffset
|
||||
nextBrotherOffset = minOffset
|
||||
}
|
||||
return {
|
||||
prevBrother,
|
||||
prevBrotherOffset,
|
||||
nextBrother,
|
||||
nextBrotherOffset
|
||||
}
|
||||
}
|
||||
|
||||
// 处理提示元素的大小
|
||||
formatPlaceholderSize(size) {
|
||||
const { nodeDragPlaceholderMaxSize } = this.mindMap.opt
|
||||
return size > 0 ? Math.min(size, nodeDragPlaceholderMaxSize) : 5
|
||||
}
|
||||
|
||||
// 设置提示元素的大小和位置
|
||||
setPlaceholderRect(w, h, x, y) {
|
||||
setPlaceholderRect({ x, y, dir, rotate, notRenderLine }) {
|
||||
let w = this.placeholderWidth
|
||||
let h = this.placeholderHeight
|
||||
if (rotate) {
|
||||
const tmp = w
|
||||
w = h
|
||||
h = tmp
|
||||
}
|
||||
this.placeholder.size(w, h).move(x, y)
|
||||
if (notRenderLine) {
|
||||
return
|
||||
}
|
||||
const { dragPlaceholderLineConfig } = this.mindMap.opt
|
||||
let node = null
|
||||
let parent = null
|
||||
if (this.overlapNode) {
|
||||
node = this.overlapNode
|
||||
parent = this.overlapNode
|
||||
} else {
|
||||
node = this.prevNode || this.nextNode
|
||||
parent = node.parent
|
||||
}
|
||||
parent = parent.fakeClone()
|
||||
node = node.fakeClone()
|
||||
const tmpNode = this.beingDragNodeList[0].fakeClone()
|
||||
tmpNode.dir = dir
|
||||
tmpNode.left = x
|
||||
tmpNode.top = y
|
||||
tmpNode.width = w
|
||||
tmpNode.height = h
|
||||
parent.children = [tmpNode]
|
||||
parent._lines = []
|
||||
this.placeHolderLine.show()
|
||||
this.mindMap.renderer.layout.renderLine(
|
||||
parent,
|
||||
[this.placeHolderLine],
|
||||
(...args) => {
|
||||
// node.styleLine(...args)
|
||||
},
|
||||
node.style.getStyle('lineStyle', true)
|
||||
)
|
||||
this.placeHolderExtraLines = [...parent._lines]
|
||||
this.placeHolderExtraLines.forEach(line => {
|
||||
this.mindMap.otherDraw.add(line)
|
||||
line
|
||||
.stroke({
|
||||
color: dragPlaceholderLineConfig.color,
|
||||
width: dragPlaceholderLineConfig.width
|
||||
})
|
||||
.fill({ color: 'none' })
|
||||
})
|
||||
}
|
||||
|
||||
// 检测是否重叠
|
||||
@@ -573,8 +1054,9 @@ class Drag extends Base {
|
||||
pos,
|
||||
nodeRect
|
||||
}) {
|
||||
let dir1 = dir === 'v' ? 'top' : 'left'
|
||||
let dir2 = dir === 'v' ? 'bottom' : 'right'
|
||||
const { TOP, LEFT, BOTTOM, RIGHT } = CONSTANTS.LAYOUT_GROW_DIR
|
||||
let dir1 = dir === 'v' ? TOP : LEFT
|
||||
let dir2 = dir === 'v' ? BOTTOM : RIGHT
|
||||
if (!this.overlapNode && !this.prevNode && !this.nextNode) {
|
||||
if (
|
||||
nodeRect[dir1] + (prevBrotherOffset > 0 ? 0 : size) <= pos &&
|
||||
@@ -638,7 +1120,7 @@ class Drag extends Base {
|
||||
this.handleHorizontalCheck(node, checkList)
|
||||
} else {
|
||||
// 处于上方的三级节点需要特殊处理,因为节点排列方向反向了
|
||||
if (node.dir === 'top' && node.layerIndex === 2) {
|
||||
if (node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP && node.layerIndex === 2) {
|
||||
this.handleVerticalCheck(node, checkList, true)
|
||||
} else {
|
||||
this.handleVerticalCheck(node, checkList)
|
||||
@@ -657,7 +1139,7 @@ class Drag extends Base {
|
||||
this.handleHorizontalCheck(node, checkList)
|
||||
} else {
|
||||
// 处于上方的三级节点需要特殊处理,因为节点排列方向反向了
|
||||
if (node.dir === 'top' && node.layerIndex === 2) {
|
||||
if (node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP && node.layerIndex === 2) {
|
||||
this.handleVerticalCheck(node, checkList, true)
|
||||
} else {
|
||||
this.handleVerticalCheck(node, checkList)
|
||||
@@ -678,6 +1160,8 @@ class Drag extends Base {
|
||||
getNodeRect(node) {
|
||||
let { scaleX, scaleY, translateX, translateY } = this.drawTransform
|
||||
let { left, top, width, height } = node
|
||||
let originWidth = width
|
||||
let originHeight = height
|
||||
let originLeft = left
|
||||
let originTop = top
|
||||
let originBottom = top + height
|
||||
@@ -687,12 +1171,12 @@ class Drag extends Base {
|
||||
left = left * scaleX + translateX
|
||||
top = top * scaleY + translateY
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
originWidth,
|
||||
originHeight,
|
||||
originLeft,
|
||||
originTop,
|
||||
originBottom,
|
||||
|
||||
@@ -54,7 +54,8 @@ class Export {
|
||||
errorHandler,
|
||||
resetCss,
|
||||
addContentToHeader,
|
||||
addContentToFooter
|
||||
addContentToFooter,
|
||||
handleBeingExportSvg
|
||||
} = this.mindMap.opt
|
||||
let { svg, svgHTML, clipData } = this.mindMap.getSvgData({
|
||||
paddingX: exportPaddingX,
|
||||
@@ -67,6 +68,7 @@ class Export {
|
||||
clipData.paddingX = exportPaddingX
|
||||
clipData.paddingY = exportPaddingY
|
||||
}
|
||||
let svgIsChange = false
|
||||
// svg的image标签,把图片的url转换成data:url类型,否则导出会丢失图片
|
||||
const task1 = this.createTransformImgTaskList(
|
||||
svg,
|
||||
@@ -87,16 +89,20 @@ class Export {
|
||||
errorHandler(ERROR_TYPES.EXPORT_LOAD_IMAGE_ERROR, error)
|
||||
}
|
||||
// 开启了节点富文本编辑,需要增加一些样式
|
||||
let isAddResetCss
|
||||
if (this.mindMap.richText) {
|
||||
const foreignObjectList = svg.find('foreignObject')
|
||||
if (foreignObjectList.length > 0) {
|
||||
foreignObjectList[0].add(SVG(`<style>${resetCss}</style>`))
|
||||
isAddResetCss = true
|
||||
svgIsChange = true
|
||||
}
|
||||
}
|
||||
// 自定义处理svg的方法
|
||||
if (typeof handleBeingExportSvg === 'function') {
|
||||
svgIsChange = true
|
||||
svg = handleBeingExportSvg(svg)
|
||||
}
|
||||
// svg节点内容有变,需要重新获取html字符串
|
||||
if (taskList.length > 0 || isAddResetCss) {
|
||||
if (taskList.length > 0 || svgIsChange) {
|
||||
svgHTML = svg.svg()
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -10,9 +10,18 @@ class Formula {
|
||||
this.opt = opt
|
||||
this.mindMap = opt.mindMap
|
||||
window.katex = katex
|
||||
this.init()
|
||||
this.extendQuill()
|
||||
}
|
||||
|
||||
init() {
|
||||
if (this.mindMap.opt.enableEditFormulaInRichTextEdit) {
|
||||
this.mindMap.opt.transformRichTextOnEnterEdit =
|
||||
this.latexRichToText.bind(this)
|
||||
this.mindMap.opt.beforeHideRichTextEdit = this.formatLatex.bind(this)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取katex配置
|
||||
getKatexConfig() {
|
||||
const config = {
|
||||
@@ -59,6 +68,74 @@ class Formula {
|
||||
richTextPlugin.setTextStyleIfNotRichText(richTextPlugin.node)
|
||||
richTextPlugin.hideEditText([node])
|
||||
}
|
||||
|
||||
// 将公式富文本转换为公式源码
|
||||
latexRichToText(nodeText) {
|
||||
if (nodeText.indexOf('class="ql-formula"') !== -1) {
|
||||
const parser = new DOMParser()
|
||||
const doc = parser.parseFromString(nodeText, 'text/html')
|
||||
const els = doc.getElementsByClassName('ql-formula')
|
||||
for (const el of els)
|
||||
nodeText = nodeText.replace(
|
||||
el.outerHTML,
|
||||
`\$${el
|
||||
.getAttribute('data-value')
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')}\$`
|
||||
)
|
||||
}
|
||||
return nodeText
|
||||
}
|
||||
|
||||
// 使用格式化的 latex 字符串内容更新 quill 内容:输入 $*****$
|
||||
formatLatex(richText) {
|
||||
const contents = richText.quill.getContents()
|
||||
const ops = contents.ops
|
||||
let mod = false
|
||||
for (let i = ops.length - 1; i >= 0; i--) {
|
||||
const op = ops[i]
|
||||
const insert = op.insert
|
||||
if (insert && typeof insert !== 'object' && insert !== '\n') {
|
||||
if (/\$.+?\$/g.test(insert)) {
|
||||
const m = [...insert.matchAll(/\$.+?\$/g)]
|
||||
const arr = insert.split(/\$.+?\$/g)
|
||||
for (let j = m.length - 1; j >= 0; j--) {
|
||||
const exp = m[j] && m[j][0] ? m[j][0].slice(1, -1) || null : null // $...$ 之间的表达式
|
||||
if (exp !== null && exp.trim().length > 0) {
|
||||
const isLegal = this.checkFormulaIsLegal(exp)
|
||||
if (isLegal) {
|
||||
arr.splice(j + 1, 0, { insert: { formula: exp } }) // 添加到对应位置之后
|
||||
mod = true
|
||||
} else {
|
||||
arr.splice(j + 1, 0, '')
|
||||
}
|
||||
} else arr.splice(j + 1, 0, '') // 表达式为空时,占位
|
||||
}
|
||||
while (arr.length > 0) {
|
||||
let v = arr.pop()
|
||||
if (typeof v === 'string') {
|
||||
if (v.length < 1) continue
|
||||
v = { insert: v }
|
||||
}
|
||||
v['attributes'] = ops[i]['attributes']
|
||||
ops.splice(i + 1, 0, v)
|
||||
}
|
||||
ops.splice(i, 1) // 删除原来的字符串
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mod) richText.quill.setContents(contents)
|
||||
}
|
||||
|
||||
checkFormulaIsLegal(str) {
|
||||
try {
|
||||
katex.renderToString(str)
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Formula.instanceName = 'formula'
|
||||
|
||||
@@ -19,6 +19,7 @@ class MiniMap {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
this.currentState = null
|
||||
}
|
||||
|
||||
// 计算小地图的渲染数据
|
||||
@@ -53,13 +54,16 @@ class MiniMap {
|
||||
let miniMapBoxScale = actWidth / rect.width
|
||||
let miniMapBoxLeft = (boxWidth - actWidth) / 2
|
||||
let miniMapBoxTop = (boxHeight - actHeight) / 2
|
||||
// 视口框大小及位置
|
||||
let _rectX = rect.x - (rect.width * scaleX - rect.width) / 2
|
||||
let _rectX2 = rect.x2 + (rect.width * scaleX - rect.width) / 2
|
||||
let _rectY = rect.y - (rect.height * scaleY - rect.height) / 2
|
||||
let _rectY2 = rect.y2 + (rect.height * scaleY - rect.height) / 2
|
||||
// 当前思维导图图形实际的宽高,即在缩放后的宽高
|
||||
let _rectWidth = rect.width * scaleX
|
||||
let _rectHeight = rect.height * scaleY
|
||||
// 视口框大小及位置
|
||||
let _rectWidthOffsetHalf = (_rectWidth - rect.width) / 2
|
||||
let _rectHeightOffsetHalf = (_rectHeight - rect.height) / 2
|
||||
let _rectX = rect.x - _rectWidthOffsetHalf
|
||||
let _rectX2 = rect.x2 + _rectWidthOffsetHalf
|
||||
let _rectY = rect.y - _rectHeightOffsetHalf
|
||||
let _rectY2 = rect.y2 + _rectHeightOffsetHalf
|
||||
let viewBoxStyle = {
|
||||
left: 0,
|
||||
top: 0,
|
||||
@@ -90,7 +94,14 @@ class MiniMap {
|
||||
})
|
||||
this.removeNodeContent(svg)
|
||||
const svgStr = svg.svg()
|
||||
|
||||
this.currentState = {
|
||||
viewBoxStyle: {
|
||||
...viewBoxStyle
|
||||
},
|
||||
miniMapBoxScale,
|
||||
miniMapBoxLeft,
|
||||
miniMapBoxTop
|
||||
}
|
||||
return {
|
||||
getImgUrl: async callback => {
|
||||
const res = await this.mindMap.doExport.fixSvgStrAndToBlob(svgStr)
|
||||
@@ -141,7 +152,7 @@ class MiniMap {
|
||||
|
||||
// 小地图鼠标移动事件
|
||||
onMousemove(e, sensitivityNum = 5) {
|
||||
if (!this.isMousedown) {
|
||||
if (!this.isMousedown || this.isViewBoxMousedown) {
|
||||
return
|
||||
}
|
||||
let ox = e.clientX - this.mousedownPos.x
|
||||
@@ -154,6 +165,57 @@ class MiniMap {
|
||||
// 小地图鼠标松开事件
|
||||
onMouseup() {
|
||||
this.isMousedown = false
|
||||
this.isViewBoxMousedown = false
|
||||
}
|
||||
|
||||
// 视口框鼠标按下事件
|
||||
onViewBoxMousedown(e) {
|
||||
this.isViewBoxMousedown = true
|
||||
this.mousedownPos = {
|
||||
x: e.clientX,
|
||||
y: e.clientY
|
||||
}
|
||||
// 保存视图当前的偏移量
|
||||
let transformData = this.mindMap.view.getTransformData()
|
||||
this.startViewPos = {
|
||||
x: transformData.state.x,
|
||||
y: transformData.state.y
|
||||
}
|
||||
}
|
||||
|
||||
// 视口框鼠标移动事件
|
||||
onViewBoxMousemove(e) {
|
||||
if (!this.isViewBoxMousedown || !this.currentState || this.isMousedown)
|
||||
return
|
||||
let ox = e.clientX - this.mousedownPos.x
|
||||
let oy = e.clientY - this.mousedownPos.y
|
||||
const { viewBoxStyle, miniMapBoxScale, miniMapBoxLeft, miniMapBoxTop } =
|
||||
this.currentState
|
||||
const left = Math.max(
|
||||
miniMapBoxLeft,
|
||||
Number.parseFloat(viewBoxStyle.left) + ox
|
||||
)
|
||||
const right = Math.max(
|
||||
miniMapBoxLeft,
|
||||
Number.parseFloat(viewBoxStyle.right) - ox
|
||||
)
|
||||
const top = Math.max(
|
||||
miniMapBoxTop,
|
||||
Number.parseFloat(viewBoxStyle.top) + oy
|
||||
)
|
||||
const bottom = Math.max(
|
||||
miniMapBoxTop,
|
||||
Number.parseFloat(viewBoxStyle.bottom) - oy
|
||||
)
|
||||
this.mindMap.emit('mini_map_view_box_position_change', {
|
||||
left: left + 'px',
|
||||
right: right + 'px',
|
||||
top: top + 'px',
|
||||
bottom: bottom + 'px'
|
||||
})
|
||||
// 在视图最初偏移量上累加更新量
|
||||
this.mindMap.view.translateXTo(-ox / miniMapBoxScale + this.startViewPos.x)
|
||||
this.mindMap.view.translateYTo(-oy / miniMapBoxScale + this.startViewPos.y)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -154,6 +154,7 @@ class NodeImgAdjust {
|
||||
})
|
||||
btnEl.addEventListener('mousedown', e => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
this.onMousedown(e)
|
||||
})
|
||||
btnEl.addEventListener('mouseup', e => {
|
||||
|
||||
394
simple-mind-map/src/plugins/OuterFrame.js
Normal file
@@ -0,0 +1,394 @@
|
||||
import {
|
||||
formatDataToArray,
|
||||
walk,
|
||||
getTopAncestorsFomNodeList,
|
||||
getNodeListBoundingRect,
|
||||
createUid
|
||||
} from '../utils'
|
||||
|
||||
// 解析要添加外框的节点实例列表
|
||||
const parseAddNodeList = list => {
|
||||
// 找出顶层节点
|
||||
list = getTopAncestorsFomNodeList(list)
|
||||
const cache = {}
|
||||
const uidToParent = {}
|
||||
// 找出列表中节点在兄弟节点中的索引,并和父节点关联起来
|
||||
list.forEach(node => {
|
||||
const parent = node.parent
|
||||
if (parent) {
|
||||
const pUid = parent.uid
|
||||
uidToParent[pUid] = parent
|
||||
const index = node.getIndexInBrothers()
|
||||
const data = {
|
||||
node,
|
||||
index
|
||||
}
|
||||
if (cache[pUid]) {
|
||||
if (
|
||||
!cache[pUid].find(item => {
|
||||
return item.index === data.index
|
||||
})
|
||||
) {
|
||||
cache[pUid].push(data)
|
||||
}
|
||||
} else {
|
||||
cache[pUid] = [data]
|
||||
}
|
||||
}
|
||||
})
|
||||
const res = []
|
||||
Object.keys(cache).forEach(uid => {
|
||||
const indexList = cache[uid]
|
||||
const parentNode = uidToParent[uid]
|
||||
if (indexList.length > 1) {
|
||||
// 多个节点
|
||||
const rangeList = indexList
|
||||
.map(item => {
|
||||
return item.index
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return a - b
|
||||
})
|
||||
const minIndex = rangeList[0]
|
||||
const maxIndex = rangeList[rangeList.length - 1]
|
||||
let curStart = -1
|
||||
let curEnd = -1
|
||||
for (let i = minIndex; i <= maxIndex; i++) {
|
||||
// 连续索引
|
||||
if (rangeList.includes(i)) {
|
||||
if (curStart === -1) {
|
||||
curStart = i
|
||||
}
|
||||
curEnd = i
|
||||
} else {
|
||||
// 连续断开
|
||||
if (curStart !== -1 && curEnd !== -1) {
|
||||
res.push({
|
||||
node: parentNode,
|
||||
range: [curStart, curEnd]
|
||||
})
|
||||
}
|
||||
curStart = -1
|
||||
curEnd = -1
|
||||
}
|
||||
}
|
||||
// 不要忘了最后一段索引
|
||||
if (curStart !== -1 && curEnd !== -1) {
|
||||
res.push({
|
||||
node: parentNode,
|
||||
range: [curStart, curEnd]
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 单个节点
|
||||
res.push({
|
||||
node: parentNode,
|
||||
range: [indexList[0].index, indexList[0].index]
|
||||
})
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
// 解析获取节点的子节点生成的外框列表
|
||||
const getNodeOuterFrameList = node => {
|
||||
const children = node.children
|
||||
if (!children || children.length <= 0) return
|
||||
const res = []
|
||||
const map = {}
|
||||
children.forEach((item, index) => {
|
||||
const outerFrameData = item.getData('outerFrame')
|
||||
if (!outerFrameData) return
|
||||
const groupId = outerFrameData.groupId
|
||||
if (groupId) {
|
||||
if (!map[groupId]) {
|
||||
map[groupId] = []
|
||||
}
|
||||
map[groupId].push({
|
||||
node: item,
|
||||
index
|
||||
})
|
||||
} else {
|
||||
res.push({
|
||||
nodeList: [item],
|
||||
range: [index, index]
|
||||
})
|
||||
}
|
||||
})
|
||||
Object.keys(map).forEach(id => {
|
||||
const list = map[id]
|
||||
res.push({
|
||||
nodeList: list.map(item => {
|
||||
return item.node
|
||||
}),
|
||||
range: [list[0].index, list[list.length - 1].index]
|
||||
})
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
// 默认外框样式
|
||||
const defaultStyle = {
|
||||
radius: 5,
|
||||
strokeWidth: 2,
|
||||
strokeColor: '#0984e3',
|
||||
strokeDasharray: '5,5',
|
||||
fill: 'rgba(9,132,227,0.05)'
|
||||
}
|
||||
|
||||
// 外框插件
|
||||
class OuterFrame {
|
||||
constructor(opt = {}) {
|
||||
this.mindMap = opt.mindMap
|
||||
this.draw = null
|
||||
this.createDrawContainer()
|
||||
this.outerFrameElList = []
|
||||
this.activeOuterFrame = null
|
||||
this.paddingX = 10
|
||||
this.paddingY = 10
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
// 创建容器
|
||||
createDrawContainer() {
|
||||
this.draw = this.mindMap.draw.group()
|
||||
this.draw.addClass('smm-outer-frame-container')
|
||||
this.draw.back() // 最底层
|
||||
this.draw.forward() // 连线层上面
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
bindEvent() {
|
||||
this.renderOuterFrames = this.renderOuterFrames.bind(this)
|
||||
this.mindMap.on('node_tree_render_end', this.renderOuterFrames)
|
||||
this.mindMap.on('data_change', this.renderOuterFrames)
|
||||
// 监听画布和节点点击事件,用于清除当前激活的连接线
|
||||
this.clearActiveOuterFrame = this.clearActiveOuterFrame.bind(this)
|
||||
this.mindMap.on('draw_click', this.clearActiveOuterFrame)
|
||||
this.mindMap.on('node_click', this.clearActiveOuterFrame)
|
||||
|
||||
this.addOuterFrame = this.addOuterFrame.bind(this)
|
||||
this.mindMap.command.add('ADD_OUTER_FRAME', this.addOuterFrame)
|
||||
|
||||
this.removeActiveOuterFrame = this.removeActiveOuterFrame.bind(this)
|
||||
this.mindMap.keyCommand.addShortcut(
|
||||
'Del|Backspace',
|
||||
this.removeActiveOuterFrame
|
||||
)
|
||||
}
|
||||
|
||||
// 解绑事件
|
||||
unBindEvent() {
|
||||
this.mindMap.off('node_tree_render_end', this.renderOuterFrames)
|
||||
this.mindMap.off('data_change', this.renderOuterFrames)
|
||||
this.mindMap.off('draw_click', this.clearActiveOuterFrame)
|
||||
this.mindMap.off('node_click', this.clearActiveOuterFrame)
|
||||
this.mindMap.command.remove('ADD_OUTER_FRAME', this.addOuterFrame)
|
||||
this.mindMap.keyCommand.removeShortcut(
|
||||
'Del|Backspace',
|
||||
this.removeActiveOuterFrame
|
||||
)
|
||||
}
|
||||
|
||||
// 给节点添加外框数据
|
||||
/*
|
||||
config: {
|
||||
text: '',
|
||||
radius: 5,
|
||||
strokeWidth: 2,
|
||||
strokeColor: '#0984e3',
|
||||
strokeDasharray: '5,5',
|
||||
fill: 'rgba(9,132,227,0.05)'
|
||||
}
|
||||
*/
|
||||
addOuterFrame(appointNodes, config = {}) {
|
||||
appointNodes = formatDataToArray(appointNodes)
|
||||
const activeNodeList = this.mindMap.renderer.activeNodeList
|
||||
if (activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||||
return
|
||||
}
|
||||
let nodeList = appointNodes.length > 0 ? appointNodes : activeNodeList
|
||||
nodeList = nodeList.filter(node => {
|
||||
return !node.isRoot && !node.isGeneralization
|
||||
})
|
||||
const list = parseAddNodeList(nodeList)
|
||||
list.forEach(({ node, range }) => {
|
||||
const childNodeList = node.children.slice(range[0], range[1] + 1)
|
||||
const groupId = createUid()
|
||||
childNodeList.forEach(child => {
|
||||
let outerFrame = child.getData('outerFrame')
|
||||
// 检查该外框是否已存在
|
||||
if (outerFrame) {
|
||||
outerFrame = {
|
||||
...outerFrame,
|
||||
...config,
|
||||
groupId
|
||||
}
|
||||
} else {
|
||||
outerFrame = {
|
||||
...config,
|
||||
groupId
|
||||
}
|
||||
}
|
||||
this.mindMap.execCommand('SET_NODE_DATA', child, {
|
||||
outerFrame
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 获取当前激活的外框
|
||||
getActiveOuterFrame() {
|
||||
return this.activeOuterFrame
|
||||
? {
|
||||
...this.activeOuterFrame
|
||||
}
|
||||
: null
|
||||
}
|
||||
|
||||
// 删除当前激活的外框
|
||||
removeActiveOuterFrame() {
|
||||
if (!this.activeOuterFrame) return
|
||||
const { node, range } = this.activeOuterFrame
|
||||
this.getRangeNodeList(node, range).forEach(child => {
|
||||
this.mindMap.execCommand('SET_NODE_DATA', child, {
|
||||
outerFrame: null
|
||||
})
|
||||
})
|
||||
this.mindMap.emit('outer_frame_delete')
|
||||
}
|
||||
|
||||
// 更新当前激活的外框
|
||||
// 执行了该方法后请立即隐藏你的样式面板,因为会清除当前激活的外框
|
||||
updateActiveOuterFrame(config = {}) {
|
||||
if (!this.activeOuterFrame) return
|
||||
const { node, range } = this.activeOuterFrame
|
||||
this.getRangeNodeList(node, range).forEach(node => {
|
||||
const outerFrame = node.getData('outerFrame')
|
||||
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
||||
outerFrame: {
|
||||
...outerFrame,
|
||||
...config
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 获取某个节点指定范围的带外框的子节点列表
|
||||
getRangeNodeList(node, range) {
|
||||
return node.children.slice(range[0], range[1] + 1).filter(child => {
|
||||
return child.getData('outerFrame')
|
||||
})
|
||||
}
|
||||
|
||||
// 渲染外框
|
||||
renderOuterFrames() {
|
||||
this.clearOuterFrameElList()
|
||||
let tree = this.mindMap.renderer.root
|
||||
if (!tree) return
|
||||
const t = this.mindMap.draw.transform()
|
||||
walk(
|
||||
tree,
|
||||
null,
|
||||
cur => {
|
||||
if (!cur) return
|
||||
const outerFrameList = getNodeOuterFrameList(cur)
|
||||
if (outerFrameList && outerFrameList.length > 0) {
|
||||
outerFrameList.forEach(({ nodeList, range }) => {
|
||||
if (range[0] === -1 || range[1] === -1) return
|
||||
const { left, top, width, height } =
|
||||
getNodeListBoundingRect(nodeList)
|
||||
const el = this.createOuterFrameEl(
|
||||
(left - this.paddingX - this.mindMap.elRect.left - t.translateX) /
|
||||
t.scaleX,
|
||||
(top - this.paddingY - this.mindMap.elRect.top - t.translateY) /
|
||||
t.scaleY,
|
||||
(width + this.paddingX * 2) / t.scaleX,
|
||||
(height + this.paddingY * 2) / t.scaleY,
|
||||
nodeList[0].getData('outerFrame') // 使用第一个节点的外框样式
|
||||
)
|
||||
el.on('click', e => {
|
||||
e.stopPropagation()
|
||||
this.setActiveOuterFrame(el, cur, range)
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
() => {},
|
||||
true,
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
// 激活外框
|
||||
setActiveOuterFrame(el, node, range) {
|
||||
this.mindMap.execCommand('CLEAR_ACTIVE_NODE')
|
||||
this.clearActiveOuterFrame()
|
||||
this.activeOuterFrame = {
|
||||
el,
|
||||
node,
|
||||
range
|
||||
}
|
||||
el.stroke({
|
||||
dasharray: 'none'
|
||||
})
|
||||
this.mindMap.emit('outer_frame_active', el, node, range)
|
||||
}
|
||||
|
||||
// 清除当前激活的外框
|
||||
clearActiveOuterFrame() {
|
||||
if (!this.activeOuterFrame) return
|
||||
const { el } = this.activeOuterFrame
|
||||
el.stroke({
|
||||
dasharray: el.cacheStyle.dasharray || defaultStyle.strokeDasharray
|
||||
})
|
||||
this.activeOuterFrame = null
|
||||
}
|
||||
|
||||
// 创建外框元素
|
||||
createOuterFrameEl(x, y, width, height, styleConfig = {}) {
|
||||
styleConfig = { ...defaultStyle, ...styleConfig }
|
||||
const el = this.draw
|
||||
.rect()
|
||||
.size(width, height)
|
||||
.radius(styleConfig.radius)
|
||||
.stroke({
|
||||
width: styleConfig.strokeWidth,
|
||||
color: styleConfig.strokeColor,
|
||||
dasharray: styleConfig.strokeDasharray
|
||||
})
|
||||
.fill({
|
||||
color: styleConfig.fill
|
||||
})
|
||||
.x(x)
|
||||
.y(y)
|
||||
el.cacheStyle = {
|
||||
dasharray: styleConfig.strokeDasharray
|
||||
}
|
||||
this.outerFrameElList.push(el)
|
||||
return el
|
||||
}
|
||||
|
||||
// 清除外框元素
|
||||
clearOuterFrameElList() {
|
||||
this.outerFrameElList.forEach(item => {
|
||||
item.remove()
|
||||
})
|
||||
this.outerFrameElList = []
|
||||
this.activeOuterFrame = null
|
||||
}
|
||||
|
||||
// 插件被移除前做的事情
|
||||
beforePluginRemove() {
|
||||
this.unBindEvent()
|
||||
}
|
||||
|
||||
// 插件被卸载前做的事情
|
||||
beforePluginDestroy() {
|
||||
this.unBindEvent()
|
||||
}
|
||||
}
|
||||
|
||||
OuterFrame.instanceName = 'outerFrame'
|
||||
|
||||
export default OuterFrame
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
isUndef,
|
||||
checkSmmFormatData,
|
||||
removeHtmlNodeByClass,
|
||||
formatGetNodeGeneralization
|
||||
formatGetNodeGeneralization,
|
||||
nodeRichTextToTextWithWrap
|
||||
} from '../utils'
|
||||
import { CONSTANTS } from '../constants/constant'
|
||||
|
||||
@@ -170,7 +171,8 @@ class RichText {
|
||||
customInnerElsAppendTo,
|
||||
nodeTextEditZIndex,
|
||||
textAutoWrapWidth,
|
||||
selectTextOnEnterEditText
|
||||
selectTextOnEnterEditText,
|
||||
transformRichTextOnEnterEdit
|
||||
} = this.mindMap.opt
|
||||
this.node = node
|
||||
this.isInserting = isInserting
|
||||
@@ -240,7 +242,10 @@ class RichText {
|
||||
}
|
||||
}
|
||||
// 节点文本内容
|
||||
const nodeText = node.getData('text')
|
||||
let nodeText = node.getData('text')
|
||||
if (typeof transformRichTextOnEnterEdit === 'function') {
|
||||
nodeText = transformRichTextOnEnterEdit(nodeText)
|
||||
}
|
||||
// 是否是空文本
|
||||
const isEmptyText = isUndef(nodeText)
|
||||
// 是否是非空的非富文本
|
||||
@@ -312,7 +317,9 @@ class RichText {
|
||||
getEditText() {
|
||||
let html = this.quill.container.firstChild.innerHTML
|
||||
// 去除ql-cursor节点
|
||||
html = removeHtmlNodeByClass(html, '.ql-cursor')
|
||||
// https://github.com/wanglin2/mind-map/commit/138cc4b3e824671143f0bf70e5c46796f48520d0
|
||||
// https://github.com/wanglin2/mind-map/commit/0760500cebe8ec4e8ad84ab63f877b8b2a193aa1
|
||||
// html = removeHtmlNodeByClass(html, '.ql-cursor')
|
||||
// 去除最后的空行
|
||||
return html.replace(/<p><br><\/p>$/, '')
|
||||
}
|
||||
@@ -322,6 +329,10 @@ class RichText {
|
||||
if (!this.showTextEdit) {
|
||||
return
|
||||
}
|
||||
const { beforeHideRichTextEdit } = this.mindMap.opt
|
||||
if (typeof beforeHideRichTextEdit === 'function') {
|
||||
beforeHideRichTextEdit(this)
|
||||
}
|
||||
let html = this.getEditText()
|
||||
let list =
|
||||
nodes && nodes.length > 0 ? nodes : this.mindMap.renderer.activeNodeList
|
||||
@@ -333,7 +344,7 @@ class RichText {
|
||||
// }
|
||||
this.mindMap.render()
|
||||
})
|
||||
this.mindMap.emit('hide_text_edit', this.textEditNode, list)
|
||||
this.mindMap.emit('hide_text_edit', this.textEditNode, list, this.node)
|
||||
this.textEditNode.style.display = 'none'
|
||||
this.showTextEdit = false
|
||||
this.mindMap.emit('rich_text_selection_change', false)
|
||||
@@ -365,6 +376,21 @@ class RichText {
|
||||
},
|
||||
theme: 'snow'
|
||||
})
|
||||
// 拦截粘贴事件
|
||||
this.quill.root.addEventListener('copy', event => {
|
||||
event.preventDefault()
|
||||
const sel = window.getSelection()
|
||||
const originStr = sel.toString()
|
||||
try {
|
||||
const range = sel.getRangeAt(0)
|
||||
const div = document.createElement('div')
|
||||
div.appendChild(range.cloneContents())
|
||||
const text = nodeRichTextToTextWithWrap(div.innerHTML)
|
||||
event.clipboardData.setData('text/plain', text)
|
||||
} catch (e) {
|
||||
event.clipboardData.setData('text/plain', originStr)
|
||||
}
|
||||
})
|
||||
this.quill.on('selection-change', range => {
|
||||
// 刚创建的节点全选不需要显示操作条
|
||||
if (this.isInserting) return
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { bfsWalk, throttle, checkTwoRectIsOverlap } from '../utils'
|
||||
import AutoMove from '../utils/AutoMove'
|
||||
|
||||
// 节点选择插件
|
||||
class Select {
|
||||
@@ -13,6 +14,7 @@ class Select {
|
||||
this.mouseMoveY = 0
|
||||
this.isSelecting = false
|
||||
this.cacheActiveList = []
|
||||
this.autoMove = new AutoMove(mindMap)
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
@@ -75,19 +77,21 @@ class Select {
|
||||
) {
|
||||
return
|
||||
}
|
||||
this.clearAutoMoveTimer()
|
||||
this.onMove(
|
||||
this.autoMove.clearAutoMoveTimer()
|
||||
this.autoMove.onMove(
|
||||
e.clientX,
|
||||
e.clientY,
|
||||
() => {
|
||||
this.isSelecting = true
|
||||
// 绘制矩形
|
||||
this.rect.plot([
|
||||
[this.mouseDownX, this.mouseDownY],
|
||||
[this.mouseMoveX, this.mouseDownY],
|
||||
[this.mouseMoveX, this.mouseMoveY],
|
||||
[this.mouseDownX, this.mouseMoveY]
|
||||
])
|
||||
if (this.rect) {
|
||||
this.rect.plot([
|
||||
[this.mouseDownX, this.mouseDownY],
|
||||
[this.mouseMoveX, this.mouseDownY],
|
||||
[this.mouseMoveX, this.mouseMoveY],
|
||||
[this.mouseDownX, this.mouseMoveY]
|
||||
])
|
||||
}
|
||||
this.checkInNodes()
|
||||
},
|
||||
(dir, step) => {
|
||||
@@ -120,7 +124,7 @@ class Select {
|
||||
return
|
||||
}
|
||||
this.checkTriggerNodeActiveEvent()
|
||||
clearTimeout(this.autoMoveTimer)
|
||||
this.autoMove.clearAutoMoveTimer()
|
||||
this.isMousedown = false
|
||||
this.cacheActiveList = []
|
||||
if (this.rect) this.rect.remove()
|
||||
@@ -154,54 +158,6 @@ class Select {
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标移动事件
|
||||
onMove(x, y, callback = () => {}, handle = () => {}) {
|
||||
callback()
|
||||
// 检测边缘移动
|
||||
let step = this.mindMap.opt.selectTranslateStep
|
||||
let limit = this.mindMap.opt.selectTranslateLimit
|
||||
let count = 0
|
||||
// 左边缘
|
||||
if (x <= this.mindMap.elRect.left + limit) {
|
||||
handle('left', step)
|
||||
this.mindMap.view.translateX(step)
|
||||
count++
|
||||
}
|
||||
// 右边缘
|
||||
if (x >= this.mindMap.elRect.right - limit) {
|
||||
handle('right', step)
|
||||
this.mindMap.view.translateX(-step)
|
||||
count++
|
||||
}
|
||||
// 上边缘
|
||||
if (y <= this.mindMap.elRect.top + limit) {
|
||||
handle('top', step)
|
||||
this.mindMap.view.translateY(step)
|
||||
count++
|
||||
}
|
||||
// 下边缘
|
||||
if (y >= this.mindMap.elRect.bottom - limit) {
|
||||
handle('bottom', step)
|
||||
this.mindMap.view.translateY(-step)
|
||||
count++
|
||||
}
|
||||
if (count > 0) {
|
||||
this.startAutoMove(x, y, callback, handle)
|
||||
}
|
||||
}
|
||||
|
||||
// 开启自动移动
|
||||
startAutoMove(x, y, callback, handle) {
|
||||
this.autoMoveTimer = setTimeout(() => {
|
||||
this.onMove(x, y, callback, handle)
|
||||
}, 20)
|
||||
}
|
||||
|
||||
// 清除自动移动定时器
|
||||
clearAutoMoveTimer() {
|
||||
clearTimeout(this.autoMoveTimer)
|
||||
}
|
||||
|
||||
// 创建矩形
|
||||
createRect(x, y) {
|
||||
if (this.rect) this.rect.remove()
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { getTwoPointDistance } from '../utils'
|
||||
|
||||
// 手势事件支持插件
|
||||
class TouchEvent {
|
||||
// 构造函数
|
||||
@@ -7,6 +9,8 @@ class TouchEvent {
|
||||
this.singleTouchstartEvent = null
|
||||
this.clickNum = 0
|
||||
this.touchStartScaleView = null
|
||||
this.lastTouchStartPosition = null
|
||||
this.lastTouchStartDistance = 0
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
@@ -16,10 +20,12 @@ class TouchEvent {
|
||||
this.onTouchmove = this.onTouchmove.bind(this)
|
||||
this.onTouchcancel = this.onTouchcancel.bind(this)
|
||||
this.onTouchend = this.onTouchend.bind(this)
|
||||
window.addEventListener('touchstart', this.onTouchstart)
|
||||
window.addEventListener('touchmove', this.onTouchmove)
|
||||
window.addEventListener('touchcancel', this.onTouchcancel)
|
||||
window.addEventListener('touchend', this.onTouchend)
|
||||
window.addEventListener('touchstart', this.onTouchstart, { passive: false })
|
||||
window.addEventListener('touchmove', this.onTouchmove, { passive: false })
|
||||
window.addEventListener('touchcancel', this.onTouchcancel, {
|
||||
passive: false
|
||||
})
|
||||
window.addEventListener('touchend', this.onTouchend, { passive: false })
|
||||
}
|
||||
|
||||
// 解绑事件
|
||||
@@ -36,6 +42,18 @@ class TouchEvent {
|
||||
this.touchStartScaleView = null
|
||||
if (this.touchesNum === 1) {
|
||||
let touch = e.touches[0]
|
||||
if (this.lastTouchStartPosition) {
|
||||
this.lastTouchStartDistance = getTwoPointDistance(
|
||||
this.lastTouchStartPosition.x,
|
||||
this.lastTouchStartPosition.y,
|
||||
touch.clientX,
|
||||
touch.clientY
|
||||
)
|
||||
}
|
||||
this.lastTouchStartPosition = {
|
||||
x: touch.clientX,
|
||||
y: touch.clientY
|
||||
}
|
||||
this.singleTouchstartEvent = touch
|
||||
this.dispatchMouseEvent('mousedown', touch.target, touch)
|
||||
}
|
||||
@@ -48,7 +66,13 @@ class TouchEvent {
|
||||
let touch = e.touches[0]
|
||||
this.dispatchMouseEvent('mousemove', touch.target, touch)
|
||||
} else if (len === 2) {
|
||||
if (this.mindMap.opt.disableTouchZoom) return
|
||||
let { disableTouchZoom, minTouchZoomScale, maxTouchZoomScale } =
|
||||
this.mindMap.opt
|
||||
if (disableTouchZoom) return
|
||||
minTouchZoomScale =
|
||||
minTouchZoomScale === -1 ? -Infinity : minTouchZoomScale / 100
|
||||
maxTouchZoomScale =
|
||||
maxTouchZoomScale === -1 ? Infinity : maxTouchZoomScale / 100
|
||||
let touch1 = e.touches[0]
|
||||
let touch2 = e.touches[1]
|
||||
let ox = touch1.clientX - touch2.clientX
|
||||
@@ -83,8 +107,14 @@ class TouchEvent {
|
||||
if (Math.abs(distance - viewBefore.distance) <= 10) {
|
||||
scale = viewBefore.scale
|
||||
}
|
||||
scale =
|
||||
scale < minTouchZoomScale
|
||||
? minTouchZoomScale
|
||||
: scale > maxTouchZoomScale
|
||||
? maxTouchZoomScale
|
||||
: scale
|
||||
const ratio = 1 - scale / viewBefore.scale
|
||||
view.scale = scale < 0.1 ? 0.1 : scale
|
||||
view.scale = scale
|
||||
view.x =
|
||||
viewBefore.x +
|
||||
(cx - viewBefore.x) * ratio +
|
||||
@@ -109,9 +139,11 @@ class TouchEvent {
|
||||
this.clickNum++
|
||||
setTimeout(() => {
|
||||
this.clickNum = 0
|
||||
this.lastTouchStartPosition = null
|
||||
this.lastTouchStartDistance = 0
|
||||
}, 300)
|
||||
let ev = this.singleTouchstartEvent
|
||||
if (this.clickNum > 1) {
|
||||
if (this.clickNum > 1 && this.lastTouchStartDistance <= 5) {
|
||||
this.clickNum = 0
|
||||
this.dispatchMouseEvent('dblclick', ev.target, ev)
|
||||
} else {
|
||||
|
||||
@@ -41,10 +41,21 @@ class Watermark {
|
||||
// 创建水印容器
|
||||
createContainer() {
|
||||
if (this.watermarkDraw) return
|
||||
this.watermarkDraw = this.mindMap.svg
|
||||
.group()
|
||||
this.watermarkDraw = new G()
|
||||
.css({ 'pointer-events': 'none', 'user-select': 'none' })
|
||||
.addClass('smm-water-mark-container')
|
||||
this.updateLayer()
|
||||
}
|
||||
|
||||
// 更新水印容器层级
|
||||
updateLayer() {
|
||||
if (!this.watermarkDraw) return
|
||||
const { belowNode } = this.mindMap.opt.watermarkConfig
|
||||
if (belowNode) {
|
||||
this.watermarkDraw.insertBefore(this.mindMap.draw)
|
||||
} else {
|
||||
this.mindMap.svg.add(this.watermarkDraw)
|
||||
}
|
||||
}
|
||||
|
||||
// 删除水印容器
|
||||
@@ -160,6 +171,7 @@ class Watermark {
|
||||
this.mindMap.opt.watermarkConfig,
|
||||
config
|
||||
)
|
||||
this.updateLayer()
|
||||
this.handleConfig(config)
|
||||
this.draw()
|
||||
}
|
||||
@@ -167,11 +179,13 @@ class Watermark {
|
||||
// 插件被移除前做的事情
|
||||
beforePluginRemove() {
|
||||
this.unBindEvent()
|
||||
this.removeContainer()
|
||||
}
|
||||
|
||||
// 插件被卸载前做的事情
|
||||
beforePluginDestroy() {
|
||||
this.unBindEvent()
|
||||
this.removeContainer()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ function createOneControlNode(pointKey) {
|
||||
// 控制点的鼠标按下事件
|
||||
function onControlPointMousedown(e, pointKey) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
this.isControlPointMousedown = true
|
||||
this.mousedownControlPointKey = pointKey
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export default {
|
||||
lineStyle: 'straight', // 曲线(curve)【仅支持logicalStructure、mindMap、verticalTimeline三种结构】、直线(straight)、直连(direct)【仅支持logicalStructure、mindMap、organizationStructure、verticalTimeline四种结构】
|
||||
// 曲线连接时,根节点和其他节点的连接线样式保持统一,默认根节点为 ( 型,其他节点为 { 型,设为true后,都为 { 型。仅支持logicalStructure、mindMap两种结构
|
||||
rootLineKeepSameInCurve: true,
|
||||
// 曲线连接时,根节点和其他节点的连线起始位置保持统一,默认根节点的连线起始位置在节点中心,其他节点在节点右侧,如果该配置设为true,那么根节点的连线起始位置也会在节点右侧
|
||||
// 曲线连接时,根节点和其他节点的连线起始位置保持统一,默认根节点的连线起始位置在节点中心,其他节点在节点右侧(或左侧),如果该配置设为true,那么根节点的连线起始位置也会在节点右侧(或左侧)
|
||||
rootLineStartPositionKeepSameInCurve: false,
|
||||
// 直线连接(straight)时,连线的圆角大小,设置为0代表没有圆角,仅支持logicalStructure、mindMap、verticalTimeline三种结构
|
||||
lineRadius: 5,
|
||||
|
||||
57
simple-mind-map/src/utils/AutoMove.js
Normal file
@@ -0,0 +1,57 @@
|
||||
// 画布自动移动类
|
||||
class AutoMove {
|
||||
constructor(mindMap) {
|
||||
this.mindMap = mindMap
|
||||
this.autoMoveTimer = null
|
||||
}
|
||||
|
||||
// 鼠标移动事件
|
||||
onMove(x, y, callback = () => {}, handle = () => {}) {
|
||||
callback()
|
||||
// 检测边缘移动
|
||||
let step = this.mindMap.opt.selectTranslateStep
|
||||
let limit = this.mindMap.opt.selectTranslateLimit
|
||||
let count = 0
|
||||
// 左边缘
|
||||
if (x <= this.mindMap.elRect.left + limit) {
|
||||
handle('left', step)
|
||||
this.mindMap.view.translateX(step)
|
||||
count++
|
||||
}
|
||||
// 右边缘
|
||||
if (x >= this.mindMap.elRect.right - limit) {
|
||||
handle('right', step)
|
||||
this.mindMap.view.translateX(-step)
|
||||
count++
|
||||
}
|
||||
// 上边缘
|
||||
if (y <= this.mindMap.elRect.top + limit) {
|
||||
handle('top', step)
|
||||
this.mindMap.view.translateY(step)
|
||||
count++
|
||||
}
|
||||
// 下边缘
|
||||
if (y >= this.mindMap.elRect.bottom - limit) {
|
||||
handle('bottom', step)
|
||||
this.mindMap.view.translateY(-step)
|
||||
count++
|
||||
}
|
||||
if (count > 0) {
|
||||
this.startAutoMove(x, y, callback, handle)
|
||||
}
|
||||
}
|
||||
|
||||
// 开启自动移动
|
||||
startAutoMove(x, y, callback, handle) {
|
||||
this.autoMoveTimer = setTimeout(() => {
|
||||
this.onMove(x, y, callback, handle)
|
||||
}, 20)
|
||||
}
|
||||
|
||||
// 清除自动移动定时器
|
||||
clearAutoMoveTimer() {
|
||||
clearTimeout(this.autoMoveTimer)
|
||||
}
|
||||
}
|
||||
|
||||
export default AutoMove
|
||||
@@ -207,7 +207,7 @@ export const copyNodeTree = (
|
||||
}
|
||||
|
||||
// 图片转成dataURL
|
||||
export const imgToDataUrl = src => {
|
||||
export const imgToDataUrl = (src, returnBlob = false) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
// 跨域图片需要添加这个属性,否则画布被污染了无法导出图片
|
||||
@@ -220,7 +220,13 @@ export const imgToDataUrl = src => {
|
||||
let ctx = canvas.getContext('2d')
|
||||
// 图片绘制到canvas里
|
||||
ctx.drawImage(img, 0, 0, img.width, img.height)
|
||||
resolve(canvas.toDataURL())
|
||||
if (returnBlob) {
|
||||
canvas.toBlob(blob => {
|
||||
resolve(blob)
|
||||
})
|
||||
} else {
|
||||
resolve(canvas.toDataURL())
|
||||
}
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
@@ -1335,7 +1341,7 @@ export const handleGetSvgDataExtraContent = ({
|
||||
if (!res) return
|
||||
const { el, cssText, height } = res
|
||||
if (el instanceof HTMLElement) {
|
||||
el.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml')
|
||||
addXmlns(el)
|
||||
const foreignObject = createForeignObjectNode({ el, height })
|
||||
callback(foreignObject, height)
|
||||
}
|
||||
@@ -1368,7 +1374,8 @@ export const getNodeTreeBoundingRect = (
|
||||
y = 0,
|
||||
paddingX = 0,
|
||||
paddingY = 0,
|
||||
excludeSelf = false
|
||||
excludeSelf = false,
|
||||
excludeGeneralization = false
|
||||
) => {
|
||||
let minX = Infinity
|
||||
let maxX = -Infinity
|
||||
@@ -1392,7 +1399,7 @@ export const getNodeTreeBoundingRect = (
|
||||
maxY = y + height
|
||||
}
|
||||
}
|
||||
if (root._generalizationList.length > 0) {
|
||||
if (!excludeGeneralization && root._generalizationList.length > 0) {
|
||||
root._generalizationList.forEach(item => {
|
||||
walk(item.generalizationNode)
|
||||
})
|
||||
@@ -1418,6 +1425,49 @@ export const getNodeTreeBoundingRect = (
|
||||
}
|
||||
}
|
||||
|
||||
// 获取多个节点总的包围框
|
||||
export const getNodeListBoundingRect = (
|
||||
nodeList,
|
||||
x = 0,
|
||||
y = 0,
|
||||
paddingX = 0,
|
||||
paddingY = 0
|
||||
) => {
|
||||
let minX = Infinity
|
||||
let maxX = -Infinity
|
||||
let minY = Infinity
|
||||
let maxY = -Infinity
|
||||
nodeList.forEach(node => {
|
||||
const { left, top, width, height } = getNodeTreeBoundingRect(
|
||||
node,
|
||||
x,
|
||||
y,
|
||||
paddingX,
|
||||
paddingY,
|
||||
false,
|
||||
true
|
||||
)
|
||||
if (left < minX) {
|
||||
minX = left
|
||||
}
|
||||
if (left + width > maxX) {
|
||||
maxX = left + width
|
||||
}
|
||||
if (top < minY) {
|
||||
minY = top
|
||||
}
|
||||
if (top + height > maxY) {
|
||||
maxY = top + height
|
||||
}
|
||||
})
|
||||
return {
|
||||
left: minX,
|
||||
top: minY,
|
||||
width: maxX - minX,
|
||||
height: maxY - minY
|
||||
}
|
||||
}
|
||||
|
||||
// 全屏事件检测
|
||||
const getOnfullscreEnevt = () => {
|
||||
if (document.documentElement.requestFullScreen) {
|
||||
@@ -1476,3 +1526,57 @@ export const formatGetNodeGeneralization = data => {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 防御 XSS 攻击,过滤恶意 HTML 标签和属性
|
||||
* @param {string} text 需要过滤的文本
|
||||
* @returns {string} 过滤后的文本
|
||||
*/
|
||||
export const defenseXSS = text => {
|
||||
text = String(text)
|
||||
|
||||
// 初始化结果变量
|
||||
let result = text
|
||||
|
||||
// 使用正则表达式匹配 HTML 标签
|
||||
const match = text.match(/<(\S*?)[^>]*>.*?|<.*? \/>/g)
|
||||
if (match == null) {
|
||||
// 如果没有匹配到任何标签,则直接返回原始文本
|
||||
return text
|
||||
}
|
||||
|
||||
// 遍历匹配到的标签
|
||||
for (let value of match) {
|
||||
// 定义白名单属性正则表达式(style、target、href)
|
||||
const whiteAttrRegex = new RegExp(/(style|target|href)=["'][^"']*["']/g)
|
||||
|
||||
// 定义黑名单href正则表达式(javascript:)
|
||||
const aHrefBlackRegex = new RegExp(/href=["']javascript:/g)
|
||||
|
||||
// 过滤 HTML 标签
|
||||
const filterHtml = value.replace(
|
||||
// 匹配属性键值对(如:key="value")
|
||||
/([a-zA-Z-]+)\s*=\s*["']([^"']*)["']/g,
|
||||
text => {
|
||||
// 如果属性值包含黑名单href或不在白名单中,则删除该属性
|
||||
if (aHrefBlackRegex.test(text) || !whiteAttrRegex.test(text)) {
|
||||
return ''
|
||||
}
|
||||
|
||||
// 否则,保留该属性
|
||||
return text
|
||||
}
|
||||
)
|
||||
|
||||
// 将过滤后的标签替换回原始文本
|
||||
result = result.replace(value, filterHtml)
|
||||
}
|
||||
|
||||
// 返回最终结果
|
||||
return result
|
||||
}
|
||||
|
||||
// 给节点添加命名空间
|
||||
export const addXmlns = el => {
|
||||
el.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml')
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
/**
|
||||
* 防御 XSS 攻击,过滤恶意 HTML 标签和属性
|
||||
* @param {string} text 需要过滤的文本
|
||||
* @returns {string} 过滤后的文本
|
||||
*/
|
||||
export function defenseXSS(text) {
|
||||
text = String(text)
|
||||
|
||||
// 初始化结果变量
|
||||
let result = text;
|
||||
|
||||
// 使用正则表达式匹配 HTML 标签
|
||||
const match = text.match(/<(\S*?)[^>]*>.*?|<.*? \/>/g);
|
||||
if (match == null) {
|
||||
// 如果没有匹配到任何标签,则直接返回原始文本
|
||||
return text;
|
||||
}
|
||||
|
||||
// 遍历匹配到的标签
|
||||
for (let value of match) {
|
||||
// 定义白名单属性正则表达式(style、target、href)
|
||||
const whiteAttrRegex = new RegExp(/(style|target|href)=["'][^"']*["']/g);
|
||||
|
||||
// 定义黑名单href正则表达式(javascript:)
|
||||
const aHrefBlackRegex = new RegExp(/href=["']javascript:/g);
|
||||
|
||||
// 过滤 HTML 标签
|
||||
const filterHtml = value.replace(
|
||||
// 匹配属性键值对(如:key="value")
|
||||
/([a-zA-Z-]+)\s*=\s*["']([^"']*)["']/g,
|
||||
(text) => {
|
||||
// 如果属性值包含黑名单href或不在白名单中,则删除该属性
|
||||
if (aHrefBlackRegex.test(text) || !whiteAttrRegex.test(text)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// 否则,保留该属性
|
||||
return text;
|
||||
}
|
||||
);
|
||||
|
||||
// 将过滤后的标签替换回原始文本
|
||||
result = result.replace(value, filterHtml);
|
||||
}
|
||||
|
||||
// 返回最终结果
|
||||
return result;
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
<!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,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0">
|
||||
<link rel="icon" href="dist/logo.ico">
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0"
|
||||
/>
|
||||
<link rel="icon" href="dist/logo.ico" />
|
||||
<title>思绪思维导图</title>
|
||||
<script>
|
||||
// 自定义静态资源的路径
|
||||
@@ -12,10 +15,26 @@
|
||||
// 接管应用
|
||||
window.takeOverApp = false
|
||||
</script>
|
||||
<script
|
||||
charset="UTF-8"
|
||||
id="LA_COLLECT"
|
||||
src="//sdk.51.la/js-sdk-pro.min.js"
|
||||
></script>
|
||||
<script>
|
||||
LA.init({
|
||||
id: 'KRO0WxK8GT66tYCQ',
|
||||
ck: 'KRO0WxK8GT66tYCQ',
|
||||
autoTrack: false
|
||||
})
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
<strong
|
||||
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
|
||||
properly without JavaScript enabled. Please enable it to
|
||||
continue.</strong
|
||||
>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
@@ -25,19 +44,19 @@
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
mindMapData: {
|
||||
root:{
|
||||
"data": {
|
||||
"text": "根节点"
|
||||
root: {
|
||||
data: {
|
||||
text: '根节点'
|
||||
},
|
||||
"children": []
|
||||
children: []
|
||||
},
|
||||
theme:{
|
||||
"template":"avocado",
|
||||
"config":{}
|
||||
theme: {
|
||||
template: 'avocado',
|
||||
config: {}
|
||||
},
|
||||
layout:"logicalStructure",
|
||||
layout: 'logicalStructure',
|
||||
config: {},
|
||||
view: null,
|
||||
view: null
|
||||
},
|
||||
lang: 'zh',
|
||||
localConfig: null
|
||||
@@ -45,14 +64,14 @@
|
||||
}, 200)
|
||||
})
|
||||
}
|
||||
const setTakeOverAppMethods = (data) => {
|
||||
const setTakeOverAppMethods = data => {
|
||||
window.takeOverAppMethods = {}
|
||||
// 获取思维导图数据的函数
|
||||
window.takeOverAppMethods.getMindMapData = () => {
|
||||
return data.mindMapData
|
||||
}
|
||||
}
|
||||
// 保存思维导图数据的函数
|
||||
window.takeOverAppMethods.saveMindMapData = (data) => {
|
||||
window.takeOverAppMethods.saveMindMapData = data => {
|
||||
console.log(data)
|
||||
}
|
||||
// 获取语言的函数
|
||||
@@ -60,7 +79,7 @@
|
||||
return data.lang
|
||||
}
|
||||
// 保存语言的函数
|
||||
window.takeOverAppMethods.saveLanguage = (lang) => {
|
||||
window.takeOverAppMethods.saveLanguage = lang => {
|
||||
console.log(lang)
|
||||
}
|
||||
// 获取本地配置的函数
|
||||
@@ -68,7 +87,7 @@
|
||||
return data.localConfig
|
||||
}
|
||||
// 保存本地配置的函数
|
||||
window.takeOverAppMethods.saveLocalConfig = (config) => {
|
||||
window.takeOverAppMethods.saveLocalConfig = config => {
|
||||
console.log(config)
|
||||
}
|
||||
}
|
||||
@@ -79,7 +98,7 @@
|
||||
// 设置全局的方法
|
||||
setTakeOverAppMethods(data)
|
||||
// 思维导图实例创建完成事件
|
||||
window.$bus.$on('app_inited', (mindMap) => {
|
||||
window.$bus.$on('app_inited', mindMap => {
|
||||
console.log(mindMap)
|
||||
})
|
||||
// 可以通过window.$bus.$on()来监听应用的一些事件
|
||||
|
||||
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
BIN
web/src/assets/avatar/Kyle.jpg
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
web/src/assets/avatar/ZX.jpg
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
web/src/assets/avatar/buddy.jpg
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
web/src/assets/avatar/lsytyrt.jpg
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
web/src/assets/avatar/好名字.jpg
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
web/src/assets/avatar/峰.jpg
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
web/src/assets/avatar/最多5个字.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
web/src/assets/avatar/木木.jpg
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
web/src/assets/avatar/秀树因馨雨.jpg
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
web/src/assets/avatar/雨馨.jpg
Normal file
|
After Width: | Height: | Size: 32 KiB |
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 2479351 */
|
||||
src: url('iconfont.woff2?t=1713438156457') format('woff2'),
|
||||
url('iconfont.woff?t=1713438156457') format('woff'),
|
||||
url('iconfont.ttf?t=1713438156457') format('truetype');
|
||||
src: url('iconfont.woff2?t=1719815803051') format('woff2'),
|
||||
url('iconfont.woff?t=1719815803051') format('woff'),
|
||||
url('iconfont.ttf?t=1719815803051') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,6 +13,14 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.iconwaikuang:before {
|
||||
content: "\e640";
|
||||
}
|
||||
|
||||
.iconhighlight:before {
|
||||
content: "\e6b8";
|
||||
}
|
||||
|
||||
.iconyanshibofang:before {
|
||||
content: "\e648";
|
||||
}
|
||||
|
||||
BIN
web/src/assets/img/docs/标记.jpg
Normal file
|
After Width: | Height: | Size: 54 KiB |