427 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="toolbarContainer" :class="{ isDark: isDark }">
<div class="toolbar" ref="toolbarRef">
<!-- 节点操作 -->
<div class="toolbarBlock">
<ToolbarNodeBtnList :list="horizontalList"></ToolbarNodeBtnList>
<!-- 更多 -->
<el-popover
v-model="popoverShow"
placement="bottom-end"
width="120"
trigger="hover"
v-if="showMoreBtn"
style="margin-left: 20px;"
>
<ToolbarNodeBtnList
dir="v"
:list="verticalList"
@click.native="popoverShow = false"
></ToolbarNodeBtnList>
<div slot="reference" class="toolbarBtn">
<span class="icon iconfont icongongshi"></span>
<span class="text">{{ $t('toolbar.more') }}</span>
</div>
</el-popover>
</div>
<!-- 导出 -->
<div class="toolbarBlock">
<div class="toolbarBtn" @click="createNewLocalFile">
<span class="icon iconfont iconxinjian"></span>
<span class="text">{{ $t('toolbar.newFile') }}</span>
</div>
<div class="toolbarBtn" @click="openLocalFile">
<span class="icon iconfont icondakai"></span>
<span class="text">{{ $t('toolbar.openFile') }}</span>
</div>
<div class="toolbarBtn" @click="saveLocalFile">
<span class="icon iconfont iconlingcunwei"></span>
<span class="text">{{ $t('toolbar.saveAs') }}</span>
</div>
<div class="toolbarBtn" @click="$bus.$emit('showImport')">
<span class="icon iconfont icondaoru"></span>
<span class="text">{{ $t('toolbar.import') }}</span>
</div>
<div class="toolbarBtn" @click="$bus.$emit('showExport')">
<span class="icon iconfont iconexport"></span>
<span class="text">{{ $t('toolbar.export') }}</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'
import ToolbarNodeBtnList from './ToolbarNodeBtnList.vue'
import { throttle } from 'simple-mind-map/src/utils/index'
/**
* @Author: 王林
* @Date: 2021-06-24 22:54:58
* @Desc: 工具栏
*/
let fileHandle = null
export default {
name: 'Toolbar',
components: {
NodeImage,
NodeHyperlink,
NodeIcon,
NodeNote,
NodeTag,
Export,
Import,
ToolbarNodeBtnList
},
data() {
return {
list: [
'back',
'forward',
'painter',
'siblingNode',
'childNode',
'deleteNode',
'image',
'icon',
'link',
'note',
'tag',
'summary',
'associativeLine',
'formula'
],
horizontalList: [],
verticalList: [],
showMoreBtn: true,
popoverShow: false
}
},
computed: {
...mapState(['isHandleLocalFile', 'isDark'])
},
watch: {
isHandleLocalFile(val) {
if (!val) {
Notification.closeAll()
}
}
},
created() {
this.$bus.$on('write_local_file', this.onWriteLocalFile)
},
mounted() {
this.computeToolbarShow()
this.computeToolbarShowThrottle = throttle(this.computeToolbarShow, 300)
window.addEventListener('resize', this.computeToolbarShowThrottle)
this.$bus.$on('lang_change', this.computeToolbarShowThrottle)
},
beforeDestroy() {
this.$bus.$off('write_local_file', this.onWriteLocalFile)
window.removeEventListener('resize', this.computeToolbarShowThrottle)
this.$bus.$off('lang_change', this.computeToolbarShowThrottle)
},
methods: {
// 计算工具按钮如何显示
computeToolbarShow() {
const windowWidth = window.innerWidth - 40
const all = [...this.list]
let index = 1
const loopCheck = () => {
if (index > all.length) return done()
this.horizontalList = all.slice(0, index)
this.$nextTick(() => {
const width = this.$refs.toolbarRef.getBoundingClientRect().width
if (width < windowWidth) {
index++
loopCheck()
} else if (index > 0 && width > windowWidth) {
index--
this.horizontalList = all.slice(0, index)
done()
}
})
}
const done = () => {
this.verticalList = all.slice(index)
this.showMoreBtn = this.verticalList.length > 0
}
loopCheck()
},
// 监听本地文件读写
onWriteLocalFile(content) {
clearTimeout(this.timer)
this.timer = setTimeout(() => {
this.writeLocalFile(content)
}, 1000)
},
// 打开本地文件
async openLocalFile() {
try {
let [_fileHandle] = await window.showOpenFilePicker({
types: [
{
description: '',
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)
if (error.toString().includes('aborted')) {
return
}
this.$message.warning(
'你的浏览器可能不支持建议使用最新版本的Chrome浏览器'
)
}
},
// 读取本地文件
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: true
})
}
fileReader.readAsText(file)
},
// 渲染读取的数据
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('文件打开失败')
}
},
// 写入本地文件
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()
},
// 创建本地文件
async createNewLocalFile() {
await this.createLocalFile(exampleData)
},
// 另存为
async saveLocalFile() {
let data = getData()
await this.createLocalFile(data)
},
// 创建本地文件
async createLocalFile(content) {
try {
let _fileHandle = await window.showSaveFilePicker({
types: [
{
description: '',
accept: { 'application/json': ['.smm'] }
}
],
suggestedName: '思维导图'
})
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)
if (error.toString().includes('aborted')) {
return
}
this.$message.warning(
'你的浏览器可能不支持建议使用最新版本的Chrome浏览器'
)
}
}
}
}
</script>
<style lang="less" scoped>
.toolbarContainer {
&.isDark {
.toolbar {
color: hsla(0, 0%, 100%, 0.9);
.toolbarBlock {
background-color: #262a2e;
}
.toolbarBtn {
.icon {
background: transparent;
border-color: transparent;
}
&:hover {
&:not(.disabled) {
.icon {
background: hsla(0, 0%, 100%, 0.05);
}
}
}
&.disabled {
color: #54595f;
}
}
}
}
.toolbar {
position: fixed;
left: 50%;
transform: translateX(-50%);
top: 20px;
width: max-content;
display: flex;
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;
flex-shrink: 0;
&: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;
}
}
}
&.active {
.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>