diff --git a/simple-mind-map/src/parse/xmind.js b/simple-mind-map/src/parse/xmind.js index 801a3e92..ed8810c2 100644 --- a/simple-mind-map/src/parse/xmind.js +++ b/simple-mind-map/src/parse/xmind.js @@ -1,5 +1,11 @@ import JSZip from 'jszip' import xmlConvert from 'xml-js' +import { + getTextFromHtml, + imgToDataUrl, + parseDataUrl, + getImageSize +} from '../utils/index' // 解析.xmind文件 const parseXmindFile = file => { @@ -10,7 +16,7 @@ const parseXmindFile = file => { let content = '' if (zip.files['content.json']) { let json = await zip.files['content.json'].async('string') - content = transformXmind(json) + content = await transformXmind(json, zip.files) } else if (zip.files['content.xml']) { let xml = await zip.files['content.xml'].async('string') let json = xmlConvert.xml2json(xml) @@ -33,11 +39,12 @@ const parseXmindFile = file => { } // 转换xmind数据 -const transformXmind = content => { +const transformXmind = async (content, files) => { let data = JSON.parse(content)[0] let nodeTree = data.rootTopic let newTree = {} - let walk = (node, newNode) => { + let waitLoadImageList = [] + let walk = async (node, newNode) => { newNode.data = { // 节点内容 text: node.title @@ -55,6 +62,42 @@ const transformXmind = content => { if (node.labels && node.labels.length > 0) { newNode.data.tag = node.labels } + // 图片 + if (node.image && /\.(jpg|jpeg|png|gif|webp)$/.test(node.image.src)) { + try { + // 处理异步逻辑 + let resolve = null + let promise = new Promise(_resolve => { + resolve = _resolve + }) + waitLoadImageList.push(promise) + // 读取图片 + let imageType = /\.([^.]+)$/.exec(node.image.src)[1] + let imageBase64 = + `data:image/${imageType};base64,` + + (await files['resources/' + node.image.src.split('/')[1]].async( + 'base64' + )) + newNode.data.image = imageBase64 + // 如果图片尺寸不存在 + if (!node.image.width && !node.image.height) { + let imageSize = await getImageSize(imageBase64) + newNode.data.imageSize = { + width: imageSize.width, + height: imageSize.height + } + } else { + newNode.data.imageSize = { + width: node.image.width, + height: node.image.height + } + } + resolve() + } catch (error) { + console.log(error) + resolve() + } + } // 子节点 newNode.children = [] if ( @@ -70,6 +113,7 @@ const transformXmind = content => { } } walk(nodeTree, newTree) + await Promise.all(waitLoadImageList) return newTree } @@ -158,8 +202,127 @@ const transformOldXmind = content => { return newTree } +// 数据转换为xmind文件 +const transformToXmind = async (data, name) => { + const id = 'simpleMindMap_' + Date.now() + const imageList = [] + // 转换核心数据 + let newTree = {} + let waitLoadImageList = [] + let walk = async (node, newNode, isRoot) => { + let newData = { + structureClass: 'org.xmind.ui.logic.right', + title: getTextFromHtml(node.data.text), // 节点文本 + children: { + attached: [] + } + } + // 备注 + if (node.data.note !== undefined) { + newData.notes = { + realHTML: { + content: node.data.note + }, + plain: { + content: node.data.note + } + } + } + // 超链接 + if (node.data.hyperlink !== undefined) { + newData.href = node.data.hyperlink + } + // 标签 + if (node.data.tag !== undefined) { + newData.labels = node.data.tag || [] + } + // 图片 + if (node.data.image) { + try { + // 处理异步逻辑 + let resolve = null + let promise = new Promise(_resolve => { + resolve = _resolve + }) + waitLoadImageList.push(promise) + let imgName = '' + let imgData = node.data.image + // 网络图片要先转换成data:url + if (/^https?:\/\//.test(node.data.image)) { + imgData = await imgToDataUrl(node.data.image) + } + // 从data:url中解析出图片类型和base64 + let dataUrlRes = parseDataUrl(imgData) + imgName = 'image_' + imageList.length + '.' + dataUrlRes.type + imageList.push({ + name: imgName, + data: dataUrlRes.base64 + }) + newData.image = { + src: 'xap:resources/' + imgName, + width: node.data.imageSize.width, + height: node.data.imageSize.height + } + resolve() + } catch (error) { + console.log(error) + resolve() + } + } + // 样式 + // 暂时不考虑样式 + if (isRoot) { + newData.class = 'topic' + newNode.id = id + newNode.class = 'sheet' + newNode.title = name + newNode.extensions = [] + newNode.topicPositioning = 'fixed' + newNode.topicOverlapping = 'overlap' + newNode.coreVersion = '2.100.0' + newNode.rootTopic = newData + } else { + Object.keys(newData).forEach(key => { + newNode[key] = newData[key] + }) + } + if (node.children && node.children.length > 0) { + node.children.forEach(child => { + let newChild = {} + walk(child, newChild) + newData.children.attached.push(newChild) + }) + } + } + walk(data, newTree, true) + await Promise.all(waitLoadImageList) + const contentData = [newTree] + // 创建压缩包 + const zip = new JSZip() + zip.file('content.json', JSON.stringify(contentData)) + zip.file( + 'metadata.json', + `{"modifier":"","dataStructureVersion":"1","layoutEngineVersion":"2","activeSheetId":"${id}"}` + ) + const manifestData = { + 'file-entries': { 'content.json': {}, 'metadata.json': {} } + } + // 图片 + if (imageList.length > 0) { + imageList.forEach(item => { + manifestData['file-entries']['resources/' + item.name] = {} + const img = zip.folder('resources') + img.file(item.name, item.data, { base64: true }) + }) + } + zip.file('manifest.json', JSON.stringify(manifestData)) + const zipData = await zip.generateAsync({ type: 'blob' }) + return zipData +} + export default { parseXmindFile, transformXmind, - transformOldXmind + transformOldXmind, + transformToXmind } diff --git a/simple-mind-map/src/plugins/Export.js b/simple-mind-map/src/plugins/Export.js index 5186ffe8..1f719d8f 100644 --- a/simple-mind-map/src/plugins/Export.js +++ b/simple-mind-map/src/plugins/Export.js @@ -180,6 +180,17 @@ class Export { this.mindMap.doExportPDF.pdf(name, img) } + // 导出为xmind + async xmind(name) { + if (!this.mindMap.doExportXMind) { + throw new Error('请注册ExportXMind插件') + } + const data = this.mindMap.getData() + const blob = await this.mindMap.doExportXMind.xmind(data, name) + const res = await readBlob(blob) + return res + } + // 导出为svg // plusCssText:附加的css样式,如果svg中存在dom节点,想要设置一些针对节点的样式可以通过这个参数传入 async svg(name, plusCssText) { diff --git a/simple-mind-map/src/plugins/ExportXMind.js b/simple-mind-map/src/plugins/ExportXMind.js new file mode 100644 index 00000000..5f54e220 --- /dev/null +++ b/simple-mind-map/src/plugins/ExportXMind.js @@ -0,0 +1,19 @@ +import xmind from '../parse/xmind' + +// 导出XMind类,需要通过Export插件使用 +class ExportXMind { + // 构造函数 + constructor(opt) { + this.mindMap = opt.mindMap + } + + // 导出xmind + async xmind(data, name) { + const zipData = await xmind.transformToXmind(data, name) + return zipData + } +} + +ExportXMind.instanceName = 'doExportXMind' + +export default ExportXMind diff --git a/simple-mind-map/src/utils/index.js b/simple-mind-map/src/utils/index.js index b5b2bd28..f84f7fd2 100644 --- a/simple-mind-map/src/utils/index.js +++ b/simple-mind-map/src/utils/index.js @@ -213,6 +213,18 @@ export const imgToDataUrl = src => { }) } +// 解析dataUrl +export const parseDataUrl = data => { + if (!/^data:/.test(data)) return data + let [typeStr, base64] = data.split(',') + let res = /^data:[^/]+\/([^;]+);/.exec(typeStr) + let type = res[1] + return { + type, + base64 + } +} + // 下载文件 export const downloadFile = (file, fileName) => { let a = document.createElement('a') @@ -392,3 +404,23 @@ export const nodeToHTML = node => { nodeToHTMLWrapEl.appendChild(node) return nodeToHTMLWrapEl.innerHTML } + +// 获取图片大小 +export const getImageSize = src => { + return new Promise(resolve => { + let img = new Image() + img.src = src + img.onload = () => { + resolve({ + width: img.width, + height: img.height + }) + } + img.onerror = () => { + resolve({ + width: 0, + height: 0 + }) + } + }) +}