mirror of
https://github.com/wanglin2/mind-map.git
synced 2026-02-18 22:38:46 +08:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
830e7e2482 | ||
|
|
13ed7f28df | ||
|
|
17e79a0b23 | ||
|
|
eee310ba49 | ||
|
|
36c8927dd0 | ||
|
|
05333daa63 | ||
|
|
5aed681198 | ||
|
|
8a438f2906 | ||
|
|
260de4987d | ||
|
|
31cc658c06 | ||
|
|
be6b41d74d | ||
|
|
4beeead53d | ||
|
|
af2df6acd3 | ||
|
|
c08d66acf2 | ||
|
|
5a9cb9ac07 | ||
|
|
1662cd1be7 | ||
|
|
9dd5b3d47e | ||
|
|
ece116317b | ||
|
|
d24d5c8281 | ||
|
|
97c01cda3a | ||
|
|
6d520ece7e | ||
|
|
745041deef | ||
|
|
ae6c10cdf9 | ||
|
|
f5338d62fc | ||
|
|
19fc12ff20 | ||
|
|
d7640bb026 |
460
README.md
460
README.md
@@ -18,19 +18,33 @@
|
||||
|
||||
- [x] 支持节点自由拖拽、拖拽调整
|
||||
|
||||
- [x] 支持多种节点形状
|
||||
|
||||
- [x] 支持导出为`json`、`png`、`svg`、`pdf`,支持从`json`、`xmind`导入
|
||||
|
||||
## 目录介绍
|
||||
|
||||
1.`simple-mind-map`
|
||||
|
||||
思维导图工具库。
|
||||
思维导图工具库,框架无关,`Vue`、`React`等框架或无框架都可以使用。
|
||||
|
||||
2.`web`
|
||||
|
||||
使用`simple-mind-map`工具库,基于`vue2.x`、`ElementUI`搭建的在线思维导图。
|
||||
使用`simple-mind-map`工具库,基于`vue2.x`、`ElementUI`搭建的在线思维导图。特性:
|
||||
|
||||
- [x] 工具栏,支持插入节点、删除节点;编辑节点图片、图标、超链接、备注、标签、概要
|
||||
|
||||
- [x] 侧边栏,基础样式设置面板、节点样式设置面板、大纲面板、主题选择面板、结构选择面板
|
||||
|
||||
- [x] 导入导出功能;数据默认保存在浏览器本地存储,也支持直接创建、打开、编辑电脑本地文件
|
||||
|
||||
- [x] 右键菜单,支持展开、收起、整理布局等操作
|
||||
|
||||
- [x] 底部栏,支持节点数量、字数统计;支持切换编辑和只读模式;支持放大缩小;支持全屏切换
|
||||
|
||||
3.`dist`
|
||||
|
||||
打包后的资源文件夹。
|
||||
打包`web`后的资源文件夹。
|
||||
|
||||
4.`docs`
|
||||
|
||||
@@ -78,6 +92,7 @@ npm run buildLibrary
|
||||
cd web
|
||||
npm run build
|
||||
```
|
||||
|
||||
会自动把`index.html`移动到根目录。
|
||||
|
||||
## 相关文章
|
||||
@@ -86,7 +101,7 @@ npm run build
|
||||
|
||||
# 安装
|
||||
|
||||
> 当然仓库版本:0.2.3,当前npm版本:0.2.3
|
||||
> 当然仓库版本:0.2.9,当前npm版本:0.2.9
|
||||
|
||||
```bash
|
||||
npm i simple-mind-map
|
||||
@@ -94,15 +109,15 @@ npm i simple-mind-map
|
||||
|
||||
`0.2.0`版本之前的注意事项:
|
||||
|
||||
>注意:本项目为源码直接发布,并未进行打包,如果出现编译失败的情况,Vue CLI创建的项目可以在vue.config.js文件中增加如下配置来让babel-loader编译本依赖:
|
||||
>
|
||||
>```js
|
||||
>module.exports = {
|
||||
> transpileDependencies: ['simple-mind-map']
|
||||
>}
|
||||
>```
|
||||
>
|
||||
>其他项目请自行修改打包配置。
|
||||
> 注意:本项目为源码直接发布,并未进行打包,如果出现编译失败的情况,Vue CLI创建的项目可以在vue.config.js文件中增加如下配置来让babel-loader编译本依赖:
|
||||
>
|
||||
> ```js
|
||||
> module.exports = {
|
||||
> transpileDependencies: ['simple-mind-map']
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> 其他项目请自行修改打包配置。
|
||||
|
||||
# API
|
||||
|
||||
@@ -121,27 +136,58 @@ const mindMap = new MindMap({
|
||||
});
|
||||
```
|
||||
|
||||
### Xmind解析方法
|
||||
|
||||
v0.2.7+
|
||||
|
||||
可以通过如下方法获取解析`Xmind`文件的方法:
|
||||
|
||||
```js
|
||||
import MindMap from "simple-mind-map";
|
||||
|
||||
console.log(MindMap.xmind)
|
||||
```
|
||||
|
||||
`MindMap.xmind`对象上挂载了两个方法:
|
||||
|
||||
#### parseXmindFile(file)
|
||||
|
||||
解析`.xmind`文件,返回解析后的数据,注意是完整的数据,包含节点树、主题、结构等,可以使用`mindMap.setFullData(data)`来将返回的数据渲染到画布上
|
||||
|
||||
`file`:`File`对象
|
||||
|
||||
#### transformXmind(content)
|
||||
|
||||
转换`xmind`数据,`.xmind`文件本质上是一个压缩包,改成`zip`后缀可以解压缩,里面存在一个`content.json`文件,如果你自己解析出了这个文件,那么可以把这个文件内容传递给这个方法进行转换,转换后的数据,注意是完整的数据,包含节点树、主题、结构等,可以使用`mindMap.setFullData(data)`来将返回的数据渲染到画布上
|
||||
|
||||
`content`:`.xmind`压缩包内的`content.json`文件内容
|
||||
|
||||
#### transformOldXmind(content)
|
||||
|
||||
v0.2.8+
|
||||
|
||||
针对`xmind8`版本的数据解析,因为该版本的`.xmind`文件内没有`content.json`,对应的是`content.xml`。
|
||||
|
||||
`content`:`.xmind`压缩包内的`content.xml`文件内容
|
||||
|
||||
### 实例化选项:
|
||||
|
||||
| 字段名称 | 类型 | 默认值 | 描述 | 是否必填 |
|
||||
| -------------------- | ------- | ---------------- | ------------------------------------------------------------ | -------- |
|
||||
| el | Element | | 容器元素,必须为DOM元素 | 是 |
|
||||
| data | Object | {} | 思维导图数据,可参考:[https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js) | |
|
||||
| layout | String | logicalStructure | 布局类型,可选列表:logicalStructure(逻辑结构图)、mindMap(思维导图)、catalogOrganization(目录组织图)、organizationStructure(组织结构图) | |
|
||||
| theme | String | default | 主题,可选列表:default(默认)、classic(脑图经典)、minions(小黄人)、pinkGrape(粉红葡萄)、mint(薄荷)、gold(金色vip)、vitalityOrange(活力橙)、greenLeaf(绿叶)、dark2(暗色2)、skyGreen(天清绿)、classic2(脑图经典2)、classic3(脑图经典3)、classic4(脑图经典4,v0.2.0+)、classicGreen(经典绿)、classicBlue(经典蓝)、blueSky(天空蓝)、brainImpairedPink(脑残粉)、dark(暗色)、earthYellow(泥土黄)、freshGreen(清新绿)、freshRed(清新红)、romanticPurple(浪漫紫) | |
|
||||
| themeConfig | Object | {} | 主题配置,会和所选择的主题进行合并,可用字段可参考:[https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/default.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/default.js) | |
|
||||
| scaleRatio | Number | 0.1 | 放大缩小的增量比例 | |
|
||||
| maxTag | Number | 5 | 节点里最多显示的标签数量,多余的会被丢弃 | |
|
||||
| exportPadding | Number | 20 | 导出图片时的内边距 | |
|
||||
| imgTextMargin | Number | 5 | 节点里图片和文字的间距 | |
|
||||
| textContentMargin | Number | 2 | 节点里各种文字信息的间距,如图标和文字的间距 | |
|
||||
| selectTranslateStep | Number | 3 | 多选节点时鼠标移动到边缘时的画布移动偏移量 | |
|
||||
| selectTranslateLimit | Number | 20 | 多选节点时鼠标移动距边缘多少距离时开始偏移 | |
|
||||
| customNoteContentShow(v0.1.6+) | Object | null | 自定义节点备注内容显示,Object类型,结构为:{show: (noteContent, left, top) => {// 你的显示节点备注逻辑 }, hide: () => {// 你的隐藏节点备注逻辑 }} | |
|
||||
| readonly(v0.1.7+) | Boolean | false | 是否是只读模式 | |
|
||||
|
||||
| 字段名称 | 类型 | 默认值 | 描述 | 是否必填 |
|
||||
| ------------------------------ | ------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- |
|
||||
| el | Element | | 容器元素,必须为DOM元素 | 是 |
|
||||
| data | Object | {} | 思维导图数据,可参考:[https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js) | |
|
||||
| layout | String | logicalStructure | 布局类型,可选列表:logicalStructure(逻辑结构图)、mindMap(思维导图)、catalogOrganization(目录组织图)、organizationStructure(组织结构图) | |
|
||||
| theme | String | default | 主题,可选列表:default(默认)、classic(脑图经典)、minions(小黄人)、pinkGrape(粉红葡萄)、mint(薄荷)、gold(金色vip)、vitalityOrange(活力橙)、greenLeaf(绿叶)、dark2(暗色2)、skyGreen(天清绿)、classic2(脑图经典2)、classic3(脑图经典3)、classic4(脑图经典4,v0.2.0+)、classicGreen(经典绿)、classicBlue(经典蓝)、blueSky(天空蓝)、brainImpairedPink(脑残粉)、dark(暗色)、earthYellow(泥土黄)、freshGreen(清新绿)、freshRed(清新红)、romanticPurple(浪漫紫) | |
|
||||
| themeConfig | Object | {} | 主题配置,会和所选择的主题进行合并,可用字段可参考:[https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/default.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/default.js) | |
|
||||
| scaleRatio | Number | 0.1 | 放大缩小的增量比例 | |
|
||||
| maxTag | Number | 5 | 节点里最多显示的标签数量,多余的会被丢弃 | |
|
||||
| exportPadding | Number | 20 | 导出图片时的内边距 | |
|
||||
| imgTextMargin | Number | 5 | 节点里图片和文字的间距 | |
|
||||
| textContentMargin | Number | 2 | 节点里各种文字信息的间距,如图标和文字的间距 | |
|
||||
| selectTranslateStep | Number | 3 | 多选节点时鼠标移动到边缘时的画布移动偏移量 | |
|
||||
| selectTranslateLimit | Number | 20 | 多选节点时鼠标移动距边缘多少距离时开始偏移 | |
|
||||
| customNoteContentShow(v0.1.6+) | Object | null | 自定义节点备注内容显示,Object类型,结构为:{show: (noteContent, left, top) => {// 你的显示节点备注逻辑 }, hide: () => {// 你的隐藏节点备注逻辑 }} | |
|
||||
| readonly(v0.1.7+) | Boolean | false | 是否是只读模式 | |
|
||||
|
||||
### 实例方法:
|
||||
|
||||
@@ -149,14 +195,10 @@ const mindMap = new MindMap({
|
||||
|
||||
触发整体渲染,会进行节点复用,性能较`reRender`会更好一点,如果只是节点位置变化了可以调用该方法进行渲染
|
||||
|
||||
|
||||
|
||||
#### reRender()
|
||||
|
||||
整体重新渲染,会清空画布,节点也会重新创建,性能不好,慎重使用
|
||||
|
||||
|
||||
|
||||
#### resize()
|
||||
|
||||
容器尺寸变化后,需要调用该方法进行适应
|
||||
@@ -171,57 +213,47 @@ v0.1.7+。切换模式为只读或编辑。
|
||||
|
||||
监听事件,事件列表:
|
||||
|
||||
| 事件名称 | 描述 | 回调参数 |
|
||||
| --------------------------- | ------------------------------------------ | ------------------------------------------------------------ |
|
||||
| data_change | 渲染树数据变化,可以监听该方法获取最新数据 | data(当前渲染树数据) |
|
||||
| view_data_change(v0.1.1+) | 视图变化数据,比如拖动或缩放时会触发 | data(当前视图状态数据) |
|
||||
| back_forward | 前进或回退 | activeHistoryIndex(当前在历史数据数组里的索引)、length(当前历史数据数组的长度) |
|
||||
| draw_click | *画布的单击事件* | e(事件对象) |
|
||||
| svg_mousedown | svg画布的鼠标按下事件 | e(事件对象) |
|
||||
| mousedown | el元素的鼠标按下事件 | e(事件对象)、this(Event事件类实例) |
|
||||
| mousemove | el元素的鼠标移动事件 | e(事件对象)、this(Event事件类实例) |
|
||||
| drag | 如果是按住左键拖动的话会触发拖动事件 | e(事件对象)、this(Event事件类实例) |
|
||||
| mouseup | el元素的鼠标松开事件 | e(事件对象)、this(Event事件类实例) |
|
||||
| mousewheel | 鼠标滚动事件 | e(事件对象)、dir(向上up还是向下down滚动)、this(Event事件类实例) |
|
||||
| contextmenu | svg画布的鼠标右键菜单事件 | e(事件对象) |
|
||||
| node_click | 节点的单击事件 | this(节点实例)、e(事件对象) |
|
||||
| node_mousedown | 节点的鼠标按下事件 | this(节点实例)、e(事件对象) |
|
||||
| node_mouseup | 节点的鼠标松开事件 | this(节点实例)、e(事件对象) |
|
||||
| node_dblclick | 节点的双击事件 | this(节点实例)、e(事件对象) |
|
||||
| node_contextmenu | 节点的右键菜单事件 | e(事件对象)、this(节点实例) |
|
||||
| before_node_active | 节点激活前事件 | this(节点实例)、activeNodeList(当前激活的所有节点列表) |
|
||||
| node_active | 节点激活事件 | this(节点实例)、activeNodeList(当前激活的所有节点列表) |
|
||||
| expand_btn_click | 节点展开或收缩事件 | this(节点实例) |
|
||||
| before_show_text_edit | 节点文本编辑框即将打开事件 | |
|
||||
| hide_text_edit | 节点文本编辑框关闭事件 | textEditNode(文本编辑框DOM节点)、activeNodeList(当前激活的所有节点列表) |
|
||||
| scale | 放大缩小事件 | scale(缩放比例) |
|
||||
|
||||
|
||||
| 事件名称 | 描述 | 回调参数 |
|
||||
| ------------------------- | --------------------- | ----------------------------------------------------- |
|
||||
| data_change | 渲染树数据变化,可以监听该方法获取最新数据 | data(当前渲染树数据) |
|
||||
| view_data_change(v0.1.1+) | 视图变化数据,比如拖动或缩放时会触发 | data(当前视图状态数据) |
|
||||
| back_forward | 前进或回退 | activeHistoryIndex(当前在历史数据数组里的索引)、length(当前历史数据数组的长度) |
|
||||
| draw_click | *画布的单击事件* | e(事件对象) |
|
||||
| svg_mousedown | svg画布的鼠标按下事件 | e(事件对象) |
|
||||
| mousedown | el元素的鼠标按下事件 | e(事件对象)、this(Event事件类实例) |
|
||||
| mousemove | el元素的鼠标移动事件 | e(事件对象)、this(Event事件类实例) |
|
||||
| drag | 如果是按住左键拖动的话会触发拖动事件 | e(事件对象)、this(Event事件类实例) |
|
||||
| mouseup | el元素的鼠标松开事件 | e(事件对象)、this(Event事件类实例) |
|
||||
| mousewheel | 鼠标滚动事件 | e(事件对象)、dir(向上up还是向下down滚动)、this(Event事件类实例) |
|
||||
| contextmenu | svg画布的鼠标右键菜单事件 | e(事件对象) |
|
||||
| node_click | 节点的单击事件 | this(节点实例)、e(事件对象) |
|
||||
| node_mousedown | 节点的鼠标按下事件 | this(节点实例)、e(事件对象) |
|
||||
| node_mouseup | 节点的鼠标松开事件 | this(节点实例)、e(事件对象) |
|
||||
| node_dblclick | 节点的双击事件 | this(节点实例)、e(事件对象) |
|
||||
| node_contextmenu | 节点的右键菜单事件 | e(事件对象)、this(节点实例) |
|
||||
| before_node_active | 节点激活前事件 | this(节点实例)、activeNodeList(当前激活的所有节点列表) |
|
||||
| node_active | 节点激活事件 | this(节点实例)、activeNodeList(当前激活的所有节点列表) |
|
||||
| expand_btn_click | 节点展开或收缩事件 | this(节点实例) |
|
||||
| before_show_text_edit | 节点文本编辑框即将打开事件 | |
|
||||
| hide_text_edit | 节点文本编辑框关闭事件 | textEditNode(文本编辑框DOM节点)、activeNodeList(当前激活的所有节点列表) |
|
||||
| scale | 放大缩小事件 | scale(缩放比例) |
|
||||
|
||||
#### emit(event, ...args)
|
||||
|
||||
触发事件,可以是上面表格里的事件,也可以是自定义事件
|
||||
|
||||
|
||||
|
||||
#### off(event, fn)
|
||||
|
||||
解绑事件
|
||||
|
||||
|
||||
|
||||
#### setTheme(theme)
|
||||
|
||||
切换主题,可选主题见上面的选项表格
|
||||
|
||||
|
||||
|
||||
#### getTheme()
|
||||
|
||||
获取当前主题
|
||||
|
||||
|
||||
|
||||
#### setThemeConfig(config)
|
||||
|
||||
设置主题配置,`config`同上面选项表格里的选项`themeConfig`
|
||||
@@ -234,64 +266,74 @@ v0.1.7+。切换模式为只读或编辑。
|
||||
|
||||
获取某个主题配置属性值
|
||||
|
||||
|
||||
|
||||
#### getLayout()
|
||||
|
||||
获取当前的布局结构
|
||||
|
||||
|
||||
|
||||
#### setLayout(layout)
|
||||
|
||||
设置布局结构,可选值见上面选项表格的`layout`字段
|
||||
|
||||
|
||||
|
||||
#### execCommand(name, ...args)
|
||||
|
||||
执行命令,每执行一个命令就会在历史堆栈里添加一条记录用于回退或前进。所有命令如下:
|
||||
|
||||
| 命令名称 | 描述 | 参数 |
|
||||
| ----------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| SELECT_ALL | 全选 | |
|
||||
| BACK | 回退指定的步数 | step(要回退的步数,默认为1) |
|
||||
| FORWARD | 前进指定的步数 | step(要前进的步数,默认为1) |
|
||||
| INSERT_NODE | 插入同级节点,操作节点为当前激活的节点,如果有多个激活节点,只会对第一个有效 | |
|
||||
| INSERT_CHILD_NODE | 插入子节点,操作节点为当前激活的节点 | |
|
||||
| UP_NODE | 上移节点,操作节点为当前激活的节点,如果有多个激活节点,只会对第一个有效,对根节点或在列表里的第一个节点使用无效 | |
|
||||
| DOWN_NODE | 操作节点为当前激活的节点,如果有多个激活节点,只会对第一个有效,对根节点或在列表里的最后一个节点使用无效 | |
|
||||
| REMOVE_NODE | 删除节点,操作节点为当前激活的节点 | |
|
||||
| PASTE_NODE | 粘贴节点到节点,操作节点为当前激活的节点 | data(要粘贴的节点数据,一般通过`renderer.copyNode()`方法和`renderer.cutNode()`方法获取) |
|
||||
| CUT_NODE | 剪切节点,操作节点为当前激活的节点,如果有多个激活节点,只会对第一个有效,对根节点使用无效 | callback(回调函数,剪切的节点数据会通过调用该函数并通过参数返回) |
|
||||
| SET_NODE_STYLE | 修改节点样式 | node(要设置样式的节点)、prop(样式属性)、value(样式属性值)、isActive(布尔值,是否设置的是激活状态的样式) |
|
||||
| SET_NODE_ACTIVE | 设置节点是否激活 | node(要设置的节点)、active(布尔值,是否激活) |
|
||||
| CLEAR_ACTIVE_NODE | 清除当前已激活节点的激活状态,操作节点为当前激活的节点 | |
|
||||
| SET_NODE_EXPAND | 设置节点是否展开 | node(要设置的节点)、expand(布尔值,是否展开) |
|
||||
| EXPAND_ALL | 展开所有节点 | |
|
||||
| UNEXPAND_ALL | 收起所有节点 | |
|
||||
| SET_NODE_DATA | 更新节点数据,即更新节点数据对象里`data`对象的数据 | node(要设置的节点)、data(对象,要更新的数据,如`{expand: true}`) |
|
||||
| SET_NODE_TEXT | 设置节点文本 | node(要设置的节点)、text(要设置的文本字符串,换行可以使用`\n`) |
|
||||
| SET_NODE_IMAGE | 设置节点图片 | node(要设置的节点)、imgData(对象,图片信息,结构为:`{url, title, width, height}`,图片的宽高必须要传) |
|
||||
| SET_NODE_ICON | 设置节点图标 | node(要设置的节点)、icons(数组,预定义的图片名称组成的数组,可用图标可在[https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js)文件里的`nodeIconList`列表里获取到,图标名称为`type_name`,如`['priority_1']`) |
|
||||
| SET_NODE_HYPERLINK | 设置节点超链接 | node(要设置的节点)、link(超链接地址)、title(超链接名称,可选) |
|
||||
| SET_NODE_NOTE | 设置节点备注 | node(要设置的节点)、note(备注文字) |
|
||||
| SET_NODE_TAG | 设置节点标签 | node(要设置的节点)、tag(字符串数组,内置颜色信息可在[https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/utils/constant.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/utils/constant.js)里获取到) |
|
||||
| INSERT_AFTER(v0.1.5+) | 将节点移动到另一个节点的后面 | node(要移动的节点)、 exist(目标节点) |
|
||||
| INSERT_BEFORE(v0.1.5+) | 将节点移动到另一个节点的前面 | node(要移动的节点)、 exist(目标节点) |
|
||||
| MOVE_NODE_TO(v0.1.5+) | 移动一个节点作为另一个节点的子节点 | node(要移动的节点)、 toNode(目标节点) |
|
||||
| ADD_GENERALIZATION(v0.2.0+) | 添加节点概要 | data(概要的数据,对象格式,节点的数字段都支持,默认为{text: '概要'}) |
|
||||
| REMOVE_GENERALIZATION(v0.2.0+) | 删除节点概要 | |
|
||||
| SET_NODE_CUSTOM_POSITION(v0.2.0+) | 设置节点自定义位置 | node(要设置的节点)、 left(自定义的x坐标,默认为undefined)、 top(自定义的y坐标,默认为undefined) |
|
||||
| RESET_LAYOUT(v0.2.0+) | 一键整理布局 | |
|
||||
|
||||
| 命令名称 | 描述 | 参数 |
|
||||
| --------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| SELECT_ALL | 全选 | |
|
||||
| BACK | 回退指定的步数 | step(要回退的步数,默认为1) |
|
||||
| FORWARD | 前进指定的步数 | step(要前进的步数,默认为1) |
|
||||
| INSERT_NODE | 插入同级节点,操作节点为当前激活的节点,如果有多个激活节点,只会对第一个有效 | |
|
||||
| INSERT_CHILD_NODE | 插入子节点,操作节点为当前激活的节点 | |
|
||||
| UP_NODE | 上移节点,操作节点为当前激活的节点,如果有多个激活节点,只会对第一个有效,对根节点或在列表里的第一个节点使用无效 | |
|
||||
| DOWN_NODE | 操作节点为当前激活的节点,如果有多个激活节点,只会对第一个有效,对根节点或在列表里的最后一个节点使用无效 | |
|
||||
| REMOVE_NODE | 删除节点,操作节点为当前激活的节点 | |
|
||||
| PASTE_NODE | 粘贴节点到节点,操作节点为当前激活的节点 | data(要粘贴的节点数据,一般通过`renderer.copyNode()`方法和`renderer.cutNode()`方法获取) |
|
||||
| CUT_NODE | 剪切节点,操作节点为当前激活的节点,如果有多个激活节点,只会对第一个有效,对根节点使用无效 | callback(回调函数,剪切的节点数据会通过调用该函数并通过参数返回) |
|
||||
| SET_NODE_STYLE | 修改节点样式 | node(要设置样式的节点)、prop(样式属性)、value(样式属性值)、isActive(布尔值,是否设置的是激活状态的样式) |
|
||||
| SET_NODE_ACTIVE | 设置节点是否激活 | node(要设置的节点)、active(布尔值,是否激活) |
|
||||
| CLEAR_ACTIVE_NODE | 清除当前已激活节点的激活状态,操作节点为当前激活的节点 | |
|
||||
| SET_NODE_EXPAND | 设置节点是否展开 | node(要设置的节点)、expand(布尔值,是否展开) |
|
||||
| EXPAND_ALL | 展开所有节点 | |
|
||||
| UNEXPAND_ALL | 收起所有节点 | |
|
||||
| UNEXPAND_TO_LEVEL(v0.2.8+) | 展开到指定层级 | level(要展开到的层级,1、2、3...) |
|
||||
| SET_NODE_DATA | 更新节点数据,即更新节点数据对象里`data`对象的数据 | node(要设置的节点)、data(对象,要更新的数据,如`{expand: true}`) |
|
||||
| SET_NODE_TEXT | 设置节点文本 | node(要设置的节点)、text(要设置的文本字符串,换行可以使用`\n`) |
|
||||
| SET_NODE_IMAGE | 设置节点图片 | node(要设置的节点)、imgData(对象,图片信息,结构为:`{url, title, width, height}`,图片的宽高必须要传) |
|
||||
| SET_NODE_ICON | 设置节点图标 | node(要设置的节点)、icons(数组,预定义的图片名称组成的数组,可用图标可在[https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js)文件里的`nodeIconList`列表里获取到,图标名称为`type_name`,如`['priority_1']`) |
|
||||
| SET_NODE_HYPERLINK | 设置节点超链接 | node(要设置的节点)、link(超链接地址)、title(超链接名称,可选) |
|
||||
| SET_NODE_NOTE | 设置节点备注 | node(要设置的节点)、note(备注文字) |
|
||||
| SET_NODE_TAG | 设置节点标签 | node(要设置的节点)、tag(字符串数组,内置颜色信息可在[https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/utils/constant.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/utils/constant.js)里获取到) |
|
||||
| INSERT_AFTER(v0.1.5+) | 将节点移动到另一个节点的后面 | node(要移动的节点)、 exist(目标节点) |
|
||||
| INSERT_BEFORE(v0.1.5+) | 将节点移动到另一个节点的前面 | node(要移动的节点)、 exist(目标节点) |
|
||||
| MOVE_NODE_TO(v0.1.5+) | 移动一个节点作为另一个节点的子节点 | node(要移动的节点)、 toNode(目标节点) |
|
||||
| ADD_GENERALIZATION(v0.2.0+) | 添加节点概要 | data(概要的数据,对象格式,节点的数字段都支持,默认为{text: '概要'}) |
|
||||
| REMOVE_GENERALIZATION(v0.2.0+) | 删除节点概要 | |
|
||||
| SET_NODE_CUSTOM_POSITION(v0.2.0+) | 设置节点自定义位置 | node(要设置的节点)、 left(自定义的x坐标,默认为undefined)、 top(自定义的y坐标,默认为undefined) |
|
||||
| RESET_LAYOUT(v0.2.0+) | 一键整理布局 | |
|
||||
| SET_NODE_SHAPE(v0.2.4+) | 设置节点形状 | node(要设置的节点)、shape(形状,全部形状:https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/Shape.js) |
|
||||
|
||||
#### setData(data)
|
||||
|
||||
动态设置思维导图数据
|
||||
动态设置思维导图数据,纯节点数据
|
||||
|
||||
`data`:思维导图结构数据
|
||||
|
||||
#### setFullData(*data*)
|
||||
|
||||
v0.2.7+
|
||||
|
||||
动态设置思维导图数据,包括节点数据、布局、主题、视图
|
||||
|
||||
`data`:完整数据,结构可参考[exportFullData](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exportFullData.json)
|
||||
|
||||
#### getData(withConfig)
|
||||
|
||||
v0.2.9+
|
||||
|
||||
获取思维导图数据
|
||||
|
||||
`withConfig`:`Boolean`,默认为`false`,即获取的数据只包括节点树,如果传`true`则会包含主题、布局、视图等数据
|
||||
|
||||
#### export(type, isDownload, fileName)
|
||||
|
||||
@@ -313,22 +355,16 @@ v0.1.5+
|
||||
|
||||
`render`实例负载整个渲染过程,可通过`mindMap.renderer`获取到
|
||||
|
||||
|
||||
|
||||
### 属性
|
||||
|
||||
#### activeNodeList
|
||||
|
||||
获取当前激活的节点列表
|
||||
|
||||
|
||||
|
||||
#### root
|
||||
|
||||
获取节点树的根节点
|
||||
|
||||
|
||||
|
||||
### 方法
|
||||
|
||||
#### clearActive()
|
||||
@@ -351,74 +387,52 @@ v0.1.5+
|
||||
|
||||
添加节点到激活列表里
|
||||
|
||||
|
||||
|
||||
#### removeActiveNode(node)
|
||||
|
||||
在激活列表里移除某个节点
|
||||
|
||||
|
||||
|
||||
#### findActiveNodeIndex(node)
|
||||
|
||||
检索某个节点在激活列表里的索引
|
||||
|
||||
|
||||
|
||||
#### getNodeIndex(node)
|
||||
|
||||
获取节点在同级里的位置索引
|
||||
|
||||
|
||||
|
||||
#### removeOneNode(node)
|
||||
|
||||
删除某个指定节点
|
||||
|
||||
|
||||
|
||||
#### copyNode()
|
||||
|
||||
复制节点,操作节点为当前激活节点,有多个激活节点只会操作第一个节点
|
||||
|
||||
|
||||
|
||||
#### setNodeDataRender(node, data)
|
||||
|
||||
设置节点数据,即`data`字段的数据,并会根据节点大小是否变化来判断是否需要重新渲染该节点,`data`为对象,如:`{text: '我是新文本'}`
|
||||
|
||||
|
||||
|
||||
#### moveNodeTo(node, toNode)
|
||||
|
||||
v0.1.5+
|
||||
|
||||
移动一个节点作为另一个节点的子节点
|
||||
|
||||
|
||||
|
||||
#### insertBefore(node, exist)
|
||||
|
||||
v0.1.5+
|
||||
|
||||
将节点移动到另一个节点的前面
|
||||
|
||||
|
||||
|
||||
#### insertAfter(node, exist)
|
||||
|
||||
v0.1.5+
|
||||
|
||||
将节点移动到另一个节点的后面
|
||||
|
||||
|
||||
|
||||
## keyCommand实例
|
||||
|
||||
`keyCommand`实例负责快捷键的添加及触发,内置了一些快捷键,也可以自行添加。可通过`mindMap.keyCommand`获取到该实例。
|
||||
|
||||
|
||||
|
||||
### 方法
|
||||
|
||||
#### addShortcut(key, fn)
|
||||
@@ -462,13 +476,10 @@ v0.2.3+。保存当前注册的快捷键数据,然后清空快捷键数据
|
||||
|
||||
v0.2.3+。恢复保存的快捷键数据,然后清空缓存数据
|
||||
|
||||
|
||||
## command实例
|
||||
|
||||
`command`实例负责命令的添加及执行,内置了很多命令,也可以自行添加,命令指需要在历史堆栈数据里添加副本的操作。可通过`mindMap.command`获取到该实例
|
||||
|
||||
|
||||
|
||||
### 方法
|
||||
|
||||
#### add(name, fn)
|
||||
@@ -479,8 +490,6 @@ v0.2.3+。恢复保存的快捷键数据,然后清空缓存数据
|
||||
|
||||
`fn`:命令要执行的方法
|
||||
|
||||
|
||||
|
||||
#### remove(name, fn)
|
||||
|
||||
移除命令。
|
||||
@@ -489,8 +498,6 @@ v0.2.3+。恢复保存的快捷键数据,然后清空缓存数据
|
||||
|
||||
`fn`:要移除的方法,不传的话移除该命令所有的方法
|
||||
|
||||
|
||||
|
||||
#### getCopyData()
|
||||
|
||||
获取渲染树数据副本
|
||||
@@ -499,81 +506,58 @@ v0.2.3+。恢复保存的快捷键数据,然后清空缓存数据
|
||||
|
||||
清空历史堆栈数据
|
||||
|
||||
|
||||
## view实例
|
||||
|
||||
`view`实例负责视图操作,可通过`mindMap.view`获取到该实例
|
||||
|
||||
|
||||
|
||||
### 方法
|
||||
|
||||
#### translateX(step)
|
||||
|
||||
`x`方向进行平移,`step`:要平移的像素
|
||||
|
||||
|
||||
|
||||
#### translateY(step)
|
||||
|
||||
`y`方向进行平移,`step`:要平移的像素
|
||||
|
||||
|
||||
|
||||
#### reset()
|
||||
|
||||
恢复到默认的变换
|
||||
|
||||
|
||||
|
||||
#### narrow()
|
||||
|
||||
缩小
|
||||
|
||||
|
||||
|
||||
#### enlarge()
|
||||
|
||||
放大
|
||||
|
||||
|
||||
|
||||
#### getTransformData()
|
||||
|
||||
v0.1.1+
|
||||
|
||||
获取当前变换数据,可用于回显
|
||||
|
||||
|
||||
|
||||
#### setTransformData(data)
|
||||
|
||||
v0.1.1+
|
||||
|
||||
动态设置变换数据,可以通过getTransformData方法获取变换数据
|
||||
|
||||
|
||||
|
||||
## doExport实例
|
||||
|
||||
`doExport`实例负责导出,可通过`mindMap.doExport`获取到该实例
|
||||
|
||||
|
||||
|
||||
### 方法
|
||||
|
||||
#### png()
|
||||
|
||||
导出为`png`,异步方法,返回图片数据,`data:url`数据,可以自行下载或显示
|
||||
|
||||
|
||||
|
||||
#### svg()
|
||||
|
||||
导出为`svg`,异步方法,返回`svg`数据,`data:url`数据,可以自行下载或显示
|
||||
|
||||
|
||||
|
||||
#### getSvgData()
|
||||
|
||||
获取`svg`数据,异步方法,返回一个对象:
|
||||
@@ -585,28 +569,20 @@ v0.1.1+
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## select实例
|
||||
|
||||
`select`实例负责鼠标右键多选节点操作,可通过`mindMap.select`获取到该实例
|
||||
|
||||
|
||||
|
||||
### 方法
|
||||
|
||||
#### toPos(x, y)
|
||||
|
||||
转换鼠标位置为相对于容器`el`的位置
|
||||
|
||||
|
||||
|
||||
## batchExecution实例
|
||||
|
||||
`batchExecution`用来批量异步的执行一些操作,如果某个操作同时多次调用,那么只会在下一个事件循环里执行一次。可以通过`mindMap.batchExecution`获取到该实例
|
||||
|
||||
|
||||
|
||||
### 方法
|
||||
|
||||
#### push(name, fn)
|
||||
@@ -617,146 +593,100 @@ v0.1.1+
|
||||
|
||||
`fn`:任务
|
||||
|
||||
|
||||
|
||||
## node实例
|
||||
|
||||
每个节点都会实例化一个`node`实例
|
||||
|
||||
|
||||
|
||||
### 属性
|
||||
|
||||
#### nodeData
|
||||
|
||||
该节点对应的真实数据
|
||||
|
||||
|
||||
|
||||
#### uid
|
||||
|
||||
该节点唯一的标识
|
||||
|
||||
|
||||
|
||||
#### isRoot
|
||||
|
||||
是否是根节点
|
||||
|
||||
|
||||
|
||||
#### layerIndex
|
||||
|
||||
节点层级
|
||||
|
||||
|
||||
|
||||
#### width
|
||||
|
||||
节点的宽
|
||||
|
||||
|
||||
|
||||
#### height
|
||||
|
||||
节点的高
|
||||
|
||||
|
||||
|
||||
#### left
|
||||
|
||||
节点的`left`位置
|
||||
|
||||
|
||||
|
||||
#### top
|
||||
|
||||
节点的`top`位置
|
||||
|
||||
|
||||
|
||||
#### parent
|
||||
|
||||
节点的父节点
|
||||
|
||||
|
||||
|
||||
#### children
|
||||
|
||||
节点的子节点列表
|
||||
|
||||
|
||||
|
||||
#### group
|
||||
|
||||
节点是内容容器,`svg`对象
|
||||
|
||||
|
||||
|
||||
#### isDrag
|
||||
|
||||
v0.1.5+
|
||||
|
||||
节点是否正在拖拽中
|
||||
|
||||
|
||||
|
||||
### 方法
|
||||
|
||||
#### addChildren(node)
|
||||
|
||||
添加子节点
|
||||
|
||||
|
||||
|
||||
#### getSize()
|
||||
|
||||
计算节点的宽高,返回一个布尔值,代表是否宽高发生了变化
|
||||
|
||||
|
||||
|
||||
#### renderNode()
|
||||
|
||||
渲染节点到画布,会移除旧的内容节点,创建新的
|
||||
|
||||
|
||||
|
||||
#### render()
|
||||
|
||||
递归渲染该节点及其所有子节点,第一次调用会创建节点内容,后续只会更新节点位置,想要重新渲染内容,可以先把`initRender`属性设为`true`
|
||||
|
||||
|
||||
|
||||
#### remove()
|
||||
|
||||
递归删除该节点及其所有子节点
|
||||
|
||||
|
||||
|
||||
#### renderLine()
|
||||
|
||||
重新渲染该节点到其子节点之间的连线
|
||||
|
||||
|
||||
|
||||
#### removeLine()
|
||||
|
||||
移除该节点到其子节点之间的连线
|
||||
|
||||
|
||||
|
||||
#### renderExpandBtn()
|
||||
|
||||
渲染展开收缩按钮的内容
|
||||
|
||||
|
||||
|
||||
#### removeExpandBtn()
|
||||
|
||||
移除展开收缩按钮
|
||||
|
||||
|
||||
|
||||
#### getStyle(prop, root, isActive)
|
||||
|
||||
获取某个最终应用到该节点的样式值
|
||||
@@ -767,141 +697,131 @@ v0.1.5+
|
||||
|
||||
`isActive`:获取的是否是激活状态的样式值,默认`false`
|
||||
|
||||
|
||||
|
||||
#### setStyle(prop, value, isActive)
|
||||
|
||||
修改节点的某个样式,`SET_NODE_STYLE`命令的快捷方法
|
||||
|
||||
|
||||
|
||||
#### getData(key)
|
||||
|
||||
获取该节点真实数据`nodeData`的`data`对象里的指定值,`key`不传返回这个`data`对象
|
||||
|
||||
|
||||
|
||||
#### setData(data)
|
||||
|
||||
设置节点数据,`SET_NODE_DATA`命令的快捷方法
|
||||
|
||||
|
||||
|
||||
#### setText(text)
|
||||
|
||||
设置节点文本,`SET_NODE_TEXT`命令的快捷方法
|
||||
|
||||
|
||||
|
||||
#### setImage(imgData)
|
||||
|
||||
设置节点图片,`SET_NODE_IMAGE`命令的快捷方法
|
||||
|
||||
|
||||
|
||||
#### setIcon(icons)
|
||||
|
||||
设置节点图标,`SET_NODE_ICON`命令的快捷方法
|
||||
|
||||
|
||||
|
||||
#### setHyperlink(link, title)
|
||||
|
||||
设置节点超链接,`SET_NODE_HYPERLINK`命令的快捷方法
|
||||
|
||||
|
||||
|
||||
#### setNote(note)
|
||||
|
||||
设置节点备注,`SET_NODE_NOTE`命令的快捷方法
|
||||
|
||||
|
||||
|
||||
#### setTag(tag)
|
||||
|
||||
设置节点标签,`SET_NODE_TAG`的快捷方法
|
||||
|
||||
|
||||
|
||||
#### hide()
|
||||
|
||||
v0.1.5+
|
||||
|
||||
隐藏节点及其下级节点
|
||||
|
||||
|
||||
|
||||
#### show()
|
||||
|
||||
v0.1.5+
|
||||
|
||||
显示节点及其下级节点
|
||||
|
||||
|
||||
|
||||
#### isParent(node)
|
||||
|
||||
v0.1.5+
|
||||
|
||||
检测当前节点是否是某个节点的祖先节点
|
||||
|
||||
|
||||
|
||||
#### isBrother(node)
|
||||
|
||||
v0.1.5+
|
||||
|
||||
检测当前节点是否是某个节点的兄弟节点
|
||||
|
||||
|
||||
|
||||
#### checkHasGeneralization()
|
||||
|
||||
v0.2.0+
|
||||
|
||||
检查是否存在概要
|
||||
|
||||
|
||||
|
||||
#### hideGeneralization()
|
||||
|
||||
v0.2.0+
|
||||
|
||||
隐藏概要节点
|
||||
|
||||
|
||||
|
||||
#### showGeneralization()
|
||||
|
||||
v0.2.0+
|
||||
|
||||
显示概要节点
|
||||
|
||||
|
||||
|
||||
#### updateGeneralization()
|
||||
|
||||
v0.2.0+
|
||||
|
||||
更新概要节点
|
||||
|
||||
|
||||
|
||||
#### hasCustomPosition()
|
||||
|
||||
v0.2.0+
|
||||
|
||||
检查节点是否存在自定义数据
|
||||
|
||||
|
||||
|
||||
#### ancestorHasCustomPosition()
|
||||
|
||||
v0.2.0+
|
||||
|
||||
检查节点是否存在自定义位置的祖先节点
|
||||
|
||||
#### getShape()
|
||||
|
||||
v0.2.4+
|
||||
|
||||
获取节点形状
|
||||
|
||||
#### setShape(shape)
|
||||
|
||||
v0.2.4+
|
||||
|
||||
设置节点形状,`SET_NODE_SHAPE`命令的快捷方法
|
||||
|
||||
#### getSelfStyle(prop)
|
||||
|
||||
v0.2.5+
|
||||
|
||||
获取节点自身的自定义样式
|
||||
|
||||
#### getParentSelfStyle(prop)
|
||||
|
||||
v0.2.5+
|
||||
|
||||
获取最近一个存在自身自定义样式的祖先节点的自定义样式
|
||||
|
||||
#### getSelfInhertStyle(prop)
|
||||
|
||||
v0.2.5+
|
||||
|
||||
获取自身可继承的自定义样式
|
||||
|
||||
## 内置工具方法
|
||||
|
||||
@@ -911,8 +831,6 @@ v0.2.0+
|
||||
import {walk, ...} from 'simple-mind-map/src/utils'
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 方法
|
||||
|
||||
#### walk(root, parent, beforeCallback, afterCallback, isRoot, layerIndex = 0, index = 0)
|
||||
@@ -939,14 +857,10 @@ import {walk, ...} from 'simple-mind-map/src/utils'
|
||||
walk(tree, null, () => {}, () => {}, false, 0, 0)
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### bfsWalk(root, callback)
|
||||
|
||||
广度优先遍历树
|
||||
|
||||
|
||||
|
||||
#### resizeImgSize(width, height, maxWidth, maxHeight)
|
||||
|
||||
缩放图片的尺寸
|
||||
@@ -961,20 +875,14 @@ walk(tree, null, () => {}, () => {}, false, 0, 0)
|
||||
|
||||
`maxWidth`和`maxHeight`可以同时都传,也可以只传一个
|
||||
|
||||
|
||||
|
||||
#### resizeImg(imgUrl, maxWidth, maxHeight)
|
||||
|
||||
缩放图片,内部先加载图片,然后调用`resizeImgSize`方法,返回一个`promise`
|
||||
|
||||
|
||||
|
||||
#### simpleDeepClone(data)
|
||||
|
||||
极简的深拷贝方法,只能针对全是基本数据的对象,否则会报错
|
||||
|
||||
|
||||
|
||||
#### copyRenderTree(tree, root)
|
||||
|
||||
复制渲染树数据,示例:
|
||||
@@ -983,8 +891,6 @@ walk(tree, null, () => {}, () => {}, false, 0, 0)
|
||||
copyRenderTree({}, this.mindMap.renderer.renderTree)
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### copyNodeTree(tree, root)
|
||||
|
||||
复制节点树数据,主要是剔除其中的引用`node`实例的`_node`,然后复制`data`对象的数据,示例:
|
||||
@@ -993,32 +899,22 @@ copyRenderTree({}, this.mindMap.renderer.renderTree)
|
||||
copyNodeTree({}, node)
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### imgToDataUrl(src)
|
||||
|
||||
图片转成dataURL
|
||||
|
||||
|
||||
|
||||
#### downloadFile(file, fileName)
|
||||
|
||||
下载文件
|
||||
|
||||
|
||||
|
||||
#### throttle(fn, time = 300, ctx)
|
||||
|
||||
节流函数
|
||||
|
||||
|
||||
|
||||
#### asyncRun(taskList, callback = () => {})
|
||||
|
||||
异步执行任务队列,多个任务是同步执行的,没有先后顺序
|
||||
|
||||
|
||||
|
||||
# 特别说明
|
||||
|
||||
本项目较粗糙,未进行完整测试,功能尚不是很完善,性能也存在一些问题,仅用于学习和参考,请勿用于实际项目。
|
||||
|
||||
@@ -1 +1 @@
|
||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>一个简单的web思维导图实现</title><link href="dist/js/chunk-2d20ec02.81d632f4.js" rel="prefetch"><link href="dist/js/chunk-2d216b67.228f2009.js" rel="prefetch"><link href="dist/js/chunk-e86f1494.f8dd20e2.js" rel="prefetch"><link href="dist/css/app.d29688d8.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.6fd71983.css" rel="preload" as="style"><link href="dist/js/app.88b2cb65.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.094115db.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.6fd71983.css" rel="stylesheet"><link href="dist/css/app.d29688d8.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="dist/js/chunk-vendors.094115db.js"></script><script src="dist/js/app.88b2cb65.js"></script></body></html>
|
||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>一个简单的web思维导图实现</title><link href="dist/js/chunk-2d20ec02.10aa67e3.js" rel="prefetch"><link href="dist/js/chunk-2d216b67.2d06497a.js" rel="prefetch"><link href="dist/js/chunk-5397ae43.9dca0f90.js" rel="prefetch"><link href="dist/css/app.891a59ac.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.597033a2.css" rel="preload" as="style"><link href="dist/js/app.6ae41667.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.9c26d9dc.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.597033a2.css" rel="stylesheet"><link href="dist/css/app.891a59ac.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="dist/js/chunk-vendors.9c26d9dc.js"></script><script src="dist/js/app.6ae41667.js"></script></body></html>
|
||||
66
simple-mind-map/example/exportFullData.json
Normal file
66
simple-mind-map/example/exportFullData.json
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"layout": "logicalStructure",
|
||||
"root": {
|
||||
"data": {
|
||||
"text": "根节点",
|
||||
"expand": true,
|
||||
"isActive": false
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "二级节点",
|
||||
"generalization": {
|
||||
"text": "概要",
|
||||
"expand": true,
|
||||
"isActive": false
|
||||
},
|
||||
"expand": true,
|
||||
"isActive": false
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
"expand": true,
|
||||
"isActive": false
|
||||
},
|
||||
"children": []
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
"expand": true,
|
||||
"isActive": false
|
||||
},
|
||||
"children": []
|
||||
}]
|
||||
}]
|
||||
},
|
||||
"theme": {
|
||||
"template": "classic4",
|
||||
"config": {}
|
||||
},
|
||||
"view": {
|
||||
"transform": {
|
||||
"scaleX": 1,
|
||||
"scaleY": 1,
|
||||
"shear": 0,
|
||||
"rotate": 0,
|
||||
"translateX": 0,
|
||||
"translateY": 0,
|
||||
"originX": 0,
|
||||
"originY": 0,
|
||||
"a": 1,
|
||||
"b": 0,
|
||||
"c": 0,
|
||||
"d": 1,
|
||||
"e": 0,
|
||||
"f": 0
|
||||
},
|
||||
"state": {
|
||||
"scale": 1,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"sx": 0,
|
||||
"sy": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ import {
|
||||
import {
|
||||
SVG
|
||||
} from '@svgdotjs/svg.js'
|
||||
import xmind from './src/parse/xmind'
|
||||
import { simpleDeepClone } from './src/utils';
|
||||
|
||||
// 默认选项配置
|
||||
const defaultOpt = {
|
||||
@@ -319,7 +321,7 @@ class MindMap {
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-03 22:58:12
|
||||
* @Desc: 动态设置思维导图数据
|
||||
* @Desc: 动态设置思维导图数据,纯节点数据
|
||||
*/
|
||||
setData(data) {
|
||||
this.execCommand('CLEAR_ACTIVE_NODE')
|
||||
@@ -328,6 +330,57 @@ class MindMap {
|
||||
this.reRender()
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-09-21 16:39:13
|
||||
* @Desc: 动态设置思维导图数据,包括节点数据、布局、主题、视图
|
||||
*/
|
||||
setFullData(data) {
|
||||
if (data.root) {
|
||||
this.setData(data.root)
|
||||
}
|
||||
if (data.layout) {
|
||||
this.setLayout(data.layout)
|
||||
}
|
||||
if (data.theme) {
|
||||
if (data.theme.template) {
|
||||
this.setTheme(data.theme.template)
|
||||
}
|
||||
if (data.theme.config) {
|
||||
this.setThemeConfig(data.theme.config)
|
||||
}
|
||||
}
|
||||
if (data.view) {
|
||||
this.view.setTransformData(data.view)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-24 14:42:07
|
||||
* @Desc: 获取思维导图数据,节点树、主题、布局等
|
||||
*/
|
||||
getData(withConfig) {
|
||||
let nodeData = this.command.getCopyData()
|
||||
let data = {}
|
||||
if (withConfig) {
|
||||
data = {
|
||||
layout: this.getLayout(),
|
||||
root: nodeData,
|
||||
theme: {
|
||||
template: this.getTheme(),
|
||||
config: this.getCustomThemeConfig()
|
||||
},
|
||||
view: this.view.getTransformData()
|
||||
}
|
||||
} else {
|
||||
data = nodeData
|
||||
}
|
||||
return simpleDeepClone(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-01 22:06:38
|
||||
@@ -369,4 +422,6 @@ class MindMap {
|
||||
}
|
||||
}
|
||||
|
||||
MindMap.xmind = xmind
|
||||
|
||||
export default MindMap
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.2.3",
|
||||
"version": "0.2.9",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
@@ -25,7 +25,9 @@
|
||||
"canvg": "^3.0.7",
|
||||
"deepmerge": "^1.5.2",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"jspdf": "^2.5.1"
|
||||
"jspdf": "^2.5.1",
|
||||
"jszip": "^3.10.1",
|
||||
"xml-js": "^1.6.11"
|
||||
},
|
||||
"keywords": [
|
||||
"javascript",
|
||||
|
||||
@@ -189,7 +189,7 @@ class Drag extends Base {
|
||||
// 连接线
|
||||
this.line = this.draw.path()
|
||||
this.line.opacity(0.5)
|
||||
this.node.style.line(this.line)
|
||||
this.node.styleLine(this.line, this.node)
|
||||
// 同级位置占位符
|
||||
this.placeholder = this.draw.rect().fill({
|
||||
color: this.node.style.merge('lineColor', true)
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { imgToDataUrl, downloadFile } from './utils'
|
||||
import JsPDF from 'jspdf'
|
||||
import {
|
||||
SVG,
|
||||
} from '@svgdotjs/svg.js'
|
||||
const URL = window.URL || window.webkitURL || window
|
||||
|
||||
/**
|
||||
@@ -23,9 +26,9 @@ class Export {
|
||||
* @Date: 2021-07-02 07:44:06
|
||||
* @Desc: 导出
|
||||
*/
|
||||
async export(type, isDownload = true, name = '思维导图') {
|
||||
async export(type, isDownload = true, name = '思维导图', ...args) {
|
||||
if (this[type]) {
|
||||
let result = await this[type](name)
|
||||
let result = await this[type](name, ...args)
|
||||
if (isDownload && type !== 'pdf') {
|
||||
downloadFile(result, name + '.' + type)
|
||||
}
|
||||
@@ -228,8 +231,9 @@ class Export {
|
||||
* @Date: 2021-07-04 14:54:07
|
||||
* @Desc: 导出为svg
|
||||
*/
|
||||
async svg() {
|
||||
async svg(name) {
|
||||
let { node } = await this.getSvgData()
|
||||
node.first().before(SVG(`<title>${name}</title>`))
|
||||
await this.drawBackgroundToSvg(node)
|
||||
let str = node.svg()
|
||||
// 转换成blob数据
|
||||
@@ -244,8 +248,8 @@ class Export {
|
||||
* @Date: 2021-08-03 22:19:17
|
||||
* @Desc: 导出为json
|
||||
*/
|
||||
json () {
|
||||
let data = this.mindMap.command.getCopyData()
|
||||
json (name, withConfig = true) {
|
||||
let data = this.mindMap.getData(withConfig)
|
||||
let str = JSON.stringify(data)
|
||||
let blob = new Blob([str])
|
||||
return URL.createObjectURL(blob)
|
||||
@@ -256,8 +260,8 @@ class Export {
|
||||
* @Date: 2021-08-03 22:24:24
|
||||
* @Desc: 专有文件,其实就是json文件
|
||||
*/
|
||||
smm () {
|
||||
return this.json();
|
||||
smm (name, withConfig) {
|
||||
return this.json(name, withConfig);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Style from './Style'
|
||||
import Shape from './Shape'
|
||||
import {
|
||||
resizeImgSize,
|
||||
asyncRun
|
||||
@@ -43,6 +44,12 @@ class Node {
|
||||
this.themeConfig = this.mindMap.themeConfig
|
||||
// 样式实例
|
||||
this.style = new Style(this, this.themeConfig)
|
||||
// 形状实例
|
||||
this.shapeInstance = new Shape(this)
|
||||
this.shapePadding = {
|
||||
paddingX: 0,
|
||||
paddingY: 0
|
||||
}
|
||||
// 是否是根节点
|
||||
this.isRoot = opt.isRoot === undefined ? false : opt.isRoot
|
||||
// 是否是概要节点
|
||||
@@ -331,9 +338,16 @@ class Node {
|
||||
// 间距
|
||||
let margin = imgContentHeight > 0 && textContentHeight > 0 ? this.blockContentMargin : 0
|
||||
let { paddingX, paddingY } = this.getPaddingVale()
|
||||
// 纯内容宽高
|
||||
let _width = Math.max(imgContentWidth, textContentWidth)
|
||||
let _height = imgContentHeight + textContentHeight
|
||||
// 计算节点形状需要的附加内边距
|
||||
let { paddingX: shapePaddingX, paddingY: shapePaddingY } = this.shapeInstance.getShapePadding(_width, _height, paddingX, paddingY)
|
||||
this.shapePadding.paddingX = shapePaddingX
|
||||
this.shapePadding.paddingY = shapePaddingY
|
||||
return {
|
||||
width: Math.max(imgContentWidth, textContentWidth) + paddingX * 2,
|
||||
height: imgContentHeight + textContentHeight + paddingY * 2 + margin
|
||||
width: _width + paddingX * 2 + shapePaddingX * 2,
|
||||
height: _height + paddingY * 2 + margin + shapePaddingY * 2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -542,6 +556,16 @@ class Node {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 22:02:07
|
||||
* @Desc: 获取节点形状
|
||||
*/
|
||||
getShape() {
|
||||
return this.style.getStyle('shape', false, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
@@ -555,6 +579,7 @@ class Node {
|
||||
textContentItemMargin
|
||||
} = this
|
||||
let { paddingY } = this.getPaddingVale()
|
||||
paddingY += this.shapePadding.paddingY
|
||||
// 创建组
|
||||
this.group = new G()
|
||||
// 概要节点添加一个带所属节点id的类名
|
||||
@@ -563,8 +588,9 @@ class Node {
|
||||
}
|
||||
this.draw.add(this.group)
|
||||
this.update(true)
|
||||
// 节点矩形
|
||||
this.style.rect(this.group.rect(width, height))
|
||||
// 节点形状
|
||||
const shape = this.getShape()
|
||||
this.style[shape === 'rectangle' ? 'rect' : 'shape'](this.shapeInstance.createShape())
|
||||
// 图片节点
|
||||
let imgHeight = 0
|
||||
if (this._imgData) {
|
||||
@@ -668,7 +694,7 @@ class Node {
|
||||
if (this.mindMap.opt.readonly) {
|
||||
return
|
||||
}
|
||||
e.stopPropagation()
|
||||
e && e.stopPropagation()
|
||||
if (this.nodeData.data.isActive) {
|
||||
return
|
||||
}
|
||||
@@ -685,6 +711,8 @@ class Node {
|
||||
* @Desc: 渲染节点到画布,会移除旧的,创建新的
|
||||
*/
|
||||
renderNode() {
|
||||
// 连线
|
||||
this.renderLine()
|
||||
this.removeAllEvent()
|
||||
this.removeAllNode()
|
||||
this.createNodeData()
|
||||
@@ -724,13 +752,13 @@ class Node {
|
||||
* @Desc: 递归渲染
|
||||
*/
|
||||
render() {
|
||||
// 连线
|
||||
this.renderLine()
|
||||
// 节点
|
||||
if (this.initRender) {
|
||||
this.initRender = false
|
||||
this.renderNode()
|
||||
} else {
|
||||
// 连线
|
||||
this.renderLine()
|
||||
this.update()
|
||||
}
|
||||
// 子节点
|
||||
@@ -741,6 +769,12 @@ class Node {
|
||||
}
|
||||
}))
|
||||
}
|
||||
// 手动插入的节点立即获得焦点并且开启编辑模式
|
||||
if (this.nodeData.inserting) {
|
||||
delete this.nodeData.inserting
|
||||
this.active()
|
||||
this.mindMap.emit('node_dblclick', this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -800,7 +834,7 @@ class Node {
|
||||
this.showGeneralization()
|
||||
if (this.parent) {
|
||||
let index = this.parent.children.indexOf(this)
|
||||
this.parent._lines[index].show()
|
||||
this.parent._lines[index] && this.parent._lines[index].show()
|
||||
}
|
||||
// 子节点
|
||||
if (this.children && this.children.length) {
|
||||
@@ -817,7 +851,7 @@ class Node {
|
||||
* @Date: 2021-04-10 22:01:53
|
||||
* @Desc: 连线
|
||||
*/
|
||||
renderLine() {
|
||||
renderLine(deep = false) {
|
||||
if (this.nodeData.data.expand === false) {
|
||||
return
|
||||
}
|
||||
@@ -835,10 +869,32 @@ class Node {
|
||||
this._lines = this._lines.slice(0, childrenLen)
|
||||
}
|
||||
// 画线
|
||||
this.renderer.layout.renderLine(this, this._lines)
|
||||
// 添加样式
|
||||
this._lines.forEach((line) => {
|
||||
this.style.line(line)
|
||||
this.renderer.layout.renderLine(this, this._lines, (line, node) => {
|
||||
// 添加样式
|
||||
this.styleLine(line, node)
|
||||
})
|
||||
// 子级的连线也需要更新
|
||||
if (deep && this.children && this.children.length > 0) {
|
||||
this.children.forEach((item) => {
|
||||
item.renderLine(deep)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: flydreame
|
||||
* @Date: 2022-09-17 12:41:29
|
||||
* @Desc: 设置连线样式
|
||||
*/
|
||||
styleLine(line, node) {
|
||||
let width = node.getSelfInhertStyle('lineWidth') || node.getStyle('lineWidth', true)
|
||||
let color = node.getSelfInhertStyle('lineColor') || node.getStyle('lineColor', true)
|
||||
let dasharray = node.getSelfInhertStyle('lineDasharray') || node.getStyle('lineDasharray', true)
|
||||
this.style.line(line, {
|
||||
width,
|
||||
color,
|
||||
dasharray,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1004,7 +1060,7 @@ class Node {
|
||||
node.x(0).y(-this.expandBtnSize / 2)
|
||||
fillNode.x(0).y(-this.expandBtnSize / 2)
|
||||
this.style.iconBtn(node, fillNode)
|
||||
this._expandBtn.add(fillNode).add(node)
|
||||
if (this._expandBtn) this._expandBtn.add(fillNode).add(node)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1125,6 +1181,40 @@ class Node {
|
||||
return v === undefined ? '' : v
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: flydreame
|
||||
* @Date: 2022-09-17 11:21:15
|
||||
* @Desc: 获取自定义样式
|
||||
*/
|
||||
getSelfStyle(prop) {
|
||||
return this.style.getSelfStyle(prop)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: flydreame
|
||||
* @Date: 2022-09-17 11:21:26
|
||||
* @Desc: 获取最近一个存在自身自定义样式的祖先节点的自定义样式
|
||||
*/
|
||||
getParentSelfStyle(prop) {
|
||||
if (this.parent) {
|
||||
return this.parent.getSelfStyle(prop) || this.parent.getParentSelfStyle(prop)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: flydreame
|
||||
* @Date: 2022-09-17 12:15:30
|
||||
* @Desc: 获取自身可继承的自定义样式
|
||||
*/
|
||||
getSelfInhertStyle(prop) {
|
||||
return this.getSelfStyle(prop) // 自身
|
||||
|| this.getParentSelfStyle(prop) // 父级
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-04 22:18:07
|
||||
@@ -1205,6 +1295,16 @@ class Node {
|
||||
setTag(tag) {
|
||||
this.mindMap.execCommand('SET_NODE_TAG', this, tag)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 21:47:45
|
||||
* @Desc: 设置形状
|
||||
*/
|
||||
setShape(shape) {
|
||||
this.mindMap.execCommand('SET_NODE_SHAPE', this, shape)
|
||||
}
|
||||
}
|
||||
|
||||
export default Node
|
||||
|
||||
@@ -5,6 +5,8 @@ import CatalogOrganization from './layouts/CatalogOrganization'
|
||||
import OrganizationStructure from './layouts/OrganizationStructure'
|
||||
import TextEdit from './TextEdit'
|
||||
import { copyNodeTree, simpleDeepClone, walk } from './utils'
|
||||
import { shapeList } from './Shape'
|
||||
import { lineStyleProps } from './themes/default'
|
||||
|
||||
// 布局列表
|
||||
const layouts = {
|
||||
@@ -142,6 +144,9 @@ class Render {
|
||||
// 收起所有节点
|
||||
this.unexpandAllNode = this.unexpandAllNode.bind(this)
|
||||
this.mindMap.command.add('UNEXPAND_ALL', this.unexpandAllNode)
|
||||
// 展开到指定层级
|
||||
this.expandToLevel = this.expandToLevel.bind(this)
|
||||
this.mindMap.command.add('UNEXPAND_TO_LEVEL', this.expandToLevel)
|
||||
// 设置节点数据
|
||||
this.setNodeData = this.setNodeData.bind(this)
|
||||
this.mindMap.command.add('SET_NODE_DATA', this.setNodeData)
|
||||
@@ -175,6 +180,9 @@ class Render {
|
||||
// 一键整理布局
|
||||
this.resetLayout = this.resetLayout.bind(this)
|
||||
this.mindMap.command.add('RESET_LAYOUT', this.resetLayout)
|
||||
// 设置节点形状
|
||||
this.setNodeShape = this.setNodeShape.bind(this)
|
||||
this.mindMap.command.add('SET_NODE_SHAPE', this.setNodeShape)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -405,6 +413,7 @@ class Render {
|
||||
}
|
||||
let index = this.getNodeIndex(first)
|
||||
first.parent.nodeData.children.splice(index + 1, 0, {
|
||||
"inserting": true,
|
||||
"data": {
|
||||
"text": text,
|
||||
"expand": true
|
||||
@@ -430,6 +439,7 @@ class Render {
|
||||
}
|
||||
let text = node.isRoot ? '二级节点' : '分支主题'
|
||||
node.nodeData.children.push({
|
||||
"inserting": true,
|
||||
"data": {
|
||||
"text": text,
|
||||
"expand": true
|
||||
@@ -727,6 +737,10 @@ class Render {
|
||||
}
|
||||
}
|
||||
this.setNodeDataRender(node, data)
|
||||
// 更新了连线的样式
|
||||
if (lineStyleProps.includes(prop)) {
|
||||
(node.parent || node).renderLine(true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -777,10 +791,7 @@ class Render {
|
||||
node.data.expand = true
|
||||
}
|
||||
}, null, true, 0, 0)
|
||||
this.mindMap.render()
|
||||
this.root.children.forEach((item) => {
|
||||
item.updateExpandBtnNode()
|
||||
})
|
||||
this.mindMap.reRender()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -789,14 +800,27 @@ class Render {
|
||||
* @Desc: 收起所有
|
||||
*/
|
||||
unexpandAllNode() {
|
||||
this.root.children.forEach((item) => {
|
||||
this.setNodeExpand(item, false)
|
||||
})
|
||||
walk(this.renderTree, null, (node, parent, isRoot) => {
|
||||
node._node = null
|
||||
if (!isRoot) {
|
||||
node.data.expand = false
|
||||
}
|
||||
}, null, true, 0, 0)
|
||||
this.mindMap.reRender()
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-09-23 16:31:27
|
||||
* @Desc: 展开到指定层级
|
||||
*/
|
||||
expandToLevel(level) {
|
||||
walk(this.renderTree, null, (node, parent, isRoot, layerIndex) => {
|
||||
node._node = null
|
||||
node.data.expand = layerIndex < level
|
||||
}, null, true, 0, 0)
|
||||
this.mindMap.reRender()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -977,6 +1001,22 @@ class Render {
|
||||
}, null, true, 0, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 21:44:01
|
||||
* @Desc: 设置节点形状
|
||||
*/
|
||||
setNodeShape(node, shape) {
|
||||
if (!shape || !shapeList.includes(shape)) {
|
||||
return
|
||||
}
|
||||
let nodeList = [node] || this.activeNodeList
|
||||
nodeList.forEach((item) => {
|
||||
this.setNodeStyle(item, 'shape', shape)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-04 14:19:48
|
||||
|
||||
265
simple-mind-map/src/Shape.js
Normal file
265
simple-mind-map/src/Shape.js
Normal file
@@ -0,0 +1,265 @@
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-08-22 21:32:50
|
||||
* @Desc: 节点形状类
|
||||
*/
|
||||
export default class Shape {
|
||||
constructor(node) {
|
||||
this.node = node
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-08-17 22:32:32
|
||||
* @Desc: 形状需要的padding
|
||||
*/
|
||||
getShapePadding(width, height, paddingX, paddingY) {
|
||||
const shape = this.node.getShape()
|
||||
const defaultPaddingX = 15
|
||||
const defaultPaddingY = 5
|
||||
const actWidth = width + paddingX * 2
|
||||
const actHeight = height + paddingY * 2
|
||||
const actOffset = Math.abs(actWidth - actHeight)
|
||||
switch (shape) {
|
||||
case 'roundedRectangle':
|
||||
return {
|
||||
paddingX: height > width ? (height - width) / 2 : 0,
|
||||
paddingY: 0
|
||||
}
|
||||
case 'diamond':
|
||||
return {
|
||||
paddingX: width / 2,
|
||||
paddingY: height / 2
|
||||
}
|
||||
case 'parallelogram':
|
||||
return {
|
||||
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
|
||||
paddingY: 0
|
||||
}
|
||||
case 'outerTriangularRectangle':
|
||||
return {
|
||||
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
|
||||
paddingY: 0
|
||||
}
|
||||
case 'innerTriangularRectangle':
|
||||
return {
|
||||
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
|
||||
paddingY: 0
|
||||
}
|
||||
case 'ellipse':
|
||||
return {
|
||||
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
|
||||
paddingY: paddingY <= 0 ? defaultPaddingY : 0
|
||||
}
|
||||
case 'circle':
|
||||
return {
|
||||
paddingX: actHeight > actWidth ? actOffset / 2 : 0,
|
||||
paddingY: actHeight < actWidth ? actOffset / 2 : 0,
|
||||
}
|
||||
default:
|
||||
return {
|
||||
paddingX: 0,
|
||||
paddingY: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-08-17 22:22:53
|
||||
* @Desc: 创建形状节点
|
||||
*/
|
||||
createShape() {
|
||||
const shape = this.node.getShape()
|
||||
let { width, height } = this.node
|
||||
let node = null
|
||||
// 矩形
|
||||
if (shape === 'rectangle') {
|
||||
node = this.node.group.rect(width, height)
|
||||
} else if (shape === 'diamond') {
|
||||
// 菱形
|
||||
node = this.createDiamond()
|
||||
} else if (shape === 'parallelogram') {
|
||||
// 平行四边形
|
||||
node = this.createParallelogram()
|
||||
} else if (shape === 'roundedRectangle') {
|
||||
// 圆角矩形
|
||||
node = this.createRoundedRectangle()
|
||||
} else if (shape === 'octagonalRectangle') {
|
||||
// 八角矩形
|
||||
node = this.createOctagonalRectangle()
|
||||
} else if (shape === 'outerTriangularRectangle') {
|
||||
// 外三角矩形
|
||||
node = this.createOuterTriangularRectangle()
|
||||
} else if (shape === 'innerTriangularRectangle') {
|
||||
// 内三角矩形
|
||||
node = this.createInnerTriangularRectangle()
|
||||
} else if (shape === 'ellipse') {
|
||||
// 椭圆
|
||||
node = this.createEllipse()
|
||||
} else if (shape === 'circle') {
|
||||
// 圆
|
||||
node = this.createCircle()
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-04 09:08:54
|
||||
* @Desc: 创建菱形
|
||||
*/
|
||||
createDiamond() {
|
||||
let { width, height } = this.node
|
||||
let halfWidth = width / 2
|
||||
let halfHeight = height / 2
|
||||
let topX = halfWidth
|
||||
let topY = 0
|
||||
let rightX = width
|
||||
let rightY = halfHeight
|
||||
let bottomX = halfWidth
|
||||
let bottomY = height
|
||||
let leftX = 0
|
||||
let leftY = halfHeight
|
||||
return this.node.group.polygon(`
|
||||
${topX}, ${topY}
|
||||
${rightX}, ${rightY}
|
||||
${bottomX}, ${bottomY}
|
||||
${leftX}, ${leftY}
|
||||
`)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-03 16:14:12
|
||||
* @Desc: 创建平行四边形
|
||||
*/
|
||||
createParallelogram() {
|
||||
let { paddingX } = this.node.getPaddingVale()
|
||||
paddingX = paddingX || this.node.shapePadding.paddingX
|
||||
let { width, height } = this.node
|
||||
return this.node.group.polygon(`
|
||||
${paddingX}, ${0}
|
||||
${width}, ${0}
|
||||
${width - paddingX}, ${height}
|
||||
${0}, ${height}
|
||||
`)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-03 16:50:23
|
||||
* @Desc: 创建圆角矩形
|
||||
*/
|
||||
createRoundedRectangle() {
|
||||
let { width, height } = this.node
|
||||
let halfHeight = height / 2
|
||||
return this.node.group.path(`
|
||||
M${halfHeight},0
|
||||
L${width - halfHeight},0
|
||||
A${height / 2},${height / 2} 0 0,1 ${width - halfHeight},${height}
|
||||
L${halfHeight},${height}
|
||||
A${height / 2},${height / 2} 0 0,1 ${halfHeight},${0}
|
||||
`)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 16:14:08
|
||||
* @Desc: 创建八角矩形
|
||||
*/
|
||||
createOctagonalRectangle() {
|
||||
let w = 5
|
||||
let { width, height } = this.node
|
||||
return this.node.group.polygon(`
|
||||
${0}, ${w}
|
||||
${w}, ${0}
|
||||
${width - w}, ${0}
|
||||
${width}, ${w}
|
||||
${width}, ${height - w}
|
||||
${width - w}, ${height}
|
||||
${w}, ${height}
|
||||
${0}, ${height - w}
|
||||
`)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 20:55:50
|
||||
* @Desc: 创建外三角矩形
|
||||
*/
|
||||
createOuterTriangularRectangle() {
|
||||
let { paddingX } = this.node.getPaddingVale()
|
||||
paddingX = paddingX || this.node.shapePadding.paddingX
|
||||
let { width, height } = this.node
|
||||
return this.node.group.polygon(`
|
||||
${paddingX}, ${0}
|
||||
${width - paddingX}, ${0}
|
||||
${width}, ${height / 2}
|
||||
${width - paddingX}, ${height}
|
||||
${paddingX}, ${height}
|
||||
${0}, ${height / 2}
|
||||
`)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 20:59:37
|
||||
* @Desc: 创建内三角矩形
|
||||
*/
|
||||
createInnerTriangularRectangle() {
|
||||
let { paddingX } = this.node.getPaddingVale()
|
||||
paddingX = paddingX || this.node.shapePadding.paddingX
|
||||
let { width, height } = this.node
|
||||
return this.node.group.polygon(`
|
||||
${0}, ${0}
|
||||
${width}, ${0}
|
||||
${width - paddingX / 2}, ${height / 2}
|
||||
${width}, ${height}
|
||||
${0}, ${height}
|
||||
${paddingX / 2}, ${height / 2}
|
||||
`)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 21:06:31
|
||||
* @Desc: 创建椭圆
|
||||
*/
|
||||
createEllipse() {
|
||||
let { width, height } = this.node
|
||||
let halfWidth = width / 2
|
||||
let halfHeight = height / 2
|
||||
return this.node.group.path(`
|
||||
M${halfWidth},0
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height}
|
||||
M${halfWidth},${height}
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0}
|
||||
`)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 21:14:04
|
||||
* @Desc: 创建圆
|
||||
*/
|
||||
createCircle() {
|
||||
let { width, height } = this.node
|
||||
let halfWidth = width / 2
|
||||
let halfHeight = height / 2
|
||||
return this.node.group.path(`
|
||||
M${halfWidth},0
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height}
|
||||
M${halfWidth},${height}
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0}
|
||||
`)
|
||||
}
|
||||
}
|
||||
|
||||
// 形状列表
|
||||
export const shapeList = ['rectangle', 'diamond', 'parallelogram', 'roundedRectangle', 'octagonalRectangle', 'outerTriangularRectangle', 'innerTriangularRectangle', 'ellipse', 'circle']
|
||||
@@ -66,7 +66,27 @@ class Style {
|
||||
}
|
||||
}
|
||||
// 优先使用节点本身的样式
|
||||
return this.ctx.nodeData.data[prop] !== undefined ? this.ctx.nodeData.data[prop] : defaultConfig[prop]
|
||||
return this.getSelfStyle(prop) !== undefined ? this.getSelfStyle(prop) : defaultConfig[prop]
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 21:55:57
|
||||
* @Desc: 获取某个样式值
|
||||
*/
|
||||
getStyle(prop, root, isActive) {
|
||||
return this.merge(prop, root, isActive)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: flydreame
|
||||
* @Date: 2022-09-17 12:09:39
|
||||
* @Desc: 获取自身自定义样式
|
||||
*/
|
||||
getSelfStyle(prop) {
|
||||
return this.ctx.nodeData.data[prop]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,13 +95,24 @@ class Style {
|
||||
* @Desc: 矩形
|
||||
*/
|
||||
rect(node) {
|
||||
this.shape(node)
|
||||
node.radius(this.merge('borderRadius'))
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 15:04:28
|
||||
* @Desc: 矩形外的其他形状
|
||||
*/
|
||||
shape(node) {
|
||||
node.fill({
|
||||
color: this.merge('fillColor')
|
||||
}).stroke({
|
||||
color: this.merge('borderColor'),
|
||||
width: this.merge('borderWidth'),
|
||||
dasharray: this.merge('borderDasharray')
|
||||
}).radius(this.merge('borderRadius'))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,8 +183,8 @@ class Style {
|
||||
* @Date: 2021-04-11 14:50:49
|
||||
* @Desc: 连线
|
||||
*/
|
||||
line(node) {
|
||||
node.stroke({ width: this.merge('lineWidth', true), color: this.merge('lineColor', true) }).fill({ color: 'none' })
|
||||
line(node, { width, color, dasharray } = {}) {
|
||||
node.stroke({ width, color, dasharray }).fill({ color: 'none' })
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -231,7 +231,7 @@ class CatalogOrganization extends Base {
|
||||
* @Date: 2021-04-11 14:42:48
|
||||
* @Desc: 绘制连线,连接该节点到其子节点
|
||||
*/
|
||||
renderLine(node, lines) {
|
||||
renderLine(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return [];
|
||||
}
|
||||
@@ -263,6 +263,7 @@ class CatalogOrganization extends Base {
|
||||
let path = `M ${x2},${y1 + s1} L ${x2},${y1 + s1 > y2 ? y2 + item.height : y2}`
|
||||
// 竖线
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
})
|
||||
minx = Math.min(minx, x1)
|
||||
maxx = Math.max(maxx, x1)
|
||||
@@ -271,12 +272,14 @@ class CatalogOrganization extends Base {
|
||||
node.style.line(line1)
|
||||
line1.plot(`M ${x1},${y1} L ${x1},${y1 + s1}`)
|
||||
node._lines.push(line1)
|
||||
style && style(line1, node)
|
||||
// 水平线
|
||||
if (len > 0) {
|
||||
let lin2 = this.draw.path()
|
||||
node.style.line(lin2)
|
||||
lin2.plot(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`)
|
||||
node._lines.push(lin2)
|
||||
style && style(lin2, node)
|
||||
}
|
||||
} else {
|
||||
// 非根节点
|
||||
@@ -320,6 +323,7 @@ class CatalogOrganization extends Base {
|
||||
path = `M ${x2},${y2} L ${_left},${y2}`
|
||||
}
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
})
|
||||
// 竖线
|
||||
if (len > 0) {
|
||||
@@ -333,6 +337,7 @@ class CatalogOrganization extends Base {
|
||||
lin2.show()
|
||||
}
|
||||
node._lines.push(lin2)
|
||||
style && style(lin2, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ class LogicalStructure extends Base {
|
||||
* @Date: 2021-04-11 14:42:48
|
||||
* @Desc: 绘制连线,连接该节点到其子节点
|
||||
*/
|
||||
renderLine(node, lines) {
|
||||
renderLine(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return [];
|
||||
}
|
||||
@@ -169,6 +169,7 @@ class LogicalStructure extends Base {
|
||||
path = this.cubicBezierPath(x1, y1, x2, y2)
|
||||
}
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ class MindMap extends Base {
|
||||
* @Date: 2021-04-11 14:42:48
|
||||
* @Desc: 绘制连线,连接该节点到其子节点
|
||||
*/
|
||||
renderLine(node, lines) {
|
||||
renderLine(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return [];
|
||||
}
|
||||
@@ -205,6 +205,7 @@ class MindMap extends Base {
|
||||
path = this.cubicBezierPath(x1, y1, x2, y2)
|
||||
}
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ class OrganizationStructure extends Base {
|
||||
* @Date: 2021-04-11 14:42:48
|
||||
* @Desc: 绘制连线,连接该节点到其子节点
|
||||
*/
|
||||
renderLine(node, lines) {
|
||||
renderLine(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return [];
|
||||
}
|
||||
@@ -177,6 +177,7 @@ class OrganizationStructure extends Base {
|
||||
}
|
||||
let path = `M ${x2},${y1 + s1} L ${x2},${y2}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
})
|
||||
minx = Math.min(x1, minx)
|
||||
maxx = Math.max(x1, maxx)
|
||||
@@ -186,12 +187,14 @@ class OrganizationStructure extends Base {
|
||||
expandBtnSize = len > 0 && !isRoot ? expandBtnSize : 0
|
||||
line1.plot(`M ${x1},${y1 + expandBtnSize} L ${x1},${y1 + s1}`)
|
||||
node._lines.push(line1)
|
||||
style && style(line1, node)
|
||||
// 水平线
|
||||
if (len > 0) {
|
||||
let lin2 = this.draw.path()
|
||||
node.style.line(lin2)
|
||||
lin2.plot(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`)
|
||||
node._lines.push(lin2)
|
||||
style && style(lin2, node)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
173
simple-mind-map/src/parse/xmind.js
Normal file
173
simple-mind-map/src/parse/xmind.js
Normal file
@@ -0,0 +1,173 @@
|
||||
import JSZip from "jszip";
|
||||
import xmlConvert from "xml-js";
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-09-21 14:07:47
|
||||
* @Desc: 解析.xmind文件
|
||||
*/
|
||||
const parseXmindFile = (file) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
JSZip.loadAsync(file).then(
|
||||
async (zip) => {
|
||||
try {
|
||||
let content = "";
|
||||
if (zip.files["content.json"]) {
|
||||
let json = await zip.files["content.json"].async("string");
|
||||
content = transformXmind(json);
|
||||
} else if (zip.files["content.xml"]) {
|
||||
let xml = await zip.files["content.xml"].async("string");
|
||||
let json = xmlConvert.xml2json(xml);
|
||||
content = transformOldXmind(json);
|
||||
}
|
||||
if (content) {
|
||||
resolve(content);
|
||||
} else {
|
||||
reject(new Error("解析失败"));
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
},
|
||||
(e) => {
|
||||
reject(e);
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-09-21 18:57:25
|
||||
* @Desc: 转换xmind数据
|
||||
*/
|
||||
const transformXmind = (content) => {
|
||||
let data = JSON.parse(content)[0];
|
||||
let nodeTree = data.rootTopic;
|
||||
let newTree = {};
|
||||
let walk = (node, newNode) => {
|
||||
newNode.data = {
|
||||
// 节点内容
|
||||
text: node.title,
|
||||
};
|
||||
// 节点备注
|
||||
if (node.notes) {
|
||||
newNode.data.note = (node.notes.realHTML || node.notes.plain).content;
|
||||
}
|
||||
// 超链接
|
||||
if (node.href && /^https?:\/\//.test(node.href)) {
|
||||
newNode.data.hyperlink = node.href;
|
||||
}
|
||||
// 标签
|
||||
if (node.labels && node.labels.length > 0) {
|
||||
newNode.data.tag = node.labels;
|
||||
}
|
||||
// 子节点
|
||||
newNode.children = [];
|
||||
if (
|
||||
node.children &&
|
||||
node.children.attached &&
|
||||
node.children.attached.length > 0
|
||||
) {
|
||||
node.children.attached.forEach((item) => {
|
||||
let newChild = {};
|
||||
newNode.children.push(newChild);
|
||||
walk(item, newChild);
|
||||
});
|
||||
}
|
||||
};
|
||||
walk(nodeTree, newTree);
|
||||
return newTree;
|
||||
};
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-09-23 15:51:51
|
||||
* @Desc: 转换旧版xmind数据,xmind8
|
||||
*/
|
||||
const transformOldXmind = (content) => {
|
||||
let data = JSON.parse(content);
|
||||
let elements = data.elements;
|
||||
let root = null;
|
||||
let getRoot = (arr) => {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (!root && arr[i].name === "topic") {
|
||||
root = arr[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
arr.forEach((item) => {
|
||||
getRoot(item.elements);
|
||||
});
|
||||
};
|
||||
getRoot(elements);
|
||||
let newTree = {};
|
||||
let getItemByName = (arr, name) => {
|
||||
return arr.find((item) => {
|
||||
return item.name === name;
|
||||
});
|
||||
};
|
||||
let walk = (node, newNode) => {
|
||||
let nodeElements = node.elements;
|
||||
newNode.data = {
|
||||
// 节点内容
|
||||
text: getItemByName(nodeElements, "title").elements[0].text,
|
||||
};
|
||||
try {
|
||||
// 节点备注
|
||||
let notesElement = getItemByName(nodeElements, "notes");
|
||||
if (notesElement) {
|
||||
newNode.data.note =
|
||||
notesElement.elements[0].elements[0].elements[0].text;
|
||||
}
|
||||
} catch (error) {}
|
||||
try {
|
||||
// 超链接
|
||||
if (
|
||||
node.attributes &&
|
||||
node.attributes["xlink:href"] &&
|
||||
/^https?:\/\//.test(node.attributes["xlink:href"])
|
||||
) {
|
||||
newNode.data.hyperlink = node.attributes["xlink:href"];
|
||||
}
|
||||
} catch (error) {}
|
||||
try {
|
||||
// 标签
|
||||
let labelsElement = getItemByName(nodeElements, "labels");
|
||||
if (labelsElement) {
|
||||
newNode.data.tag = labelsElement.elements.map((item) => {
|
||||
return item.elements[0].text;
|
||||
});
|
||||
}
|
||||
} catch (error) {}
|
||||
// 子节点
|
||||
newNode.children = [];
|
||||
let _children = getItemByName(nodeElements, "children");
|
||||
if (_children && _children.elements && _children.elements.length > 0) {
|
||||
_children.elements.forEach((item) => {
|
||||
if (item.name === "topics") {
|
||||
item.elements.forEach((item2) => {
|
||||
let newChild = {};
|
||||
newNode.children.push(newChild);
|
||||
walk(item2, newChild);
|
||||
});
|
||||
} else {
|
||||
let newChild = {};
|
||||
newNode.children.push(newChild);
|
||||
walk(item, newChild);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
walk(root, newTree);
|
||||
return newTree;
|
||||
};
|
||||
|
||||
export default {
|
||||
parseXmindFile,
|
||||
transformXmind,
|
||||
transformOldXmind,
|
||||
};
|
||||
@@ -17,6 +17,8 @@ export default {
|
||||
lineWidth: 1,
|
||||
// 连线的颜色
|
||||
lineColor: '#549688',
|
||||
// 连线样式
|
||||
lineDasharray: 'none',
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 1,
|
||||
// 概要连线的颜色
|
||||
@@ -33,6 +35,7 @@ export default {
|
||||
backgroundRepeat: 'no-repeat',
|
||||
// 根节点样式
|
||||
root: {
|
||||
shape: 'rectangle',
|
||||
fillColor: '#549688',
|
||||
fontFamily: '微软雅黑, Microsoft YaHei',
|
||||
color: '#fff',
|
||||
@@ -53,6 +56,7 @@ export default {
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
shape: 'rectangle',
|
||||
marginX: 100,
|
||||
marginY: 40,
|
||||
fillColor: '#fff',
|
||||
@@ -75,6 +79,7 @@ export default {
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
shape: 'rectangle',
|
||||
marginX: 50,
|
||||
marginY: 0,
|
||||
fillColor: 'transparent',
|
||||
@@ -97,6 +102,7 @@ export default {
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
shape: 'rectangle',
|
||||
marginX: 100,
|
||||
marginY: 40,
|
||||
fillColor: '#fff',
|
||||
@@ -117,4 +123,10 @@ export default {
|
||||
borderDasharray: 'none',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 支持激活样式的属性
|
||||
// 简单来说,会改变节点大小的都不支持在激活时设置,为了性能考虑,节点切换激活态时不会重新计算节点大小
|
||||
export const supportActiveStyle = ['fillColor', 'color', 'fontWeight', 'fontStyle', 'borderColor', 'borderWidth', 'borderDasharray', 'borderRadius', 'textDecoration']
|
||||
|
||||
export const lineStyleProps = ['lineColor', 'lineDasharray', 'lineWidth']
|
||||
BIN
web/src/.DS_Store
vendored
BIN
web/src/.DS_Store
vendored
Binary file not shown.
@@ -1,5 +1,6 @@
|
||||
import exampleData from "simple-mind-map/example/exampleData"
|
||||
import { simpleDeepClone } from 'simple-mind-map/src/utils/index'
|
||||
import Vue from 'vue'
|
||||
|
||||
const SIMPLE_MIND_MAP_DATA = 'SIMPLE_MIND_MAP_DATA'
|
||||
|
||||
@@ -47,6 +48,7 @@ export const storeData = (data) => {
|
||||
try {
|
||||
let originData = getData()
|
||||
originData.root = copyMindMapTreeData({}, data)
|
||||
Vue.prototype.$bus.$emit('write_local_file', originData)
|
||||
let dataStr = JSON.stringify(originData)
|
||||
localStorage.setItem(SIMPLE_MIND_MAP_DATA, dataStr)
|
||||
} catch (error) {
|
||||
@@ -66,6 +68,7 @@ export const storeConfig = (config) => {
|
||||
...originData,
|
||||
...config
|
||||
}
|
||||
Vue.prototype.$bus.$emit('write_local_file', originData)
|
||||
let dataStr = JSON.stringify(originData)
|
||||
localStorage.setItem(SIMPLE_MIND_MAP_DATA, dataStr)
|
||||
} catch (error) {
|
||||
|
||||
BIN
web/src/assets/.DS_Store
vendored
BIN
web/src/assets/.DS_Store
vendored
Binary file not shown.
BIN
web/src/assets/icon-font/.DS_Store
vendored
BIN
web/src/assets/icon-font/.DS_Store
vendored
Binary file not shown.
@@ -3,8 +3,8 @@
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>iconfont Demo</title>
|
||||
<link rel="shortcut icon" href="//img.alicdn.com/imgextra/i2/O1CN01ZyAlrn1MwaMhqz36G_!!6000000001499-73-tps-64-64.ico" type="image/x-icon"/>
|
||||
<link rel="icon" type="image/svg+xml" href="//img.alicdn.com/imgextra/i4/O1CN01EYTRnJ297D6vehehJ_!!6000000008020-55-tps-64-64.svg"/>
|
||||
<link rel="shortcut icon" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg" type="image/x-icon"/>
|
||||
<link rel="icon" type="image/svg+xml" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg"/>
|
||||
<link rel="stylesheet" href="https://g.alicdn.com/thx/cube/1.3.2/cube.min.css">
|
||||
<link rel="stylesheet" href="demo.css">
|
||||
<link rel="stylesheet" href="iconfont.css">
|
||||
@@ -54,6 +54,36 @@
|
||||
<div class="content unicode" style="display: block;">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">导出</div>
|
||||
<div class="code-name">&#xe63e;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">另存为</div>
|
||||
<div class="code-name">&#xe657;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">export</div>
|
||||
<div class="code-name">&#xe642;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">打开</div>
|
||||
<div class="code-name">&#xebdf;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">新建</div>
|
||||
<div class="code-name">&#xe64e;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">剪切</div>
|
||||
@@ -300,9 +330,9 @@
|
||||
<pre><code class="language-css"
|
||||
>@font-face {
|
||||
font-family: 'iconfont';
|
||||
src: url('iconfont.woff2?t=1659615576455') format('woff2'),
|
||||
url('iconfont.woff?t=1659615576455') format('woff'),
|
||||
url('iconfont.ttf?t=1659615576455') format('truetype');
|
||||
src: url('iconfont.woff2?t=1664005697217') format('woff2'),
|
||||
url('iconfont.woff?t=1664005697217') format('woff'),
|
||||
url('iconfont.ttf?t=1664005697217') format('truetype');
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
||||
@@ -328,6 +358,51 @@
|
||||
<div class="content font-class">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icondaochu1"></span>
|
||||
<div class="name">
|
||||
导出
|
||||
</div>
|
||||
<div class="code-name">.icondaochu1
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont iconlingcunwei"></span>
|
||||
<div class="name">
|
||||
另存为
|
||||
</div>
|
||||
<div class="code-name">.iconlingcunwei
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont iconexport"></span>
|
||||
<div class="name">
|
||||
export
|
||||
</div>
|
||||
<div class="code-name">.iconexport
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icondakai"></span>
|
||||
<div class="name">
|
||||
打开
|
||||
</div>
|
||||
<div class="code-name">.icondakai
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont iconxinjian"></span>
|
||||
<div class="name">
|
||||
新建
|
||||
</div>
|
||||
<div class="code-name">.iconxinjian
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont iconjianqie"></span>
|
||||
<div class="name">
|
||||
@@ -697,6 +772,46 @@
|
||||
<div class="content symbol">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icondaochu1"></use>
|
||||
</svg>
|
||||
<div class="name">导出</div>
|
||||
<div class="code-name">#icondaochu1</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#iconlingcunwei"></use>
|
||||
</svg>
|
||||
<div class="name">另存为</div>
|
||||
<div class="code-name">#iconlingcunwei</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#iconexport"></use>
|
||||
</svg>
|
||||
<div class="name">export</div>
|
||||
<div class="code-name">#iconexport</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icondakai"></use>
|
||||
</svg>
|
||||
<div class="name">打开</div>
|
||||
<div class="code-name">#icondakai</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#iconxinjian"></use>
|
||||
</svg>
|
||||
<div class="name">新建</div>
|
||||
<div class="code-name">#iconxinjian</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#iconjianqie"></use>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 2479351 */
|
||||
src: url('iconfont.woff2?t=1659615576455') format('woff2'),
|
||||
url('iconfont.woff?t=1659615576455') format('woff'),
|
||||
url('iconfont.ttf?t=1659615576455') format('truetype');
|
||||
src: url('iconfont.woff2?t=1664005697217') format('woff2'),
|
||||
url('iconfont.woff?t=1664005697217') format('woff'),
|
||||
url('iconfont.ttf?t=1664005697217') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,6 +13,26 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icondaochu1:before {
|
||||
content: "\e63e";
|
||||
}
|
||||
|
||||
.iconlingcunwei:before {
|
||||
content: "\e657";
|
||||
}
|
||||
|
||||
.iconexport:before {
|
||||
content: "\e642";
|
||||
}
|
||||
|
||||
.icondakai:before {
|
||||
content: "\ebdf";
|
||||
}
|
||||
|
||||
.iconxinjian:before {
|
||||
content: "\e64e";
|
||||
}
|
||||
|
||||
.iconjianqie:before {
|
||||
content: "\e601";
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -5,6 +5,41 @@
|
||||
"css_prefix_text": "icon",
|
||||
"description": "思维导图",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "1305460",
|
||||
"name": "导出",
|
||||
"font_class": "daochu1",
|
||||
"unicode": "e63e",
|
||||
"unicode_decimal": 58942
|
||||
},
|
||||
{
|
||||
"icon_id": "4784101",
|
||||
"name": "另存为",
|
||||
"font_class": "lingcunwei",
|
||||
"unicode": "e657",
|
||||
"unicode_decimal": 58967
|
||||
},
|
||||
{
|
||||
"icon_id": "9929033",
|
||||
"name": "export",
|
||||
"font_class": "export",
|
||||
"unicode": "e642",
|
||||
"unicode_decimal": 58946
|
||||
},
|
||||
{
|
||||
"icon_id": "4570294",
|
||||
"name": "打开",
|
||||
"font_class": "dakai",
|
||||
"unicode": "ebdf",
|
||||
"unicode_decimal": 60383
|
||||
},
|
||||
{
|
||||
"icon_id": "5086088",
|
||||
"name": "新建",
|
||||
"font_class": "xinjian",
|
||||
"unicode": "e64e",
|
||||
"unicode_decimal": 58958
|
||||
},
|
||||
{
|
||||
"icon_id": "1117",
|
||||
"name": "剪切",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -311,4 +311,44 @@ export const shortcutKeyList = [
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
// 形状列表
|
||||
export const shapeList = [
|
||||
{
|
||||
name: '矩形',
|
||||
value: 'rectangle'
|
||||
},
|
||||
{
|
||||
name: '菱形',
|
||||
value: 'diamond'
|
||||
},
|
||||
{
|
||||
name: '平行四边形',
|
||||
value: 'parallelogram'
|
||||
},
|
||||
{
|
||||
name: '圆角矩形',
|
||||
value: 'roundedRectangle'
|
||||
},
|
||||
{
|
||||
name: '八角矩形',
|
||||
value: 'octagonalRectangle'
|
||||
},
|
||||
{
|
||||
name: '外三角矩形',
|
||||
value: 'outerTriangularRectangle'
|
||||
},
|
||||
{
|
||||
name: '内三角矩形',
|
||||
value: 'innerTriangularRectangle'
|
||||
},
|
||||
{
|
||||
name: '椭圆',
|
||||
value: 'ellipse'
|
||||
},
|
||||
{
|
||||
name: '圆',
|
||||
value: 'circle'
|
||||
}
|
||||
]
|
||||
@@ -101,10 +101,10 @@
|
||||
<span class="name">颜色</span>
|
||||
<span
|
||||
class="block"
|
||||
v-popover:popover
|
||||
v-popover:popover2
|
||||
:style="{ backgroundColor: style.generalizationLineColor }"
|
||||
></span>
|
||||
<el-popover ref="popover" placement="bottom" trigger="click">
|
||||
<el-popover ref="popover2" placement="bottom" trigger="click">
|
||||
<Color
|
||||
:color="style.generalizationLineColor"
|
||||
@change="
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="contextmenuContainer"
|
||||
class="contextmenuContainer listBox"
|
||||
v-if="isShow"
|
||||
:style="{ left: left + 'px', top: top + 'px' }"
|
||||
>
|
||||
@@ -62,6 +62,12 @@
|
||||
<div class="item" @click="exec('RETURN_CENTER')">回到中心</div>
|
||||
<div class="item" @click="exec('EXPAND_ALL')">展开所有</div>
|
||||
<div class="item" @click="exec('UNEXPAND_ALL')">收起所有</div>
|
||||
<div class="item">
|
||||
展开到
|
||||
<div class="subItems listBox">
|
||||
<div class="item" v-for="(item, index) in expandList" :key="item" @click="exec('UNEXPAND_TO_LEVEL', false, index + 1)">{{item}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item" @click="exec('RESET_LAYOUT')">
|
||||
一键整理布局
|
||||
<span class="desc">Ctrl + L</span>
|
||||
@@ -93,7 +99,8 @@ export default {
|
||||
type: "",
|
||||
isMousedown: false,
|
||||
mosuedownX: 0,
|
||||
mosuedownY: 0
|
||||
mosuedownY: 0,
|
||||
expandList: ['一级主题', '二级主题', '三级主题', '四级主题', '五级主题', '六级主题']
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -221,7 +228,7 @@ export default {
|
||||
* @Date: 2021-07-14 23:27:54
|
||||
* @Desc: 执行命令
|
||||
*/
|
||||
exec(key, disabled) {
|
||||
exec(key, disabled, ...args) {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
@@ -241,7 +248,7 @@ export default {
|
||||
this.mindMap.view.reset();
|
||||
break;
|
||||
default:
|
||||
this.$bus.$emit("execCommand", key);
|
||||
this.$bus.$emit("execCommand", key, ...args);
|
||||
break;
|
||||
}
|
||||
this.hide();
|
||||
@@ -278,20 +285,23 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.contextmenuContainer {
|
||||
position: fixed;
|
||||
.listBox {
|
||||
width: 200px;
|
||||
background: #fff;
|
||||
box-shadow: 0 4px 12px 0 hsla(0, 0%, 69%, 0.5);
|
||||
border-radius: 4px;
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
.contextmenuContainer {
|
||||
position: fixed;
|
||||
font-size: 14px;
|
||||
font-family: PingFangSC-Regular, PingFang SC;
|
||||
font-weight: 400;
|
||||
color: #1a1a1a;
|
||||
|
||||
.item {
|
||||
position: relative;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
padding: 0 16px;
|
||||
@@ -305,6 +315,10 @@ export default {
|
||||
|
||||
&:hover {
|
||||
background: #f5f5f5;
|
||||
|
||||
.subItems {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
@@ -320,6 +334,13 @@ export default {
|
||||
.desc {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.subItems {
|
||||
position: absolute;
|
||||
left: 100%;
|
||||
top: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -154,12 +154,8 @@ export default {
|
||||
if (this.openTest) {
|
||||
return
|
||||
}
|
||||
let data = this.mindMap.command.getCopyData()
|
||||
storeData(data)
|
||||
let viewData = this.mindMap.view.getTransformData()
|
||||
storeConfig({
|
||||
view: viewData,
|
||||
})
|
||||
let data = this.mindMap.getData(true)
|
||||
storeConfig(data)
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -215,7 +211,11 @@ export default {
|
||||
* @Desc: 动态设置思维导图数据
|
||||
*/
|
||||
setData(data) {
|
||||
this.mindMap.setData(data)
|
||||
if (data.root) {
|
||||
this.mindMap.setFullData(data)
|
||||
} else {
|
||||
this.mindMap.setData(data)
|
||||
}
|
||||
this.manualSave()
|
||||
},
|
||||
|
||||
|
||||
@@ -3,21 +3,22 @@
|
||||
class="nodeDialog"
|
||||
title="导出"
|
||||
:visible.sync="dialogVisible"
|
||||
width="500"
|
||||
width="700px"
|
||||
>
|
||||
<div>
|
||||
<div class="nameInputBox">
|
||||
<span class="name">导出文件名称</span>
|
||||
<el-input style="width: 300px" v-model="fileName" size="mini"></el-input>
|
||||
<el-checkbox v-show="['smm', 'json'].includes(exportType)" v-model="widthConfig" style="margin-left: 12px">是否包含主题、结构等配置数据</el-checkbox>
|
||||
</div>
|
||||
<el-radio-group v-model="exportType">
|
||||
<el-radio label="smm">专有文件(.smm)</el-radio>
|
||||
<el-radio label="json">json文件(.json)</el-radio>
|
||||
<el-radio label="png">图片文件(.png)</el-radio>
|
||||
<el-radio label="svg">svg文件(.svg)</el-radio>
|
||||
<el-radio label="pdf">pdf文件(.pdf)</el-radio>
|
||||
<el-radio-group v-model="exportType" size="mini">
|
||||
<el-radio-button label="smm">专有文件(.smm)</el-radio-button>
|
||||
<el-radio-button label="json">json文件(.json)</el-radio-button>
|
||||
<el-radio-button label="png">图片文件(.png)</el-radio-button>
|
||||
<el-radio-button label="svg">svg文件(.svg)</el-radio-button>
|
||||
<el-radio-button label="pdf">pdf文件(.pdf)</el-radio-button>
|
||||
</el-radio-group>
|
||||
<div class="tip">tips:.smm文件可用于导入</div>
|
||||
<div class="tip">tips:.smm和.json文件可用于导入</div>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
@@ -38,7 +39,8 @@ export default {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
exportType: "smm",
|
||||
fileName: '思维导图'
|
||||
fileName: '思维导图',
|
||||
widthConfig: true
|
||||
};
|
||||
},
|
||||
created() {
|
||||
@@ -62,7 +64,7 @@ export default {
|
||||
* @Desc: 确定
|
||||
*/
|
||||
confirm() {
|
||||
this.$bus.$emit("export", this.exportType, true, this.fileName);
|
||||
this.$bus.$emit("export", this.exportType, true, this.fileName, this.widthConfig);
|
||||
this.$notify.info({
|
||||
title: '消息',
|
||||
message: '如果没有触发下载,请检查是否被浏览器拦截了'
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<el-dialog class="nodeDialog" title="导入" :visible.sync="dialogVisible" width="500">
|
||||
<el-dialog class="nodeDialog" title="导入" :visible.sync="dialogVisible" width="300px">
|
||||
<el-upload ref="upload" action="x" :file-list="fileList" :auto-upload="false" :multiple="false" :on-change="onChange" :limit="1" :on-exceed="onExceed">
|
||||
<el-button slot="trigger" size="small" type="primary">选取文件</el-button>
|
||||
<div slot="tip" class="el-upload__tip">只能上传.smm文件</div>
|
||||
<div slot="tip" class="el-upload__tip">支持.smm、.json、.xmind文件</div>
|
||||
</el-upload>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
@@ -12,6 +12,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MindMap from 'simple-mind-map'
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-24 22:53:54
|
||||
@@ -44,9 +46,9 @@ export default {
|
||||
* @Desc: 文件选择
|
||||
*/
|
||||
onChange(file) {
|
||||
let reg = /\.smm$/;
|
||||
let reg = /\.(smm|xmind|json)$/;
|
||||
if (!reg.test(file.name)) {
|
||||
this.$message.error("请选择.smm文件");
|
||||
this.$message.error("请选择.smm、.json、.xmind文件");
|
||||
this.fileList = [];
|
||||
} else {
|
||||
this.fileList.push(file)
|
||||
@@ -80,7 +82,17 @@ export default {
|
||||
if (this.fileList.length <= 0) {
|
||||
return this.$message.error("请选择要导入的文件");
|
||||
}
|
||||
this.$store.commit('setIsHandleLocalFile', false);
|
||||
let file = this.fileList[0];
|
||||
if (/\.(smm|json)$/.test(file.name)) {
|
||||
this.handleSmm(file)
|
||||
} else if (/\.xmind$/.test(file.name)) {
|
||||
this.handleXmind(file)
|
||||
}
|
||||
this.cancel();
|
||||
},
|
||||
|
||||
handleSmm(file) {
|
||||
let fileReader = new FileReader()
|
||||
fileReader.readAsText(file.raw)
|
||||
fileReader.onload = (evt) => {
|
||||
@@ -96,8 +108,18 @@ export default {
|
||||
this.$message.error("文件解析失败");
|
||||
}
|
||||
}
|
||||
this.cancel();
|
||||
},
|
||||
|
||||
async handleXmind(file) {
|
||||
try {
|
||||
let data = await MindMap.xmind.parseXmindFile(file.raw)
|
||||
this.$bus.$emit('setData', data)
|
||||
this.$message.success("导入成功");
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
this.$message.error("文件解析失败");
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
size="mini"
|
||||
v-model="style.fontFamily"
|
||||
placeholder=""
|
||||
:disabled="checkDisabled('fontFamily')"
|
||||
@change="update('fontFamily')"
|
||||
>
|
||||
<el-option
|
||||
@@ -36,6 +37,7 @@
|
||||
style="width: 80px"
|
||||
v-model="style.fontSize"
|
||||
placeholder=""
|
||||
:disabled="checkDisabled('fontSize')"
|
||||
@change="update('fontSize')"
|
||||
>
|
||||
<el-option
|
||||
@@ -54,6 +56,7 @@
|
||||
style="width: 80px"
|
||||
v-model="style.lineHeight"
|
||||
placeholder=""
|
||||
:disabled="checkDisabled('lineHeight')"
|
||||
@change="update('lineHeight')"
|
||||
>
|
||||
<el-option
|
||||
@@ -69,7 +72,7 @@
|
||||
<div class="row">
|
||||
<div class="btnGroup">
|
||||
<el-tooltip content="颜色" placement="bottom">
|
||||
<div class="styleBtn" v-popover:popover>
|
||||
<div class="styleBtn" v-popover:popover :class="{ disabled: checkDisabled('color') }">
|
||||
A
|
||||
<span
|
||||
class="colorShow"
|
||||
@@ -80,7 +83,7 @@
|
||||
<el-tooltip content="加粗" placement="bottom">
|
||||
<div
|
||||
class="styleBtn"
|
||||
:class="{ actived: style.fontWeight === 'bold' }"
|
||||
:class="{ actived: style.fontWeight === 'bold', disabled: checkDisabled('fontWeight') }"
|
||||
@click="toggleFontWeight"
|
||||
>
|
||||
B
|
||||
@@ -89,7 +92,7 @@
|
||||
<el-tooltip content="斜体" placement="bottom">
|
||||
<div
|
||||
class="styleBtn i"
|
||||
:class="{ actived: style.fontStyle === 'italic' }"
|
||||
:class="{ actived: style.fontStyle === 'italic', disabled: checkDisabled('fontStyle') }"
|
||||
@click="toggleFontStyle"
|
||||
>
|
||||
I
|
||||
@@ -99,16 +102,17 @@
|
||||
<div
|
||||
class="styleBtn u"
|
||||
:style="{ textDecoration: style.textDecoration || 'none' }"
|
||||
:class="{ disabled: checkDisabled('textDecoration') }"
|
||||
v-popover:popover2
|
||||
>
|
||||
U
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-popover ref="popover" placement="bottom" trigger="click">
|
||||
<el-popover ref="popover" placement="bottom" trigger="click" :disabled="checkDisabled('color')">
|
||||
<Color :color="style.color" @change="changeFontColor"></Color>
|
||||
</el-popover>
|
||||
<el-popover ref="popover2" placement="bottom" trigger="click">
|
||||
<el-popover ref="popover2" placement="bottom" trigger="click" :disabled="checkDisabled('textDecoration')">
|
||||
<el-radio-group
|
||||
size="mini"
|
||||
v-model="style.textDecoration"
|
||||
@@ -129,8 +133,9 @@
|
||||
class="block"
|
||||
v-popover:popover3
|
||||
:style="{ width: '80px', backgroundColor: style.borderColor }"
|
||||
:class="{ disabled: checkDisabled('borderColor') }"
|
||||
></span>
|
||||
<el-popover ref="popover3" placement="bottom" trigger="click">
|
||||
<el-popover ref="popover3" placement="bottom" trigger="click" :disabled="checkDisabled('borderColor')">
|
||||
<Color
|
||||
:color="style.borderColor"
|
||||
@change="changeBorderColor"
|
||||
@@ -138,12 +143,13 @@
|
||||
</el-popover>
|
||||
</div>
|
||||
<div class="rowItem">
|
||||
<span class="name" v-popover:popover5>样式</span>
|
||||
<span class="name">样式</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 80px"
|
||||
v-model="style.borderDasharray"
|
||||
placeholder=""
|
||||
:disabled="checkDisabled('borderDasharray')"
|
||||
@change="update('borderDasharray')"
|
||||
>
|
||||
<el-option
|
||||
@@ -164,6 +170,7 @@
|
||||
style="width: 80px"
|
||||
v-model="style.borderWidth"
|
||||
placeholder=""
|
||||
:disabled="checkDisabled('borderWidth')"
|
||||
@change="update('borderWidth')"
|
||||
>
|
||||
<el-option
|
||||
@@ -182,6 +189,7 @@
|
||||
style="width: 80px"
|
||||
v-model="style.borderRadius"
|
||||
placeholder=""
|
||||
:disabled="checkDisabled('borderRadius')"
|
||||
@change="update('borderRadius')"
|
||||
>
|
||||
<el-option
|
||||
@@ -203,12 +211,95 @@
|
||||
class="block"
|
||||
v-popover:popover4
|
||||
:style="{ backgroundColor: style.fillColor }"
|
||||
:class="{ disabled: checkDisabled('fillColor') }"
|
||||
></span>
|
||||
<el-popover ref="popover4" placement="bottom" trigger="click">
|
||||
<el-popover ref="popover4" placement="bottom" trigger="click" :disabled="checkDisabled('fillColor')">
|
||||
<Color :color="style.fillColor" @change="changeFillColor"></Color>
|
||||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 形状 -->
|
||||
<div class="title">形状</div>
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">形状</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 120px"
|
||||
v-model="style.shape"
|
||||
placeholder=""
|
||||
:disabled="checkDisabled('shape')"
|
||||
@change="update('shape')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in shapeList"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 线条 -->
|
||||
<div class="title">线条</div>
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">颜色</span>
|
||||
<span
|
||||
class="block"
|
||||
v-popover:popover5
|
||||
:style="{ width: '80px', backgroundColor: style.lineColor }"
|
||||
:class="{ disabled: checkDisabled('lineColor') }"
|
||||
></span>
|
||||
<el-popover ref="popover5" placement="bottom" trigger="click" :disabled="checkDisabled('lineColor')">
|
||||
<Color
|
||||
:color="style.lineColor"
|
||||
@change="changeLineColor"
|
||||
></Color>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div class="rowItem">
|
||||
<span class="name">样式</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 80px"
|
||||
v-model="style.lineDasharray"
|
||||
placeholder=""
|
||||
:disabled="checkDisabled('lineDasharray')"
|
||||
@change="update('lineDasharray')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in borderDasharrayList"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">宽度</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 80px"
|
||||
v-model="style.lineWidth"
|
||||
placeholder=""
|
||||
:disabled="checkDisabled('lineWidth')"
|
||||
@change="update('lineWidth')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in borderWidthList"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 节点内边距 -->
|
||||
<div class="title noTop">节点内边距</div>
|
||||
<div class="row">
|
||||
@@ -217,6 +308,7 @@
|
||||
<el-slider
|
||||
style="width: 200px"
|
||||
v-model="style.paddingX"
|
||||
:disabled="checkDisabled('paddingX')"
|
||||
@change="update('paddingX')"
|
||||
></el-slider>
|
||||
</div>
|
||||
@@ -227,6 +319,7 @@
|
||||
<el-slider
|
||||
style="width: 200px"
|
||||
v-model="style.paddingY"
|
||||
:disabled="checkDisabled('paddingY')"
|
||||
@change="update('paddingY')"
|
||||
></el-slider>
|
||||
</div>
|
||||
@@ -246,7 +339,9 @@ import {
|
||||
borderDasharrayList,
|
||||
borderRadiusList,
|
||||
lineHeightList,
|
||||
shapeList,
|
||||
} from "@/config";
|
||||
import { supportActiveStyle } from 'simple-mind-map/src/themes/default';
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
@@ -261,6 +356,8 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
supportActiveStyle,
|
||||
shapeList,
|
||||
fontFamilyList,
|
||||
fontSizeList,
|
||||
borderWidthList,
|
||||
@@ -270,6 +367,7 @@ export default {
|
||||
activeNodes: [],
|
||||
activeTab: "normal",
|
||||
style: {
|
||||
shape: '',
|
||||
paddingX: 0,
|
||||
paddingY: 0,
|
||||
color: "",
|
||||
@@ -284,16 +382,19 @@ export default {
|
||||
fillColor: "",
|
||||
borderDasharray: "",
|
||||
borderRadius: "",
|
||||
lineColor: "",
|
||||
lineDasharray: "",
|
||||
lineWidth: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.$bus.$on("node_active", (...args) => {
|
||||
this.$refs.sidebar.show = false;
|
||||
if (this.$refs.sidebar) this.$refs.sidebar.show = false;
|
||||
this.$nextTick(() => {
|
||||
this.activeTab = "normal";
|
||||
this.activeNodes = args[1];
|
||||
this.$refs.sidebar.show = this.activeNodes.length > 0;
|
||||
if (this.$refs.sidebar) this.$refs.sidebar.show = this.activeNodes.length > 0;
|
||||
this.initNodeStyle();
|
||||
});
|
||||
});
|
||||
@@ -308,6 +409,15 @@ export default {
|
||||
this.initNodeStyle();
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 22:16:56
|
||||
* @Desc: 检查是否禁用
|
||||
*/
|
||||
checkDisabled(prop) {
|
||||
return this.activeTab === 'active' && !this.supportActiveStyle.includes(prop)
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-05 09:48:52
|
||||
@@ -319,6 +429,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
[
|
||||
"shape",
|
||||
"paddingX",
|
||||
"paddingY",
|
||||
"color",
|
||||
@@ -333,6 +444,9 @@ export default {
|
||||
"fillColor",
|
||||
"borderDasharray",
|
||||
"borderRadius",
|
||||
"lineColor",
|
||||
"lineDasharray",
|
||||
"lineWidth",
|
||||
].forEach((item) => {
|
||||
this.style[item] = this.activeNodes[0].getStyle(
|
||||
item,
|
||||
@@ -401,6 +515,16 @@ export default {
|
||||
this.update("borderColor");
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: flydreame
|
||||
* @Date: 2022-09-17 10:18:15
|
||||
* @Desc: 修改线条颜色
|
||||
*/
|
||||
changeLineColor(color) {
|
||||
this.style.lineColor = color;
|
||||
this.update("lineColor");
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-05 10:18:59
|
||||
@@ -472,6 +596,13 @@ export default {
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
&.disabled {
|
||||
background-color: #F5F7FA !important;
|
||||
border-color: #E4E7ED !important;
|
||||
color: #C0C4CC !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,6 +623,13 @@ export default {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background-color: #F5F7FA !important;
|
||||
border-color: #E4E7ED !important;
|
||||
color: #C0C4CC !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
&.i {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
<div
|
||||
class="toolbarBtn"
|
||||
:class="{
|
||||
disabled: activeNodes.length <= 0 || hasRoot,
|
||||
disabled: activeNodes.length <= 0 || hasRoot || hasGeneralization,
|
||||
}"
|
||||
@click="$bus.$emit('execCommand', 'ADD_GENERALIZATION')"
|
||||
>
|
||||
@@ -135,12 +135,24 @@
|
||||
</div>
|
||||
<!-- 导出 -->
|
||||
<div class="toolbarBlock">
|
||||
<div class="toolbarBtn" @click="createNewLocalFile">
|
||||
<span class="icon iconfont iconxinjian"></span>
|
||||
<span class="text">新建</span>
|
||||
</div>
|
||||
<div class="toolbarBtn" @click="openLocalFile">
|
||||
<span class="icon iconfont icondakai"></span>
|
||||
<span class="text">打开</span>
|
||||
</div>
|
||||
<div class="toolbarBtn" @click="saveLocalFile">
|
||||
<span class="icon iconfont iconlingcunwei"></span>
|
||||
<span class="text">另存为</span>
|
||||
</div>
|
||||
<div class="toolbarBtn" @click="$bus.$emit('showImport')">
|
||||
<span class="icon iconfont icondaoru"></span>
|
||||
<span class="text">导入</span>
|
||||
</div>
|
||||
<div class="toolbarBtn" @click="$bus.$emit('showExport')">
|
||||
<span class="icon iconfont icondaochu"></span>
|
||||
<span class="icon iconfont iconexport"></span>
|
||||
<span class="text">导出</span>
|
||||
</div>
|
||||
<div class="toolbarBtn" @click="$bus.$emit('showShortcutKey')">
|
||||
@@ -167,12 +179,17 @@ import NodeNote from "./NodeNote";
|
||||
import NodeTag from "./NodeTag";
|
||||
import Export from "./Export";
|
||||
import Import from './Import';
|
||||
import { mapState } from 'vuex';
|
||||
import { Notification } from 'element-ui';
|
||||
import exampleData from 'simple-mind-map/example/exampleData';
|
||||
import { getData } from '../../../api';
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-24 22:54:58
|
||||
* @Desc: 工具栏
|
||||
*/
|
||||
let fileHandle = null;
|
||||
export default {
|
||||
name: "Toolbar",
|
||||
components: {
|
||||
@@ -189,19 +206,29 @@ export default {
|
||||
activeNodes: [],
|
||||
backEnd: false,
|
||||
forwardEnd: true,
|
||||
readonly: false
|
||||
readonly: false,
|
||||
isFullDataFile: false,
|
||||
timer: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['isHandleLocalFile']),
|
||||
hasRoot() {
|
||||
return this.activeNodes.find((node) => {
|
||||
return this.activeNodes.findIndex((node) => {
|
||||
return node.isRoot;
|
||||
});
|
||||
}) !== -1;
|
||||
},
|
||||
hasGeneralization() {
|
||||
return this.activeNodes.find((node) => {
|
||||
return this.activeNodes.findIndex((node) => {
|
||||
return node.isGeneralization;
|
||||
});
|
||||
}) !== -1;;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isHandleLocalFile(val) {
|
||||
if (!val) {
|
||||
Notification.closeAll();
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -215,7 +242,168 @@ export default {
|
||||
this.backEnd = index <= 0
|
||||
this.forwardEnd = index >= len - 1
|
||||
});
|
||||
this.$bus.$on("write_local_file", (content) => {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => {
|
||||
this.writeLocalFile(content);
|
||||
}, 1000);
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-24 15:40:09
|
||||
* @Desc: 打开本地文件
|
||||
*/
|
||||
async openLocalFile() {
|
||||
try {
|
||||
let [ _fileHandle ] = await window.showOpenFilePicker({
|
||||
types: [
|
||||
{
|
||||
description: 'file',
|
||||
accept: {
|
||||
'application/*': ['.json', '.smm']
|
||||
}
|
||||
},
|
||||
],
|
||||
excludeAcceptAllOption: true,
|
||||
multiple: false
|
||||
});
|
||||
if (!_fileHandle) {
|
||||
return;
|
||||
}
|
||||
fileHandle = _fileHandle;
|
||||
if (fileHandle.kind === 'directory') {
|
||||
this.$message.warning('请选择文件');
|
||||
return;
|
||||
}
|
||||
this.readFile();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
this.$message.warning('你的浏览器可能不支持哦');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-24 15:40:18
|
||||
* @Desc: 读取本地文件
|
||||
*/
|
||||
async readFile() {
|
||||
let file = await fileHandle.getFile();
|
||||
let fileReader = new FileReader();
|
||||
fileReader.onload = async () => {
|
||||
this.$store.commit('setIsHandleLocalFile', true);
|
||||
this.setData(fileReader.result);
|
||||
Notification.closeAll();
|
||||
Notification({
|
||||
title: '提示',
|
||||
message: `当前正在编辑你本机的【${ file.name }】文件`,
|
||||
duration: 0,
|
||||
showClose: false
|
||||
});
|
||||
}
|
||||
fileReader.readAsText(file);
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-24 15:40:26
|
||||
* @Desc: 渲染读取的数据
|
||||
*/
|
||||
setData(str) {
|
||||
try {
|
||||
let data = JSON.parse(str);
|
||||
if (typeof data !== 'object') {
|
||||
throw new Error('文件内容有误');
|
||||
}
|
||||
if (data.root) {
|
||||
this.isFullDataFile = true;
|
||||
} else {
|
||||
this.isFullDataFile = false;
|
||||
data = {
|
||||
...exampleData,
|
||||
root: data
|
||||
}
|
||||
}
|
||||
this.$bus.$emit('setData', data);
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
this.$message.error("文件打开失败");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-24 15:40:42
|
||||
* @Desc: 写入本地文件
|
||||
*/
|
||||
async writeLocalFile(content) {
|
||||
if (!fileHandle || !this.isHandleLocalFile) {
|
||||
return;
|
||||
}
|
||||
if (!this.isFullDataFile) {
|
||||
content = content.root;
|
||||
}
|
||||
let string = JSON.stringify(content);
|
||||
const writable = await fileHandle.createWritable();
|
||||
await writable.write(string);
|
||||
await writable.close();
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-24 15:40:48
|
||||
* @Desc: 创建本地文件
|
||||
*/
|
||||
async createNewLocalFile() {
|
||||
await this.createLocalFile(exampleData);
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-24 15:49:17
|
||||
* @Desc: 另存为
|
||||
*/
|
||||
async saveLocalFile() {
|
||||
let data = getData();
|
||||
await this.createLocalFile(data);
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-24 15:50:22
|
||||
* @Desc: 创建本地文件
|
||||
*/
|
||||
async createLocalFile(content) {
|
||||
try {
|
||||
let _fileHandle = await window.showSaveFilePicker({
|
||||
types: [{
|
||||
description: 'file',
|
||||
accept: {'application/*': ['.json', '.smm']},
|
||||
}],
|
||||
});
|
||||
if (!_fileHandle) {
|
||||
return;
|
||||
}
|
||||
const loading = this.$loading({
|
||||
lock: true,
|
||||
text: '正在创建文件',
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
});
|
||||
fileHandle = _fileHandle;
|
||||
this.$store.commit('setIsHandleLocalFile', true);
|
||||
this.isFullDataFile = true;
|
||||
await this.writeLocalFile(content);
|
||||
await this.readFile();
|
||||
loading.close();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
this.$message.warning('你的浏览器可能不支持哦');
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ Vue.use(Vuex)
|
||||
|
||||
const store = new Vuex.Store({
|
||||
state: {
|
||||
mindMapData: null // 思维导图数据
|
||||
mindMapData: null, // 思维导图数据
|
||||
isHandleLocalFile: false// 是否操作的是本地文件
|
||||
},
|
||||
mutations: {
|
||||
/**
|
||||
@@ -16,6 +17,16 @@ const store = new Vuex.Store({
|
||||
*/
|
||||
setMindMapData(state, data) {
|
||||
state.mindMapData = data
|
||||
},
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-24 13:55:38
|
||||
* @Desc: 设置操作本地文件标志位
|
||||
*/
|
||||
setIsHandleLocalFile(state, data) {
|
||||
state.isHandleLocalFile = data
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
|
||||
Reference in New Issue
Block a user