483 lines
13 KiB
Vue
483 lines
13 KiB
Vue
<template>
|
|
<div class="toolbarContainer">
|
|
<div class="toolbar">
|
|
<!-- 节点操作 -->
|
|
<div class="toolbarBlock">
|
|
<div
|
|
class="toolbarBtn"
|
|
:class="{
|
|
disabled: readonly || backEnd,
|
|
}"
|
|
@click="$bus.$emit('execCommand', 'BACK')"
|
|
>
|
|
<span class="icon iconfont iconhoutui-shi"></span>
|
|
<span class="text">回退</span>
|
|
</div>
|
|
<div
|
|
class="toolbarBtn"
|
|
:class="{
|
|
disabled: readonly || forwardEnd,
|
|
}"
|
|
@click="$bus.$emit('execCommand', 'FORWARD')"
|
|
>
|
|
<span class="icon iconfont iconqianjin1"></span>
|
|
<span class="text">前进</span>
|
|
</div>
|
|
<div
|
|
class="toolbarBtn"
|
|
:class="{
|
|
disabled: activeNodes.length <= 0 || hasRoot || hasGeneralization,
|
|
}"
|
|
@click="$bus.$emit('execCommand', 'INSERT_NODE')"
|
|
>
|
|
<span class="icon iconfont iconjiedian"></span>
|
|
<span class="text">插入同级节点</span>
|
|
</div>
|
|
<div
|
|
class="toolbarBtn"
|
|
:class="{
|
|
disabled: activeNodes.length <= 0 || hasGeneralization,
|
|
}"
|
|
@click="$bus.$emit('execCommand', 'INSERT_CHILD_NODE')"
|
|
>
|
|
<span class="icon iconfont icontianjiazijiedian"></span>
|
|
<span class="text">插入子节点</span>
|
|
</div>
|
|
<div
|
|
class="toolbarBtn"
|
|
:class="{
|
|
disabled: activeNodes.length <= 0,
|
|
}"
|
|
@click="$bus.$emit('execCommand', 'REMOVE_NODE')"
|
|
>
|
|
<span class="icon iconfont iconshanchu"></span>
|
|
<span class="text">删除节点</span>
|
|
</div>
|
|
<div
|
|
class="toolbarBtn"
|
|
:class="{
|
|
disabled: activeNodes.length <= 0,
|
|
}"
|
|
@click="$bus.$emit('showNodeImage')"
|
|
>
|
|
<span class="icon iconfont iconimage"></span>
|
|
<span class="text">图片</span>
|
|
</div>
|
|
<div
|
|
class="toolbarBtn"
|
|
:class="{
|
|
disabled: activeNodes.length <= 0,
|
|
}"
|
|
@click="$bus.$emit('showNodeIcon')"
|
|
>
|
|
<span class="icon iconfont iconxiaolian"></span>
|
|
<span class="text">图标</span>
|
|
</div>
|
|
<div
|
|
class="toolbarBtn"
|
|
:class="{
|
|
disabled: activeNodes.length <= 0,
|
|
}"
|
|
@click="$bus.$emit('showNodeLink')"
|
|
>
|
|
<span class="icon iconfont iconchaolianjie"></span>
|
|
<span class="text">超链接</span>
|
|
</div>
|
|
<div
|
|
class="toolbarBtn"
|
|
:class="{
|
|
disabled: activeNodes.length <= 0,
|
|
}"
|
|
@click="$bus.$emit('showNodeNote')"
|
|
>
|
|
<span class="icon iconfont iconflow-Mark"></span>
|
|
<span class="text">备注</span>
|
|
</div>
|
|
<div
|
|
class="toolbarBtn"
|
|
:class="{
|
|
disabled: activeNodes.length <= 0,
|
|
}"
|
|
@click="$bus.$emit('showNodeTag')"
|
|
>
|
|
<span class="icon iconfont iconbiaoqian"></span>
|
|
<span class="text">标签</span>
|
|
</div>
|
|
<div
|
|
class="toolbarBtn"
|
|
:class="{
|
|
disabled: activeNodes.length <= 0 || hasRoot || hasGeneralization,
|
|
}"
|
|
@click="$bus.$emit('execCommand', 'ADD_GENERALIZATION')"
|
|
>
|
|
<span class="icon iconfont icongaikuozonglan"></span>
|
|
<span class="text">概要</span>
|
|
</div>
|
|
</div>
|
|
<!-- 通用操作 -->
|
|
<div class="toolbarBlock">
|
|
<div class="toolbarBtn" @click="$bus.$emit('showOutline')">
|
|
<span class="icon iconfont iconfuhao-dagangshu"></span>
|
|
<span class="text">显示大纲</span>
|
|
</div>
|
|
<div class="toolbarBtn" @click="$bus.$emit('showBaseStyle')">
|
|
<span class="icon iconfont iconyangshi"></span>
|
|
<span class="text">基础样式</span>
|
|
</div>
|
|
<div class="toolbarBtn" @click="$bus.$emit('showTheme')">
|
|
<span class="icon iconfont iconjingzi"></span>
|
|
<span class="text">主题</span>
|
|
</div>
|
|
<div class="toolbarBtn" @click="$bus.$emit('showStructure')">
|
|
<span class="icon iconfont iconjiegou"></span>
|
|
<span class="text">结构</span>
|
|
</div>
|
|
</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 iconexport"></span>
|
|
<span class="text">导出</span>
|
|
</div>
|
|
<div class="toolbarBtn" @click="$bus.$emit('showShortcutKey')">
|
|
<span class="icon iconfont iconjianpan"></span>
|
|
<span class="text">快捷键</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<NodeImage></NodeImage>
|
|
<NodeHyperlink></NodeHyperlink>
|
|
<NodeIcon></NodeIcon>
|
|
<NodeNote></NodeNote>
|
|
<NodeTag></NodeTag>
|
|
<Export></Export>
|
|
<Import></Import>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import NodeImage from "./NodeImage";
|
|
import NodeHyperlink from "./NodeHyperlink";
|
|
import NodeIcon from "./NodeIcon";
|
|
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: {
|
|
NodeImage,
|
|
NodeHyperlink,
|
|
NodeIcon,
|
|
NodeNote,
|
|
NodeTag,
|
|
Export,
|
|
Import
|
|
},
|
|
data() {
|
|
return {
|
|
activeNodes: [],
|
|
backEnd: false,
|
|
forwardEnd: true,
|
|
readonly: false,
|
|
isFullDataFile: false,
|
|
timer: null,
|
|
};
|
|
},
|
|
computed: {
|
|
...mapState(['isHandleLocalFile']),
|
|
hasRoot() {
|
|
return this.activeNodes.findIndex((node) => {
|
|
return node.isRoot;
|
|
}) !== -1;
|
|
},
|
|
hasGeneralization() {
|
|
return this.activeNodes.findIndex((node) => {
|
|
return node.isGeneralization;
|
|
}) !== -1;;
|
|
}
|
|
},
|
|
watch: {
|
|
isHandleLocalFile(val) {
|
|
if (!val) {
|
|
Notification.closeAll();
|
|
}
|
|
}
|
|
},
|
|
created() {
|
|
this.$bus.$on("mode_change", (mode) => {
|
|
this.readonly = mode === 'readonly'
|
|
});
|
|
this.$bus.$on("node_active", (...args) => {
|
|
this.activeNodes = args[1];
|
|
});
|
|
this.$bus.$on("back_forward", (index, len) => {
|
|
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>
|
|
|
|
<style lang="less" scoped>
|
|
.toolbarContainer {
|
|
.toolbar {
|
|
position: fixed;
|
|
left: 0;
|
|
top: 0;
|
|
display: flex;
|
|
padding: 0 20px;
|
|
padding-top: 20px;
|
|
font-size: 12px;
|
|
font-family: PingFangSC-Regular, PingFang SC;
|
|
font-weight: 400;
|
|
color: rgba(26, 26, 26, 0.8);
|
|
z-index: 2;
|
|
|
|
.toolbarBlock {
|
|
display: flex;
|
|
background-color: #fff;
|
|
padding: 10px 20px;
|
|
border-radius: 6px;
|
|
box-shadow: 0 2px 16px 0 rgba(0, 0, 0, 0.06);
|
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
margin-right: 20px;
|
|
|
|
&:last-of-type {
|
|
margin-right: 0;
|
|
}
|
|
}
|
|
|
|
.toolbarBtn {
|
|
display: flex;
|
|
justify-content: center;
|
|
flex-direction: column;
|
|
cursor: pointer;
|
|
margin-right: 20px;
|
|
|
|
&:last-of-type {
|
|
margin-right: 0;
|
|
}
|
|
|
|
&:hover {
|
|
&:not(.disabled) {
|
|
.icon {
|
|
background: #f5f5f5;
|
|
}
|
|
}
|
|
}
|
|
|
|
&.disabled {
|
|
color: #bcbcbc;
|
|
cursor: not-allowed;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.icon {
|
|
display: flex;
|
|
height: 26px;
|
|
background: #fff;
|
|
border-radius: 4px;
|
|
border: 1px solid #e9e9e9;
|
|
justify-content: center;
|
|
flex-direction: column;
|
|
text-align: center;
|
|
padding: 0 5px;
|
|
}
|
|
|
|
.text {
|
|
margin-top: 3px;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|