diff --git a/README.md b/README.md
index 7f8fe810..c10c720e 100644
--- a/README.md
+++ b/README.md
@@ -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`
@@ -91,7 +101,7 @@ npm run build
# 安装
-> 当然仓库版本:0.2.8,当前npm版本:0.2.8
+> 当然仓库版本:0.2.9,当前npm版本:0.2.9
```bash
npm i simple-mind-map
@@ -303,8 +313,6 @@ v0.1.7+。切换模式为只读或编辑。
| 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)
动态设置思维导图数据,纯节点数据
@@ -319,6 +327,14 @@ 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)
导出
diff --git a/simple-mind-map/index.js b/simple-mind-map/index.js
index b8738a54..6ed87c18 100644
--- a/simple-mind-map/index.js
+++ b/simple-mind-map/index.js
@@ -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
diff --git a/simple-mind-map/package.json b/simple-mind-map/package.json
index 4fa34939..960626ea 100644
--- a/simple-mind-map/package.json
+++ b/simple-mind-map/package.json
@@ -1,6 +1,6 @@
{
"name": "simple-mind-map",
- "version": "0.2.8",
+ "version": "0.2.9",
"description": "一个简单的web在线思维导图",
"authors": [
{
diff --git a/simple-mind-map/src/Export.js b/simple-mind-map/src/Export.js
index f3b25a96..13baac0b 100644
--- a/simple-mind-map/src/Export.js
+++ b/simple-mind-map/src/Export.js
@@ -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)
diff --git a/web/src/.DS_Store b/web/src/.DS_Store
index b6ab7531..568d022a 100644
Binary files a/web/src/.DS_Store and b/web/src/.DS_Store differ
diff --git a/web/src/api/index.js b/web/src/api/index.js
index 33b52155..fc3cea4d 100644
--- a/web/src/api/index.js
+++ b/web/src/api/index.js
@@ -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) {
diff --git a/web/src/assets/.DS_Store b/web/src/assets/.DS_Store
index a1654cd4..96b41531 100644
Binary files a/web/src/assets/.DS_Store and b/web/src/assets/.DS_Store differ
diff --git a/web/src/assets/icon-font/.DS_Store b/web/src/assets/icon-font/.DS_Store
index 3f691a8f..5008ddfc 100644
Binary files a/web/src/assets/icon-font/.DS_Store and b/web/src/assets/icon-font/.DS_Store differ
diff --git a/web/src/assets/icon-font/demo_index.html b/web/src/assets/icon-font/demo_index.html
index 30c6b028..830536f3 100644
--- a/web/src/assets/icon-font/demo_index.html
+++ b/web/src/assets/icon-font/demo_index.html
@@ -3,8 +3,8 @@
iconfont Demo
-
-
+
+
@@ -54,6 +54,36 @@
+ -
+
+
导出
+ 
+
+
+ -
+
+
另存为
+ 
+
+
+ -
+
+
export
+ 
+
+
+ -
+
+
打开
+ 
+
+
+ -
+
+
新建
+ 
+
+
-
剪切
@@ -300,9 +330,9 @@
@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');
}
第二步:定义使用 iconfont 的样式
@@ -328,6 +358,51 @@
+ -
+
+
+ 导出
+
+ .icondaochu1
+
+
+
+ -
+
+
+ 另存为
+
+ .iconlingcunwei
+
+
+
+ -
+
+
+ export
+
+ .iconexport
+
+
+
+ -
+
+
+ 打开
+
+ .icondakai
+
+
+
+ -
+
+
+ 新建
+
+ .iconxinjian
+
+
+
-
@@ -697,6 +772,46 @@
+ -
+
+
导出
+ #icondaochu1
+
+
+ -
+
+
另存为
+ #iconlingcunwei
+
+
+ -
+
+
export
+ #iconexport
+
+
+ -
+
+
打开
+ #icondakai
+
+
+ -
+
+
新建
+ #iconxinjian
+
+
-
+
+
+ 新建
+
+
+
+ 打开
+
+
+
+ 另存为
+
导入
-
+
导出
@@ -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('你的浏览器可能不支持哦');
+ }
+ },
+ }
};
diff --git a/web/src/store.js b/web/src/store.js
index 2642bbe2..49db3a16 100644
--- a/web/src/store.js
+++ b/web/src/store.js
@@ -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: {