mirror of
https://github.com/wanglin2/mind-map.git
synced 2026-02-17 22:08:25 +08:00
新增导出为json;优化一些细节
This commit is contained in:
@@ -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实例
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
BIN
web/src/.DS_Store
vendored
Binary file not shown.
@@ -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) {
|
||||
|
||||
BIN
web/src/assets/.DS_Store
vendored
BIN
web/src/assets/.DS_Store
vendored
Binary file not shown.
@@ -54,6 +54,12 @@
|
||||
<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">&#xe6a3;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></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>
|
||||
|
||||
@@ -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
@@ -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": "后退-实",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -233,6 +233,11 @@ export const shortcutKeyList = [
|
||||
icon: 'iconhuanhang',
|
||||
name: '文本换行',
|
||||
value: 'Shift + Enter'
|
||||
},
|
||||
{
|
||||
icon: 'iconhoutui-shi',
|
||||
name: '回退',
|
||||
value: 'Ctrl + z'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
107
web/src/pages/Edit/components/Import.vue
Normal file
107
web/src/pages/Edit/components/Import.vue
Normal 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>
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user