Compare commits

...

11 Commits

Author SHA1 Message Date
wanglin2
5dfa215538 修改文档 2022-09-30 22:16:27 +08:00
wanglin25
d90013da71 打包 2022-09-30 14:48:08 +08:00
wanglin25
b3b74323f7 逻辑结构图、思维导图新增直线连接风格、直连风格 2022-09-30 14:44:40 +08:00
wanglin2
f9000ea478 逻辑结构图和思维导图支持直线连接线开发中 2022-09-25 22:07:21 +08:00
wanglin2
830e7e2482 打包 2022-09-24 20:42:53 +08:00
wanglin2
13ed7f28df 优化:手动创建节点时立即聚焦 2022-09-24 20:38:47 +08:00
wanglin2
17e79a0b23 修复连线样式深度更新问题 2022-09-24 20:13:53 +08:00
wanglin2
eee310ba49 支持新建、打开、 2022-09-24 17:08:11 +08:00
wanglin2
36c8927dd0 修改文档 2022-09-24 10:51:09 +08:00
wanglin2
05333daa63 支持展开到指定层级 2022-09-24 10:49:40 +08:00
wanglin25
5aed681198 支持展开到指定层级 2022-09-23 17:41:38 +08:00
29 changed files with 838 additions and 345 deletions

388
README.md
View File

@@ -30,7 +30,17 @@
2.`web`
使用`simple-mind-map`工具库,基于`vue2.x``ElementUI`搭建的在线思维导图。
使用`simple-mind-map`工具库,基于`vue2.x``ElementUI`搭建的在线思维导图。特性:
- [x] 工具栏,支持插入节点、删除节点;编辑节点图片、图标、超链接、备注、标签、概要
- [x] 侧边栏,基础样式设置面板、节点样式设置面板、大纲面板、主题选择面板、结构选择面板
- [x] 导入导出功能;数据默认保存在浏览器本地存储,也支持直接创建、打开、编辑电脑本地文件
- [x] 右键菜单,支持展开、收起、整理布局等操作
- [x] 底部栏,支持节点数量、字数统计;支持切换编辑和只读模式;支持放大缩小;支持全屏切换
3.`dist`
@@ -82,6 +92,7 @@ npm run buildLibrary
cd web
npm run build
```
会自动把`index.html`移动到根目录。
## 相关文章
@@ -90,7 +101,7 @@ npm run build
# 安装
> 当然仓库版本0.2.8当前npm版本0.2.7
> 当然仓库版本0.2.10当前npm版本0.2.10
```bash
npm i simple-mind-map
@@ -98,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
@@ -161,23 +172,22 @@ v0.2.8+
### 实例化选项:
| 字段名称 | 类型 | 默认值 | 描述 | 是否必填 |
| -------------------- | ------- | ---------------- | ------------------------------------------------------------ | -------- |
| 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脑图经典4v0.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 | 多选节点时鼠标移动距边缘多少距离时开始偏移 | |
| customNoteContentShowv0.1.6+ | Object | null | 自定义节点备注内容显示Object类型结构为{show: (noteContent, left, top) => {// 你的显示节点备注逻辑 }, hide: () => {// 你的隐藏节点备注逻辑 }} | |
| readonlyv0.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脑图经典4v0.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 | 多选节点时鼠标移动距边缘多少距离时开始偏移 | |
| customNoteContentShowv0.1.6+ | Object | null | 自定义节点备注内容显示Object类型结构为{show: (noteContent, left, top) => {// 你的显示节点备注逻辑 }, hide: () => {// 你的隐藏节点备注逻辑 }} | |
| readonlyv0.1.7+ | Boolean | false | 是否是只读模式 | |
### 实例方法:
@@ -185,14 +195,10 @@ v0.2.8+
触发整体渲染,会进行节点复用,性能较`reRender`会更好一点,如果只是节点位置变化了可以调用该方法进行渲染
#### reRender()
整体重新渲染,会清空画布,节点也会重新创建,性能不好,慎重使用
#### resize()
容器尺寸变化后,需要调用该方法进行适应
@@ -207,57 +213,47 @@ v0.1.7+。切换模式为只读或编辑。
监听事件,事件列表:
| 事件名称 | 描述 | 回调参数 |
| --------------------------- | ------------------------------------------ | ------------------------------------------------------------ |
| data_change | 渲染树数据变化,可以监听该方法获取最新数据 | data当前渲染树数据 |
| view_data_changev0.1.1+ | 视图变化数据,比如拖动或缩放时会触发 | data当前视图状态数据 |
| back_forward | 前进或回退 | activeHistoryIndex当前在历史数据数组里的索引、length当前历史数据数组的长度 |
| draw_click | *画布的单击事件* | e事件对象 |
| svg_mousedown | svg画布的鼠标按下事件 | e事件对象 |
| mousedown | el元素的鼠标按下事件 | e事件对象、thisEvent事件类实例 |
| mousemove | el元素的鼠标移动事件 | e事件对象、thisEvent事件类实例 |
| drag | 如果是按住左键拖动的话会触发拖动事件 | e事件对象、thisEvent事件类实例 |
| mouseup | el元素的鼠标松开事件 | e事件对象、thisEvent事件类实例 |
| mousewheel | 鼠标滚动事件 | e事件对象、dir向上up还是向下down滚动、thisEvent事件类实例 |
| 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_changev0.1.1+ | 视图变化数据,比如拖动或缩放时会触发 | data当前视图状态数据 |
| back_forward | 前进或回退 | activeHistoryIndex当前在历史数据数组里的索引、length当前历史数据数组的长度 |
| draw_click | *画布的单击事件* | e事件对象 |
| svg_mousedown | svg画布的鼠标按下事件 | e事件对象 |
| mousedown | el元素的鼠标按下事件 | e事件对象、thisEvent事件类实例 |
| mousemove | el元素的鼠标移动事件 | e事件对象、thisEvent事件类实例 |
| drag | 如果是按住左键拖动的话会触发拖动事件 | e事件对象、thisEvent事件类实例 |
| mouseup | el元素的鼠标松开事件 | e事件对象、thisEvent事件类实例 |
| mousewheel | 鼠标滚动事件 | e事件对象、dir向上up还是向下down滚动、thisEvent事件类实例 |
| 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`
@@ -270,58 +266,52 @@ 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_AFTERv0.1.5+ | 将节点移动到另一个节点的后面 | node移动的节点)、 exist目标节点 |
| INSERT_BEFOREv0.1.5+ | 将节点移动到另一个节点的前面 | node要移动的节点、 exist目标节点 |
| MOVE_NODE_TOv0.1.5+ | 移动一个节点作为另一个节点的子节点 | node要移动的节点、 toNode目标节点 |
| ADD_GENERALIZATIONv0.2.0+ | 添加节点概要 | data概要的数据对象格式节点的数字段都支持默认为{text: '概要'} |
| REMOVE_GENERALIZATIONv0.2.0+ | 删除节点概要 | |
| SET_NODE_CUSTOM_POSITIONv0.2.0+ | 设置节点自定义位置 | node要设置的节点、 left自定义的x坐标默认为undefined、 top自定义的y坐标默认为undefined |
| RESET_LAYOUTv0.2.0+ | 一键整理布局 | |
| SET_NODE_SHAPEv0.2.4+ | 设置节点形状 | node要设置的节点、shape形状全部形状https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/Shape.js |
| 命令名称 | 描述 | 参数 |
| --------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 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_LEVELv0.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_AFTERv0.1.5+ | 将节点移动到另一个节点的后面 | node要移动的节点、 exist目标节点 |
| INSERT_BEFOREv0.1.5+ | 将节点移动到另一个节点的前面 | node要移动的节点、 exist目标节点 |
| MOVE_NODE_TOv0.1.5+ | 移动一个节点作为另一个节点的子节点 | node要移动的节点、 toNode目标节点 |
| ADD_GENERALIZATIONv0.2.0+ | 添加节点概要 | data概要的数据对象格式节点的数字段都支持默认为{text: '概要'} |
| REMOVE_GENERALIZATIONv0.2.0+ | 删除节点概要 | |
| SET_NODE_CUSTOM_POSITIONv0.2.0+ | 设置节点自定义位置 | node要设置的节点、 left自定义的x坐标默认为undefined、 top自定义的y坐标默认为undefined |
| RESET_LAYOUTv0.2.0+ | 一键整理布局 | |
| SET_NODE_SHAPEv0.2.4+ | 设置节点形状 | node要设置的节点、shape形状全部形状https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/Shape.js |
#### setData(data)
@@ -337,6 +327,13 @@ 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)
@@ -358,22 +355,16 @@ v0.1.5+
`render`实例负载整个渲染过程,可通过`mindMap.renderer`获取到
### 属性
#### activeNodeList
获取当前激活的节点列表
#### root
获取节点树的根节点
### 方法
#### clearActive()
@@ -396,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)
@@ -507,13 +476,10 @@ v0.2.3+。保存当前注册的快捷键数据,然后清空快捷键数据
v0.2.3+。恢复保存的快捷键数据,然后清空缓存数据
## command实例
`command`实例负责命令的添加及执行,内置了很多命令,也可以自行添加,命令指需要在历史堆栈数据里添加副本的操作。可通过`mindMap.command`获取到该实例
### 方法
#### add(name, fn)
@@ -524,8 +490,6 @@ v0.2.3+。恢复保存的快捷键数据,然后清空缓存数据
`fn`:命令要执行的方法
#### remove(name, fn)
移除命令。
@@ -534,8 +498,6 @@ v0.2.3+。恢复保存的快捷键数据,然后清空缓存数据
`fn`:要移除的方法,不传的话移除该命令所有的方法
#### getCopyData()
获取渲染树数据副本
@@ -544,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`数据,异步方法,返回一个对象:
@@ -630,28 +569,20 @@ v0.1.1+
}
```
## select实例
`select`实例负责鼠标右键多选节点操作,可通过`mindMap.select`获取到该实例
### 方法
#### toPos(x, y)
转换鼠标位置为相对于容器`el`的位置
## batchExecution实例
`batchExecution`用来批量异步的执行一些操作,如果某个操作同时多次调用,那么只会在下一个事件循环里执行一次。可以通过`mindMap.batchExecution`获取到该实例
### 方法
#### push(name, fn)
@@ -662,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)
获取某个最终应用到该节点的样式值
@@ -812,150 +697,108 @@ 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+
@@ -980,7 +823,6 @@ v0.2.5+
获取自身可继承的自定义样式
## 内置工具方法
引用:
@@ -989,8 +831,6 @@ v0.2.5+
import {walk, ...} from 'simple-mind-map/src/utils'
```
### 方法
#### walk(root, parent, beforeCallback, afterCallback, isRoot, layerIndex = 0, index = 0)
@@ -1017,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)
缩放图片的尺寸
@@ -1039,20 +875,14 @@ walk(tree, null, () => {}, () => {}, false, 0, 0)
`maxWidth``maxHeight`可以同时都传,也可以只传一个
#### resizeImg(imgUrl, maxWidth, maxHeight)
缩放图片,内部先加载图片,然后调用`resizeImgSize`方法,返回一个`promise`
#### simpleDeepClone(data)
极简的深拷贝方法,只能针对全是基本数据的对象,否则会报错
#### copyRenderTree(tree, root)
复制渲染树数据,示例:
@@ -1061,8 +891,6 @@ walk(tree, null, () => {}, () => {}, false, 0, 0)
copyRenderTree({}, this.mindMap.renderer.renderTree)
```
#### copyNodeTree(tree, root)
复制节点树数据,主要是剔除其中的引用`node`实例的`_node`,然后复制`data`对象的数据,示例:
@@ -1071,32 +899,22 @@ copyRenderTree({}, this.mindMap.renderer.renderTree)
copyNodeTree({}, node)
```
#### imgToDataUrl(src)
图片转成dataURL
#### downloadFile(file, fileName)
下载文件
#### throttle(fn, time = 300, ctx)
节流函数
#### asyncRun(taskList, callback = () => {})
异步执行任务队列,多个任务是同步执行的,没有先后顺序
# 特别说明
本项目较粗糙,未进行完整测试,功能尚不是很完善,性能也存在一些问题,仅用于学习和参考,请勿用于实际项目。

View File

@@ -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-35b0a040.cb76da7d.js" rel="prefetch"><link href="dist/css/app.9be46b7b.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.6fd71983.css" rel="preload" as="style"><link href="dist/js/app.91233d74.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.e4b722f1.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.6fd71983.css" rel="stylesheet"><link href="dist/css/app.9be46b7b.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.e4b722f1.js"></script><script src="dist/js/app.91233d74.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.81d632f4.js" rel="prefetch"><link href="dist/js/chunk-2d216b67.228f2009.js" rel="prefetch"><link href="dist/js/chunk-35b0a040.cb76da7d.js" rel="prefetch"><link href="dist/css/app.f9570665.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.6fd71983.css" rel="preload" as="style"><link href="dist/js/app.db7bd644.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.d724da21.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.6fd71983.css" rel="stylesheet"><link href="dist/css/app.f9570665.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.d724da21.js"></script><script src="dist/js/app.db7bd644.js"></script></body></html>

View File

@@ -17,6 +17,7 @@ import {
SVG
} from '@svgdotjs/svg.js'
import xmind from './src/parse/xmind'
import { simpleDeepClone } from './src/utils';
// 默认选项配置
const defaultOpt = {
@@ -355,6 +356,31 @@ class MindMap {
}
}
/**
* 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

View File

@@ -1,6 +1,6 @@
{
"name": "simple-mind-map",
"version": "0.2.8",
"version": "0.2.10",
"description": "一个简单的web在线思维导图",
"authors": [
{

View File

@@ -249,21 +249,7 @@ class Export {
* @Desc: 导出为json
*/
json (name, withConfig = true) {
let nodeData = this.mindMap.command.getCopyData()
let data = {}
if (withConfig) {
data = {
layout: this.mindMap.getLayout(),
root: nodeData,
theme: {
template: this.mindMap.getTheme(),
config: this.mindMap.getCustomThemeConfig()
},
view: this.mindMap.view.getTransformData()
}
} else {
data = nodeData
}
let data = this.mindMap.getData(withConfig)
let str = JSON.stringify(data)
let blob = new Blob([str])
return URL.createObjectURL(blob)

View File

@@ -694,7 +694,7 @@ class Node {
if (this.mindMap.opt.readonly) {
return
}
e.stopPropagation()
e && e.stopPropagation()
if (this.nodeData.data.isActive) {
return
}
@@ -769,6 +769,12 @@ class Node {
}
}))
}
// 手动插入的节点立即获得焦点并且开启编辑模式
if (this.nodeData.inserting) {
delete this.nodeData.inserting
this.active()
this.mindMap.emit('node_dblclick', this)
}
}
/**
@@ -828,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) {
@@ -845,7 +851,7 @@ class Node {
* @Date: 2021-04-10 22:01:53
* @Desc: 连线
*/
renderLine() {
renderLine(deep = false) {
if (this.nodeData.data.expand === false) {
return
}
@@ -866,10 +872,12 @@ class Node {
this.renderer.layout.renderLine(this, this._lines, (line, node) => {
// 添加样式
this.styleLine(line, node)
})
// 和父级的连线也需要更新
if (this.parent) {
this.parent.renderLine()
}, this.style.getStyle('lineStyle', true))
// 级的连线也需要更新
if (deep && this.children && this.children.length > 0) {
this.children.forEach((item) => {
item.renderLine(deep)
})
}
}

View File

@@ -5,7 +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 { shapeList } from './Shape'
import { lineStyleProps } from './themes/default'
// 布局列表
const layouts = {
@@ -143,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)
@@ -409,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
@@ -434,6 +439,7 @@ class Render {
}
let text = node.isRoot ? '二级节点' : '分支主题'
node.nodeData.children.push({
"inserting": true,
"data": {
"text": text,
"expand": true
@@ -731,6 +737,10 @@ class Render {
}
}
this.setNodeDataRender(node, data)
// 更新了连线的样式
if (lineStyleProps.includes(prop)) {
(node.parent || node).renderLine(true)
}
}
/**
@@ -781,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()
}
/**
@@ -793,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()
}
/**

View File

@@ -146,7 +146,81 @@ class LogicalStructure extends Base {
* @Date: 2021-04-11 14:42:48
* @Desc: 绘制连线,连接该节点到其子节点
*/
renderLine(node, lines, style) {
renderLine(node, lines, style, lineStyle) {
if (lineStyle === 'curve') {
this.renderLineCurve(node, lines, style)
} else if (lineStyle === 'direct') {
this.renderLineDirect(node, lines, style)
} else {
this.renderLineStraight(node, lines, style)
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-09-30 14:17:30
* @Desc: 直线风格连线
*/
renderLineStraight(node, lines, style) {
if (node.children.length <= 0) {
return [];
}
let {
left,
top,
width,
height,
expandBtnSize
} = node
let marginX = this.getMarginX(node.layerIndex + 1)
let s1 = (marginX - expandBtnSize) * 0.6
node.children.forEach((item, index) => {
let x1 = node.layerIndex === 0 ? left + width : left + width + expandBtnSize
let y1 = top + height / 2
let x2 = item.left
let y2 = item.top + item.height / 2
let path = `M ${x1},${y1} L ${x1 + s1},${y1} L ${x1 + s1},${y2} L ${x2},${y2}`
lines[index].plot(path)
style && style(lines[index], item)
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-09-30 14:34:41
* @Desc: 直连风格
*/
renderLineDirect(node, lines, style) {
if (node.children.length <= 0) {
return [];
}
let {
left,
top,
width,
height,
expandBtnSize
} = node
node.children.forEach((item, index) => {
let x1 = node.layerIndex === 0 ? left + width / 2 : left + width + expandBtnSize
let y1 = top + height / 2
let x2 = item.left
let y2 = item.top + item.height / 2
let path = `M ${x1},${y1} L ${x2},${y2}`
lines[index].plot(path)
style && style(lines[index], item)
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-09-30 14:17:43
* @Desc: 曲线风格连线
*/
renderLineCurve(node, lines, style) {
if (node.children.length <= 0) {
return [];
}

View File

@@ -182,7 +182,89 @@ class MindMap extends Base {
* @Date: 2021-04-11 14:42:48
* @Desc: 绘制连线,连接该节点到其子节点
*/
renderLine(node, lines, style) {
renderLine(node, lines, style, lineStyle) {
if (lineStyle === 'curve') {
this.renderLineCurve(node, lines, style)
} else if (lineStyle === 'direct') {
this.renderLineDirect(node, lines, style)
} else {
this.renderLineStraight(node, lines, style)
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-09-30 14:10:47
* @Desc: 直线风格连线
*/
renderLineStraight(node, lines, style) {
if (node.children.length <= 0) {
return [];
}
let {
left,
top,
width,
height,
expandBtnSize
} = node
let marginX = this.getMarginX(node.layerIndex + 1)
let s1 = (marginX - expandBtnSize) * 0.6
node.children.forEach((item, index) => {
let x1 = 0
let _s = 0
if (item.dir === 'left') {
_s = -s1
x1 = node.layerIndex === 0 ? left : left - expandBtnSize
} else {
_s = s1
x1 = node.layerIndex === 0 ? left + width : left + width + expandBtnSize
}
let y1 = top + height / 2
let x2 = item.dir === 'left' ? item.left + item.width : item.left
let y2 = item.top + item.height / 2
let path = path = `M ${x1},${y1} L ${x1 + _s},${y1} L ${x1 + _s},${y2} L ${x2},${y2}`
lines[index].plot(path)
style && style(lines[index], item)
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-09-30 14:34:41
* @Desc: 直连风格
*/
renderLineDirect(node, lines, style) {
if (node.children.length <= 0) {
return [];
}
let {
left,
top,
width,
height,
expandBtnSize
} = node
node.children.forEach((item, index) => {
let x1 = node.layerIndex === 0 ? left + width / 2 : item.dir === 'left' ? left - expandBtnSize : left + width + expandBtnSize
let y1 = top + height / 2
let x2 = item.dir === 'left' ? item.left + item.width : item.left
let y2 = item.top + item.height / 2
let path = `M ${x1},${y1} L ${x2},${y2}`
lines[index].plot(path)
style && style(lines[index], item)
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-09-30 14:10:56
* @Desc: 曲线风格连线
*/
renderLineCurve(node, lines, style) {
if (node.children.length <= 0) {
return [];
}

View File

@@ -147,7 +147,48 @@ class OrganizationStructure extends Base {
* @Date: 2021-04-11 14:42:48
* @Desc: 绘制连线,连接该节点到其子节点
*/
renderLine(node, lines, style) {
renderLine(node, lines, style, lineStyle) {
if (lineStyle === 'direct') {
this.renderLineDirect(node, lines, style)
} else {
this.renderLineStraight(node, lines, style)
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-09-30 14:34:41
* @Desc: 直连风格
*/
renderLineDirect(node, lines, style) {
if (node.children.length <= 0) {
return [];
}
let {
left,
top,
width,
height,
} = node
let x1 = left + width / 2
let y1 = top + height
node.children.forEach((item, index) => {
let x2 = item.left + item.width / 2
let y2 = item.top
let path = `M ${x1},${y1} L ${x2},${y2}`
lines[index].plot(path)
style && style(lines[index], item)
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-09-30 14:39:07
* @Desc: 直线风格连线
*/
renderLineStraight(node, lines, style) {
if (node.children.length <= 0) {
return [];
}

View File

@@ -19,6 +19,8 @@ export default {
lineColor: '#549688',
// 连线样式
lineDasharray: 'none',
// 连线风格
lineStyle: 'straight',// 针对logicalStructure、mindMap两种结构。曲线curve、直线straight、直连direct
// 概要连线的粗细
generalizationLineWidth: 1,
// 概要连线的颜色
@@ -127,4 +129,6 @@ export default {
// 支持激活样式的属性
// 简单来说,会改变节点大小的都不支持在激活时设置,为了性能考虑,节点切换激活态时不会重新计算节点大小
export const supportActiveStyle = ['fillColor', 'color', 'fontWeight', 'fontStyle', 'borderColor', 'borderWidth', 'borderDasharray', 'borderRadius', 'textDecoration']
export const supportActiveStyle = ['fillColor', 'color', 'fontWeight', 'fontStyle', 'borderColor', 'borderWidth', 'borderDasharray', 'borderRadius', 'textDecoration']
export const lineStyleProps = ['lineColor', 'lineDasharray', 'lineWidth']

BIN
web/src/.DS_Store vendored

Binary file not shown.

View File

@@ -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) {

Binary file not shown.

Binary file not shown.

View File

@@ -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">&#xe63e;</span>
<div class="name">导出</div>
<div class="code-name">&amp;#xe63e;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe657;</span>
<div class="name">另存为</div>
<div class="code-name">&amp;#xe657;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe642;</span>
<div class="name">export</div>
<div class="code-name">&amp;#xe642;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xebdf;</span>
<div class="name">打开</div>
<div class="code-name">&amp;#xebdf;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe64e;</span>
<div class="name">新建</div>
<div class="code-name">&amp;#xe64e;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe601;</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>

View File

@@ -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

View File

@@ -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": "剪切",

View File

@@ -130,6 +130,22 @@ export const borderRadiusList = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// 线宽
export const lineWidthList = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// 连线风格
export const lineStyleList = [
{
name: '直线',
value: 'straight'
},
{
name: '曲线',
value: 'curve'
},
{
name: '直连',
value: 'direct'
}
]
// 图片重复方式
export const backgroundRepeatList = [
{

View File

@@ -94,6 +94,30 @@
</el-select>
</div>
</div>
<div class="row">
<div class="rowItem">
<span class="name">风格</span>
<el-select
size="mini"
style="width: 80px"
v-model="style.lineStyle"
placeholder=""
@change="
(value) => {
update('lineStyle', value);
}
"
>
<el-option
v-for="item in lineStyleList"
:key="item.value"
:label="item.name"
:value="item.value"
>
</el-option>
</el-select>
</div>
</div>
<!-- 概要连线 -->
<div class="title noTop">概要的连线</div>
<div class="row">
@@ -267,6 +291,7 @@ import Sidebar from "./Sidebar";
import Color from "./Color";
import {
lineWidthList,
lineStyleList,
backgroundRepeatList
} from "@/config";
import ImgUpload from "@/components/ImgUpload";
@@ -296,6 +321,7 @@ export default {
data() {
return {
lineWidthList,
lineStyleList,
backgroundRepeatList,
activeTab: "color",
marginActiveTab: "second",
@@ -303,6 +329,7 @@ export default {
backgroundColor: "",
lineColor: "",
lineWidth: "",
lineStyle: "",
generalizationLineWidth: "",
generalizationLineColor: "",
paddingX: 0,
@@ -336,6 +363,7 @@ export default {
[
"backgroundColor",
"lineWidth",
"lineStyle",
"lineColor",
"generalizationLineWidth",
"generalizationLineColor",

View File

@@ -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>

View File

@@ -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)
},
/**

View File

@@ -82,6 +82,7 @@ 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)

View File

@@ -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,10 +206,13 @@ export default {
activeNodes: [],
backEnd: false,
forwardEnd: true,
readonly: false
readonly: false,
isFullDataFile: false,
timer: null,
};
},
computed: {
...mapState(['isHandleLocalFile']),
hasRoot() {
return this.activeNodes.findIndex((node) => {
return node.isRoot;
@@ -204,6 +224,13 @@ export default {
}) !== -1;;
}
},
watch: {
isHandleLocalFile(val) {
if (!val) {
Notification.closeAll();
}
}
},
created() {
this.$bus.$on("mode_change", (mode) => {
this.readonly = mode === 'readonly'
@@ -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>

View File

@@ -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: {