diff --git a/simple-mind-map/src/constants/constant.js b/simple-mind-map/src/constants/constant.js index c538e5ad..a31e4a6f 100644 --- a/simple-mind-map/src/constants/constant.js +++ b/simple-mind-map/src/constants/constant.js @@ -351,3 +351,14 @@ export const cssContent = ` stroke-width: 2; } ` + +// html自闭合标签列表 +export const selfCloseTagList = [ + 'img', + 'br', + 'hr', + 'input', + 'link', + 'meta', + 'area' +] diff --git a/simple-mind-map/src/core/render/node/Node.js b/simple-mind-map/src/core/render/node/Node.js index 0d718a22..9e7b42fb 100644 --- a/simple-mind-map/src/core/render/node/Node.js +++ b/simple-mind-map/src/core/render/node/Node.js @@ -179,6 +179,7 @@ class Node { let { isUseCustomNodeContent, customCreateNodeContent } = this.mindMap.opt if (isUseCustomNodeContent && customCreateNodeContent) { this._customNodeContent = customCreateNodeContent(this) + this._customNodeContent.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml') } // 如果没有返回内容,那么还是使用内置的节点内容 if (this._customNodeContent) return diff --git a/simple-mind-map/src/plugins/Export.js b/simple-mind-map/src/plugins/Export.js index 55bfe3c7..32a89fdf 100644 --- a/simple-mind-map/src/plugins/Export.js +++ b/simple-mind-map/src/plugins/Export.js @@ -3,7 +3,8 @@ import { downloadFile, readBlob, removeHTMLEntities, - resizeImgSize + resizeImgSize, + handleSelfCloseTags } from '../utils' import { SVG } from '@svgdotjs/svg.js' import drawBackgroundImageToCanvas from '../utils/simulateCSSBackgroundInCanvas' @@ -20,7 +21,7 @@ class Export { // 导出 async export(type, isDownload = true, name = '思维导图', ...args) { if (this[type]) { - let result = await this[type](name, ...args) + const result = await this[type](name, ...args) if (isDownload && type !== 'pdf') { downloadFile(result, name + '.' + type) } @@ -30,6 +31,20 @@ class Export { } } + // 创建图片url转换任务 + createTransformImgTaskList(svg, tagName, propName, getUrlFn) { + const imageList = svg.find(tagName) + return imageList.map(async item => { + const imgUlr = getUrlFn(item) + // 已经是data:URL形式不用转换 + if (/^data:/.test(imgUlr) || imgUlr === 'none') { + return + } + const imgData = await imgToDataUrl(imgUlr) + item.attr(propName, imgData) + }) + } + // 获取svg数据 async getSvgData() { let { exportPaddingX, exportPaddingY } = this.mindMap.opt @@ -37,19 +52,34 @@ class Export { paddingX: exportPaddingX, paddingY: exportPaddingY }) - // 把图片的url转换成data:url类型,否则导出会丢失图片 - let imageList = svg.find('image') - let task = imageList.map(async item => { - let imgUlr = item.attr('href') || item.attr('xlink:href') - // 已经是data:URL形式不用转换 - if (/^data:/.test(imgUlr) || imgUlr === 'none') { - return + // svg的image标签,把图片的url转换成data:url类型,否则导出会丢失图片 + const task1 = this.createTransformImgTaskList( + svg, + 'image', + 'href', + item => { + return item.attr('href') || item.attr('xlink:href') } - let imgData = await imgToDataUrl(imgUlr) - item.attr('href', imgData) + ) + // html的img标签 + const task2 = this.createTransformImgTaskList(svg, 'img', 'src', item => { + return item.attr('src') }) - await Promise.all(task) - if (imageList.length > 0) { + const taskList = [...task1, ...task2] + await Promise.all(taskList) + // 开启了节点富文本编辑,需要增加一些样式 + let isAddResetCss + if (this.mindMap.richText) { + const foreignObjectList = svg.find('foreignObject') + if (foreignObjectList.length > 0) { + foreignObjectList[0].add( + SVG(``) + ) + isAddResetCss = true + } + } + // svg节点内容有变,需要重新获取html字符串 + if (taskList.length > 0 || isAddResetCss) { svgHTML = svg.svg() } return { @@ -131,7 +161,7 @@ class Export { // 在canvas上绘制思维导图背景 drawBackgroundToCanvas(ctx, width, height) { return new Promise((resolve, reject) => { - let { + const { backgroundColor = '#fff', backgroundImage, backgroundRepeat = 'no-repeat', @@ -175,7 +205,7 @@ class Export { // 在svg上绘制思维导图背景 drawBackgroundToSvg(svg) { return new Promise(async resolve => { - let { + const { backgroundColor = '#fff', backgroundImage, backgroundRepeat = 'repeat' @@ -184,7 +214,7 @@ class Export { svg.css('background-color', backgroundColor) // 背景图片 if (backgroundImage && backgroundImage !== 'none') { - let imgDataUrl = await imgToDataUrl(backgroundImage) + const imgDataUrl = await imgToDataUrl(backgroundImage) svg.css('background-image', `url(${imgDataUrl})`) svg.css('background-repeat', backgroundRepeat) resolve() @@ -200,35 +230,10 @@ class Export { * 方法2.把svg的图片提取出来再挨个绘制到canvas里,最后一起转换 */ async png(name, transparent = false, checkRotate, compress) { - let { node, str } = await this.getSvgData() - // 如果开启了富文本,则使用htmltocanvas转换为图片 - if (this.mindMap.richText) { - // 覆盖html默认的样式 - let foreignObjectList = node.find('foreignObject') - if (foreignObjectList.length > 0) { - foreignObjectList[0].add( - SVG(``) - ) - } - str = node.svg() - // 使用其他库(html2canvas、dom-to-image-more等)来完成导出 - // let res = await this.mindMap.richText.handleExportPng(node.node) - // let imgDataUrl = await this.svgToPng( - // res, - // transparent, - // checkRotate - // ) - // return imgDataUrl - } - str = removeHTMLEntities(str) - // 转换成blob数据 - let blob = new Blob([str], { - type: 'image/svg+xml' - }) - // 转换成data:url数据 - let svgUrl = await readBlob(blob) + const { str } = await this.getSvgData() + const svgUrl = await this.fixSvgStrAndToBlob(str) // 绘制到canvas上 - let res = await this.svgToPng(svgUrl, transparent, checkRotate, compress) + const res = await this.svgToPng(svgUrl, transparent, checkRotate, compress) return res } @@ -237,7 +242,7 @@ class Export { if (!this.mindMap.doExportPDF) { throw new Error('请注册ExportPDF插件') } - let img = await this.png( + const img = await this.png( '', false, (width, height) => { @@ -263,51 +268,50 @@ class Export { } // 导出为svg - // plusCssText:附加的css样式,如果svg中存在dom节点,想要设置一些针对节点的样式可以通过这个参数传入 async svg(name) { - let { node } = await this.getSvgData() - // 开启了节点富文本编辑 - if (this.mindMap.richText) { - let foreignObjectList = node.find('foreignObject') - if (foreignObjectList.length > 0) { - foreignObjectList[0].add( - SVG(``) - ) - } - } + const { node } = await this.getSvgData() node.first().before(SVG(`
+ // `
// return el
- // },
+ // }
})
if (this.openNodeRichText) this.addRichTextPlugin()
this.mindMap.keyCommand.addShortcut('Control+s', () => {