新增导出为json;优化一些细节

This commit is contained in:
wanglin
2021-08-04 07:26:01 +08:00
parent 09d1f82021
commit 48172bc035
22 changed files with 326 additions and 23 deletions

View File

@@ -218,6 +218,12 @@ const mindMap = new MindMap({
| 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)里获取到) |
#### setData(data)
动态设置思维导图数据
`data`:思维导图结构数据
#### export(type, isDownload)
@@ -352,6 +358,9 @@ mindMap.keyCommand.addShortcut('Control+Enter', () => {})
获取渲染树数据副本
#### clearHistory()
清空历史堆栈数据
## view实例

View File

@@ -299,6 +299,18 @@ class MindMap {
this.command.exec(...args)
}
/**
* @Author: 王林
* @Date: 2021-08-03 22:58:12
* @Desc: 动态设置思维导图数据
*/
setData(data) {
this.execCommand('CLEAR_ACTIVE_NODE')
this.command.clearHistory()
this.renderer.renderTree = data
this.reRender()
}
/**
* @Author: 王林
* @Date: 2021-07-01 22:06:38

View File

@@ -17,6 +17,30 @@ class Command {
this.commands = {}
this.history = []
this.activeHistoryIndex = 0
// 注册快捷键
this.registerShortcutKeys()
}
/**
* @Author: 王林
* @Date: 2021-08-03 23:06:55
* @Desc: 清空历史数据
*/
clearHistory() {
this.history = []
this.activeHistoryIndex = 0
this.mindMap.emit('back_forward', 0, 0)
}
/**
* @Author: 王林
* @Date: 2021-08-02 23:23:19
* @Desc: 注册快捷键
*/
registerShortcutKeys() {
this.mindMap.keyCommand.addShortcut('Control+z', () => {
this.mindMap.execCommand('BACK')
})
}
/**

View File

@@ -200,6 +200,27 @@ class Export {
})
return URL.createObjectURL(blob)
}
/**
* @Author: 王林
* @Date: 2021-08-03 22:19:17
* @Desc: 导出为json
*/
json () {
let data = this.mindMap.command.getCopyData()
let str = JSON.stringify(data)
let blob = new Blob([str])
return URL.createObjectURL(blob)
}
/**
* @Author: 王林
* @Date: 2021-08-03 22:24:24
* @Desc: 专有文件其实就是json文件
*/
smm () {
return this.json();
}
}
export default Export

View File

@@ -44,6 +44,8 @@ class Render {
this.activeNodeList = []
// 根节点
this.root = null
// 文本编辑框需要再bindEvent之前实例化否则单击事件只能触发隐藏文本编辑框而无法保存文本修改
this.textEdit = new TextEdit(this)
// 布局
this.setLayout()
// 绑定事件
@@ -52,8 +54,6 @@ class Render {
this.registerCommands()
// 注册快捷键
this.registerShortcutKeys()
// 文本编辑框
this.textEdit = new TextEdit(this)
}
/**
@@ -122,8 +122,8 @@ class Render {
this.setNodeActive = this.setNodeActive.bind(this)
this.mindMap.command.add('SET_NODE_ACTIVE', this.setNodeActive)
// 清除所有激活节点
this.clearActive = this.clearActive.bind(this)
this.mindMap.command.add('CLEAR_ACTIVE_NODE', this.clearActive)
this.clearAllActive = this.clearAllActive.bind(this)
this.mindMap.command.add('CLEAR_ACTIVE_NODE', this.clearAllActive)
// 切换节点是否展开
this.setNodeExpand = this.setNodeExpand.bind(this)
this.mindMap.command.add('SET_NODE_EXPAND', this.setNodeExpand)
@@ -164,15 +164,16 @@ class Render {
registerShortcutKeys() {
// 插入下级节点
this.mindMap.keyCommand.addShortcut('Tab', () => {
this.insertChildNode()
this.mindMap.execCommand('INSERT_CHILD_NODE')
})
// 插入同级节点
this.mindMap.keyCommand.addShortcut('Enter', () => {
let insertNodeWrap = () => {
if (this.textEdit.showTextEdit) {
return
}
this.insertNode()
})
this.mindMap.execCommand('INSERT_NODE')
}
this.mindMap.keyCommand.addShortcut('Enter', insertNodeWrap)
// 展开/收起节点
this.mindMap.keyCommand.addShortcut('/', () => {
this.activeNodeList.forEach((node) => {
@@ -183,12 +184,18 @@ class Render {
})
})
// 删除节点
this.mindMap.keyCommand.addShortcut('Del|Backspace', this.removeNode)
let removeNodeWrap = () => {
this.mindMap.execCommand('REMOVE_NODE')
}
this.mindMap.keyCommand.addShortcut('Del|Backspace', removeNodeWrap)
// 节点编辑时某些快捷键会存在冲突,需要暂时去除
this.mindMap.on('before_show_text_edit', () => {
this.mindMap.keyCommand.removeShortcut('Del|Backspace')
this.mindMap.keyCommand.removeShortcut('Enter', insertNodeWrap)
})
this.mindMap.on('hide_text_edit', () => {
this.mindMap.keyCommand.addShortcut('Del|Backspace', this.removeNode)
this.mindMap.keyCommand.addShortcut('Del|Backspace', removeNodeWrap)
this.mindMap.keyCommand.addShortcut('Enter', insertNodeWrap)
})
}
@@ -220,6 +227,16 @@ class Render {
this.activeNodeList = []
}
/**
* @Author: 王林
* @Date: 2021-08-03 23:14:34
* @Desc: 清除当前所有激活节点,并会触发事件
*/
clearAllActive() {
this.clearActive()
this.mindMap.emit('node_active', null, [])
}
/**
* @Author: 王林
* @Date: 2021-07-11 10:54:00

View File

@@ -53,7 +53,7 @@ export default class TextEdit {
})
// 注册编辑快捷键
this.mindMap.keyCommand.addShortcut('F2', () => {
if (this.renderer.activeNodeList.length <= 0){
if (this.renderer.activeNodeList.length <= 0) {
return
}
this.show(this.renderer.activeNodeList[0])
@@ -90,6 +90,21 @@ export default class TextEdit {
this.textEditNode.style.top = rect.top + 'px'
this.textEditNode.style.display = 'block'
this.showTextEdit = true
// 选中文本
this.selectNodeText()
}
/**
* @Author: 王林
* @Date: 2021-08-02 23:13:50
* @Desc: 选中文本
*/
selectNodeText() {
let selection = window.getSelection()
let range = document.createRange()
range.selectNodeContents(this.textEditNode)
selection.removeAllRanges()
selection.addRange(range)
}
/**

View File

@@ -66,4 +66,4 @@ export const keyMap = map
export const isKey = (e, key) => {
let code = typeof e === 'object' ? e.keyCode : e
return map[key] === code
}
}

BIN
web/src/.DS_Store vendored

Binary file not shown.

View File

@@ -3,6 +3,23 @@ import { simpleDeepClone } from 'simple-mind-map/src/utils/index'
const SIMPLE_MIND_MAP_DATA = 'SIMPLE_MIND_MAP_DATA'
/**
* @Author: 王林
* @Date: 2021-08-02 22:36:48
* @Desc: 克隆思维导图数据,去除激活状态
*/
const copyMindMapTreeData = (tree, root) => {
tree.data = simpleDeepClone(root.data)
tree.data.isActive = false
tree.children = []
if (root.children && root.children.length > 0) {
root.children.forEach((item, index) => {
tree.children[index] = copyMindMapTreeData({}, item)
})
}
return tree;
}
/**
* @Author: 王林
* @Date: 2021-08-01 10:10:49
@@ -29,7 +46,7 @@ export const getData = () => {
export const storeData = (data) => {
try {
let originData = getData()
originData.root = data
originData.root = copyMindMapTreeData({}, data)
let dataStr = JSON.stringify(originData)
localStorage.setItem(SIMPLE_MIND_MAP_DATA, dataStr)
} catch (error) {

Binary file not shown.

View File

@@ -54,6 +54,12 @@
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe6a3;</span>
<div class="name">导入</div>
<div class="code-name">&amp;#xe6a3;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe656;</span>
<div class="name">后退-实</div>
@@ -246,9 +252,9 @@
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1626099544460') format('woff2'),
url('iconfont.woff?t=1626099544460') format('woff'),
url('iconfont.ttf?t=1626099544460') format('truetype');
src: url('iconfont.woff2?t=1628001202194') format('woff2'),
url('iconfont.woff?t=1628001202194') format('woff'),
url('iconfont.ttf?t=1628001202194') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -274,6 +280,15 @@
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont icondaoru"></span>
<div class="name">
导入
</div>
<div class="code-name">.icondaoru
</div>
</li>
<li class="dib">
<span class="icon iconfont iconhoutui-shi"></span>
<div class="name">
@@ -562,6 +577,14 @@
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icondaoru"></use>
</svg>
<div class="name">导入</div>
<div class="code-name">#icondaoru</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconhoutui-shi"></use>

View File

@@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 2479351 */
src: url('iconfont.woff2?t=1626099544460') format('woff2'),
url('iconfont.woff?t=1626099544460') format('woff'),
url('iconfont.ttf?t=1626099544460') format('truetype');
src: url('iconfont.woff2?t=1628001202194') format('woff2'),
url('iconfont.woff?t=1628001202194') format('woff'),
url('iconfont.ttf?t=1628001202194') format('truetype');
}
.iconfont {
@@ -13,6 +13,10 @@
-moz-osx-font-smoothing: grayscale;
}
.icondaoru:before {
content: "\e6a3";
}
.iconhoutui-shi:before {
content: "\e656";
}

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,13 @@
"css_prefix_text": "icon",
"description": "思维导图",
"glyphs": [
{
"icon_id": "17606306",
"name": "导入",
"font_class": "daoru",
"unicode": "e6a3",
"unicode_decimal": 59043
},
{
"icon_id": "5110748",
"name": "后退-实",

View File

@@ -233,6 +233,11 @@ export const shortcutKeyList = [
icon: 'iconhuanhang',
name: '文本换行',
value: 'Shift + Enter'
},
{
icon: 'iconhoutui-shi',
name: '回退',
value: 'Ctrl + z'
}
]
},

View File

@@ -56,6 +56,7 @@ export default {
this.init();
this.$bus.$on("execCommand", this.execCommand);
this.$bus.$on("export", this.export);
this.$bus.$on("setData", this.setData);
},
methods: {
/**
@@ -79,6 +80,16 @@ export default {
});
},
/**
* @Author: 王林
* @Date: 2021-08-02 23:19:52
* @Desc: 手动保存
*/
manualSave() {
let data = this.mindMap.command.getCopyData();
storeData(data);
},
/**
* @Author: 王林
* @Date: 2021-04-10 15:01:01
@@ -93,6 +104,9 @@ export default {
theme: theme.template,
themeConfig: theme.config,
});
this.mindMap.keyCommand.addShortcut("Control+s", () => {
this.manualSave();
});
// 转发事件
[
"node_active",
@@ -112,6 +126,16 @@ export default {
this.bindSaveEvent();
},
/**
* @Author: 王林
* @Date: 2021-08-03 23:01:13
* @Desc: 动态设置思维导图数据
*/
setData(data) {
this.mindMap.setData(data)
this.manualSave()
},
/**
* @Author: 王林
* @Date: 2021-05-05 13:32:11

View File

@@ -11,9 +11,12 @@
<el-input style="width: 300px" v-model="fileName" size="mini"></el-input>
</div>
<el-radio-group v-model="exportType">
<el-radio label="png">图片文件PNG</el-radio>
<el-radio label="svg">svg文件SVG</el-radio>
<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-group>
<div class="tip">tips.smm文件可用于导入</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="cancel"> </el-button>
@@ -33,7 +36,7 @@ export default {
data() {
return {
dialogVisible: false,
exportType: "png",
exportType: "smm",
fileName: '思维导图'
};
},
@@ -59,6 +62,10 @@ export default {
*/
confirm() {
this.$bus.$emit("export", this.exportType);
this.$notify.info({
title: '消息',
message: '如果没有触发下载,请检查是否被浏览器拦截了'
});
this.cancel();
},
},
@@ -74,5 +81,9 @@ export default {
margin-right: 10px;
}
}
.tip {
margin-top: 10px;
}
}
</style>

View File

@@ -0,0 +1,107 @@
<template>
<el-dialog class="nodeDialog" title="导入" :visible.sync="dialogVisible" width="500">
<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>
</el-upload>
<span slot="footer" class="dialog-footer">
<el-button @click="cancel"> </el-button>
<el-button type="primary" @click="confirm"> </el-button>
</span>
</el-dialog>
</template>
<script>
/**
* @Author: 王林
* @Date: 2021-06-24 22:53:54
* @Desc: 导入
*/
export default {
name: "Import",
data() {
return {
dialogVisible: false,
fileList: [],
};
},
watch: {
dialogVisible(val, oldVal) {
if (!val && oldVal) {
this.fileList = [];
}
},
},
created() {
this.$bus.$on("showImport", () => {
this.dialogVisible = true;
});
},
methods: {
/**
* @Author: 王林
* @Date: 2021-08-03 22:48:42
* @Desc: 文件选择
*/
onChange(file) {
let reg = /\.smm$/;
if (!reg.test(file.name)) {
this.$message.error("请选择.smm文件");
this.fileList = [];
} else {
this.fileList.push(file)
}
},
/**
* @Author: 王林
* @Date: 2021-08-03 22:48:47
* @Desc: 数量超出限制
*/
onExceed() {
this.$message.error("最多只能选择一个文件");
},
/**
* @Author: 王林
* @Date: 2021-06-22 22:08:11
* @Desc: 取消
*/
cancel() {
this.dialogVisible = false;
},
/**
* @Author: 王林
* @Date: 2021-06-06 22:28:20
* @Desc: 确定
*/
confirm() {
if (this.fileList.length <= 0) {
return this.$message.error("请选择要导入的文件");
}
let file = this.fileList[0];
let fileReader = new FileReader()
fileReader.readAsText(file.raw)
fileReader.onload = (evt) => {
try {
let data = JSON.parse(evt.target.result)
if (typeof data !== 'object') {
throw new Error('文件内容有误')
}
this.$bus.$emit('setData', data)
this.$message.success("导入成功");
} catch (error) {
console.log(error)
this.$message.error("文件解析失败");
}
}
this.cancel();
},
},
};
</script>
<style lang="less" scoped>
.nodeDialog {}
</style>

View File

@@ -125,6 +125,10 @@
</div>
<!-- 导出 -->
<div class="toolbarBlock">
<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="text">导出</span>
@@ -141,6 +145,7 @@
<NodeNote></NodeNote>
<NodeTag></NodeTag>
<Export></Export>
<Import></Import>
</div>
</template>
@@ -151,6 +156,7 @@ import NodeIcon from "./NodeIcon";
import NodeNote from "./NodeNote";
import NodeTag from "./NodeTag";
import Export from "./Export";
import Import from './Import';
/**
* @Author: 王林
@@ -166,6 +172,7 @@ export default {
NodeNote,
NodeTag,
Export,
Import
},
data() {
return {