Demo:支持导入导出Excel

This commit is contained in:
街角小林 2024-09-11 17:41:57 +08:00
parent 9b7305de1e
commit f4d84aeb55
12 changed files with 122 additions and 87 deletions

View File

@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 2479351 */ font-family: "iconfont"; /* Project id 2479351 */
src: url('iconfont.woff2?t=1719815803051') format('woff2'), src: url('iconfont.woff2?t=1726022313538') format('woff2'),
url('iconfont.woff?t=1719815803051') format('woff'), url('iconfont.woff?t=1726022313538') format('woff'),
url('iconfont.ttf?t=1719815803051') format('truetype'); url('iconfont.ttf?t=1726022313538') format('truetype');
} }
.iconfont { .iconfont {
@ -13,6 +13,14 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.iconfile-excel:before {
content: "\e7b7";
}
.iconfreemind:before {
content: "\e97d";
}
.iconwaikuang:before { .iconwaikuang:before {
content: "\e640"; content: "\e640";
} }

View File

@ -499,8 +499,14 @@ export const downTypeList = [
{ {
name: 'FreeMind', name: 'FreeMind',
type: 'mm', type: 'mm',
icon: 'iconTXT', icon: 'iconfreemind',
desc: 'FreeMind software format' desc: 'FreeMind software format'
},
{
name: 'Excel',
type: 'xlsx',
icon: 'iconfile-excel',
desc: 'Excel software format'
} }
] ]

View File

@ -593,8 +593,14 @@ export const downTypeList = [
{ {
name: 'FreeMind', name: 'FreeMind',
type: 'mm', type: 'mm',
icon: 'iconTXT', icon: 'iconfreemind',
desc: 'FreeMind软件格式' desc: 'FreeMind软件格式'
},
{
name: 'Excel',
type: 'xlsx',
icon: 'iconfile-excel',
desc: 'Excel软件格式'
} }
] ]

View File

@ -157,8 +157,9 @@ export default {
import: { import: {
title: 'Import', title: 'Import',
selectFile: 'Select file', selectFile: 'Select file',
supportFile: 'Support .smm、.json、.xmind、.xlsx、.md、 .mm file', support: 'Support',
enableFileTip: 'Please select .smm、.json、.xmind、.xlsx、.md、 .mm file', file: 'file',
pleaseSelect: 'Please select',
maxFileNum: 'At most one file can be selected', maxFileNum: 'At most one file can be selected',
notSelectTip: 'Please select the file to import', notSelectTip: 'Please select the file to import',
fileContentError: 'The file content is incorrect', fileContentError: 'The file content is incorrect',

View File

@ -155,8 +155,9 @@ export default {
import: { import: {
title: '导入', title: '导入',
selectFile: '选取文件', selectFile: '选取文件',
supportFile: '支持.smm、.json、.xmind、.xlsx、.md、 .mm文件', support: '支持',
enableFileTip: '请选择.smm、.json、.xmind、.xlsx、.md、 .mm文件', file: '文件',
pleaseSelect: '请选择',
maxFileNum: '最多只能选择一个文件', maxFileNum: '最多只能选择一个文件',
notSelectTip: '请选择要导入的文件', notSelectTip: '请选择要导入的文件',
fileContentError: '文件内容有误', fileContentError: '文件内容有误',

View File

@ -80,7 +80,10 @@ import OuterFrame from 'simple-mind-map/src/plugins/OuterFrame.js'
// //
// import Numbers from 'simple-mind-map-plugin-numbers' // import Numbers from 'simple-mind-map-plugin-numbers'
// Freemind // Freemind
import Freemind from 'simple-mind-map-plugin-freemind' // import Freemind from 'simple-mind-map-plugin-freemind'
// Excel
// import Excel from 'simple-mind-map-plugin-excel'
// npm link simple-mind-map-plugin-excel simple-mind-map-plugin-freemind simple-mind-map-plugin-numbers simple-mind-map-plugin-notation simple-mind-map-plugin-handdrawnlikestyle simple-mind-map
import OutlineSidebar from './OutlineSidebar' import OutlineSidebar from './OutlineSidebar'
import Style from './Style' import Style from './Style'
import BaseStyle from './BaseStyle' import BaseStyle from './BaseStyle'
@ -137,7 +140,6 @@ MindMap.usePlugin(MiniMap)
.usePlugin(RainbowLines) .usePlugin(RainbowLines)
.usePlugin(Demonstrate) .usePlugin(Demonstrate)
.usePlugin(OuterFrame) .usePlugin(OuterFrame)
.usePlugin(Freemind)
// .usePlugin(Cooperate) // // .usePlugin(Cooperate) //
// //
@ -566,6 +568,16 @@ export default {
this.mindMap.addPlugin(Numbers) this.mindMap.addPlugin(Numbers)
this.$store.commit('setSupportNumbers', true) this.$store.commit('setSupportNumbers', true)
} }
if (typeof Freemind !== 'undefined') {
this.mindMap.addPlugin(Freemind)
this.$store.commit('setSupportFreemind', true)
Vue.prototype.Freemind = Freemind
}
if (typeof Excel !== 'undefined') {
this.mindMap.addPlugin(Excel)
this.$store.commit('setSupportExcel', true)
Vue.prototype.Excel = Excel
}
this.mindMap.keyCommand.addShortcut('Control+s', () => { this.mindMap.keyCommand.addShortcut('Control+s', () => {
this.manualSave() this.manualSave()
}) })

View File

@ -126,11 +126,23 @@ export default {
computed: { computed: {
...mapState({ ...mapState({
openNodeRichText: state => state.localConfig.openNodeRichText, openNodeRichText: state => state.localConfig.openNodeRichText,
isDark: state => state.localConfig.isDark isDark: state => state.localConfig.isDark,
supportFreemind: state => state.supportFreemind,
supportExcel: state => state.supportExcel
}), }),
downTypeList() { downTypeList() {
return downTypeList[this.$i18n.locale] || downTypeList.zh const list = downTypeList[this.$i18n.locale] || downTypeList.zh
return list.filter(item => {
if (item.type === 'mm') {
return this.supportFreemind
}
if (item.type === 'xlsx') {
return this.supportExcel
} else {
return true
}
})
} }
}, },
created() { created() {

View File

@ -9,7 +9,7 @@
<el-upload <el-upload
ref="upload" ref="upload"
action="x" action="x"
accept=".smm,.json,.xmind,.xlsx,.md,.mm" :accept="supportFileStr"
:file-list="fileList" :file-list="fileList"
:auto-upload="false" :auto-upload="false"
:multiple="false" :multiple="false"
@ -22,7 +22,7 @@
$t('import.selectFile') $t('import.selectFile')
}}</el-button> }}</el-button>
<div slot="tip" class="el-upload__tip"> <div slot="tip" class="el-upload__tip">
{{ $t('import.supportFile') }} {{ $t('import.support') }}{{ supportFileStr }}{{ $t('import.file') }}
</div> </div>
</el-upload> </el-upload>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
@ -59,10 +59,8 @@
<script> <script>
import xmind from 'simple-mind-map/src/parse/xmind.js' import xmind from 'simple-mind-map/src/parse/xmind.js'
import markdown from 'simple-mind-map/src/parse/markdown.js' import markdown from 'simple-mind-map/src/parse/markdown.js'
import { freemindToSmm } from 'simple-mind-map-plugin-freemind/freemindTo.js' import { mapMutations, mapState } from 'vuex'
import { fileToBuffer } from '@/utils' import Vue from 'vue'
import { read, utils } from 'xlsx'
import { mapMutations } from 'vuex'
/** /**
* @Author: 王林 * @Author: 王林
@ -81,6 +79,22 @@ export default {
canvasList: [] canvasList: []
} }
}, },
computed: {
...mapState({
supportFreemind: state => state.supportFreemind,
supportExcel: state => state.supportExcel
}),
supportFileStr() {
let res = '.smm,.json,.xmind,.md'
if (this.supportFreemind) {
res += ',.mm'
}
if (this.supportExcel) {
res += ',.xlsx'
}
return res
}
},
watch: { watch: {
dialogVisible(val, oldVal) { dialogVisible(val, oldVal) {
if (!val && oldVal) { if (!val && oldVal) {
@ -105,12 +119,20 @@ export default {
this.dialogVisible = true this.dialogVisible = true
}, },
getRegexp() {
return new RegExp(
`\.(smm|json|xmind|md${this.supportFreemind ? '|mm' : ''}${
this.supportExcel ? '|xlsx' : ''
})$`
)
},
// url // url
async handleFileURL() { async handleFileURL() {
try { try {
const fileURL = this.$route.query.fileURL const fileURL = this.$route.query.fileURL
if (!fileURL) return if (!fileURL) return
const macth = /\.(smm|json|xmind|md|xlsx|mm)$/.exec(fileURL) const macth = this.getRegexp().exec(fileURL)
if (!macth) { if (!macth) {
return return
} }
@ -138,9 +160,12 @@ export default {
// //
onChange(file) { onChange(file) {
let reg = /\.(smm|xmind|json|xlsx|md|mm)$/ if (!this.getRegexp().test(file.name)) {
if (!reg.test(file.name)) { this.$message.error(
this.$message.error(this.$t('import.enableFileTip')) this.$t('import.pleaseSelect') +
this.supportFileStr +
this.$t('import.file')
)
this.fileList = [] this.fileList = []
} else { } else {
this.fileList.push(file) this.fileList.push(file)
@ -226,18 +251,21 @@ export default {
fileReader.readAsText(file.raw) fileReader.readAsText(file.raw)
fileReader.onload = async evt => { fileReader.onload = async evt => {
try { try {
const data = await freemindToSmm(evt.target.result, { const data = await Vue.prototype.Freemind.freemindToSmm(
// withStyle: true, evt.target.result,
transformImg: image => { {
return new Promise(resolve => { // withStyle: true,
if (/^https?:\/\//.test(image)) { transformImg: image => {
resolve({ url: image }) return new Promise(resolve => {
} else { if (/^https?:\/\//.test(image)) {
resolve(null) resolve({ url: image })
} } else {
}) resolve(null)
}
})
}
} }
}) )
this.$bus.$emit('setData', data) this.$bus.$emit('setData', data)
this.$message.success(this.$t('import.importSuccess')) this.$message.success(this.$t('import.importSuccess'))
} catch (error) { } catch (error) {
@ -265,59 +293,8 @@ export default {
// .xlsx // .xlsx
async handleExcel(file) { async handleExcel(file) {
try { try {
const wb = read(await fileToBuffer(file.raw)) const res = await Vue.prototype.Excel.excelTo(file.raw)
const data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], { this.$bus.$emit('setData', res)
header: 1
})
if (data.length <= 0) {
return
}
let max = 0
data.forEach(arr => {
if (arr.length > max) {
max = arr.length
}
})
let layers = []
let walk = layer => {
if (!layers[layer]) {
layers[layer] = []
}
for (let i = 0; i < data.length; i++) {
if (data[i][layer]) {
let node = {
data: {
text: data[i][layer]
},
children: [],
_row: i
}
layers[layer].push(node)
}
}
if (layer < max - 1) {
walk(layer + 1)
}
}
walk(0)
let getParent = (arr, row) => {
for (let i = arr.length - 1; i >= 0; i--) {
if (row >= arr[i]._row) {
return arr[i]
}
}
}
for (let i = 1; i < layers.length; i++) {
let arr = layers[i]
for (let j = 0; j < arr.length; j++) {
let item = arr[j]
let parent = getParent(layers[i - 1], item._row)
if (parent) {
parent.children.push(item)
}
}
}
this.$bus.$emit('setData', layers[0][0])
this.$message.success(this.$t('import.importSuccess')) this.$message.success(this.$t('import.importSuccess'))
} catch (error) { } catch (error) {
console.log(error) console.log(error)

View File

@ -31,6 +31,8 @@ const store = new Vuex.Store({
supportHandDrawnLikeStyle: false, // 是否支持设置手绘风格 supportHandDrawnLikeStyle: false, // 是否支持设置手绘风格
supportMark: false, // 是否支持标记 supportMark: false, // 是否支持标记
supportNumbers: false, // 是否支持编号 supportNumbers: false, // 是否支持编号
supportFreemind: false, // 是否支持Freemind插件
supportExcel: false, // 是否支持Excel插件
isDragOutlineTreeNode: false // 当前是否正在拖拽大纲树的节点 isDragOutlineTreeNode: false // 当前是否正在拖拽大纲树的节点
}, },
mutations: { mutations: {
@ -93,6 +95,16 @@ const store = new Vuex.Store({
state.supportNumbers = data state.supportNumbers = data
}, },
// 设置是否支持Freemind插件
setSupportFreemind(state, data) {
state.supportFreemind = data
},
// 设置是否支持Excel插件
setSupportExcel(state, data) {
state.supportExcel = data
},
// 设置树节点拖拽 // 设置树节点拖拽
setIsDragOutlineTreeNode(state, data) { setIsDragOutlineTreeNode(state, data) {
state.isDragOutlineTreeNode = data state.isDragOutlineTreeNode = data