Compare commits

...

34 Commits
0.5.5 ... 0.5.7

Author SHA1 Message Date
wanglin2
65151f4b0a 优化性能:1.节点位置没有变化不触发位置设置;2.展开收起状态没有变化不触发按钮更新 2023-04-24 10:25:23 +08:00
wanglin2
942706fb63 Merge branch 'feature' into main 2023-04-24 09:09:33 +08:00
wanglin2
5f4492d4b7 打包0.5.7 2023-04-24 09:08:48 +08:00
wanglin2
ace1f62a40 更新文档 2023-04-24 09:02:01 +08:00
wanglin2
706c88c7d5 Featyre:富文本模式下,导入数据、初始化数据、切换主题场景节点样式支持跟随主题变化 2023-04-23 16:18:49 +08:00
wanglin2
4512fb16eb 优化富文本节点编辑 2023-04-23 15:20:33 +08:00
wanglin2
e446ff12e7 Demo:修复主题的标题显示错误问题 2023-04-23 14:39:16 +08:00
wanglin2
4318646abe 更新文档 2023-04-23 14:36:47 +08:00
wanglin2
2cbfe4f0e7 Feature:富文本模式导出改为使用html2canvas转换整个svg 2023-04-23 14:09:41 +08:00
wanglin2
b7910c4665 优化节点编辑 2023-04-22 14:24:05 +08:00
wanglin2
fc1ba24834 更新群二维码 2023-04-21 10:08:49 +08:00
wanglin2
cf16937160 Merge branch 'feature' into main 2023-04-21 10:08:11 +08:00
wanglin2
c3393abed6 打包0.5.6 2023-04-21 10:07:08 +08:00
wanglin2
2abff3e21b Demo:关闭新特性提示 2023-04-21 09:56:14 +08:00
wanglin2
e39a94c5e2 优化节点富文本编辑 2023-04-21 09:55:29 +08:00
wanglin2
a6fff7f7a3 Demo:修复节点文字数量计数不正确的问题 2023-04-21 09:37:49 +08:00
wanglin2
e584081b41 更新文档 2023-04-21 09:32:36 +08:00
wanglin2
b91dde8084 更新文档 2023-04-20 16:31:28 +08:00
wanglin2
077478d654 Feature:添加最大历史记录数限制 2023-04-20 16:10:25 +08:00
wanglin2
2be97cc1a0 Fix:修复节点正在编辑中时拖动画布导致编辑框和节点分离的问题 2023-04-20 15:29:16 +08:00
wanglin2
0f7dc949a4 优化富文本编辑 2023-04-20 09:40:47 +08:00
wanglin2
c7e91cc9eb Fix:修复短时间快速多次渲染时节点位置错乱的问题 2023-04-20 08:31:47 +08:00
wanglin2
6eacfab9c2 Demo:支持右键删除概要节点 2023-04-19 16:07:52 +08:00
wanglin2
e36a408275 Fix:修复快速多次渲染时节点位置错乱的问题 2023-04-19 15:45:51 +08:00
wanglin2
abda5b7d06 Merge branch 'feature' into main 2023-04-19 09:41:17 +08:00
wanglin2
f815f71dd7 更新文档 2023-04-19 09:40:58 +08:00
wanglin2
fa2c5b420c Merge branch 'feature' into main 2023-04-19 08:36:50 +08:00
wanglin2
4c19bc76a7 打包0.5.5-fix.1,修复小地图报错 2023-04-19 08:35:58 +08:00
wanglin2
d08a317920 Merge branch 'feature' into main 2023-04-18 17:38:38 +08:00
wanglin2
bd805836cd 打包0.5.5-fix.1 2023-04-18 17:37:10 +08:00
wanglin2
e804a8f2f7 Demo:修复富文本编辑时工具栏层级比节点编辑框低的问题 2023-04-18 17:19:43 +08:00
wanglin2
8bf876d446 优化:多选节点时改为节点只要和选区重叠就算被选中 2023-04-18 17:11:40 +08:00
wanglin2
f2521f663e 优化:切换结构时重置画布缩放,以修复当存在缩放时切换结构后第一次拖动会突变的问题 2023-04-18 16:59:12 +08:00
wanglin2
e676bff453 优化:当编辑节点文本时节点在画布外时移入画布内 2023-04-18 16:40:25 +08:00
63 changed files with 1474 additions and 279 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -101,7 +101,9 @@ const defaultOpt = {
// 节点备注浮层的z-index
nodeNoteTooltipZIndex: 3000,
// 是否在点击了画布外的区域时结束节点文本的编辑状态
isEndNodeTextEditOnClickOuter: true
isEndNodeTextEditOnClickOuter: true,
// 最大历史记录数
maxHistoryCount: 1000
}
// 思维导图
@@ -281,6 +283,7 @@ class MindMap {
layout = CONSTANTS.LAYOUT.LOGICAL_STRUCTURE
}
this.opt.layout = layout
this.view.reset()
this.renderer.setLayout()
this.render()
}
@@ -295,7 +298,11 @@ class MindMap {
this.execCommand('CLEAR_ACTIVE_NODE')
this.command.clearHistory()
this.command.addHistory()
this.renderer.renderTree = data
if (this.richText) {
this.renderer.renderTree = this.richText.handleSetData(data)
} else {
this.renderer.renderTree = data
}
this.reRender()
}
@@ -368,7 +375,7 @@ class MindMap {
}
// 获取svg数据
getSvgData({ paddingX = 0, paddingY = 0 }) {
getSvgData({ paddingX = 0, paddingY = 0 } = {}) {
const svg = this.svg
const draw = this.draw
// 保存原始信息

View File

@@ -1,6 +1,6 @@
{
"name": "simple-mind-map",
"version": "0.5.5",
"version": "0.5.7",
"description": "一个简单的web在线思维导图",
"authors": [
{

View File

@@ -84,6 +84,10 @@ class Command {
// 删除当前历史指针后面的数据
this.history = this.history.slice(0, this.activeHistoryIndex + 1)
this.history.push(simpleDeepClone(data))
// 历史记录数超过最大数量
if (this.history.length > this.mindMap.opt.maxHistoryCount) {
this.history.shift()
}
this.activeHistoryIndex = this.history.length - 1
this.mindMap.emit('data_change', this.removeDataUid(data))
this.mindMap.emit(

View File

@@ -27,7 +27,7 @@ class Export {
}
// 获取svg数据
async getSvgData(domToImage) {
async getSvgData() {
let { exportPaddingX, exportPaddingY } = this.mindMap.opt
let { svg, svgHTML } = this.mindMap.getSvgData({
paddingX: exportPaddingX,
@@ -44,24 +44,14 @@ class Export {
if (imageList.length > 0) {
svgHTML = svg.svg()
}
// 如果开启了富文本编辑需要把svg中的dom元素转换成图片
let nodeWithDomToImg = null
if (domToImage && this.mindMap.richText) {
let res = await this.mindMap.richText.handleSvgDomElements(svg)
if (res) {
nodeWithDomToImg = res.svg
svgHTML = res.svgHTML
}
}
return {
node: svg,
str: svgHTML,
nodeWithDomToImg
str: svgHTML
}
}
// svg转png
svgToPng(svgSrc) {
svgToPng(svgSrc, transparent) {
return new Promise((resolve, reject) => {
const img = new Image()
// 跨域图片需要添加这个属性,否则画布被污染了无法导出图片
@@ -73,7 +63,9 @@ class Export {
canvas.height = img.height + this.exportPadding * 2
let ctx = canvas.getContext('2d')
// 绘制背景
await this.drawBackgroundToCanvas(ctx, canvas.width, canvas.height)
if (!transparent) {
await this.drawBackgroundToCanvas(ctx, canvas.width, canvas.height)
}
// 图片绘制到canvas里
ctx.drawImage(
img,
@@ -140,8 +132,14 @@ class Export {
* 方法1.把svg的图片都转化成data:url格式再转换
* 方法2.把svg的图片提取出来再挨个绘制到canvas里最后一起转换
*/
async png() {
let { str } = await this.getSvgData(true)
async png(name, transparent = false) {
let { node, str } = await this.getSvgData()
// 如果开启了富文本则使用htmltocanvas转换为图片
if (this.mindMap.richText) {
let res = await this.mindMap.richText.handleExportPng(node.node)
let imgDataUrl = await this.svgToPng(res, transparent)
return imgDataUrl
}
// 转换成blob数据
let blob = new Blob([str], {
type: 'image/svg+xml'
@@ -149,7 +147,7 @@ class Export {
// 转换成data:url数据
let svgUrl = URL.createObjectURL(blob)
// 绘制到canvas上
let imgDataUrl = await this.svgToPng(svgUrl)
let imgDataUrl = await this.svgToPng(svgUrl, transparent)
URL.revokeObjectURL(svgUrl)
return imgDataUrl
}
@@ -209,15 +207,12 @@ class Export {
}
// 导出为svg
// domToImage是否将svg中的dom节点转换成图片的形式
// plusCssText附加的css样式如果svg中存在dom节点想要设置一些针对节点的样式可以通过这个参数传入
async svg(name, domToImage = false, plusCssText) {
let { node, nodeWithDomToImg } = await this.getSvgData(domToImage)
async svg(name, plusCssText) {
let { node } = await this.getSvgData()
// 开启了节点富文本编辑
if (this.mindMap.richText) {
if (domToImage) {
node = nodeWithDomToImg
} else if (plusCssText) {
if (plusCssText) {
let foreignObjectList = node.find('foreignObject')
if (foreignObjectList.length > 0) {
foreignObjectList[0].add(SVG(`<style>${plusCssText}</style>`))

View File

@@ -67,6 +67,7 @@ class Node {
this._noteData = null
this.noteEl = null
this._expandBtn = null
this._lastExpandBtnType = null
this._openExpandNode = null
this._closeExpandNode = null
this._fillExpandNode = null
@@ -389,7 +390,7 @@ class Node {
// 右键菜单事件
this.group.on('contextmenu', e => {
// 按住ctrl键点击鼠标左键不知为何触发的是contextmenu事件
if (this.mindMap.opt.readonly || this.isGeneralization || e.ctrlKey) {
if (this.mindMap.opt.readonly || e.ctrlKey) {// || this.isGeneralization
return
}
e.stopPropagation()
@@ -436,6 +437,8 @@ class Node {
this.renderGeneralization()
// 更新节点位置
let t = this.group.transform()
// 如果节点位置没有变化,则返回
if (this.left === t.translateX && this.top === t.translateY) return
if (!isLayout && enableNodeTransitionMove) {
this.group
.animate(nodeTransitionMoveDuration)

View File

@@ -250,6 +250,7 @@ class Render {
// 渲染
render(callback = () => {}, source) {
let t = Date.now()
// 如果当前还没有渲染完毕,不再触发渲染
if (this.isRendering) {
// 等待当前渲染完毕后再进行一次渲染
@@ -281,7 +282,7 @@ class Render {
// 更新根节点
this.root = root
// 渲染节点
this.root.render(() => {
const onEnd = () => {
this.isRendering = false
this.mindMap.emit('node_tree_render_end')
callback && callback()
@@ -289,6 +290,18 @@ class Render {
this.hasWaitRendering = false
this.render(callback, source)
}
}
let { enableNodeTransitionMove, nodeTransitionMoveDuration } =
this.mindMap.opt
this.root.render(() => {
let dur = Date.now() - t
if (enableNodeTransitionMove && dur <= nodeTransitionMoveDuration) {
setTimeout(() => {
onEnd()
}, nodeTransitionMoveDuration - dur);
} else {
onEnd()
}
})
})
this.mindMap.emit('node_active', null, this.activeNodeList)

View File

@@ -1,9 +1,7 @@
import Quill from 'quill'
import 'quill/dist/quill.snow.css'
import './css/quill.css'
import html2canvas from 'html2canvas'
import { Image as SvgImage } from '@svgdotjs/svg.js'
import { walk } from './utils'
import { walk, getTextFromHtml } from './utils'
import { CONSTANTS } from './utils/constant'
let extended = false
@@ -41,8 +39,49 @@ class RichText {
this.range = null
this.lastRange = null
this.node = null
this.styleEl = null
this.cacheEditingText = ''
this.initOpt()
this.extendQuill()
this.appendCss()
// 处理数据,转成富文本格式
if (this.mindMap.opt.data) {
this.mindMap.opt.data = this.handleSetData(this.mindMap.opt.data)
}
}
// 插入样式
appendCss() {
let cssText = `
.ql-editor {
overflow: hidden;
padding: 0;
height: auto;
line-height: normal;
}
.ql-container {
height: auto;
font-size: inherit;
}
.ql-container.ql-snow {
border: none;
}
.smm-richtext-node-wrap p {
font-family: auto;
}
.smm-richtext-node-edit-wrap p {
font-family: auto;
}
`
this.styleEl = document.createElement('style')
this.styleEl.type = 'text/css'
this.styleEl.innerHTML = cssText
document.head.appendChild(this.styleEl)
}
// 处理选项参数
@@ -96,40 +135,46 @@ class RichText {
if (!rect) rect = node._textData.node.node.getBoundingClientRect()
this.mindMap.emit('before_show_text_edit')
this.mindMap.renderer.textEdit.registerTmpShortcut()
// 原始宽高
let g = node._textData.node
let originWidth = g.attr('data-width')
let originHeight = g.attr('data-height')
// 缩放值
let scaleX = rect.width / originWidth
let scaleY = rect.height / originHeight
// 内边距
const paddingX = 6
const paddingY = 4
if (!this.textEditNode) {
this.textEditNode = document.createElement('div')
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;box-shadow: 0 0 20px rgba(0,0,0,.5);outline: none; word-break: break-all;padding: 3px 5px;margin-left: -5px;margin-top: -3px;`
this.textEditNode.classList.add('smm-richtext-node-edit-wrap')
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;box-shadow: 0 0 20px rgba(0,0,0,.5);outline: none; word-break: break-all;padding: ${paddingY}px ${paddingX}px;`
this.textEditNode.addEventListener('click', e => {
e.stopPropagation()
})
document.body.appendChild(this.textEditNode)
}
// 原始宽高
let g = node._textData.node
let originWidth = g.attr('data-width')
let originHeight = g.attr('data-height')
// 使用节点的填充色,否则如果节点颜色是白色的话编辑时看不见
let bgColor = node.style.merge('fillColor')
this.textEditNode.style.marginLeft = `-${paddingX * scaleX}px`
this.textEditNode.style.marginTop = `-${paddingY * scaleY}px`
this.textEditNode.style.zIndex = this.mindMap.opt.nodeTextEditZIndex
this.textEditNode.style.backgroundColor = bgColor === 'transparent' ? '#fff' : bgColor
this.textEditNode.style.minWidth = originWidth + 'px'
this.textEditNode.style.minWidth = originWidth + paddingX * 2 + 'px'
this.textEditNode.style.minHeight = originHeight + 'px'
this.textEditNode.style.left =
rect.left + (rect.width - originWidth) / 2 + 'px'
this.textEditNode.style.top =
rect.top + (rect.height - originHeight) / 2 + 'px'
this.textEditNode.style.left = rect.left + 'px'
this.textEditNode.style.top = rect.top + 'px'
this.textEditNode.style.display = 'block'
this.textEditNode.style.maxWidth = this.mindMap.opt.textAutoWrapWidth + 'px'
this.textEditNode.style.transform = `scale(${rect.width / originWidth}, ${
rect.height / originHeight
})`
this.textEditNode.style.maxWidth = this.mindMap.opt.textAutoWrapWidth + paddingX * 2 + 'px'
this.textEditNode.style.transform = `scale(${scaleX}, ${scaleY})`
this.textEditNode.style.transformOrigin = 'left top'
if (!node.nodeData.data.richText) {
// 还不是富文本的情况
let text = node.nodeData.data.text.split(/\n/gim).join('<br>')
let html = `<p>${text}</p>`
this.textEditNode.innerHTML = html
this.textEditNode.innerHTML = this.cacheEditingText || html
} else {
this.textEditNode.innerHTML = node.nodeData.data.text
this.textEditNode.innerHTML = this.cacheEditingText || node.nodeData.data.text
}
this.initQuillEditor()
document.querySelector('.ql-editor').style.minHeight = originHeight + 'px'
@@ -139,6 +184,7 @@ class RichText {
// 如果是非富文本的情况,需要手动应用文本样式
this.setTextStyleIfNotRichText(node)
}
this.cacheEditingText = ''
}
// 如果是非富文本的情况,需要手动应用文本样式
@@ -155,14 +201,19 @@ class RichText {
this.formatAllText(style)
}
// 获取当前正在编辑的内容
getEditText() {
let html = this.quill.container.firstChild.innerHTML
// 去除最后的空行
return html.replace(/<p><br><\/p>$/, '')
}
// 隐藏文本编辑控件,即完成编辑
hideEditText(nodes) {
if (!this.showTextEdit) {
return
}
let html = this.quill.container.firstChild.innerHTML
// 去除最后的空行
html = html.replace(/<p><br><\/p>$/, '')
let html = this.getEditText()
let list = nodes && nodes.length > 0 ? nodes : this.mindMap.renderer.activeNodeList
list.forEach(node => {
this.mindMap.execCommand('SET_NODE_TEXT', node, html, true)
@@ -353,94 +404,40 @@ class RichText {
return data
}
// 将svg中嵌入的dom元素转换成图片
async _handleSvgDomElements(svg) {
svg = svg.clone()
let foreignObjectList = svg.find('foreignObject')
let task = foreignObjectList.map(async item => {
let clone = item.first().node.cloneNode(true)
let div = document.createElement('div')
div.style.cssText = `position: fixed; left: -999999px;`
div.appendChild(clone)
this.mindMap.el.appendChild(div)
let canvas = await html2canvas(clone, {
backgroundColor: null
})
this.mindMap.el.removeChild(div)
let imgNode = new SvgImage()
.load(canvas.toDataURL())
.size(canvas.width, canvas.height)
item.replace(imgNode)
})
await Promise.all(task)
return {
svg: svg,
svgHTML: svg.svg()
// 处理导出为图片
async handleExportPng(node) {
let el = document.createElement('div')
el.style.position = 'absolute'
el.style.left = '-9999999px'
el.appendChild(node)
this.mindMap.el.appendChild(el)
// 遍历所有节点将它们的margin和padding设为0
let walk = (root) => {
root.style.margin = 0
root.style.padding = 0
if (root.hasChildNodes()) {
Array.from(root.children).forEach((item) => {
walk(item)
})
}
}
}
// 将svg中嵌入的dom元素转换成图片
handleSvgDomElements(svg) {
return new Promise((resolve, reject) => {
svg = svg.clone()
let foreignObjectList = svg.find('foreignObject')
let index = 0
let len = foreignObjectList.length
let transform = async () => {
this.mindMap.emit('transforming-dom-to-images', index, len)
try {
let item = foreignObjectList[index++]
let parent = item.parent()
let clone = item.first().node.cloneNode(true)
let div = document.createElement('div')
div.style.cssText = `position: fixed; left: -999999px;`
div.appendChild(clone)
this.mindMap.el.appendChild(div)
let canvas = await html2canvas(clone, {
backgroundColor: null
})
// 优先使用原始宽高因为当设备的window.devicePixelRatio不为1时html2canvas输出的图片会更大
let imgNodeWidth = parent.attr('data-width') || canvas.width
let imgNodeHeight = parent.attr('data-height') || canvas.height
this.mindMap.el.removeChild(div)
let imgNode = new SvgImage()
.load(canvas.toDataURL())
.size(imgNodeWidth, imgNodeHeight)
.x((parent ? parent.attr('data-offsetx') : 0) || 0)
item.replace(imgNode)
if (index <= len - 1) {
setTimeout(() => {
transform()
}, 0)
} else {
resolve({
svg: svg,
svgHTML: svg.svg()
})
}
} catch (error) {
reject(error)
}
}
if (len > 0) {
transform()
} else {
resolve(null)
}
walk(node)
let canvas = await html2canvas(el, {
backgroundColor: null
})
this.mindMap.el.removeChild(el)
return canvas.toDataURL()
}
// 将所有节点转换成非富文本节点
transformAllNodesToNormalNode() {
let div = document.createElement('div')
walk(
this.mindMap.renderer.renderTree,
null,
node => {
if (node.data.richText) {
node.data.richText = false
div.innerHTML = node.data.text
node.data.text = div.textContent
node.data.text = getTextFromHtml(node.data.text)
// delete node.data.uid
}
},
@@ -455,9 +452,27 @@ class RichText {
this.mindMap.render(null, CONSTANTS.TRANSFORM_TO_NORMAL_NODE)
}
// 处理导入数据
handleSetData(data) {
let walk = (root) => {
if (!root.data.richText) {
root.data.richText = true
root.data.resetRichText = true
}
if (root.children && root.children.length > 0) {
Array.from(root.children).forEach((item) => {
walk(item)
})
}
}
walk(data)
return data
}
// 插件被移除前做的事情
beforePluginRemove() {
this.transformAllNodesToNormalNode()
document.head.removeChild(this.styleEl)
}
}

View File

@@ -17,7 +17,7 @@ class Select {
// 绑定事件
bindEvent() {
this.checkInNodes = throttle(this.checkInNodes, 500, this)
this.checkInNodes = throttle(this.checkInNodes, 300, this)
this.mindMap.on('mousedown', e => {
if (this.mindMap.opt.readonly) {
return
@@ -146,22 +146,26 @@ class Select {
let bottom = (top + height) * scaleY + translateY
left = left * scaleX + translateX
top = top * scaleY + translateY
if (left >= minx && right <= maxx && top >= miny && bottom <= maxy) {
this.mindMap.batchExecution.push('activeNode' + node.uid, () => {
if ((left >= minx && left <= maxx ||
right >= minx && right <= maxx) &&
(top >= miny && top <= maxy ||
bottom >= miny && bottom <= maxy)
) {
// this.mindMap.batchExecution.push('activeNode' + node.uid, () => {
if (node.nodeData.data.isActive) {
return
}
this.mindMap.renderer.setNodeActive(node, true)
this.mindMap.renderer.addActiveNode(node)
})
// })
} else if (node.nodeData.data.isActive) {
this.mindMap.batchExecution.push('activeNode' + node.uid, () => {
// this.mindMap.batchExecution.push('activeNode' + node.uid, () => {
if (!node.nodeData.data.isActive) {
return
}
this.mindMap.renderer.setNodeActive(node, false)
this.mindMap.renderer.removeActiveNode(node)
})
// })
}
})
}

View File

@@ -109,6 +109,18 @@ class Style {
})
}
// 生成内联样式
createStyleText() {
return `
color: ${this.merge('color')};
font-family: ${this.merge('fontFamily')};
font-size: ${this.merge('fontSize') + 'px'};
font-weight: ${this.merge('fontWeight')};
font-style: ${this.merge('fontStyle')};
text-decoration: ${this.merge('textDecoration')}
`
}
// 获取文本样式
getTextFontStyle() {
return {
@@ -120,11 +132,11 @@ class Style {
}
// html文字节点
domText(node, fontSizeScale = 1, textLines) {
domText(node, fontSizeScale = 1, isMultiLine) {
node.style.fontFamily = this.merge('fontFamily')
node.style.fontSize = this.merge('fontSize') * fontSizeScale + 'px'
node.style.fontWeight = this.merge('fontWeight') || 'normal'
node.style.lineHeight = textLines === 1 ? 'normal' : this.merge('lineHeight')
node.style.lineHeight = !isMultiLine ? 'normal' : this.merge('lineHeight')
node.style.fontStyle = this.merge('fontStyle')
}

View File

@@ -1,4 +1,4 @@
import { getStrWithBrFromHtml } from './utils'
import { getStrWithBrFromHtml, checkNodeOuter } from './utils'
// 节点文字编辑类
export default class TextEdit {
@@ -6,16 +6,21 @@ export default class TextEdit {
constructor(renderer) {
this.renderer = renderer
this.mindMap = renderer.mindMap
// 当前编辑的节点
this.currentNode = null
// 文本编辑框
this.textEditNode = null
// 文本编辑框是否显示
this.showTextEdit = false
// 如果编辑过程中缩放画布了,那么缓存当前编辑的内容
this.cacheEditingText = ''
this.bindEvent()
}
// 事件
bindEvent() {
this.show = this.show.bind(this)
this.onScale = this.onScale.bind(this)
// 节点双击事件
this.mindMap.on('node_dblclick', this.show)
// 点击事件
@@ -48,6 +53,7 @@ export default class TextEdit {
}
this.show(this.renderer.activeNodeList[0])
})
this.mindMap.on('scale', this.onScale)
}
// 注册临时快捷键
@@ -60,6 +66,9 @@ export default class TextEdit {
// 显示文本编辑框
show(node) {
this.currentNode = node
let { offsetLeft, offsetTop } = checkNodeOuter(this.mindMap, node)
this.mindMap.view.translateXY(offsetLeft, offsetTop)
let rect = node._textData.node.node.getBoundingClientRect()
if (this.mindMap.richText) {
this.mindMap.richText.showEditText(node, rect)
@@ -68,6 +77,19 @@ export default class TextEdit {
this.showEditTextBox(node, rect)
}
// 处理画布缩放
onScale() {
if (!this.currentNode) return
if (this.mindMap.richText) {
this.mindMap.richText.cacheEditingText = this.mindMap.richText.getEditText()
this.mindMap.richText.showTextEdit = false
} else {
this.cacheEditingText = this.getEditText()
this.showTextEdit = false
}
this.show(this.currentNode)
}
// 显示文本编辑框
showEditTextBox(node, rect) {
this.mindMap.emit('before_show_text_edit')
@@ -87,8 +109,9 @@ export default class TextEdit {
let scale = this.mindMap.view.scale
let lineHeight = node.style.merge('lineHeight')
let fontSize = node.style.merge('fontSize')
let textLines = node.nodeData.data.text.split(/\n/gim)
node.style.domText(this.textEditNode, scale, textLines.length)
let textLines = (this.cacheEditingText || node.nodeData.data.text).split(/\n/gim)
let isMultiLine = node._textData.node.attr('data-ismultiLine') === 'true'
node.style.domText(this.textEditNode, scale, isMultiLine)
this.textEditNode.style.zIndex = this.mindMap.opt.nodeTextEditZIndex
this.textEditNode.innerHTML = textLines.join('<br>')
this.textEditNode.style.minWidth = rect.width + 10 + 'px'
@@ -97,12 +120,15 @@ export default class TextEdit {
this.textEditNode.style.top = rect.top + 'px'
this.textEditNode.style.display = 'block'
this.textEditNode.style.maxWidth = this.mindMap.opt.textAutoWrapWidth * scale + 'px'
if (textLines.length > 1 && lineHeight !== 1) {
this.textEditNode.style.transform = `translateY(${-((lineHeight * fontSize - fontSize) / 2 - 2) * scale}px)`
if (isMultiLine && lineHeight !== 1) {
this.textEditNode.style.transform = `translateY(${-((lineHeight * fontSize - fontSize) / 2) * scale}px)`
}
this.showTextEdit = true
// 选中文本
this.selectNodeText()
if (!this.cacheEditingText) {
this.selectNodeText()
}
this.cacheEditingText = ''
}
// 选中文本
@@ -114,8 +140,14 @@ export default class TextEdit {
selection.addRange(range)
}
// 获取当前正在编辑的内容
getEditText() {
return getStrWithBrFromHtml(this.textEditNode.innerHTML)
}
// 隐藏文本编辑框
hideEditTextBox() {
this.currentNode = null
if (this.mindMap.richText) {
return this.mindMap.richText.hideEditText()
}
@@ -123,7 +155,7 @@ export default class TextEdit {
return
}
this.renderer.activeNodeList.forEach(node => {
let str = getStrWithBrFromHtml(this.textEditNode.innerHTML)
let str = this.getEditText()
this.mindMap.execCommand('SET_NODE_TEXT', node, str)
if (node.isGeneralization) {
// 概要节点

View File

@@ -124,6 +124,13 @@ class View {
}
}
// 平移x,y方向
translateXY(x, y) {
this.x += x
this.y += y
this.transform()
}
// 平移x方向
translateX(step) {
this.x += step
@@ -160,10 +167,14 @@ class View {
// 恢复
reset() {
let scaleChange = this.scale !== 1
this.scale = 1
this.x = 0
this.y = 0
this.transform()
if (scaleChange) {
this.mindMap.emit('scale', this.scale)
}
}
// 缩小

View File

@@ -1,11 +0,0 @@
.ql-editor {
overflow: hidden;
padding: 0;
height: auto;
line-height: normal;
}
.ql-container {
height: auto;
font-size: inherit;
}

View File

@@ -245,9 +245,15 @@ class LogicalStructure extends Base {
let nodeUseLineStyleOffset = this.mindMap.themeConfig.nodeUseLineStyle
? height / 2
: 0
// 位置没有变化则返回
let _x = width
let _y = height / 2 + nodeUseLineStyleOffset
if (_x === translateX && _y === translateY) {
return
}
btn.translate(
width - translateX,
height / 2 - translateY + nodeUseLineStyleOffset
_x - translateX,
_y - translateY
)
}

View File

@@ -310,8 +310,14 @@ class MindMap extends Base {
let nodeUseLineStyleOffset = this.mindMap.themeConfig.nodeUseLineStyle
? height / 2
: 0
let x = (node.dir === 'left' ? 0 - expandBtnSize : width) - translateX
let y = height / 2 - translateY + nodeUseLineStyleOffset
// 位置没有变化则返回
let _x = (node.dir === 'left' ? 0 - expandBtnSize : width)
let _y = height / 2 + nodeUseLineStyleOffset
if (_x === translateX && _y === translateY) {
return
}
let x = _x - translateX
let y = _y - translateY
btn.translate(x, y)
}

View File

@@ -302,4 +302,44 @@ export const nextTick = function (fn, ctx) {
pending = true
timerFunc(handle, 0)
}
}
// 检查节点是否超出画布
export const checkNodeOuter = (mindMap, node) => {
let elRect = mindMap.elRect
let { scaleX, scaleY, translateX, translateY } = mindMap.draw.transform()
let { left, top, width, height } = node
let right = (left + width) * scaleX + translateX
let bottom = (top + height) * scaleY + translateY
left = left * scaleX + translateX
top = top * scaleY + translateY
let offsetLeft = 0
let offsetTop = 0
if (left < 0) {
offsetLeft = -left
}
if (right > elRect.width) {
offsetLeft = -(right - elRect.width)
}
if (top < 0) {
offsetTop = -top
}
if (bottom > elRect.height) {
offsetTop = -(bottom - elRect.height)
}
return {
isOuter: offsetLeft !== 0 || offsetTop !== 0,
offsetLeft,
offsetTop
}
}
// 提取html字符串里的纯文本
let getTextFromHtmlEl = null
export const getTextFromHtml = (html) => {
if (!getTextFromHtmlEl) {
getTextFromHtmlEl = document.createElement('div')
}
getTextFromHtmlEl.innerHTML = html
return getTextFromHtmlEl.textContent
}

View File

@@ -1,6 +1,7 @@
import { measureText, resizeImgSize } from '../utils'
import { measureText, resizeImgSize, getTextFromHtml } from '../utils'
import { Image, SVG, A, G, Rect, Text, ForeignObject } from '@svgdotjs/svg.js'
import iconsSvg from '../svg/icons'
import { CONSTANTS } from './constant'
// 创建图片节点
function createImgNode() {
@@ -52,11 +53,18 @@ function createIconNode() {
// 创建富文本节点
function createRichTextNode() {
let g = new G()
// 重新设置富文本节点内容
if (this.nodeData.data.resetRichText || [CONSTANTS.CHANGE_THEME].includes(this.mindMap.renderer.renderSource)) {
delete this.nodeData.data.resetRichText
let text = getTextFromHtml(this.nodeData.data.text)
this.nodeData.data.text = `<p><span style="${this.style.createStyleText()}">${text}</span></p>`
}
let html = `<div>${this.nodeData.data.text}</div>`
let div = document.createElement('div')
div.innerHTML = html
div.style.cssText = `position: fixed; left: -999999px;`
let el = div.children[0]
el.classList.add('smm-richtext-node-wrap')
el.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml')
el.style.maxWidth = this.mindMap.opt.textAutoWrapWidth + 'px'
this.mindMap.el.appendChild(div)
@@ -95,21 +103,27 @@ function createTextNode() {
let textStyle = this.style.getTextFontStyle()
let textArr = this.nodeData.data.text.split(/\n/gim)
let maxWidth = this.mindMap.opt.textAutoWrapWidth
let isMultiLine = false
textArr.forEach((item, index) => {
let arr = item.split('')
let lines = []
let line = []
while (arr.length) {
line.push(arr.shift())
let text = line.join('')
if (measureText(text, textStyle).width >= maxWidth) {
lines.push(text)
line = []
let str = arr.shift()
let text = [...line, str].join('')
if (measureText(text, textStyle).width <= maxWidth) {
line.push(str)
} else {
lines.push(line.join(''))
line = [str]
}
}
if (line.length > 0) {
lines.push(line.join(''))
}
if (lines.length > 1) {
isMultiLine = true
}
textArr[index] = lines.join('\n')
})
textArr = textArr.join('\n').split(/\n/gim)
@@ -124,6 +138,7 @@ function createTextNode() {
height = Math.ceil(height)
g.attr('data-width', width)
g.attr('data-height', height)
g.attr('data-ismultiLine', isMultiLine || textArr.length > 1)
return {
node: g,
width,

View File

@@ -32,6 +32,8 @@ function createExpandNodeContent() {
// 创建或更新展开收缩按钮内容
function updateExpandBtnNode() {
// 如果本次和上次的展开状态一样则返回
if (this.nodeData.data.expand === this._lastExpandBtnType) return
if (this._expandBtn) {
this._expandBtn.clear()
}
@@ -39,8 +41,10 @@ function updateExpandBtnNode() {
let node
if (this.nodeData.data.expand === false) {
node = this._openExpandNode
this._lastExpandBtnType = false
} else {
node = this._closeExpandNode
this._lastExpandBtnType = true
}
if (this._expandBtn) this._expandBtn.add(this._fillExpandNode).add(node)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -90,7 +90,7 @@ export default {
pdfFile: 'pdf file',
markdownFile: 'markdown file',
tips: 'tips: .smm and .json file can be import',
domToImage: 'Whether to convert rich text nodes in svg into pictures',
isTransparent: 'Background is transparent',
pngTips: 'tips: Exporting pictures in rich text mode is time-consuming. It is recommended to export to svg format',
svgTips: 'tips: Exporting pictures in rich text mode is time-consuming',
transformingDomToImages: 'Converting nodes: ',

View File

@@ -90,7 +90,7 @@ export default {
pdfFile: 'pdf文件',
markdownFile: 'markdown文件',
tips: 'tips.smm和.json文件可用于导入',
domToImage: '是否将svg中富文本节点转换成图片',
isTransparent: '背景是否透明',
pngTips: 'tips富文本模式导出图片非常耗时建议导出为svg格式',
svgTips: 'tips富文本模式导出图片非常耗时',
transformingDomToImages: '正在转换节点:',

View File

@@ -10,7 +10,7 @@ let langList = [
path: 'en'
}
]
let StartList = ['introduction', 'start', 'translate', 'changelog']
let StartList = ['introduction', 'start', 'deploy', 'translate', 'changelog']
let CourseList = new Array(18).fill(0).map((_, index) => {
return 'course' + (index + 1)
})

View File

@@ -1,5 +1,29 @@
# Changelog
## 0.5.7
Breaking changeIn rich text mode, exporting png has been changed to using html2canvas to convert the entire svg, greatly improving the export speed. However, html2canvas has a bug where the text color inline with the dom node in the foreignObject element cannot be recognized. Therefore, the text color of the exported node is fixed. However, compared to the previously unavailable state of the export, it can at least be exported quickly and smoothly.
optimization: Optimize the rich text node editing experience.
New: In rich text mode, importing data, initializing data, and switching theme scene node styles support following theme changes.
## 0.5.6
Fix: 1.Fix the issue of node position disorder during fast and multiple renderings in a short period of time. 2.Fix the issue of dragging the canvas while the node is being edited, causing the edit box and node to separate.
New: 1.Add a maximum history limit.
## 0.5.5-fix.1
Fix: 1.Fix the issue where the edit box is also outside the canvas when editing nodes outside the canvas. 2.After modifying the structure, reset the transformation to prevent the problem of sudden position changes during the first drag after switching the structure during scaling.
optimization: 1.When multiple nodes are selected, as long as there is a cross between the node and the selection area, it is considered selected.
## 0.5.5-fix.2
Fix: 1.Fix mini map error.
## 0.5.5
New: 1.Supports configuring the padding when exporting to PNG, SVG, or PDF. 2.Support the configuration of z-index for node text editing boxes and node comment floating layer elements. 3.Support clicking on areas outside the canvas to end node editing status.

View File

@@ -1,6 +1,18 @@
<template>
<div>
<h1>Changelog</h1>
<h2>0.5.7</h2>
<p>Breaking changeIn rich text mode, exporting png has been changed to using html2canvas to convert the entire svg, greatly improving the export speed. However, html2canvas has a bug where the text color inline with the dom node in the foreignObject element cannot be recognized. Therefore, the text color of the exported node is fixed. However, compared to the previously unavailable state of the export, it can at least be exported quickly and smoothly.</p>
<p>optimization: Optimize the rich text node editing experience.</p>
<p>New: In rich text mode, importing data, initializing data, and switching theme scene node styles support following theme changes.</p>
<h2>0.5.6</h2>
<p>Fix: 1.Fix the issue of node position disorder during fast and multiple renderings in a short period of time. 2.Fix the issue of dragging the canvas while the node is being edited, causing the edit box and node to separate.</p>
<p>New: 1.Add a maximum history limit.</p>
<h2>0.5.5-fix.1</h2>
<p>Fix: 1.Fix the issue where the edit box is also outside the canvas when editing nodes outside the canvas. 2.After modifying the structure, reset the transformation to prevent the problem of sudden position changes during the first drag after switching the structure during scaling.</p>
<p>optimization: 1.When multiple nodes are selected, as long as there is a cross between the node and the selection area, it is considered selected.</p>
<h2>0.5.5-fix.2</h2>
<p>Fix: 1.Fix mini map error.</p>
<h2>0.5.5</h2>
<p>New: 1.Supports configuring the padding when exporting to PNG, SVG, or PDF. 2.Support the configuration of z-index for node text editing boxes and node comment floating layer elements. 3.Support clicking on areas outside the canvas to end node editing status.</p>
<h2>0.5.4</h2>

View File

@@ -58,6 +58,7 @@ const mindMap = new MindMap({
| nodeTextEditZIndexv0.5.5+ | Number | 3000 | | z-index of node text edit box elements |
| nodeNoteTooltipZIndexv0.5.5+ | Number | 3000 | z-index of floating layer elements in node comments | |
| isEndNodeTextEditOnClickOuterv0.5.5+ | Boolean | true | Whether to end the editing status of node text when clicking on an area outside the canvas | |
| maxHistoryCountv0.5.6+ | Number | 1000 | | Maximum number of history records |
### Watermark config
@@ -354,6 +355,18 @@ smm (essentially also json)
`fileName`: (v0.1.6+) the name of the exported file, default is `思维导图` (mind
map).
If it is exported as `png`, the fourth parameter can be passed:
`transparent`: v0.5.7+, `Boolean`, default is `false`, Specify whether the background of the exported image is transparent
If it is exported as `svg`, the fourth parameter can be passed:
`plusCssText`: Additional `CSS` style. If there is a `dom` node in `svg`, you can pass in some styles specific to the node through this parameter
If it is exported as `json` or `smm`, the fourth parameter can be passed:
`withConfig`: `Boolean`, default is `true`, Specify whether the exported data includes configuration data, otherwise only pure node tree data will be exported
### toPos(x, y)
> v0.1.5+

View File

@@ -266,6 +266,13 @@
<td>Whether to end the editing status of node text when clicking on an area outside the canvas</td>
<td></td>
</tr>
<tr>
<td>maxHistoryCountv0.5.6+</td>
<td>Number</td>
<td>1000</td>
<td></td>
<td>Maximum number of history records</td>
</tr>
</tbody>
</table>
<h3>Watermark config</h3>
@@ -807,6 +814,12 @@ smm (essentially also json)</p>
<code>false</code></p>
<p><code>fileName</code>: (v0.1.6+) the name of the exported file, default is <code>思维导图</code> (mind
map).</p>
<p>If it is exported as <code>png</code>, the fourth parameter can be passed:</p>
<p><code>transparent</code>: v0.5.7+, <code>Boolean</code>, default is <code>false</code>, Specify whether the background of the exported image is transparent</p>
<p>If it is exported as <code>svg</code>, the fourth parameter can be passed:</p>
<p><code>plusCssText</code>: Additional <code>CSS</code> style. If there is a <code>dom</code> node in <code>svg</code>, you can pass in some styles specific to the node through this parameter</p>
<p>If it is exported as <code>json</code> or <code>smm</code>, the fourth parameter can be passed:</p>
<p><code>withConfig</code>: <code>Boolean</code>, default is <code>true</code>, Specify whether the exported data includes configuration data, otherwise only pure node tree data will be exported</p>
<h3>toPos(x, y)</h3>
<blockquote>
<p>v0.1.5+</p>

View File

@@ -0,0 +1 @@
# Deploy

View File

@@ -15,17 +15,19 @@ After registration and instantiation of `MindMap`, the instance can be obtained
## Methods
### png()
### png(name, transparent = false)
- `name`: Name, optional
- `transparent`: v0.5.7+, Specify whether the background of the exported image is transparent
Exports as `png`, an async method that returns image data, `data:url` data which
can be downloaded or displayed.
### svg(name, domToImage = false, plusCssText)
### svg(name, plusCssText)
- `name``svg` title
- `domToImage`v0.4.0+, When node rich text editing is enabled, you can use this parameter to specify whether to convert the `dom` node in the `svg` into a picture
- `plusCssText`v0.4.0+, When node rich text editing is enabled and `domToImage` passes `false`, additional `css` styles can be added. If there is a `dom` node in `svg`, you can set some styles for the node through this parameter, such as:
```js
@@ -43,9 +45,7 @@ svg(
Exports as `svg`, an async method that returns `svg` data, `data:url` data which
can be downloaded or displayed.
### getSvgData(domToImage)
- `domToImage`v0.4.0+, If node rich text is enabled, you can use this parameter to specify whether to convert the `DOM` node embedded in `svg` into a picture.
### getSvgData()
Gets `svg` data, an async method that returns an object:
@@ -71,4 +71,10 @@ Export as `pdf`
`withConfig``Boolean`, default `true`, Whether the data contains configuration, otherwise it is pure mind map node data
Return `json` data, `data:url` type, you can download it yourself
Return `json` data, `data:url` type, you can download it yourself
### md()
> v0.4.7+
Export as `markdown` file, Return `json` data, `data:url` type, you can download it yourself

View File

@@ -10,18 +10,23 @@ MindMap.usePlugin(Export)
</code></pre>
<p>After registration and instantiation of <code>MindMap</code>, the instance can be obtained through <code>mindMap.doExport</code>.</p>
<h2>Methods</h2>
<h3>png()</h3>
<h3>png(name, transparent = false)</h3>
<ul>
<li>
<p><code>name</code>: Name, optional</p>
</li>
<li>
<p><code>transparent</code>: v0.5.7+, Specify whether the background of the exported image is transparent</p>
</li>
</ul>
<p>Exports as <code>png</code>, an async method that returns image data, <code>data:url</code> data which
can be downloaded or displayed.</p>
<h3>svg(name, domToImage = false, plusCssText)</h3>
<h3>svg(name, plusCssText)</h3>
<ul>
<li>
<p><code>name</code><code>svg</code> title</p>
</li>
<li>
<p><code>domToImage</code>v0.4.0+, When node rich text editing is enabled, you can use this parameter to specify whether to convert the <code>dom</code> node in the <code>svg</code> into a picture</p>
</li>
<li>
<p><code>plusCssText</code>v0.4.0+, When node rich text editing is enabled and <code>domToImage</code> passes <code>false</code>, additional <code>css</code> styles can be added. If there is a <code>dom</code> node in <code>svg</code>, you can set some styles for the node through this parameter, such as:</p>
</li>
</ul>
@@ -37,10 +42,7 @@ can be downloaded or displayed.</p>
</code></pre>
<p>Exports as <code>svg</code>, an async method that returns <code>svg</code> data, <code>data:url</code> data which
can be downloaded or displayed.</p>
<h3>getSvgData(domToImage)</h3>
<ul>
<li><code>domToImage</code>v0.4.0+, If node rich text is enabled, you can use this parameter to specify whether to convert the <code>DOM</code> node embedded in <code>svg</code> into a picture.</li>
</ul>
<h3>getSvgData()</h3>
<p>Gets <code>svg</code> data, an async method that returns an object:</p>
<pre class="hljs"><code>{
node; <span class="hljs-comment">// svg object</span>
@@ -58,6 +60,11 @@ can be downloaded or displayed.</p>
<p><code>name</code>It is temporarily useless, just pass an empty string</p>
<p><code>withConfig``Boolean</code>, default <code>true</code>, Whether the data contains configuration, otherwise it is pure mind map node data</p>
<p>Return <code>json</code> data, <code>data:url</code> type, you can download it yourself</p>
<h3>md()</h3>
<blockquote>
<p>v0.4.7+</p>
</blockquote>
<p>Export as <code>markdown</code> file, Return <code>json</code> data, <code>data:url</code> type, you can download it yourself</p>
</div>
</template>

View File

@@ -10,9 +10,11 @@ By default, node editing can only uniformly apply styles to all text in the node
The principle of this plugin is to use [Quill](https://github.com/quilljs/quill) editor implements rich text editing, and then uses the edited `DOM` node directly as the text data of the node, and embeds the `DOM` node through the `svg` `foreignObject` tag during rendering.
This also caused a problem, that is, the function of exporting as a picture was affected, The original principle of exporting `svg` as an image is very simple, Get the `svg` string, and then create the `blob` data of the `type=image/svg+xml` type. Then use the `URL.createObjectURL` method to generate the `data:url` data. Then create a `Image` tag, use the `data:url` as the `src` of the image, and finally draw the image on the `canvas` object for export, However, after testing, when the `DOM` node is embedded in the `svg`, this method of export will cause errors, and after trying many ways, the perfect export effect cannot be achieved, The current method is to traverse the `foreignObject` node in `svg`, using [html2canvas](https://github.com/niklasvh/html2canvas) Convert the `DOM` node in the `foreignObject` node into an image and then replace the `foreignObject` node. This method can work, but it is very time-consuming. Because the `html2canvas` conversion takes a long time, it takes about 2 seconds to convert a node. This leads to the more nodes, the slower the conversion time. Therefore, it is recommended not to use this plugin if you cannot tolerate the long time of export.
> The following prompts exist in versions prior to v0.5.6:
>
> This also caused a problem, that is, the function of exporting as a picture was affected, The original principle of exporting `svg` as an image is very simple, Get the `svg` string, and then create the `blob` data of the `type=image/svg+xml` type. Then use the `URL.createObjectURL` method to generate the `data:url` data. Then create a `Image` tag, use the `data:url` as the `src` of the image, and finally draw the image on the `canvas` object for export, However, after testing, when the `DOM` node is embedded in the `svg`, this method of export will cause errors, and after trying many ways, the perfect export effect cannot be achieved, The current method is to traverse the `foreignObject` node in `svg`, using [html2canvas](https://github.com/niklasvh/html2canvas) Convert the `DOM` node in the `foreignObject` node into an image and then replace the `foreignObject` node. This method can work, but it is very time-consuming. Because the `html2canvas` conversion takes a long time, it takes about 2 seconds to convert a node. This leads to the more nodes, the slower the conversion time. Therefore, it is recommended not to use this plugin if you cannot tolerate the long time of export.
If you have a better way, please leave a message.
The version of `v0.5.7+` directly uses `html2canvas` to convert the entire `svg`, which is no longer an issue with speed. However, there is currently a bug where the color of the node does not take effect after export.
## Register

View File

@@ -10,8 +10,11 @@
<p>This plugin provides the ability to edit rich text of nodes, and takes effect after registration.</p>
<p>By default, node editing can only uniformly apply styles to all text in the node. This plugin can support rich text editing effects. Currently, it supports bold, italic, underline, strikethrough, font, font size, color, and backgroundColor. Underline and line height are not supported.</p>
<p>The principle of this plugin is to use <a href="https://github.com/quilljs/quill">Quill</a> editor implements rich text editing, and then uses the edited <code>DOM</code> node directly as the text data of the node, and embeds the <code>DOM</code> node through the <code>svg</code> <code>foreignObject</code> tag during rendering.</p>
<blockquote>
<p>The following prompts exist in versions prior to v0.5.6:</p>
<p>This also caused a problem, that is, the function of exporting as a picture was affected, The original principle of exporting <code>svg</code> as an image is very simple, Get the <code>svg</code> string, and then create the <code>blob</code> data of the <code>type=image/svg+xml</code> type. Then use the <code>URL.createObjectURL</code> method to generate the <code>data:url</code> data. Then create a <code>Image</code> tag, use the <code>data:url</code> as the <code>src</code> of the image, and finally draw the image on the <code>canvas</code> object for export, However, after testing, when the <code>DOM</code> node is embedded in the <code>svg</code>, this method of export will cause errors, and after trying many ways, the perfect export effect cannot be achieved, The current method is to traverse the <code>foreignObject</code> node in <code>svg</code>, using <a href="https://github.com/niklasvh/html2canvas">html2canvas</a> Convert the <code>DOM</code> node in the <code>foreignObject</code> node into an image and then replace the <code>foreignObject</code> node. This method can work, but it is very time-consuming. Because the <code>html2canvas</code> conversion takes a long time, it takes about 2 seconds to convert a node. This leads to the more nodes, the slower the conversion time. Therefore, it is recommended not to use this plugin if you cannot tolerate the long time of export.</p>
<p>If you have a better way, please leave a message.</p>
</blockquote>
<p>The version of <code>v0.5.7+</code> directly uses <code>html2canvas</code> to convert the entire <code>svg</code>, which is no longer an issue with speed. However, there is currently a bug where the color of the node does not take effect after export.</p>
<h2>Register</h2>
<pre class="hljs"><code><span class="hljs-keyword">import</span> MindMap <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map&#x27;</span>
<span class="hljs-keyword">import</span> RichText <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/RichText.js&#x27;</span>

View File

@@ -24,9 +24,8 @@ MindMap.xmind
### xmind.parseXmindFile(file)
Parsing the `.xmind` file and returning the parsed data. Note that this is
complete data, including the node tree, theme, and structure. You can use
`mindMap.setFullData(data)` to render the returned data to the canvas.
Parsing the `.xmind` file and returning the parsed data. You can use
`mindMap.setData(data)` to render the returned data to the canvas.
`file`: `File` object
@@ -35,9 +34,8 @@ complete data, including the node tree, theme, and structure. You can use
Convert `xmind` data. The `.xmind` file is essentially a `zip` file that can be
decompressed by changing the suffix to zip. Inside, there is a `content.json`
file. If you have parsed this file yourself, you can pass the contents of this
file to this method for conversion. The converted data is the complete data,
including the node tree, theme, structure, etc. You can use
`mindMap.setFullData(data)` to render the returned data to the canvas.
file to this method for conversion. You can use
`mindMap.setData(data)` to render the returned data to the canvas.
`content`: the contents of the `content.json` file within the `.xmind` zip
package

View File

@@ -15,17 +15,15 @@
</code></pre>
<h2>Methods</h2>
<h3>xmind.parseXmindFile(file)</h3>
<p>Parsing the <code>.xmind</code> file and returning the parsed data. Note that this is
complete data, including the node tree, theme, and structure. You can use
<code>mindMap.setFullData(data)</code> to render the returned data to the canvas.</p>
<p>Parsing the <code>.xmind</code> file and returning the parsed data. You can use
<code>mindMap.setData(data)</code> to render the returned data to the canvas.</p>
<p><code>file</code>: <code>File</code> object</p>
<h3>xmind.transformXmind(content)</h3>
<p>Convert <code>xmind</code> data. The <code>.xmind</code> file is essentially a <code>zip</code> file that can be
decompressed by changing the suffix to zip. Inside, there is a <code>content.json</code>
file. If you have parsed this file yourself, you can pass the contents of this
file to this method for conversion. The converted data is the complete data,
including the node tree, theme, structure, etc. You can use
<code>mindMap.setFullData(data)</code> to render the returned data to the canvas.</p>
file to this method for conversion. You can use
<code>mindMap.setData(data)</code> to render the returned data to the canvas.</p>
<p><code>content</code>: the contents of the <code>content.json</code> file within the <code>.xmind</code> zip
package</p>
<h3>xmind.transformOldXmind(content)</h3>

View File

@@ -41,7 +41,8 @@ export default [
{ path: 'utils', title: '内置工具方法' },
{ path: 'view', title: 'View实例' },
{ path: 'watermark', title: 'Watermark插件' },
{ path: 'xmind', title: 'XMind解析' }
{ path: 'xmind', title: 'XMind解析' },
{ path: 'deploy', title: '部署' }
]
},
{
@@ -68,7 +69,8 @@ export default [
{ path: 'utils', title: 'Utility Methods' },
{ path: 'view', title: 'View instance' },
{ path: 'watermark', title: 'Watermark plugin' },
{ path: 'xmind', title: 'XMind parse' }
{ path: 'xmind', title: 'XMind parse' },
{ path: 'deploy', title: 'Deploy' }
]
}
]

View File

@@ -1,9 +1,33 @@
# Changelog
## 0.5.7
破坏性更新富文本模式下导出png改为使用html2canvas转换整个svg大幅提高导出速度不过html2canvas存在一个bugforeignObject元素中的dom节点内联的文字颜色无法识别所以导出节点的文字颜色是固定的不过相对于之前的导出基本不可用状态目前至少能快速顺利的导出。
优化:优化富文本节点编辑体验。
新增:富文本模式下,导入数据、初始化数据、切换主题场景节点样式支持跟随主题变化。
## 0.5.6
修复1.修复短时间快速多次渲染时节点位置错乱的问题。 2.修复节点正在编辑中时拖动画布导致编辑框和节点分离的问题。
新增1.添加最大历史记录数限制。
## 0.5.5
新增1.支持配置导出为png、svg、pdf时的内边距。 2.支持配置节点文本编辑框、节点备注浮层元素的z-index。 3.支持点击画布外的区域结束节点编辑状态。
## 0.5.5-fix.1
修复1.修复节点在画布外编辑时编辑框也在画布外的问题。 2.修改结构后复位变换,防止存在缩放时切换结构后第一次拖动时会发生位置突变的问题。
优化1.节点多选时只要节点和选区存在交叉即认为被选中。
## 0.5.5-fix.2
修复1.修复小地图报错。
## 0.5.4
新增1.添加新主题。 2.新增时间轴和鱼骨结构。

View File

@@ -1,8 +1,20 @@
<template>
<div>
<h1>Changelog</h1>
<h2>0.5.7</h2>
<p>破坏性更新富文本模式下导出png改为使用html2canvas转换整个svg大幅提高导出速度不过html2canvas存在一个bugforeignObject元素中的dom节点内联的文字颜色无法识别所以导出节点的文字颜色是固定的不过相对于之前的导出基本不可用状态目前至少能快速顺利的导出</p>
<p>优化优化富文本节点编辑体验</p>
<p>新增富文本模式下导入数据初始化数据切换主题场景节点样式支持跟随主题变化</p>
<h2>0.5.6</h2>
<p>修复1.修复短时间快速多次渲染时节点位置错乱的问题 2.修复节点正在编辑中时拖动画布导致编辑框和节点分离的问题</p>
<p>新增1.添加最大历史记录数限制</p>
<h2>0.5.5</h2>
<p>新增1.支持配置导出为pngsvgpdf时的内边距 2.支持配置节点文本编辑框节点备注浮层元素的z-index 3.支持点击画布外的区域结束节点编辑状态</p>
<h2>0.5.5-fix.1</h2>
<p>修复1.修复节点在画布外编辑时编辑框也在画布外的问题 2.修改结构后复位变换防止存在缩放时切换结构后第一次拖动时会发生位置突变的问题</p>
<p>优化1.节点多选时只要节点和选区存在交叉即认为被选中</p>
<h2>0.5.5-fix.2</h2>
<p>修复1.修复小地图报错</p>
<h2>0.5.4</h2>
<p>新增1.添加新主题 2.新增时间轴和鱼骨结构</p>
<p>修复1.修复节点右键和画布右键的冲突问题 2.修复组织结构图目录组织图等节点拖拽时存在线段未隐藏的bug</p>

View File

@@ -58,6 +58,7 @@ const mindMap = new MindMap({
| nodeTextEditZIndexv0.5.5+ | Number | 3000 | 节点文本编辑框元素的z-index | |
| nodeNoteTooltipZIndexv0.5.5+ | Number | 3000 | 节点备注浮层元素的z-index | |
| isEndNodeTextEditOnClickOuterv0.5.5+ | Boolean | true | 是否在点击了画布外的区域时结束节点文本的编辑状态 | |
| maxHistoryCountv0.5.6+ | Number | 1000 | 最大历史记录数 | |
### 水印配置
@@ -343,6 +344,18 @@ mindMap.updateConfig({
`fileName`v0.1.6+)导出文件的名称,默认为`思维导图`
如果是导出为`png`,那么可以传递第四个参数:
`transparent`v0.5.7+, `Boolean`,默认为`false`,指定导出图片的背景是否是透明的
如果是导出为`svg`,那么可以传递第四个参数:
`plusCssText`:附加的`css`样式,如果`svg`中存在`dom`节点,想要设置一些针对节点的样式可以通过这个参数传入
如果是导出为`json``smm`,那么可以传递第四个参数:
`withConfig``Boolean`,默认为`true`,指定导出的数据中是否包含配置数据,否则只导出纯节点树数据
### toPos(x, y)
> v0.1.5+

View File

@@ -266,6 +266,13 @@
<td>是否在点击了画布外的区域时结束节点文本的编辑状态</td>
<td></td>
</tr>
<tr>
<td>maxHistoryCountv0.5.6+</td>
<td>Number</td>
<td>1000</td>
<td>最大历史记录数</td>
<td></td>
</tr>
</tbody>
</table>
<h3>水印配置</h3>
@@ -796,6 +803,12 @@ mindMap.setTheme(<span class="hljs-string">&#x27;主题名称&#x27;</span>)
<p><code>type</code>要导出的类型可选值pngsvgjsonpdfv0.2.1+smm本质也是json</p>
<p><code>isDownload</code>是否需要直接触发下载布尔值默认为<code>false</code></p>
<p><code>fileName</code>v0.1.6+导出文件的名称默认为<code>思维导图</code></p>
<p>如果是导出为<code>png</code>那么可以传递第四个参数</p>
<p><code>transparent</code>v0.5.7+, <code>Boolean</code>默认为<code>false</code>指定导出图片的背景是否是透明的</p>
<p>如果是导出为<code>svg</code>那么可以传递第四个参数</p>
<p><code>plusCssText</code>附加的<code>css</code>样式如果<code>svg</code>中存在<code>dom</code>节点想要设置一些针对节点的样式可以通过这个参数传入</p>
<p>如果是导出为<code>json</code><code>smm</code>那么可以传递第四个参数</p>
<p><code>withConfig</code><code>Boolean</code>默认为<code>true</code>指定导出的数据中是否包含配置数据否则只导出纯节点树数据</p>
<h3>toPos(x, y)</h3>
<blockquote>
<p>v0.1.5+</p>

View File

@@ -1,6 +1,6 @@
# 结构
`simple-mind-map`目前支持四种结构logicalStructure逻辑结构图、mindMap思维导图、organizationStructure组织结构图、catalogOrganization目录组织图
`simple-mind-map`目前支持四种结构logicalStructure逻辑结构图、mindMap思维导图、organizationStructure组织结构图、catalogOrganization目录组织图、timeline时间轴、timeline2时间轴2、fishbone鱼骨图
可以在实例化`simple-mind-map`时通过选项指定使用的结构:

View File

@@ -1,7 +1,7 @@
<template>
<div>
<h1>结构</h1>
<p><code>simple-mind-map</code>目前支持四种结构logicalStructure逻辑结构图mindMap思维导图organizationStructure组织结构图catalogOrganization目录组织图</p>
<p><code>simple-mind-map</code>目前支持四种结构logicalStructure逻辑结构图mindMap思维导图organizationStructure组织结构图catalogOrganization目录组织图timeline时间轴timeline2时间轴2fishbone鱼骨图</p>
<p>可以在实例化<code>simple-mind-map</code>时通过选项指定使用的结构</p>
<pre class="hljs"><code><span class="hljs-keyword">new</span> MindMap({
<span class="hljs-comment">// ...</span>

View File

@@ -1,3 +1,101 @@
# 如何渲染富文本的悬浮工具栏
> 要支持节点富文本编辑需要使用富文本插件
> 要支持节点富文本编辑需要使用富文本插件
如果开启了节点富文本编辑,那么可以对节点内的部分文本应用样式,一般当选中文本时上方会出现一个工具栏,有加粗、斜体、改变颜色等等的按钮。
首先要监听`rich_text_selection_change`事件,也就是选中文本的事件:
```js
mindMap.on('rich_text_selection_change', (hasRange, rect, formatInfo) => {
// hasRange是否存在选区
// rectInfo选区的尺寸和位置信息
// formatInfo选区的文本格式化信息
// 显示你的工具栏
})
```
可以通过`hasRange`来判断是否显示工具栏,工具栏的位置可以通过`rectInfo`获取,通过`formatInfo`可以获取当前选中文本的样式信息,比如已经被加粗了,那么你的加粗按钮就可以渲染为激活状态。
### 工具栏定位
```js
let left = rect.left + rect.width / 2 + 'px'
let top = rect.top - 60 + 'px'
```
计算出来的是相对于浏览器窗口左上角的位置所以你的工具栏元素最好是添加在body元素下面并且使用固定定位或相对定位另外`z-index`的属性最好也设置的高一点,否则在弹窗等场景下可能会被挡住。
### 加粗/取消加粗
```js
mindMap.richText.formatText({
bold: true/false
})
```
### 斜体/取消斜体
```js
mindMap.richText.formatText({
italic: true/false
})
```
### 下划线/取消下划线
```js
mindMap.richText.formatText({
underline: true/false
})
```
### 删除线/取消删除线
```js
mindMap.richText.formatText({
strike: true/false
})
```
### 设置字体
```js
mindMap.richText.formatText({
font: '宋体, SimSun, Songti SC'
})
```
### 设置字号
```js
mindMap.richText.formatText({
font: 16 + 'px'
})
```
### 设置文字颜色
```js
mindMap.richText.formatText({
color: '#fff'
})
```
### 设置文字背景颜色
```js
mindMap.richText.formatText({
background: '#fff'
})
```
### 清除样式
```js
mindMap.richText.removeFormat()
```
## 完整示例
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFVk+P20QU/yqDUWUHEiegcjHZUlpA6mERWhUJialWjj2OB8Yzlj1Otkp96akUJCQOcOFQceHeA6J74ct0u+Vb8N6M/202aXMjUaKZN7/3fm/m/ZnZOJ/mub+qmBM48zIqeK5JyXSV36KSZ7kqNNmQgiVjouSxqqRm8ZiUaSiEWp+whNQkKVRGXLDgdhrHXMbHYW6XqFOCWLBJBtJJFubUoZIQKgXTBGWIPCKyEoJKKiMlS3AhVev7SolFWMAa8HtJKEo2atcFS3SzMOuEWqGhq7KoKgom9ReqyEJ9TyaqQWxqgPSKy6Vgd5SIYdUbkaNbZIM+XlP2V6GomL+wyHfetI76ze78gkfpfXam/cQgcegZAkIQGryRCHHobL3t79cyZoXgkh3mdDWA7/W8Ax3ofoffv4crJrc2EqWhXLK7SigM8wGbiBqoW7AY8u0gH41O0Kt0PnQZ7Q2YBxnJ1m0mt6aYCEisoioD3/wl058LhsM7D+/Fntto3lVSh7Ddwh2NrVYc6jCw1vFDHRRQZyCyYg1Oo5g6r569eP308eXjF7ZW8FM3xhAYpVzEcD4I/ra3sWVuJ8s208vzny7P/9wmu0q4g/RBvzbE/U8etMNG1upxyfWJUvpLFbOvVMk1VxI0XWwe7pi4EYQOwvTAwOvRx7Yt9TmlpOdiXp2is6clEyxCE6c2a8GCl4blCY7H0FMiPSY2+TBf+4QCP5IeOeoPA92waW16UqR909Xet+M1j3VKpuRDELj5GWQu1fiFJndVCbvehNycdTg8KwDuKSFQG4TD932vd5o8ekRMY9w6VyxWJZgv1NLbY7ZRalQG7btjbU8AN2JKEH7zqb1y4LKBiWZwT4SawYyQecxXJBJhWR5Rp4nIZyxT1DHLDYDH/WpXeACZT2F1CGwtaesVdchqwhMQDFwFYVDqh4KBeIOxCEyMxnixBPhXd+RgdVFBLCS5HQkefW8st5cIoDabHS3MXBufEPfi519f/fXk4umzy+e/uQQ6UzOs6/nUWn0bS9f691H1zb7je/n3jxdPfrk8/8dQ9rNDWAd9Ghj//eP31z8839LqT7wdzaeDgMLUHK1B3G7eCdTxp/Zx0PRZn5WZH5Uldbpi9AexbzPX1EZAPpjNbhgcIXlX3gUDRr5iZsGkI/7e3c6R1lSvGC4gxyttFW11BmTWzEwGtJPr9CnjyxTgN2ez/Kxl3s37XsuchcWSA29rNQ/jmMtlK+hchwK376C3eayLUJZYzJCrOMST/8abfDS7YVsbWIR6M0Fwxo4NAb7H/O9KJeH5Z+zTZgFC0PVt6sDrzjZrfwpDv4Bbk2cMozVZFGpdsgKMUKfpuztefFb3eqxRq/Gtdur/AJ09jnE=" />

View File

@@ -4,7 +4,66 @@
<blockquote>
<p>要支持节点富文本编辑需要使用富文本插件</p>
</blockquote>
<p>如果开启了节点富文本编辑那么可以对节点内的部分文本应用样式一般当选中文本时上方会出现一个工具栏有加粗斜体改变颜色等等的按钮</p>
<p>首先要监听<code>rich_text_selection_change</code>事件也就是选中文本的事件</p>
<pre class="hljs"><code>mindMap.on(<span class="hljs-string">&#x27;rich_text_selection_change&#x27;</span>, <span class="hljs-function">(<span class="hljs-params">hasRange, rect, formatInfo</span>) =&gt;</span> {
<span class="hljs-comment">// hasRange是否存在选区</span>
<span class="hljs-comment">// rectInfo选区的尺寸和位置信息</span>
<span class="hljs-comment">// formatInfo选区的文本格式化信息</span>
<span class="hljs-comment">// 显示你的工具栏</span>
})
</code></pre>
<p>可以通过<code>hasRange</code>来判断是否显示工具栏工具栏的位置可以通过<code>rectInfo</code>获取通过<code>formatInfo</code>可以获取当前选中文本的样式信息比如已经被加粗了那么你的加粗按钮就可以渲染为激活状态</p>
<h3>工具栏定位</h3>
<pre class="hljs"><code><span class="hljs-keyword">let</span> left = rect.left + rect.width / <span class="hljs-number">2</span> + <span class="hljs-string">&#x27;px&#x27;</span>
<span class="hljs-keyword">let</span> top = rect.top - <span class="hljs-number">60</span> + <span class="hljs-string">&#x27;px&#x27;</span>
</code></pre>
<p>计算出来的是相对于浏览器窗口左上角的位置所以你的工具栏元素最好是添加在body元素下面并且使用固定定位或相对定位另外<code>z-index</code>的属性最好也设置的高一点否则在弹窗等场景下可能会被挡住</p>
<h3>加粗/取消加粗</h3>
<pre class="hljs"><code>mindMap.richText.formatText({
<span class="hljs-attr">bold</span>: <span class="hljs-literal">true</span>/<span class="hljs-literal">false</span>
})
</code></pre>
<h3>斜体/取消斜体</h3>
<pre class="hljs"><code>mindMap.richText.formatText({
<span class="hljs-attr">italic</span>: <span class="hljs-literal">true</span>/<span class="hljs-literal">false</span>
})
</code></pre>
<h3>下划线/取消下划线</h3>
<pre class="hljs"><code>mindMap.richText.formatText({
<span class="hljs-attr">underline</span>: <span class="hljs-literal">true</span>/<span class="hljs-literal">false</span>
})
</code></pre>
<h3>删除线/取消删除线</h3>
<pre class="hljs"><code>mindMap.richText.formatText({
<span class="hljs-attr">strike</span>: <span class="hljs-literal">true</span>/<span class="hljs-literal">false</span>
})
</code></pre>
<h3>设置字体</h3>
<pre class="hljs"><code>mindMap.richText.formatText({
<span class="hljs-attr">font</span>: <span class="hljs-string">&#x27;宋体, SimSun, Songti SC&#x27;</span>
})
</code></pre>
<h3>设置字号</h3>
<pre class="hljs"><code>mindMap.richText.formatText({
<span class="hljs-attr">font</span>: <span class="hljs-number">16</span> + <span class="hljs-string">&#x27;px&#x27;</span>
})
</code></pre>
<h3>设置文字颜色</h3>
<pre class="hljs"><code>mindMap.richText.formatText({
<span class="hljs-attr">color</span>: <span class="hljs-string">&#x27;#fff&#x27;</span>
})
</code></pre>
<h3>设置文字背景颜色</h3>
<pre class="hljs"><code>mindMap.richText.formatText({
<span class="hljs-attr">background</span>: <span class="hljs-string">&#x27;#fff&#x27;</span>
})
</code></pre>
<h3>清除样式</h3>
<pre class="hljs"><code>mindMap.richText.removeFormat()
</code></pre>
<h2>完整示例</h2>
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFVk+P20QU/yqDUWUHEiegcjHZUlpA6mERWhUJialWjj2OB8Yzlj1Otkp96akUJCQOcOFQceHeA6J74ct0u+Vb8N6M/202aXMjUaKZN7/3fm/m/ZnZOJ/mub+qmBM48zIqeK5JyXSV36KSZ7kqNNmQgiVjouSxqqRm8ZiUaSiEWp+whNQkKVRGXLDgdhrHXMbHYW6XqFOCWLBJBtJJFubUoZIQKgXTBGWIPCKyEoJKKiMlS3AhVev7SolFWMAa8HtJKEo2atcFS3SzMOuEWqGhq7KoKgom9ReqyEJ9TyaqQWxqgPSKy6Vgd5SIYdUbkaNbZIM+XlP2V6GomL+wyHfetI76ze78gkfpfXam/cQgcegZAkIQGryRCHHobL3t79cyZoXgkh3mdDWA7/W8Ax3ofoffv4crJrc2EqWhXLK7SigM8wGbiBqoW7AY8u0gH41O0Kt0PnQZ7Q2YBxnJ1m0mt6aYCEisoioD3/wl058LhsM7D+/Fntto3lVSh7Ddwh2NrVYc6jCw1vFDHRRQZyCyYg1Oo5g6r569eP308eXjF7ZW8FM3xhAYpVzEcD4I/ra3sWVuJ8s208vzny7P/9wmu0q4g/RBvzbE/U8etMNG1upxyfWJUvpLFbOvVMk1VxI0XWwe7pi4EYQOwvTAwOvRx7Yt9TmlpOdiXp2is6clEyxCE6c2a8GCl4blCY7H0FMiPSY2+TBf+4QCP5IeOeoPA92waW16UqR909Xet+M1j3VKpuRDELj5GWQu1fiFJndVCbvehNycdTg8KwDuKSFQG4TD932vd5o8ekRMY9w6VyxWJZgv1NLbY7ZRalQG7btjbU8AN2JKEH7zqb1y4LKBiWZwT4SawYyQecxXJBJhWR5Rp4nIZyxT1DHLDYDH/WpXeACZT2F1CGwtaesVdchqwhMQDFwFYVDqh4KBeIOxCEyMxnixBPhXd+RgdVFBLCS5HQkefW8st5cIoDabHS3MXBufEPfi519f/fXk4umzy+e/uQQ6UzOs6/nUWn0bS9f691H1zb7je/n3jxdPfrk8/8dQ9rNDWAd9Ghj//eP31z8839LqT7wdzaeDgMLUHK1B3G7eCdTxp/Zx0PRZn5WZH5Uldbpi9AexbzPX1EZAPpjNbhgcIXlX3gUDRr5iZsGkI/7e3c6R1lSvGC4gxyttFW11BmTWzEwGtJPr9CnjyxTgN2ez/Kxl3s37XsuchcWSA29rNQ/jmMtlK+hchwK376C3eayLUJZYzJCrOMST/8abfDS7YVsbWIR6M0Fwxo4NAb7H/O9KJeH5Z+zTZgFC0PVt6sDrzjZrfwpDv4Bbk2cMozVZFGpdsgKMUKfpuztefFb3eqxRq/Gtdur/AJ09jnE=" />
</div>
</template>

View File

@@ -1,3 +1,241 @@
# 导入和导出
编写中。。。
## 导出
> 要使用导出功能需要使用导出插件。
目前支持导出为`.smm``.json``.svg``.png``.pdf``.md`文件。
`.smm``simple-mind-map`自己定义的一种文件,其实就是`json`文件,换了一个扩展名而已。
导出直接调用`export`方法即可。
### 导出为smm、json
这两种文件的导出是一样的:
```js
mindMap.export(type, isDownload, fileName, withConfig)
```
`withConfig`指定导出的数据中是否要包含节点数据外的配置数据,比如使用的布局、主题等,传`true`,导出的结构如下:
```js
{
layout,
root,
theme: {
template,
config
},
view
}
```
如果传`false`,导出的数据只有`root`部分,也就是纯节点树。
示例:
```js
mindMap.export('smm', true, '文件名', true)
mindMap.export('json', true, '文件名', false)
```
### 导出为png、pdf
导出这两种文件很简单:
```js
mindMap.export('png', true, '文件名')
mindMap.export('pdf', true, '文件名')
```
### 导出为svg
导出为`svg`可以传递的参数如下:
```js
mindMap.export(type, isDownload, fileName, plusCssText = '')
```
如果开启了节点富文本编辑,也就是`svg`中会存在节点的`html`结构,这就又存在一个问题,因为浏览器对每个元素默认会设置一些样式,影响最大的就是`margin``padding`,这就有可能会导致节点中的文字错位,所以可以通过`plusCssText`参数传入`css`样式:
```js
mindMap.export(
'svg',
true,
'文件名',
false,
`* {
margin: 0;
padding: 0;
box-sizing: border-box;
}`
)
```
### 导出为md
导出为`markdown`文件只要传递默认的三个参数即可:
```js
mindMap.export('md', true, '文件名')
```
## 导入
目前支持从`.smm``.json``.xmind``.xlsx``.md`格式的文件导入。
### 导入smm、json
这两个文件导入很简单,直接读取文件内容,转成对象,然后调用相关方法渲染到画布即可。
因为导出这两种类型时可以选择是否包含配置数据,所以导入的时候调用的方法也是不一样的:
```js
let data = JSON.parse('json数据')
// 如果数据中存在root属性那么代表是包含配置的完整数据则使用setFullData方法导入数据
if (data.root) {
mindMap.setFullData(data)
} else {
// 否则使用setData方法导入
mindMap.setData(data)
}
// 导入数据后有可能新数据渲染在可视区域外了,所以为了更好的体验,可以复位一下视图的变换
mindMap.view.reset()
```
### 导入xmind
要导入`xmind`文件,需要引入`xmind`的解析方法:
```js
import xmind from 'simple-mind-map/src/parse/xmind.js'
```
如果使用的是`umd`文件,可以这样获取:
```js
MindMap.xmind
```
如果你是通过`input type=file`等方式获取到的`File`文件对象,那么可以直接传递给`parseXmindFile`方法解析,注意返回的是一个`Promise`实例,会返回解析后的节点树数据,使用`setData`方法渲染到画布即可。
```js
let data = await xmind.parseXmindFile(file)
mindMap.setData(data)
```
`.xmind`文件本质上是一个压缩包,改成`zip`后缀可以解压缩,里面存在一个`content.json`文件,如果你自己解析出了这个文件,那么可以把这个文件内容传递给这个`transformXmind`方法进行转换:
```js
let data = await xmind.transformXmind(fileContent)
mindMap.setData(data)
```
另外如果导入的是`xmind8`版本的数据,需要使用`transformOldXmind`方法。
### 导入xlsx
这个文件的导入没有内置方法,需要你自己开发,以下是一个使用`xlsx`库的方式:
```js
import { read, utils } from 'xlsx'
// 文件转buffer
export const fileToBuffer = file => {
return new Promise(r => {
const reader = new FileReader()
reader.onload = () => {
r(reader.result)
}
reader.readAsArrayBuffer(file)
})
}
// File文件对象
const transformXLSXToJson = async (file) => {
const wb = read(await fileToBuffer(file))
const data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], {
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)
}
}
}
return layers[0][0]
}
let data = transformXLSXToJson('xlsx文件对象')
mindMap.setData(data)
```
### 导入md
要导入`markdown`文件需要引入相应的解析方法:
```js
import markdown from 'simple-mind-map/src/parse/markdown.js'
```
如果使用的是umd格式的文件那么可以通过如下方式获取
```js
MindMap.markdown
```
获取到`md`文件的内容后调用`transformMarkdownTo`方法转换即可,返回一个`Promise`实例:
```js
let data = await markdown.transformMarkdownTo('md文件内容')
mindMap.setData(data)
```
### 完整示例
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFV81u20YQfpUN24JUoVAK0JMrB3YbB0gApYGTQ4EwhzW5kuiQu8TuypJhC0iDJkHaBsglPfXQQ4Oil7intnb7NvVf36KzfxRNUXUuQQ3bImdnvm925tsf7XnrRRHujIm34vVEzNNCIkHkuLge0TQvGJdoD3EyaCNG+2xMJUnaSIxwlrHJJhmgGRpwliMfEPwyop/SpI8LMxR5AswZuZqD9WqOi8iLKEIRzYhEyqY8VxEdZ1lEI9rpoJODv06evjn9/vnxn79FNGZUSGC/RYuxBMeAtNDqdbSnQBTEIM0ImEkoMR8SGap38aD70I1zMoThThSFgcjz/ami3N8WjO5PMzHdz5PWhx3lmw5QcAWcQ0mEDBRKSHFOWi1DhRDOCJeBf37w+z+PX5x++0sIcH8//ipUWOpTI+sHwFWfeWLm4LcMAIe6cpgiQjNHWKalQCCRpeQjTJOM3MtzPagBZ4hkgpQwmv5ShC+V13IMyPxSiI1pTLKlEPnlOfSrCUQUfk3bf/r67NWzalUvSqAsgRKBBljQwSbBCeFKTWSCbpaGQFPNHUIOH+viPpmaLOvDjGYMJ0pUO6A4y4GQ5Lvu0VAmWGLwun3vizthgbkgAQQ4HXIixpm0rTfdlrsFYQMTdmV1Fflsa5vE0i/ro1lGnE30BDY4ZzzwTRVOnj09eXt4+sOL84MDJygrJPUDa/YGwAYK247OUIxlPIIFo3AqHKqcDHqTsaEdswHNDdHCamqF1hLMH4tdGtdbUilWpVR4gtNyfzDIpnIaS3Vs3pCGSS2ZUvOEGmYC8m6aiFb0solA+NnrN3WsHPNHCZs0SrS/tCjvS6eG7N3UerEFbh6h5JiKAeN531rus/9Q83uRm9n2X/96+vKtK6jlUSteU5XzU8tJWULOmCzJ7HESQthNOE/q4tEb1aJrzU39c6M7KZmouROpelPN9PnR8R9HapNyqZKpOvvWxW2wqXxVrprLYRmHwFdBfhv6MyZt5Fb3q5dgGmBIsImnoMM6zV2qjjXNYqZUp4GYJhYHXx7nwSKGFabVSGArRrIVlLB4nBMqQ9DERkbU42e7t5LAt5GfMypxSgn3W20Tpeq6MhdD5ClD5FVMxixB5Moceac/Hp5/8+TsyaG5KGipWDDlGI/SLOGEKucHc4waXCNLnen46Luzo5/rZBcJG0j11WLR73/KwD1am4tLaSo3YW3cYQm5y0QqU0Yh0s/IQIIu/BhaB216qN1nrU9BFiCNXsfcAeH2By+SwMUNSwJvCPWSdAfFGRZiNfJsu2+QnEWeHrYOaTIfLcUALr0OjFYdHZJkLNvCysUMRrKX6pueOixhXG13kYfWtBHe7UWw9Ae4rbGUsObW4iyNH4FLdSWC34XV2usY78ujYYFVg2E51WLnU3JPvU6lYvAq5G5mirdmb8aRF3bMddhtwETkYSxE5EEH1K0YobBSXKeeSZrI0Qq61u1+pP0QKsqecgKM6Q7RA1oO6u+DehMc1DwQb8G+PJYmUJ0RA7mCuvZNsmL+skg/IulwBO6fdLvF1DE3835cbrlwlKTA61ALnCQpHTpDmXpoNfGOGV9zGdiky3cABD3rHnhtz3RAfQHRd0v4vqPhIzsAHSjXauTB1xmzQMMOPIYcdso0J6pZV7fgeibg8N2GCLvWGr7imNjFVqsod+55s38BRuykJA==" />

View File

@@ -1,8 +1,180 @@
<template>
<div>
<h1>导入和导出</h1>
<p>编写中</p>
<h2>导出</h2>
<blockquote>
<p>要使用导出功能需要使用导出插件</p>
</blockquote>
<p>目前支持导出为<code>.smm</code><code>.json</code><code>.svg</code><code>.png</code><code>.pdf</code><code>.md</code>文件</p>
<p><code>.smm</code><code>simple-mind-map</code>自己定义的一种文件其实就是<code>json</code>文件换了一个扩展名而已</p>
<p>导出直接调用<code>export</code>方法即可</p>
<h3>导出为smmjson</h3>
<p>这两种文件的导出是一样的</p>
<pre class="hljs"><code>mindMap.export(type, isDownload, fileName, withConfig)
</code></pre>
<p><code>withConfig</code>指定导出的数据中是否要包含节点数据外的配置数据比如使用的布局主题等<code>true</code>导出的结构如下</p>
<pre class="hljs"><code>{
layout,
root,
<span class="hljs-attr">theme</span>: {
template,
config
},
view
}
</code></pre>
<p>如果传<code>false</code>导出的数据只有<code>root</code>部分也就是纯节点树</p>
<p>示例</p>
<pre class="hljs"><code>mindMap.export(<span class="hljs-string">&#x27;smm&#x27;</span>, <span class="hljs-literal">true</span>, <span class="hljs-string">&#x27;文件名&#x27;</span>, <span class="hljs-literal">true</span>)
mindMap.export(<span class="hljs-string">&#x27;json&#x27;</span>, <span class="hljs-literal">true</span>, <span class="hljs-string">&#x27;文件名&#x27;</span>, <span class="hljs-literal">false</span>)
</code></pre>
<h3>导出为pngpdf</h3>
<p>导出这两种文件很简单</p>
<pre class="hljs"><code>mindMap.export(<span class="hljs-string">&#x27;png&#x27;</span>, <span class="hljs-literal">true</span>, <span class="hljs-string">&#x27;文件名&#x27;</span>)
mindMap.export(<span class="hljs-string">&#x27;pdf&#x27;</span>, <span class="hljs-literal">true</span>, <span class="hljs-string">&#x27;文件名&#x27;</span>)
</code></pre>
<h3>导出为svg</h3>
<p>导出为<code>svg</code>可以传递的参数如下</p>
<pre class="hljs"><code>mindMap.export(type, isDownload, fileName, plusCssText = <span class="hljs-string">&#x27;&#x27;</span>)
</code></pre>
<p>如果开启了节点富文本编辑也就是<code>svg</code>中会存在节点的<code>html</code>结构这就又存在一个问题因为浏览器对每个元素默认会设置一些样式影响最大的就是<code>margin</code><code>padding</code>这就有可能会导致节点中的文字错位所以可以通过<code>plusCssText</code>参数传入<code>css</code>样式</p>
<pre class="hljs"><code>mindMap.export(
<span class="hljs-string">&#x27;svg&#x27;</span>,
<span class="hljs-literal">true</span>,
<span class="hljs-string">&#x27;文件名&#x27;</span>,
<span class="hljs-literal">false</span>,
<span class="hljs-string">`* {
margin: 0;
padding: 0;
box-sizing: border-box;
}`</span>
)
</code></pre>
<h3>导出为md</h3>
<p>导出为<code>markdown</code>文件只要传递默认的三个参数即可</p>
<pre class="hljs"><code>mindMap.export(<span class="hljs-string">&#x27;md&#x27;</span>, <span class="hljs-literal">true</span>, <span class="hljs-string">&#x27;文件名&#x27;</span>)
</code></pre>
<h2>导入</h2>
<p>目前支持从<code>.smm</code><code>.json</code><code>.xmind</code><code>.xlsx</code><code>.md</code>格式的文件导入</p>
<h3>导入smmjson</h3>
<p>这两个文件导入很简单直接读取文件内容转成对象然后调用相关方法渲染到画布即可</p>
<p>因为导出这两种类型时可以选择是否包含配置数据所以导入的时候调用的方法也是不一样的</p>
<pre class="hljs"><code><span class="hljs-keyword">let</span> data = <span class="hljs-built_in">JSON</span>.parse(<span class="hljs-string">&#x27;json数据&#x27;</span>)
<span class="hljs-comment">// 如果数据中存在root属性那么代表是包含配置的完整数据则使用setFullData方法导入数据</span>
<span class="hljs-keyword">if</span> (data.root) {
mindMap.setFullData(data)
} <span class="hljs-keyword">else</span> {
<span class="hljs-comment">// 否则使用setData方法导入</span>
mindMap.setData(data)
}
<span class="hljs-comment">// 导入数据后有可能新数据渲染在可视区域外了,所以为了更好的体验,可以复位一下视图的变换</span>
mindMap.view.reset()
</code></pre>
<h3>导入xmind</h3>
<p>要导入<code>xmind</code>文件需要引入<code>xmind</code>的解析方法</p>
<pre class="hljs"><code><span class="hljs-keyword">import</span> xmind <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/parse/xmind.js&#x27;</span>
</code></pre>
<p>如果使用的是<code>umd</code>文件可以这样获取</p>
<pre class="hljs"><code>MindMap.xmind
</code></pre>
<p>如果你是通过<code>input type=file</code>等方式获取到的<code>File</code>文件对象那么可以直接传递给<code>parseXmindFile</code>方法解析注意返回的是一个<code>Promise</code>实例会返回解析后的节点树数据使用<code>setData</code>方法渲染到画布即可</p>
<pre class="hljs"><code><span class="hljs-keyword">let</span> data = <span class="hljs-keyword">await</span> xmind.parseXmindFile(file)
mindMap.setData(data)
</code></pre>
<p><code>.xmind</code>文件本质上是一个压缩包改成<code>zip</code>后缀可以解压缩里面存在一个<code>content.json</code>文件如果你自己解析出了这个文件那么可以把这个文件内容传递给这个<code>transformXmind</code>方法进行转换</p>
<pre class="hljs"><code><span class="hljs-keyword">let</span> data = <span class="hljs-keyword">await</span> xmind.transformXmind(fileContent)
mindMap.setData(data)
</code></pre>
<p>另外如果导入的是<code>xmind8</code>版本的数据需要使用<code>transformOldXmind</code>方法</p>
<h3>导入xlsx</h3>
<p>这个文件的导入没有内置方法需要你自己开发以下是一个使用<code>xlsx</code>库的方式</p>
<pre class="hljs"><code><span class="hljs-keyword">import</span> { read, utils } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;xlsx&#x27;</span>
<span class="hljs-comment">// 文件转buffer</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> fileToBuffer = <span class="hljs-function"><span class="hljs-params">file</span> =&gt;</span> {
<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function"><span class="hljs-params">r</span> =&gt;</span> {
<span class="hljs-keyword">const</span> reader = <span class="hljs-keyword">new</span> FileReader()
reader.onload = <span class="hljs-function">() =&gt;</span> {
r(reader.result)
}
reader.readAsArrayBuffer(file)
})
}
<span class="hljs-comment">// File文件对象</span>
<span class="hljs-keyword">const</span> transformXLSXToJson = <span class="hljs-keyword">async</span> (file) =&gt; {
<span class="hljs-keyword">const</span> wb = read(<span class="hljs-keyword">await</span> fileToBuffer(file))
<span class="hljs-keyword">const</span> data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[<span class="hljs-number">0</span>]], {
<span class="hljs-attr">header</span>: <span class="hljs-number">1</span>
})
<span class="hljs-keyword">if</span> (data.length &lt;= <span class="hljs-number">0</span>) {
<span class="hljs-keyword">return</span>
}
<span class="hljs-keyword">let</span> max = <span class="hljs-number">0</span>
data.forEach(<span class="hljs-function"><span class="hljs-params">arr</span> =&gt;</span> {
<span class="hljs-keyword">if</span> (arr.length &gt; max) {
max = arr.length
}
})
<span class="hljs-keyword">let</span> layers = []
<span class="hljs-keyword">let</span> walk = <span class="hljs-function"><span class="hljs-params">layer</span> =&gt;</span> {
<span class="hljs-keyword">if</span> (!layers[layer]) {
layers[layer] = []
}
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; data.length; i++) {
<span class="hljs-keyword">if</span> (data[i][layer]) {
<span class="hljs-keyword">let</span> node = {
<span class="hljs-attr">data</span>: {
<span class="hljs-attr">text</span>: data[i][layer]
},
<span class="hljs-attr">children</span>: [],
<span class="hljs-attr">_row</span>: i
}
layers[layer].push(node)
}
}
<span class="hljs-keyword">if</span> (layer &lt; max - <span class="hljs-number">1</span>) {
walk(layer + <span class="hljs-number">1</span>)
}
}
walk(<span class="hljs-number">0</span>)
<span class="hljs-keyword">let</span> getParent = <span class="hljs-function">(<span class="hljs-params">arr, row</span>) =&gt;</span> {
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = arr.length - <span class="hljs-number">1</span>; i &gt;= <span class="hljs-number">0</span>; i--) {
<span class="hljs-keyword">if</span> (row &gt;= arr[i]._row) {
<span class="hljs-keyword">return</span> arr[i]
}
}
}
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">1</span>; i &lt; layers.length; i++) {
<span class="hljs-keyword">let</span> arr = layers[i]
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> j = <span class="hljs-number">0</span>; j &lt; arr.length; j++) {
<span class="hljs-keyword">let</span> item = arr[j]
<span class="hljs-keyword">let</span> parent = getParent(layers[i - <span class="hljs-number">1</span>], item._row)
<span class="hljs-keyword">if</span> (parent) {
parent.children.push(item)
}
}
}
<span class="hljs-keyword">return</span> layers[<span class="hljs-number">0</span>][<span class="hljs-number">0</span>]
}
<span class="hljs-keyword">let</span> data = transformXLSXToJson(<span class="hljs-string">&#x27;xlsx文件对象&#x27;</span>)
mindMap.setData(data)
</code></pre>
<h3>导入md</h3>
<p>要导入<code>markdown</code>文件需要引入相应的解析方法</p>
<pre class="hljs"><code><span class="hljs-keyword">import</span> markdown <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/parse/markdown.js&#x27;</span>
</code></pre>
<p>如果使用的是umd格式的文件那么可以通过如下方式获取</p>
<pre class="hljs"><code>MindMap.markdown
</code></pre>
<p>获取到<code>md</code>文件的内容后调用<code>transformMarkdownTo</code>方法转换即可返回一个<code>Promise</code>实例</p>
<pre class="hljs"><code><span class="hljs-keyword">let</span> data = <span class="hljs-keyword">await</span> markdown.transformMarkdownTo(<span class="hljs-string">&#x27;md文件内容&#x27;</span>)
mindMap.setData(data)
</code></pre>
<h3>完整示例</h3>
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFV81u20YQfpUN24JUoVAK0JMrB3YbB0gApYGTQ4EwhzW5kuiQu8TuypJhC0iDJkHaBsglPfXQQ4Oil7intnb7NvVf36KzfxRNUXUuQQ3bImdnvm925tsf7XnrRRHujIm34vVEzNNCIkHkuLge0TQvGJdoD3EyaCNG+2xMJUnaSIxwlrHJJhmgGRpwliMfEPwyop/SpI8LMxR5AswZuZqD9WqOi8iLKEIRzYhEyqY8VxEdZ1lEI9rpoJODv06evjn9/vnxn79FNGZUSGC/RYuxBMeAtNDqdbSnQBTEIM0ImEkoMR8SGap38aD70I1zMoThThSFgcjz/ami3N8WjO5PMzHdz5PWhx3lmw5QcAWcQ0mEDBRKSHFOWi1DhRDOCJeBf37w+z+PX5x++0sIcH8//ipUWOpTI+sHwFWfeWLm4LcMAIe6cpgiQjNHWKalQCCRpeQjTJOM3MtzPagBZ4hkgpQwmv5ShC+V13IMyPxSiI1pTLKlEPnlOfSrCUQUfk3bf/r67NWzalUvSqAsgRKBBljQwSbBCeFKTWSCbpaGQFPNHUIOH+viPpmaLOvDjGYMJ0pUO6A4y4GQ5Lvu0VAmWGLwun3vizthgbkgAQQ4HXIixpm0rTfdlrsFYQMTdmV1Fflsa5vE0i/ro1lGnE30BDY4ZzzwTRVOnj09eXt4+sOL84MDJygrJPUDa/YGwAYK247OUIxlPIIFo3AqHKqcDHqTsaEdswHNDdHCamqF1hLMH4tdGtdbUilWpVR4gtNyfzDIpnIaS3Vs3pCGSS2ZUvOEGmYC8m6aiFb0solA+NnrN3WsHPNHCZs0SrS/tCjvS6eG7N3UerEFbh6h5JiKAeN531rus/9Q83uRm9n2X/96+vKtK6jlUSteU5XzU8tJWULOmCzJ7HESQthNOE/q4tEb1aJrzU39c6M7KZmouROpelPN9PnR8R9HapNyqZKpOvvWxW2wqXxVrprLYRmHwFdBfhv6MyZt5Fb3q5dgGmBIsImnoMM6zV2qjjXNYqZUp4GYJhYHXx7nwSKGFabVSGArRrIVlLB4nBMqQ9DERkbU42e7t5LAt5GfMypxSgn3W20Tpeq6MhdD5ClD5FVMxixB5Moceac/Hp5/8+TsyaG5KGipWDDlGI/SLOGEKucHc4waXCNLnen46Luzo5/rZBcJG0j11WLR73/KwD1am4tLaSo3YW3cYQm5y0QqU0Yh0s/IQIIu/BhaB216qN1nrU9BFiCNXsfcAeH2By+SwMUNSwJvCPWSdAfFGRZiNfJsu2+QnEWeHrYOaTIfLcUALr0OjFYdHZJkLNvCysUMRrKX6pueOixhXG13kYfWtBHe7UWw9Ae4rbGUsObW4iyNH4FLdSWC34XV2usY78ujYYFVg2E51WLnU3JPvU6lYvAq5G5mirdmb8aRF3bMddhtwETkYSxE5EEH1K0YobBSXKeeSZrI0Qq61u1+pP0QKsqecgKM6Q7RA1oO6u+DehMc1DwQb8G+PJYmUJ0RA7mCuvZNsmL+skg/IulwBO6fdLvF1DE3835cbrlwlKTA61ALnCQpHTpDmXpoNfGOGV9zGdiky3cABD3rHnhtz3RAfQHRd0v4vqPhIzsAHSjXauTB1xmzQMMOPIYcdso0J6pZV7fgeibg8N2GCLvWGr7imNjFVqsod+55s38BRuykJA==" />
</div>
</template>

View File

@@ -1,3 +1,102 @@
# 如何持久化数据
编写中。。
在线`demo`的数据是存储在电脑本地的,也就是`localStorage`里,当然,你也可以存储到数据库中
## 保存数据
保存数据,一般有两种做法,一是让用户手动保存,二是当画布上的数据改变后自动保存,显然,第二中体验更好一点。
要获取画布的数据,可以使用`getData`方法,可以传递一个参数,`true`指定返回的数据中包含配置数据,`false`指定只返回节点树数据。
```js
const data = mindMap.getData(true)
```
包含配置的完整数据结构:
```js
{
layout,
root,
theme: {
template,
config
},
view
}
```
你可以直接把获取到的数据保存起来即可。
如果要自动保存,那么肯定需要监听相关事件:
```js
this.$bus.$on('data_change', data => {
// 节点树数据改变
// data即完整数据中的root部分
})
this.$bus.$on('view_data_change', data => {
// 视图数据改变
// data即完整数据中的view部分
})
```
主题和结构的改变一般是开发者提供一个ui界面让用户选择所以可以自行触发保存。
## 回显数据
当从数据库获取到了保存的数据,那么怎么渲染到画布上呢,首先可以直接在`new`一个`MindMap`实例时直接传入:
```js
// 从数据中取出各个部分
let { root, layout, theme, view } = storeData
let mindMap = new MindMap({
el: container,
data: root,
layout: layout,
theme: theme.template,
themeConfig: theme.config,
viewData: view,
// ...
})
```
其次如果是包含配置的完整数据也可以调用`setFullData`方法:
```js
mindMap.setFullData(data)
```
如果是纯节点数据可以调用`setData`方法:
```js
mindMap.setData(data)
```
修改结构可以调用`setLayout`方法:
```js
mindMap.setLayout(layout)
```
设置主题可以调用`setTheme`方法:
```js
mindMap.setTheme(theme)
```
设置主题配置可以调用`setThemeConfig`方法:
```js
mindMap.setThemeConfig(themeConfig)
```
设置视图数据可以调用`view.setTransformData`方法:
```js
mindMap.view.setTransformData(view)
```
### 完整示例
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFVc1u00AQfpXRIpQEpXYqcQpuVaAggdSCypHtYWNvkoX1ruVdN42qXHpEBU7lzI1bxQEJtc9D0z4Gs/6Lm0QIiQOWLO3OzPd945md9Ql5nCTeUcZJnwQmTEViwXCbJdtUiTjRqYUTSPmwC1rt6UxZHnXBjJmUenLAhzCDYapjaCFDq0bsCRXtsaRwUWLQLPlGjNaNmCWUUAVAleQWnM1FboHKpKSKKt+H208/rz9/ub44m5//mJ9/n3+8oCrUylgYcfsc43aZZQhhZqpCaHdgaxtOHCeTPLXtl29e7XvGpkKNxHDaLiU8xDpc26YZ73Q6VM3uyN1+OL05vVyR+yepIZOmoVVXsN0galSAT6rKtXMXAJd9iHSYxVxZx/pMcrd8Mn0RtVsl8qlWlgnF01anW6Ai1O4X7O6hxBkoaZgKs+XH1pkpmX+9LL6/6I17ZiWZCwzHQkYpVy747YJjiW6tyrLSr6uzm6tvy2J3BdeIHi58zbj/lEG1LG0VTihhD7S2+zrir7URVmiFyJbkQ9vqQivE1mGbDvPwWecRHgs8GoFfjB0OHG4sx1lhluMOIIjEEYSSGbNFSdnuXR5rSnJ3GSCihbc+DBgS+OhtBlZMVms5YC6k+pBgkFmrFeyEUoTvMaQxaBi2OpCBXyD+yHAH3ZyvJfQi0WoV+I064NbYqSxKslNeMZR4fnGvlCPjcRN7oTGUYF3d9QLgNUpWnYmJiOy4D5u93v08DiCpO5VyVBRHPHfkTXbvveXSVlQLIBsYLTNbAAFcv/vQK3dWJ4vNqvyYi9EYwx/2eslxpbxe90GlHLN0JFC3Yk1YFOEVVBnq1L2y03+Z8WaVQZl0vUdCPKV5D0iXFB1wN7n3zmiFP46cnpYO7EA9gZTgf6EYO8/HpZfi/Sdi7pq1MUj1xPAUSSgpJ2jNv6LArrbaocrcZmT2G71jRY0=" />

View File

@@ -1,8 +1,69 @@
<template>
<div>
<h1>如何持久化数据</h1>
<p>编写中</p>
<p>在线<code>demo</code>的数据是存储在电脑本地的也就是<code>localStorage</code>当然你也可以存储到数据库中</p>
<h2>保存数据</h2>
<p>保存数据一般有两种做法一是让用户手动保存二是当画布上的数据改变后自动保存显然第二中体验更好一点</p>
<p>要获取画布的数据可以使用<code>getData</code>方法可以传递一个参数<code>true</code>指定返回的数据中包含配置数据<code>false</code>指定只返回节点树数据</p>
<pre class="hljs"><code><span class="hljs-keyword">const</span> data = mindMap.getData(<span class="hljs-literal">true</span>)
</code></pre>
<p>包含配置的完整数据结构</p>
<pre class="hljs"><code>{
layout,
root,
<span class="hljs-attr">theme</span>: {
template,
config
},
view
}
</code></pre>
<p>你可以直接把获取到的数据保存起来即可</p>
<p>如果要自动保存那么肯定需要监听相关事件</p>
<pre class="hljs"><code><span class="hljs-built_in">this</span>.$bus.$on(<span class="hljs-string">&#x27;data_change&#x27;</span>, <span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> {
<span class="hljs-comment">// 节点树数据改变</span>
<span class="hljs-comment">// data即完整数据中的root部分</span>
})
<span class="hljs-built_in">this</span>.$bus.$on(<span class="hljs-string">&#x27;view_data_change&#x27;</span>, <span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> {
<span class="hljs-comment">// 视图数据改变</span>
<span class="hljs-comment">// data即完整数据中的view部分</span>
})
</code></pre>
<p>主题和结构的改变一般是开发者提供一个ui界面让用户选择所以可以自行触发保存</p>
<h2>回显数据</h2>
<p>当从数据库获取到了保存的数据那么怎么渲染到画布上呢首先可以直接在<code>new</code>一个<code>MindMap</code>实例时直接传入</p>
<pre class="hljs"><code><span class="hljs-comment">// 从数据中取出各个部分</span>
<span class="hljs-keyword">let</span> { root, layout, theme, view } = storeData
<span class="hljs-keyword">let</span> mindMap = <span class="hljs-keyword">new</span> MindMap({
<span class="hljs-attr">el</span>: container,
<span class="hljs-attr">data</span>: root,
<span class="hljs-attr">layout</span>: layout,
<span class="hljs-attr">theme</span>: theme.template,
<span class="hljs-attr">themeConfig</span>: theme.config,
<span class="hljs-attr">viewData</span>: view,
<span class="hljs-comment">// ...</span>
})
</code></pre>
<p>其次如果是包含配置的完整数据也可以调用<code>setFullData</code>方法</p>
<pre class="hljs"><code>mindMap.setFullData(data)
</code></pre>
<p>如果是纯节点数据可以调用<code>setData</code>方法</p>
<pre class="hljs"><code>mindMap.setData(data)
</code></pre>
<p>修改结构可以调用<code>setLayout</code>方法</p>
<pre class="hljs"><code>mindMap.setLayout(layout)
</code></pre>
<p>设置主题可以调用<code>setTheme</code>方法</p>
<pre class="hljs"><code>mindMap.setTheme(theme)
</code></pre>
<p>设置主题配置可以调用<code>setThemeConfig</code>方法</p>
<pre class="hljs"><code>mindMap.setThemeConfig(themeConfig)
</code></pre>
<p>设置视图数据可以调用<code>view.setTransformData</code>方法</p>
<pre class="hljs"><code>mindMap.view.setTransformData(view)
</code></pre>
<h3>完整示例</h3>
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFVc1u00AQfpXRIpQEpXYqcQpuVaAggdSCypHtYWNvkoX1ruVdN42qXHpEBU7lzI1bxQEJtc9D0z4Gs/6Lm0QIiQOWLO3OzPd945md9Ql5nCTeUcZJnwQmTEViwXCbJdtUiTjRqYUTSPmwC1rt6UxZHnXBjJmUenLAhzCDYapjaCFDq0bsCRXtsaRwUWLQLPlGjNaNmCWUUAVAleQWnM1FboHKpKSKKt+H208/rz9/ub44m5//mJ9/n3+8oCrUylgYcfsc43aZZQhhZqpCaHdgaxtOHCeTPLXtl29e7XvGpkKNxHDaLiU8xDpc26YZ73Q6VM3uyN1+OL05vVyR+yepIZOmoVVXsN0galSAT6rKtXMXAJd9iHSYxVxZx/pMcrd8Mn0RtVsl8qlWlgnF01anW6Ai1O4X7O6hxBkoaZgKs+XH1pkpmX+9LL6/6I17ZiWZCwzHQkYpVy747YJjiW6tyrLSr6uzm6tvy2J3BdeIHi58zbj/lEG1LG0VTihhD7S2+zrir7URVmiFyJbkQ9vqQivE1mGbDvPwWecRHgs8GoFfjB0OHG4sx1lhluMOIIjEEYSSGbNFSdnuXR5rSnJ3GSCihbc+DBgS+OhtBlZMVms5YC6k+pBgkFmrFeyEUoTvMaQxaBi2OpCBXyD+yHAH3ZyvJfQi0WoV+I064NbYqSxKslNeMZR4fnGvlCPjcRN7oTGUYF3d9QLgNUpWnYmJiOy4D5u93v08DiCpO5VyVBRHPHfkTXbvveXSVlQLIBsYLTNbAAFcv/vQK3dWJ4vNqvyYi9EYwx/2eslxpbxe90GlHLN0JFC3Yk1YFOEVVBnq1L2y03+Z8WaVQZl0vUdCPKV5D0iXFB1wN7n3zmiFP46cnpYO7EA9gZTgf6EYO8/HpZfi/Sdi7pq1MUj1xPAUSSgpJ2jNv6LArrbaocrcZmT2G71jRY0=" />
</div>
</template>

View File

@@ -0,0 +1,66 @@
# 部署
本项目的`web`目录下提供了一个基于`simple-mind-map`库、`Vue2.x``ElementUI`开发的完整项目,数据默认存储在电脑本地,此外可以操作电脑本地文件,原意是作为一个线上`demo`,但是也完全可以直接把它当做一个在线版思维导图应用使用,在线地址:[https://wanglin2.github.io/mind-map/](https://wanglin2.github.io/mind-map/)。
如果你的网络环境访问`GitHub`服务很慢,你也可以部署到你的服务器上。
## 部署到静态文件服务器
项目本身不依赖后端,所以完全可以部署到一个静态文件服务器上,可以依次执行如下命令:
```bash
git clone https://github.com/wanglin2/mind-map.git
cd mind-map
cd simple-mind-map
npm i
npm link
cd ..
cd web
npm i
npm link simple-mind-map
```
然后你可以选择启动本地服务:
```bash
npm run serve
```
也可以直接打包生成构建产物:
```bash
npm run build
```
打包完后的入口页面`index.html`可以在项目根目录找到,对应的静态资源在根目录下的`dist`目录,`html`文件中会通过相对路径访问`dist`目录的资源,比如`dist/xxx`。你可以直接把这两个文件或目录上传到你的静态文件服务器,事实上,本项目就是这样部署到`GitHub Pages`上的。
如果你没有代码修改需求的话,直接从本仓库复制这些文件也是可以的。
如果你想把`index.html`也打包进`dist`目录,可以修改`web/package.json`文件的`scripts.build`命令,把`vue-cli-service build && node ../copy.js`中的` && node ../copy.js`删除即可。
如果你想修改打包输出的目录,可以修改`web/vue.config.js`文件的`outputDir`配置,改成你想要输出的路径即可。
如果你想修改`index.html`文件引用静态资源的路径的话可以修改`web/vue.config.js`文件的`publicPath`配置。
另外默认使用的是`hash`路由,也就是路径中会在`#`,如果你想使用`history`路由,可以修改`web/src/router.js`文件,将:
```js
const router = new VueRouter({
routes
})
```
改成:
```js
const router = new VueRouter({
mode: 'history',
routes
})
```
不过这需要后台支持因为我们的应用是个单页客户端应用如果后台没有正确的配置当用户在浏览器直接访问子路由时会返回404所以呢你要在服务端增加一个覆盖所有情况的候选资源如果`URL`匹配不到任何静态资源,则应该返回同一个`index.html`页面。
## Docker
编写中。。。

View File

@@ -0,0 +1,55 @@
<template>
<div>
<h1>部署</h1>
<p>本项目的<code>web</code>目录下提供了一个基于<code>simple-mind-map</code><code>Vue2.x</code><code>ElementUI</code>开发的完整项目数据默认存储在电脑本地此外可以操作电脑本地文件原意是作为一个线上<code>demo</code>但是也完全可以直接把它当做一个在线版思维导图应用使用在线地址<a href="https://wanglin2.github.io/mind-map/">https://wanglin2.github.io/mind-map/</a>。</p>
<p>如果你的网络环境访问<code>GitHub</code>服务很慢你也可以部署到你的服务器上</p>
<h2>部署到静态文件服务器</h2>
<p>项目本身不依赖后端所以完全可以部署到一个静态文件服务器上可以依次执行如下命令</p>
<pre class="hljs"><code>git <span class="hljs-built_in">clone</span> https://github.com/wanglin2/mind-map.git
<span class="hljs-built_in">cd</span> mind-map
<span class="hljs-built_in">cd</span> simple-mind-map
npm i
npm link
<span class="hljs-built_in">cd</span> ..
<span class="hljs-built_in">cd</span> web
npm i
npm link simple-mind-map
</code></pre>
<p>然后你可以选择启动本地服务</p>
<pre class="hljs"><code>npm run serve
</code></pre>
<p>也可以直接打包生成构建产物</p>
<pre class="hljs"><code>npm run build
</code></pre>
<p>打包完后的入口页面<code>index.html</code>可以在项目根目录找到对应的静态资源在根目录下的<code>dist</code>目录<code>html</code>文件中会通过相对路径访问<code>dist</code>目录的资源比如<code>dist/xxx</code>你可以直接把这两个文件或目录上传到你的静态文件服务器事实上本项目就是这样部署到<code>GitHub Pages</code>上的</p>
<p>如果你没有代码修改需求的话直接从本仓库复制这些文件也是可以的</p>
<p>如果你想把<code>index.html</code>也打包进<code>dist</code>目录可以修改<code>web/package.json</code>文件的<code>scripts.build</code>命令<code>vue-cli-service build &amp;&amp; node ../copy.js</code>中的<code> &amp;&amp; node ../copy.js</code>删除即可</p>
<p>如果你想修改打包输出的目录可以修改<code>web/vue.config.js</code>文件的<code>outputDir</code>配置改成你想要输出的路径即可</p>
<p>如果你想修改<code>index.html</code>文件引用静态资源的路径的话可以修改<code>web/vue.config.js</code>文件的<code>publicPath</code>配置</p>
<p>另外默认使用的是<code>hash</code>路由也就是路径中会在<code>#</code>如果你想使用<code>history</code>路由可以修改<code>web/src/router.js</code>文件</p>
<pre class="hljs"><code><span class="hljs-keyword">const</span> router = <span class="hljs-keyword">new</span> VueRouter({
routes
})
</code></pre>
<p>改成</p>
<pre class="hljs"><code><span class="hljs-keyword">const</span> router = <span class="hljs-keyword">new</span> VueRouter({
<span class="hljs-attr">mode</span>: <span class="hljs-string">&#x27;history&#x27;</span>,
routes
})
</code></pre>
<p>不过这需要后台支持因为我们的应用是个单页客户端应用如果后台没有正确的配置当用户在浏览器直接访问子路由时会返回404所以呢你要在服务端增加一个覆盖所有情况的候选资源如果<code>URL</code>匹配不到任何静态资源则应该返回同一个<code>index.html</code>页面</p>
<h2>Docker</h2>
<p>编写中</p>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>

View File

@@ -15,16 +15,18 @@ MindMap.usePlugin(Export)
## 方法
### png()
### png(name, transparent = false)
- `name`:名称,可不传
- `transparent`v0.5.7+,指定导出图片的背景是否是透明的
导出为`png`,异步方法,返回图片数据,`data:url`数据,可以自行下载或显示
### svg(name, domToImage = false, plusCssText)
### svg(name, plusCssText)
- `name``svg`标题
- `domToImage`v0.4.0+,当开启了节点富文本编辑,可以通过该参数指定是否将`svg`中的`dom`节点转换成图片的形式
- `plusCssText`v0.4.0+,当开启了节点富文本编辑,且`domToImage`传了`false`时,可以添加附加的`css`样式,如果`svg`中存在`dom`节点,想要设置一些针对节点的样式可以通过这个参数传入,比如:
```js
@@ -41,9 +43,7 @@ svg(
导出为`svg`,异步方法,返回`svg`数据,`data:url`数据,可以自行下载或显示
### getSvgData(domToImage)
- `domToImage`v0.4.0+,如果开启了节点富文本,则可以通过该参数指定是否要将`svg`中嵌入的`DOM`节点转换为图片。
### getSvgData()
获取`svg`数据,异步方法,返回一个对象:
@@ -70,3 +70,9 @@ svg(
`withConfig``Boolean`, 默认为`true`,数据中是否包含配置,否则为纯思维导图节点数据
返回`json`数据,`data:url`数据,可以自行下载
### md()
> v0.4.7+
导出`markdown`文件,返回`data:url`数据,可以自行下载

View File

@@ -10,17 +10,22 @@ MindMap.usePlugin(Export)
</code></pre>
<p>注册完且实例化<code>MindMap</code>后可通过<code>mindMap.doExport</code>获取到该实例</p>
<h2>方法</h2>
<h3>png()</h3>
<h3>png(name, transparent = false)</h3>
<ul>
<li>
<p><code>name</code>名称可不传</p>
</li>
<li>
<p><code>transparent</code>v0.5.7+指定导出图片的背景是否是透明的</p>
</li>
</ul>
<p>导出为<code>png</code>异步方法返回图片数据<code>data:url</code>数据可以自行下载或显示</p>
<h3>svg(name, domToImage = false, plusCssText)</h3>
<h3>svg(name, plusCssText)</h3>
<ul>
<li>
<p><code>name</code><code>svg</code>标题</p>
</li>
<li>
<p><code>domToImage</code>v0.4.0+当开启了节点富文本编辑可以通过该参数指定是否将<code>svg</code>中的<code>dom</code>节点转换成图片的形式</p>
</li>
<li>
<p><code>plusCssText</code>v0.4.0+当开启了节点富文本编辑<code>domToImage</code>传了<code>false</code>可以添加附加的<code>css</code>样式如果<code>svg</code>中存在<code>dom</code>节点想要设置一些针对节点的样式可以通过这个参数传入比如</p>
</li>
</ul>
@@ -35,10 +40,7 @@ MindMap.usePlugin(Export)
)
</code></pre>
<p>导出为<code>svg</code>异步方法返回<code>svg</code>数据<code>data:url</code>数据可以自行下载或显示</p>
<h3>getSvgData(domToImage)</h3>
<ul>
<li><code>domToImage</code>v0.4.0+如果开启了节点富文本则可以通过该参数指定是否要将<code>svg</code>中嵌入的<code>DOM</code>节点转换为图片</li>
</ul>
<h3>getSvgData()</h3>
<p>获取<code>svg</code>数据异步方法返回一个对象</p>
<pre class="hljs"><code>{
node<span class="hljs-comment">// svg对象</span>
@@ -56,6 +58,11 @@ MindMap.usePlugin(Export)
<p><code>name</code>暂时没有用处传空字符串即可</p>
<p><code>withConfig``Boolean</code>, 默认为<code>true</code>数据中是否包含配置否则为纯思维导图节点数据</p>
<p>返回<code>json</code>数据<code>data:url</code>数据可以自行下载</p>
<h3>md()</h3>
<blockquote>
<p>v0.4.7+</p>
</blockquote>
<p>导出<code>markdown</code>文件返回<code>data:url</code>数据可以自行下载</p>
</div>
</template>

View File

@@ -99,4 +99,11 @@
<img src="../../../../assets/img/alipay.jpg" style="width: 300px" />
<img src="../../../../assets/img/wechat.jpg" style="width: 300px" />
<img src="../../../../assets/img/wechat.jpg" style="width: 300px" />
<div style="display: flex;">
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/Think.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>Think</p>
</div>
</div>

View File

@@ -7,19 +7,19 @@
</blockquote>
<h2>特性</h2>
<ul>
<li><input type="checkbox" id="checkbox0" checked="true" /><label for="checkbox0">插件化架构除核心功能外其他功能作为插件提供按需使用减小整体体积</label></li>
<li><input type="checkbox" id="checkbox1" checked="true" /><label for="checkbox1">支持逻辑结构图思维导图组织结构图目录组织图时间轴鱼骨图六种结构</label></li>
<li><input type="checkbox" id="checkbox2" checked="true" /><label for="checkbox2">内置多种主题允许高度自定义样式支持注册新主题</label></li>
<li><input type="checkbox" id="checkbox3" checked="true" /><label for="checkbox3">支持快捷键</label></li>
<li><input type="checkbox" id="checkbox4" checked="true" /><label for="checkbox4">节点内容支持图片图标超链接备注标签概要</label></li>
<li><input type="checkbox" id="checkbox5" checked="true" /><label for="checkbox5">支持前进后退</label></li>
<li><input type="checkbox" id="checkbox6" checked="true" /><label for="checkbox6">支持拖动缩放</label></li>
<li><input type="checkbox" id="checkbox7" checked="true" /><label for="checkbox7">支持右键和Ctrl+左键两种多选方式</label></li>
<li><input type="checkbox" id="checkbox8" checked="true" /><label for="checkbox8">支持节点自由拖拽拖拽调整</label></li>
<li><input type="checkbox" id="checkbox9" checked="true" /><label for="checkbox9">支持多种节点形状</label></li>
<li><input type="checkbox" id="checkbox10" checked="true" /><label for="checkbox10">支持导出为</label><code>json</code><code>png</code><code>svg</code><code>pdf</code><code>markdown</code>支持从<code>json</code><code>xmind</code><code>markdown</code>导入</li>
<li><input type="checkbox" id="checkbox11" checked="true" /><label for="checkbox11">支持小地图支持水印</label></li>
<li><input type="checkbox" id="checkbox12" checked="true" /><label for="checkbox12">支持关联线</label></li>
<li><input type="checkbox" id="checkbox216" checked="true" /><label for="checkbox216">插件化架构除核心功能外其他功能作为插件提供按需使用减小整体体积</label></li>
<li><input type="checkbox" id="checkbox217" checked="true" /><label for="checkbox217">支持逻辑结构图思维导图组织结构图目录组织图时间轴鱼骨图六种结构</label></li>
<li><input type="checkbox" id="checkbox218" checked="true" /><label for="checkbox218">内置多种主题允许高度自定义样式支持注册新主题</label></li>
<li><input type="checkbox" id="checkbox219" checked="true" /><label for="checkbox219">支持快捷键</label></li>
<li><input type="checkbox" id="checkbox220" checked="true" /><label for="checkbox220">节点内容支持图片图标超链接备注标签概要</label></li>
<li><input type="checkbox" id="checkbox221" checked="true" /><label for="checkbox221">支持前进后退</label></li>
<li><input type="checkbox" id="checkbox222" checked="true" /><label for="checkbox222">支持拖动缩放</label></li>
<li><input type="checkbox" id="checkbox223" checked="true" /><label for="checkbox223">支持右键和Ctrl+左键两种多选方式</label></li>
<li><input type="checkbox" id="checkbox224" checked="true" /><label for="checkbox224">支持节点自由拖拽拖拽调整</label></li>
<li><input type="checkbox" id="checkbox225" checked="true" /><label for="checkbox225">支持多种节点形状</label></li>
<li><input type="checkbox" id="checkbox226" checked="true" /><label for="checkbox226">支持导出为</label><code>json</code><code>png</code><code>svg</code><code>pdf</code><code>markdown</code>支持从<code>json</code><code>xmind</code><code>markdown</code>导入</li>
<li><input type="checkbox" id="checkbox227" checked="true" /><label for="checkbox227">支持小地图支持水印</label></li>
<li><input type="checkbox" id="checkbox228" checked="true" /><label for="checkbox228">支持关联线</label></li>
</ul>
<h2>仓库目录介绍</h2>
<p>1.<code>simple-mind-map</code></p>
@@ -27,11 +27,11 @@
<p>2.<code>web</code></p>
<p>使用<code>simple-mind-map</code>基于<code>vue2.x</code><code>ElementUI</code>搭建的在线思维导图特性</p>
<ul>
<li><input type="checkbox" id="checkbox13" checked="true" /><label for="checkbox13">工具栏支持插入节点删除节点编辑节点图片图标超链接备注标签概要</label></li>
<li><input type="checkbox" id="checkbox14" checked="true" /><label for="checkbox14">侧边栏基础样式设置面板节点样式设置面板大纲面板主题选择面板结构选择面板</label></li>
<li><input type="checkbox" id="checkbox15" checked="true" /><label for="checkbox15">导入导出功能数据默认保存在浏览器本地存储也支持直接创建打开编辑电脑本地文件</label></li>
<li><input type="checkbox" id="checkbox16" checked="true" /><label for="checkbox16">右键菜单支持展开收起整理布局等操作</label></li>
<li><input type="checkbox" id="checkbox17" checked="true" /><label for="checkbox17">底部栏支持节点数量字数统计支持切换编辑和只读模式支持放大缩小支持全屏切换支持小地图</label></li>
<li><input type="checkbox" id="checkbox229" checked="true" /><label for="checkbox229">工具栏支持插入节点删除节点编辑节点图片图标超链接备注标签概要</label></li>
<li><input type="checkbox" id="checkbox230" checked="true" /><label for="checkbox230">侧边栏基础样式设置面板节点样式设置面板大纲面板主题选择面板结构选择面板</label></li>
<li><input type="checkbox" id="checkbox231" checked="true" /><label for="checkbox231">导入导出功能数据默认保存在浏览器本地存储也支持直接创建打开编辑电脑本地文件</label></li>
<li><input type="checkbox" id="checkbox232" checked="true" /><label for="checkbox232">右键菜单支持展开收起整理布局等操作</label></li>
<li><input type="checkbox" id="checkbox233" checked="true" /><label for="checkbox233">底部栏支持节点数量字数统计支持切换编辑和只读模式支持放大缩小支持全屏切换支持小地图</label></li>
</ul>
<p>提供文档页面服务</p>
<p>3.<code>dist</code></p>
@@ -67,6 +67,12 @@
</blockquote>
<img src="../../../../assets/img/alipay.jpg" style="width: 300px" />
<img src="../../../../assets/img/wechat.jpg" style="width: 300px" />
<div style="display: flex;">
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/Think.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>Think</p>
</div>
</div>
</div>
</template>

View File

@@ -10,9 +10,11 @@
该插件的原理是使用[Quill](https://github.com/quilljs/quill)编辑器实现富文本编辑,然后把编辑后生成的`DOM`节点直接作为节点的文本数据,并且在渲染的时候通过`svg``foreignObject`标签嵌入`DOM`节点。
这样也造成了一个问题,就是导出为图片的功能受到了影响,原本将`svg`导出为图片的原理很简单,获取到`svg`字符串,然后创建为`type=image/svg+xml`类型的`blob`数据,再使用`URL.createObjectURL`方法生成`data:url`数据,再创建一个`Image`标签,将`data:url`作为该图片的`src`,最后再将这个图片绘制到`canvas`对象上进行导出,但是经过测试,当`svg`中嵌入了`DOM`节点,这种方式导出会出错,并且尝试了多种方式后都无法实现完美的导出效果,目前的方式是遍历`svg`中的`foreignObject`节点,使用[html2canvas](https://github.com/niklasvh/html2canvas)将`foreignObject`节点内的`DOM`节点转换为图片再替换掉`foreignObject`节点,这种方式可以工作,但是非常耗时,因为`html2canvas`转换一次的时间很长导致转换一个节点都需要耗时差不多2秒这样导致节点越多转换时间越慢所以如果无法忍受长时间的导出的话推荐不要使用该插件。
> v0.5.6即以前的版本存在以下提示:
>
> 这样也造成了一个问题,就是导出为图片的功能受到了影响,原本将`svg`导出为图片的原理很简单,获取到`svg`字符串,然后创建为`type=image/svg+xml`类型的`blob`数据,再使用`URL.createObjectURL`方法生成`data:url`数据,再创建一个`Image`标签,将`data:url`作为该图片的`src`,最后再将这个图片绘制到`canvas`对象上进行导出,但是经过测试,当`svg`中嵌入了`DOM`节点,这种方式导出会出错,并且尝试了多种方式后都无法实现完美的导出效果,目前的方式是遍历`svg`中的`foreignObject`节点,使用[html2canvas](https://github.com/niklasvh/html2canvas)将`foreignObject`节点内的`DOM`节点转换为图片再替换掉`foreignObject`节点,这种方式可以工作,但是非常耗时,因为`html2canvas`转换一次的时间很长导致转换一个节点都需要耗时差不多2秒这样导致节点越多转换时间越慢所以如果无法忍受长时间的导出的话推荐不要使用该插件。
如果你有更好的方式也欢迎留言
`v0.5.7+`的版本直接使用`html2canvas`转换整个`svg`,速度不再是问题,但是目前存在一个`bug`,就是节点的颜色导出后不生效
## 注册

View File

@@ -10,8 +10,11 @@
<p>该插件提供节点富文本编辑的能力注册了即可生效</p>
<p>默认节点编辑只能对节点内所有文本统一应用样式通过该插件可以支持富文本编辑的效果目前支持加粗斜体下划线删除线字体字号颜色背景颜色不支持上划线行高</p>
<p>该插件的原理是使用<a href="https://github.com/quilljs/quill">Quill</a>编辑器实现富文本编辑然后把编辑后生成的<code>DOM</code>节点直接作为节点的文本数据并且在渲染的时候通过<code>svg</code><code>foreignObject</code>标签嵌入<code>DOM</code>节点</p>
<blockquote>
<p>v0.5.6即以前的版本存在以下提示</p>
<p>这样也造成了一个问题就是导出为图片的功能受到了影响原本将<code>svg</code>导出为图片的原理很简单获取到<code>svg</code>字符串然后创建为<code>type=image/svg+xml</code>类型的<code>blob</code>数据再使用<code>URL.createObjectURL</code>方法生成<code>data:url</code>数据再创建一个<code>Image</code>标签<code>data:url</code>作为该图片的<code>src</code>最后再将这个图片绘制到<code>canvas</code>对象上进行导出但是经过测试<code>svg</code>中嵌入了<code>DOM</code>节点这种方式导出会出错并且尝试了多种方式后都无法实现完美的导出效果目前的方式是遍历<code>svg</code>中的<code>foreignObject</code>节点使用<a href="https://github.com/niklasvh/html2canvas">html2canvas</a><code>foreignObject</code>节点内的<code>DOM</code>节点转换为图片再替换掉<code>foreignObject</code>节点这种方式可以工作但是非常耗时因为<code>html2canvas</code>转换一次的时间很长导致转换一个节点都需要耗时差不多2秒这样导致节点越多转换时间越慢所以如果无法忍受长时间的导出的话推荐不要使用该插件</p>
<p>如果你有更好的方式也欢迎留言</p>
</blockquote>
<p><code>v0.5.7+</code>的版本直接使用<code>html2canvas</code>转换整个<code>svg</code>速度不再是问题但是目前存在一个<code>bug</code>就是节点的颜色导出后不生效</p>
<h2>注册</h2>
<pre class="hljs"><code><span class="hljs-keyword">import</span> MindMap <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map&#x27;</span>
<span class="hljs-keyword">import</span> RichText <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/RichText.js&#x27;</span>

View File

@@ -24,13 +24,13 @@ MindMap.xmind
### xmind.parseXmindFile(file)
解析`.xmind`文件,返回解析后的数据,注意是完整的数据,包含节点树、主题、结构等,可以使用`mindMap.setFullData(data)`来将返回的数据渲染到画布上
解析`.xmind`文件,返回解析后的数据,可以使用`mindMap.setData(data)`来将返回的数据渲染到画布上
`file``File`对象
### xmind.transformXmind(content)
转换`xmind`数据,`.xmind`文件本质上是一个压缩包,改成`zip`后缀可以解压缩,里面存在一个`content.json`文件,如果你自己解析出了这个文件,那么可以把这个文件内容传递给这个方法进行转换,转换后的数据,注意是完整的数据,包含节点树、主题、结构等,可以使用`mindMap.setFullData(data)`来将返回的数据渲染到画布上
转换`xmind`数据,`.xmind`文件本质上是一个压缩包,改成`zip`后缀可以解压缩,里面存在一个`content.json`文件,如果你自己解析出了这个文件,那么可以把这个文件内容传递给这个方法进行转换,转换后的数据,可以使用`mindMap.setData(data)`来将返回的数据渲染到画布上
`content``.xmind`压缩包内的`content.json`文件内容

View File

@@ -15,10 +15,10 @@
</code></pre>
<h2>方法</h2>
<h3>xmind.parseXmindFile(file)</h3>
<p>解析<code>.xmind</code>文件返回解析后的数据注意是完整的数据包含节点树主题结构等可以使用<code>mindMap.setFullData(data)</code>来将返回的数据渲染到画布上</p>
<p>解析<code>.xmind</code>文件返回解析后的数据可以使用<code>mindMap.setData(data)</code>来将返回的数据渲染到画布上</p>
<p><code>file</code><code>File</code>对象</p>
<h3>xmind.transformXmind(content)</h3>
<p>转换<code>xmind</code>数据<code>.xmind</code>文件本质上是一个压缩包改成<code>zip</code>后缀可以解压缩里面存在一个<code>content.json</code>文件如果你自己解析出了这个文件那么可以把这个文件内容传递给这个方法进行转换转换后的数据注意是完整的数据包含节点树主题结构等可以使用<code>mindMap.setFullData(data)</code>来将返回的数据渲染到画布上</p>
<p>转换<code>xmind</code>数据<code>.xmind</code>文件本质上是一个压缩包改成<code>zip</code>后缀可以解压缩里面存在一个<code>content.json</code>文件如果你自己解析出了这个文件那么可以把这个文件内容传递给这个方法进行转换转换后的数据可以使用<code>mindMap.setData(data)</code>来将返回的数据渲染到画布上</p>
<p><code>content</code><code>.xmind</code>压缩包内的<code>content.json</code>文件内容</p>
<h3>xmind.transformOldXmind(content)</h3>
<blockquote>

View File

@@ -13,7 +13,7 @@
{{ $t('contextmenu.insertSiblingNode') }}
<span class="desc">Enter</span>
</div>
<div class="item" @click="exec('INSERT_CHILD_NODE')">
<div class="item" @click="exec('INSERT_CHILD_NODE')" :class="{ disabled: isGeneralization }">
{{ $t('contextmenu.insertChildNode') }}
<span class="desc">Tab</span>
</div>
@@ -45,11 +45,11 @@
{{ $t('contextmenu.deleteNode') }}
<span class="desc">Delete</span>
</div>
<div class="item" @click="exec('COPY_NODE')">
<div class="item" @click="exec('COPY_NODE')" :class="{ disabled: isGeneralization }">
{{ $t('contextmenu.copyNode') }}
<span class="desc">Ctrl + C</span>
</div>
<div class="item" @click="exec('CUT_NODE')">
<div class="item" @click="exec('CUT_NODE')" :class="{ disabled: isGeneralization }">
{{ $t('contextmenu.cutNode') }}
<span class="desc">Ctrl + X</span>
</div>
@@ -140,10 +140,10 @@ export default {
]
},
insertNodeBtnDisabled() {
return !this.node || this.node.isRoot
return !this.node || this.node.isRoot || this.node.isGeneralization
},
upNodeBtnDisabled() {
if (!this.node || this.node.isRoot) {
if (!this.node || this.node.isRoot || this.node.isGeneralization) {
return true
}
let isFirst =
@@ -153,7 +153,7 @@ export default {
return isFirst
},
downNodeBtnDisabled() {
if (!this.node || this.node.isRoot) {
if (!this.node || this.node.isRoot || this.node.isGeneralization) {
return true
}
let children = this.node.parent.children
@@ -163,6 +163,9 @@ export default {
}) ===
children.length - 1
return isLast
},
isGeneralization() {
return this.node.isGeneralization
}
},
created() {

View File

@@ -17,11 +17,14 @@
* @Date: 2021-06-24 22:53:10
* @Desc: 字数及节点数量统计
*/
let countEl = document.createElement('div')
export default {
name: 'Count',
props: {},
data() {
return {
textStr: '',
words: 0,
num: 0
}
@@ -39,9 +42,12 @@ export default {
* @Desc: 监听数据变化
*/
onDataChange(data) {
this.textStr = ''
this.words = 0
this.num = 0
this.walk(data)
countEl.innerHTML = this.textStr
this.words = countEl.textContent.length
},
/**
@@ -51,7 +57,7 @@ export default {
*/
walk(data) {
this.num++
this.words += (String(data.data.text) || '').length
this.textStr += String(data.data.text) || ''
if (data.children && data.children.length > 0) {
data.children.forEach(item => {
this.walk(item)

View File

@@ -111,7 +111,7 @@ export default {
}
},
mounted() {
this.showNewFeatureInfo()
// this.showNewFeatureInfo()
this.getData()
this.init()
this.$bus.$on('execCommand', this.execCommand)
@@ -277,6 +277,8 @@ export default {
theme: theme.template,
themeConfig: theme.config,
viewData: view,
nodeTextEditZIndex: 1000,
nodeNoteTooltipZIndex: 1000,
customNoteContentShow: {
show: (content, left, top) => {
this.$bus.$emit('showNoteContent', content, left, top)
@@ -306,7 +308,8 @@ export default {
'mode_change',
'node_tree_render_end',
'rich_text_selection_change',
'transforming-dom-to-images'
'transforming-dom-to-images',
'generalization_node_contextmenu'
].forEach(event => {
this.mindMap.on(event, (...args) => {
this.$bus.$emit(event, ...args)

View File

@@ -23,12 +23,6 @@
style="margin-left: 12px"
>{{ $t('export.include') }}</el-checkbox
>
<el-checkbox
v-show="['svg'].includes(exportType)"
v-model="domToImage"
style="margin-left: 12px"
>{{ $t('export.domToImage') }}</el-checkbox
>
</div>
<div class="paddingInputBox" v-show="['svg', 'png', 'pdf'].includes(exportType)">
<span class="name">{{ $t('export.paddingX') }}</span>
@@ -45,6 +39,12 @@
size="mini"
@change="onPaddingChange"
></el-input>
<el-checkbox
v-show="['png'].includes(exportType)"
v-model="isTransparent"
style="margin-left: 12px"
>{{ $t('export.isTransparent') }}</el-checkbox
>
</div>
<div class="downloadTypeList">
<div
@@ -62,7 +62,6 @@
</div>
</div>
<div class="tip">{{ $t('export.tips') }}</div>
<div class="tip warning" v-if="openNodeRichText && ['png', 'pdf'].includes(exportType)">{{ $t('export.pngTips') }}</div>
<div class="tip warning" v-if="openNodeRichText && exportType === 'svg' && domToImage">{{ $t('export.svgTips') }}</div>
</div>
<span slot="footer" class="dialog-footer">
@@ -91,7 +90,7 @@ export default {
exportType: 'smm',
fileName: '思维导图',
widthConfig: true,
domToImage: false,
isTransparent: false,
loading: false,
loadingText: '',
paddingX: 10,
@@ -111,13 +110,6 @@ export default {
this.$bus.$on('showExport', () => {
this.dialogVisible = true
})
this.$bus.$on('transforming-dom-to-images', (index, len) => {
this.loading = true
this.loadingText = `${this.$t('export.transformingDomToImages')}${index + 1}/${len}`
if (index >= len - 1) {
this.loading = false
}
})
},
methods: {
onPaddingChange() {
@@ -148,14 +140,13 @@ export default {
this.exportType,
true,
this.fileName,
this.domToImage,
`* {
margin: 0;
padding: 0;
box-sizing: border-box;
}`
)
} else {
} else if (['smm', 'json'].includes(this.exportType)) {
this.$bus.$emit(
'export',
this.exportType,
@@ -163,6 +154,21 @@ export default {
this.fileName,
this.widthConfig
)
} else if (this.exportType === 'png') {
this.$bus.$emit(
'export',
this.exportType,
true,
this.fileName,
this.isTransparent
)
} else {
this.$bus.$emit(
'export',
this.exportType,
true,
this.fileName
)
}
this.$notify.info({
title: this.$t('export.notifyTitle'),

View File

@@ -150,6 +150,7 @@ export default {
async handleXmind(file) {
try {
let data = await xmind.parseXmindFile(file.raw)
console.log(data);
this.$bus.$emit('setData', data)
this.$message.success('导入成功')
} catch (error) {

View File

@@ -1,5 +1,5 @@
<template>
<Sidebar ref="sidebar" :title="$t('style.title')">
<Sidebar ref="sidebar" :title="$t('theme.title')">
<div class="themeList">
<div
class="themeItem"