mirror of
https://github.com/wanglin2/mind-map.git
synced 2026-02-19 23:08:32 +08:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc1ba24834 | ||
|
|
cf16937160 | ||
|
|
c3393abed6 | ||
|
|
2abff3e21b | ||
|
|
e39a94c5e2 | ||
|
|
a6fff7f7a3 | ||
|
|
e584081b41 | ||
|
|
b91dde8084 | ||
|
|
077478d654 | ||
|
|
2be97cc1a0 | ||
|
|
0f7dc949a4 | ||
|
|
c7e91cc9eb | ||
|
|
6eacfab9c2 | ||
|
|
e36a408275 | ||
|
|
abda5b7d06 | ||
|
|
f815f71dd7 | ||
|
|
fa2c5b420c | ||
|
|
4c19bc76a7 | ||
|
|
d08a317920 | ||
|
|
bd805836cd | ||
|
|
e804a8f2f7 | ||
|
|
8bf876d446 | ||
|
|
f2521f663e | ||
|
|
e676bff453 | ||
|
|
8f2cc72d3c | ||
|
|
ec22656bee | ||
|
|
4acf8ba2ac | ||
|
|
d45a18904e | ||
|
|
9fc2dbabd4 | ||
|
|
b83b81f52e | ||
|
|
d1e2db993e | ||
|
|
ab931901e2 | ||
|
|
9879a25f9b | ||
|
|
16fb8eb092 | ||
|
|
de859927ed | ||
|
|
7bde59f664 | ||
|
|
be9668c7b8 | ||
|
|
95fe3189d5 | ||
|
|
9c60857c6a | ||
|
|
3b7cea9ee3 | ||
|
|
d959420d6e | ||
|
|
79d81b92e6 | ||
|
|
940c60f23d | ||
|
|
965ab8151e | ||
|
|
87d55b31ca | ||
|
|
bb68575aca | ||
|
|
e561e804be | ||
|
|
5a8c3aa9d3 | ||
|
|
f84639debd | ||
|
|
de77a2b613 | ||
|
|
3825c3769f | ||
|
|
876908e922 | ||
|
|
25ecde8948 | ||
|
|
2de0334e3b |
@@ -22,7 +22,7 @@
|
||||
# 特性
|
||||
|
||||
- [x] 插件化架构,除核心功能外,其他功能作为插件提供,按需使用,减小打包体积
|
||||
- [x] 支持逻辑结构图、思维导图、组织结构图、目录组织图四种结构
|
||||
- [x] 支持逻辑结构图、思维导图、组织结构图、目录组织图、时间轴、鱼骨图六种结构
|
||||
- [x] 内置多种主题,允许高度自定义样式,支持注册新主题
|
||||
- [x] 支持快捷键
|
||||
- [x] 节点内容支持图片、图标、超链接、备注、标签、概要
|
||||
|
||||
File diff suppressed because one or more lines are too long
BIN
qrcode.jpg
BIN
qrcode.jpg
Binary file not shown.
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
@@ -18,6 +18,8 @@ const defaultOpt = {
|
||||
readonly: false,
|
||||
// 布局
|
||||
layout: CONSTANTS.LAYOUT.LOGICAL_STRUCTURE,
|
||||
// 如果结构为鱼骨图,那么可以通过该选项控制倾斜角度
|
||||
fishboneDeg: 45,
|
||||
// 主题
|
||||
theme: 'default', // 内置主题:default(默认主题)
|
||||
// 主题配置,会和所选择的主题进行合并
|
||||
@@ -90,7 +92,18 @@ const defaultOpt = {
|
||||
// 如果开启节点动画过渡,可以通过该属性设置过渡的时间,单位ms
|
||||
nodeTransitionMoveDuration: 300,
|
||||
// 初始根节点的位置
|
||||
initRootNodePosition: null
|
||||
initRootNodePosition: null,
|
||||
// 导出png、svg、pdf时的图形内边距
|
||||
exportPaddingX: 10,
|
||||
exportPaddingY: 10,
|
||||
// 节点文本编辑框的z-index
|
||||
nodeTextEditZIndex: 3000,
|
||||
// 节点备注浮层的z-index
|
||||
nodeNoteTooltipZIndex: 3000,
|
||||
// 是否在点击了画布外的区域时结束节点文本的编辑状态
|
||||
isEndNodeTextEditOnClickOuter: true,
|
||||
// 最大历史记录数
|
||||
maxHistoryCount: 1000
|
||||
}
|
||||
|
||||
// 思维导图
|
||||
@@ -270,6 +283,7 @@ class MindMap {
|
||||
layout = CONSTANTS.LAYOUT.LOGICAL_STRUCTURE
|
||||
}
|
||||
this.opt.layout = layout
|
||||
this.view.reset()
|
||||
this.renderer.setLayout()
|
||||
this.render()
|
||||
}
|
||||
@@ -357,7 +371,7 @@ class MindMap {
|
||||
}
|
||||
|
||||
// 获取svg数据
|
||||
getSvgData() {
|
||||
getSvgData({ paddingX = 0, paddingY = 0 } = {}) {
|
||||
const svg = this.svg
|
||||
const draw = this.draw
|
||||
// 保存原始信息
|
||||
@@ -369,6 +383,10 @@ class MindMap {
|
||||
draw.scale(1 / origTransform.scaleX, 1 / origTransform.scaleY)
|
||||
// 获取变换后的位置尺寸信息,其实是getBoundingClientRect方法的包装方法
|
||||
const rect = draw.rbox()
|
||||
// 内边距
|
||||
rect.width += paddingX
|
||||
rect.height += paddingY
|
||||
draw.translate(paddingX / 2, paddingY / 2)
|
||||
// 将svg设置为实际内容的宽高
|
||||
svg.size(rect.width, rect.height)
|
||||
// 把实际内容变换
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.5.4",
|
||||
"version": "0.5.6",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -27,6 +27,7 @@ class Event extends EventEmitter {
|
||||
|
||||
// 绑定函数上下文
|
||||
bindFn() {
|
||||
this.onBodyClick = this.onBodyClick.bind(this)
|
||||
this.onDrawClick = this.onDrawClick.bind(this)
|
||||
this.onMousedown = this.onMousedown.bind(this)
|
||||
this.onMousemove = this.onMousemove.bind(this)
|
||||
@@ -41,6 +42,7 @@ class Event extends EventEmitter {
|
||||
|
||||
// 绑定事件
|
||||
bind() {
|
||||
document.body.addEventListener('click', this.onBodyClick)
|
||||
this.mindMap.svg.on('click', this.onDrawClick)
|
||||
this.mindMap.el.addEventListener('mousedown', this.onMousedown)
|
||||
this.mindMap.svg.on('mousedown', this.onSvgMousedown)
|
||||
@@ -55,6 +57,7 @@ class Event extends EventEmitter {
|
||||
|
||||
// 解绑事件
|
||||
unbind() {
|
||||
document.body.removeEventListener('click', this.onBodyClick)
|
||||
this.mindMap.svg.off('click', this.onDrawClick)
|
||||
this.mindMap.el.removeEventListener('mousedown', this.onMousedown)
|
||||
window.removeEventListener('mousemove', this.onMousemove)
|
||||
@@ -71,6 +74,11 @@ class Event extends EventEmitter {
|
||||
this.emit('draw_click', e)
|
||||
}
|
||||
|
||||
// 页面的单击事件
|
||||
onBodyClick(e) {
|
||||
this.emit('body_click', e)
|
||||
}
|
||||
|
||||
// svg画布的鼠标按下事件
|
||||
onSvgMousedown(e) {
|
||||
this.emit('svg_mousedown', e)
|
||||
|
||||
@@ -28,7 +28,11 @@ class Export {
|
||||
|
||||
// 获取svg数据
|
||||
async getSvgData(domToImage) {
|
||||
let { svg, svgHTML } = this.mindMap.getSvgData()
|
||||
let { exportPaddingX, exportPaddingY } = this.mindMap.opt
|
||||
let { svg, svgHTML } = this.mindMap.getSvgData({
|
||||
paddingX: exportPaddingX,
|
||||
paddingY: exportPaddingY
|
||||
})
|
||||
// 把图片的url转换成data:url类型,否则导出会丢失图片
|
||||
let imageList = svg.find('image')
|
||||
let task = imageList.map(async item => {
|
||||
|
||||
@@ -8,7 +8,6 @@ import nodeCommandWrapsMethods from './utils/nodeCommandWraps'
|
||||
import nodeCreateContentsMethods from './utils/nodeCreateContents'
|
||||
import { CONSTANTS } from './utils/constant'
|
||||
|
||||
|
||||
// 节点类
|
||||
class Node {
|
||||
// 构造函数
|
||||
@@ -58,7 +57,7 @@ class Node {
|
||||
this.children = opt.children || []
|
||||
// 节点内容的容器
|
||||
this.group = null
|
||||
this.shapeNode = null// 节点形状节点
|
||||
this.shapeNode = null // 节点形状节点
|
||||
// 节点内容对象
|
||||
this._imgData = null
|
||||
this._iconData = null
|
||||
@@ -95,19 +94,19 @@ class Node {
|
||||
// 是否需要重新layout
|
||||
this.needLayout = false
|
||||
// 概要相关方法
|
||||
Object.keys(nodeGeneralizationMethods).forEach((item) => {
|
||||
Object.keys(nodeGeneralizationMethods).forEach(item => {
|
||||
this[item] = nodeGeneralizationMethods[item].bind(this)
|
||||
})
|
||||
// 展开收起按钮相关方法
|
||||
Object.keys(nodeExpandBtnMethods).forEach((item) => {
|
||||
Object.keys(nodeExpandBtnMethods).forEach(item => {
|
||||
this[item] = nodeExpandBtnMethods[item].bind(this)
|
||||
})
|
||||
// 命令的相关方法
|
||||
Object.keys(nodeCommandWrapsMethods).forEach((item) => {
|
||||
Object.keys(nodeCommandWrapsMethods).forEach(item => {
|
||||
this[item] = nodeCommandWrapsMethods[item].bind(this)
|
||||
})
|
||||
// 创建节点内容的相关方法
|
||||
Object.keys(nodeCreateContentsMethods).forEach((item) => {
|
||||
Object.keys(nodeCreateContentsMethods).forEach(item => {
|
||||
this[item] = nodeCreateContentsMethods[item].bind(this)
|
||||
})
|
||||
// 初始化
|
||||
@@ -339,6 +338,9 @@ class Node {
|
||||
this.active(e)
|
||||
})
|
||||
this.group.on('mousedown', e => {
|
||||
if (this.isRoot && e.which === 3) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
if (!this.isRoot) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
@@ -346,9 +348,16 @@ class Node {
|
||||
if (e.ctrlKey) {
|
||||
this.isMultipleChoice = true
|
||||
let isActive = this.nodeData.data.isActive
|
||||
if (!isActive) this.mindMap.emit('before_node_active', this, this.renderer.activeNodeList)
|
||||
if (!isActive)
|
||||
this.mindMap.emit(
|
||||
'before_node_active',
|
||||
this,
|
||||
this.renderer.activeNodeList
|
||||
)
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', this, !isActive)
|
||||
this.mindMap.renderer[isActive ? 'removeActiveNode' : 'addActiveNode'](this)
|
||||
this.mindMap.renderer[isActive ? 'removeActiveNode' : 'addActiveNode'](
|
||||
this
|
||||
)
|
||||
this.mindMap.emit(
|
||||
'node_active',
|
||||
isActive ? null : this,
|
||||
@@ -380,7 +389,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()
|
||||
@@ -414,7 +423,8 @@ class Node {
|
||||
if (!this.group) {
|
||||
return
|
||||
}
|
||||
let { enableNodeTransitionMove, nodeTransitionMoveDuration } = this.mindMap.opt
|
||||
let { enableNodeTransitionMove, nodeTransitionMoveDuration } =
|
||||
this.mindMap.opt
|
||||
// 需要移除展开收缩按钮
|
||||
if (this._expandBtn && this.nodeData.children.length <= 0) {
|
||||
this.removeExpandBtn()
|
||||
@@ -429,15 +439,9 @@ class Node {
|
||||
if (!isLayout && enableNodeTransitionMove) {
|
||||
this.group
|
||||
.animate(nodeTransitionMoveDuration)
|
||||
.translate(
|
||||
this.left - t.translateX,
|
||||
this.top - t.translateY
|
||||
)
|
||||
.translate(this.left - t.translateX, this.top - t.translateY)
|
||||
} else {
|
||||
this.group.translate(
|
||||
this.left - t.translateX,
|
||||
this.top - t.translateY
|
||||
)
|
||||
this.group.translate(this.left - t.translateX, this.top - t.translateY)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,12 +457,15 @@ class Node {
|
||||
updateNodeShape() {
|
||||
if (!this.shapeNode) return
|
||||
const shape = this.getShape()
|
||||
this.style[shape === CONSTANTS.SHAPE.RECTANGLE ? 'rect' : 'shape'](this.shapeNode)
|
||||
this.style[shape === CONSTANTS.SHAPE.RECTANGLE ? 'rect' : 'shape'](
|
||||
this.shapeNode
|
||||
)
|
||||
}
|
||||
|
||||
// 递归渲染
|
||||
render(callback = () => {}) {
|
||||
let { enableNodeTransitionMove, nodeTransitionMoveDuration } = this.mindMap.opt
|
||||
let { enableNodeTransitionMove, nodeTransitionMoveDuration } =
|
||||
this.mindMap.opt
|
||||
// 节点
|
||||
// 重新渲染连线
|
||||
this.renderLine()
|
||||
@@ -553,7 +560,10 @@ class Node {
|
||||
this.hideGeneralization()
|
||||
if (this.parent) {
|
||||
let index = this.parent.children.indexOf(this)
|
||||
this.parent._lines[index].hide()
|
||||
this.parent._lines[index] && this.parent._lines[index].hide()
|
||||
this._lines.forEach(item => {
|
||||
item.hide()
|
||||
})
|
||||
}
|
||||
// 子节点
|
||||
if (this.children && this.children.length) {
|
||||
@@ -577,6 +587,9 @@ class Node {
|
||||
if (this.parent) {
|
||||
let index = this.parent.children.indexOf(this)
|
||||
this.parent._lines[index] && this.parent._lines[index].show()
|
||||
this._lines.forEach(item => {
|
||||
item.show()
|
||||
})
|
||||
}
|
||||
// 子节点
|
||||
if (this.children && this.children.length) {
|
||||
@@ -596,6 +609,13 @@ class Node {
|
||||
return
|
||||
}
|
||||
let childrenLen = this.nodeData.children.length
|
||||
// 切换为鱼骨结构时,清空根节点和二级节点的连线
|
||||
if (
|
||||
this.mindMap.opt.layout === CONSTANTS.LAYOUT.FISHBONE &&
|
||||
(this.isRoot || this.layerIndex === 1)
|
||||
) {
|
||||
childrenLen = 0
|
||||
}
|
||||
if (childrenLen > this._lines.length) {
|
||||
// 创建缺少的线
|
||||
new Array(childrenLen - this._lines.length).fill(0).forEach(() => {
|
||||
|
||||
@@ -3,6 +3,8 @@ import LogicalStructure from './layouts/LogicalStructure'
|
||||
import MindMap from './layouts/MindMap'
|
||||
import CatalogOrganization from './layouts/CatalogOrganization'
|
||||
import OrganizationStructure from './layouts/OrganizationStructure'
|
||||
import Timeline from './layouts/Timeline'
|
||||
import Fishbone from './layouts/Fishbone'
|
||||
import TextEdit from './TextEdit'
|
||||
import { copyNodeTree, simpleDeepClone, walk } from './utils'
|
||||
import { shapeList } from './Shape'
|
||||
@@ -18,7 +20,13 @@ const layouts = {
|
||||
// 目录组织图
|
||||
[CONSTANTS.LAYOUT.CATALOG_ORGANIZATION]: CatalogOrganization,
|
||||
// 组织结构图
|
||||
[CONSTANTS.LAYOUT.ORGANIZATION_STRUCTURE]: OrganizationStructure
|
||||
[CONSTANTS.LAYOUT.ORGANIZATION_STRUCTURE]: OrganizationStructure,
|
||||
// 时间轴
|
||||
[CONSTANTS.LAYOUT.TIMELINE]: Timeline,
|
||||
// 时间轴2
|
||||
[CONSTANTS.LAYOUT.TIMELINE2]: Timeline,
|
||||
// 鱼骨图
|
||||
[CONSTANTS.LAYOUT.FISHBONE]: Fishbone,
|
||||
}
|
||||
|
||||
// 渲染
|
||||
@@ -65,7 +73,7 @@ class Render {
|
||||
layouts[this.mindMap.opt.layout]
|
||||
? layouts[this.mindMap.opt.layout]
|
||||
: layouts[CONSTANTS.LAYOUT.LOGICAL_STRUCTURE]
|
||||
)(this)
|
||||
)(this, this.mindMap.opt.layout)
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
@@ -242,6 +250,7 @@ class Render {
|
||||
|
||||
// 渲染
|
||||
render(callback = () => {}, source) {
|
||||
let t = Date.now()
|
||||
// 如果当前还没有渲染完毕,不再触发渲染
|
||||
if (this.isRendering) {
|
||||
// 等待当前渲染完毕后再进行一次渲染
|
||||
@@ -273,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()
|
||||
@@ -281,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)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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'
|
||||
@@ -41,8 +40,43 @@ class RichText {
|
||||
this.range = null
|
||||
this.lastRange = null
|
||||
this.node = null
|
||||
this.styleEl = null
|
||||
this.cacheEditingText = ''
|
||||
this.initOpt()
|
||||
this.extendQuill()
|
||||
this.appendCss()
|
||||
}
|
||||
|
||||
// 插入样式
|
||||
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 {
|
||||
// display: flex;
|
||||
// }
|
||||
|
||||
// .smm-richtext-node-edit-wrap p {
|
||||
// display: flex;
|
||||
// }
|
||||
this.styleEl = document.createElement('style')
|
||||
this.styleEl.type = 'text/css'
|
||||
this.styleEl.innerHTML = cssText
|
||||
document.head.appendChild(this.styleEl)
|
||||
}
|
||||
|
||||
// 处理选项参数
|
||||
@@ -96,9 +130,15 @@ class RichText {
|
||||
if (!rect) rect = node._textData.node.node.getBoundingClientRect()
|
||||
this.mindMap.emit('before_show_text_edit')
|
||||
this.mindMap.renderer.textEdit.registerTmpShortcut()
|
||||
const paddingX = 5
|
||||
const paddingY = 3
|
||||
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;margin-left: -${paddingX}px;margin-top: -${paddingY}px;`
|
||||
this.textEditNode.addEventListener('click', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
document.body.appendChild(this.textEditNode)
|
||||
}
|
||||
// 原始宽高
|
||||
@@ -107,15 +147,16 @@ class RichText {
|
||||
let originHeight = g.attr('data-height')
|
||||
// 使用节点的填充色,否则如果节点颜色是白色的话编辑时看不见
|
||||
let bgColor = node.style.merge('fillColor')
|
||||
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.display = 'block'
|
||||
this.textEditNode.style.maxWidth = this.mindMap.opt.textAutoWrapWidth + 'px'
|
||||
this.textEditNode.style.maxWidth = this.mindMap.opt.textAutoWrapWidth + paddingX * 2 + 'px'
|
||||
this.textEditNode.style.transform = `scale(${rect.width / originWidth}, ${
|
||||
rect.height / originHeight
|
||||
})`
|
||||
@@ -123,9 +164,9 @@ class 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'
|
||||
@@ -135,6 +176,7 @@ class RichText {
|
||||
// 如果是非富文本的情况,需要手动应用文本样式
|
||||
this.setTextStyleIfNotRichText(node)
|
||||
}
|
||||
this.cacheEditingText = ''
|
||||
}
|
||||
|
||||
// 如果是非富文本的情况,需要手动应用文本样式
|
||||
@@ -151,14 +193,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)
|
||||
@@ -454,6 +501,7 @@ class RichText {
|
||||
// 插件被移除前做的事情
|
||||
beforePluginRemove() {
|
||||
this.transformAllNodesToNormalNode()
|
||||
document.head.removeChild(this.styleEl)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
// })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
// 点击事件
|
||||
@@ -23,6 +28,12 @@ export default class TextEdit {
|
||||
// 隐藏文本编辑框
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
this.mindMap.on('body_click', () => {
|
||||
// 隐藏文本编辑框
|
||||
if (this.mindMap.opt.isEndNodeTextEditOnClickOuter) {
|
||||
this.hideEditTextBox()
|
||||
}
|
||||
})
|
||||
this.mindMap.on('svg_mousedown', () => {
|
||||
// 隐藏文本编辑框
|
||||
this.hideEditTextBox()
|
||||
@@ -42,6 +53,7 @@ export default class TextEdit {
|
||||
}
|
||||
this.show(this.renderer.activeNodeList[0])
|
||||
})
|
||||
this.mindMap.on('scale', this.onScale)
|
||||
}
|
||||
|
||||
// 注册临时快捷键
|
||||
@@ -54,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)
|
||||
@@ -62,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')
|
||||
@@ -73,13 +101,17 @@ export default class TextEdit {
|
||||
this.textEditNode.addEventListener('keyup', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
this.textEditNode.addEventListener('click', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
document.body.appendChild(this.textEditNode)
|
||||
}
|
||||
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)
|
||||
let textLines = (this.cacheEditingText || node.nodeData.data.text).split(/\n/gim)
|
||||
node.style.domText(this.textEditNode, scale, textLines.length)
|
||||
this.textEditNode.style.zIndex = this.mindMap.opt.nodeTextEditZIndex
|
||||
this.textEditNode.innerHTML = textLines.join('<br>')
|
||||
this.textEditNode.style.minWidth = rect.width + 10 + 'px'
|
||||
this.textEditNode.style.minHeight = rect.height + 6 + 'px'
|
||||
@@ -92,7 +124,10 @@ export default class TextEdit {
|
||||
}
|
||||
this.showTextEdit = true
|
||||
// 选中文本
|
||||
this.selectNodeText()
|
||||
if (!this.cacheEditingText) {
|
||||
this.selectNodeText()
|
||||
}
|
||||
this.cacheEditingText = ''
|
||||
}
|
||||
|
||||
// 选中文本
|
||||
@@ -104,8 +139,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()
|
||||
}
|
||||
@@ -113,7 +154,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) {
|
||||
// 概要节点
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
// 缩小
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
.ql-editor {
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
height: auto;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.ql-container {
|
||||
height: auto;
|
||||
font-size: inherit;
|
||||
}
|
||||
@@ -157,6 +157,37 @@ class Base {
|
||||
})
|
||||
}
|
||||
|
||||
// 更新子节点多个属性
|
||||
updateChildrenPro(children, props) {
|
||||
children.forEach(item => {
|
||||
Object.keys(props).forEach((prop) => {
|
||||
item[prop] += props[prop]
|
||||
})
|
||||
if (item.children && item.children.length && !item.hasCustomPosition()) {
|
||||
// 适配自定义位置
|
||||
this.updateChildrenPro(item.children, props)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 递归计算节点的宽度
|
||||
getNodeAreaWidth(node) {
|
||||
let widthArr = []
|
||||
let loop = (node, width) => {
|
||||
if (node.children.length) {
|
||||
width += node.width / 2
|
||||
node.children.forEach(item => {
|
||||
loop(item, width)
|
||||
})
|
||||
} else {
|
||||
width += node.width
|
||||
widthArr.push(width)
|
||||
}
|
||||
}
|
||||
loop(node, 0)
|
||||
return Math.max(...widthArr)
|
||||
}
|
||||
|
||||
// 二次贝塞尔曲线
|
||||
quadraticCurvePath(x1, y1, x2, y2) {
|
||||
let cx = x1 + (x2 - x1) * 0.2
|
||||
@@ -266,6 +297,11 @@ class Base {
|
||||
generalizationNodeMargin
|
||||
}
|
||||
}
|
||||
|
||||
// 获取节点实际存在几个子节点
|
||||
getNodeActChildrenLength(node) {
|
||||
return node.nodeData.children && node.nodeData.children.length
|
||||
}
|
||||
}
|
||||
|
||||
export default Base
|
||||
|
||||
@@ -87,11 +87,11 @@ class CatalogOrganization extends Base {
|
||||
totalLeft += cur.width + marginX
|
||||
})
|
||||
} else {
|
||||
let totalTop = node.top + node.height + marginY + node.expandBtnSize
|
||||
let totalTop = node.top + node.height + marginY + (this.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0)
|
||||
node.children.forEach(cur => {
|
||||
cur.left = node.left + node.width * 0.5
|
||||
cur.top = totalTop
|
||||
totalTop += cur.height + marginY + node.expandBtnSize
|
||||
totalTop += cur.height + marginY + (this.getNodeActChildrenLength(cur) > 0 ? cur.expandBtnSize : 0)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -115,7 +115,7 @@ class CatalogOrganization extends Base {
|
||||
let areaWidth = this.getNodeAreaWidth(node)
|
||||
let difference = areaWidth - node.width
|
||||
if (difference > 0) {
|
||||
this.updateBrothersLeft(node, difference / 2)
|
||||
this.updateBrothersLeft(node, difference)
|
||||
}
|
||||
}
|
||||
// 调整top
|
||||
@@ -124,36 +124,24 @@ class CatalogOrganization extends Base {
|
||||
let marginY = this.getMarginY(layerIndex + 1)
|
||||
let totalHeight =
|
||||
node.children.reduce((h, item) => {
|
||||
return h + item.height
|
||||
return h + item.height + (this.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
}, 0) +
|
||||
(len + 1) * marginY +
|
||||
len * node.expandBtnSize
|
||||
len * marginY
|
||||
this.updateBrothersTop(node, totalHeight)
|
||||
}
|
||||
},
|
||||
null,
|
||||
(node, parent, isRoot) => {
|
||||
if (isRoot) {
|
||||
let { right, left } = this.getNodeBoundaries(node, 'h')
|
||||
let childrenWidth = right - left
|
||||
let offset = (node.left - left) - (childrenWidth - node.width) / 2
|
||||
this.updateChildren(node.children, 'left', offset)
|
||||
}
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 递归计算节点的宽度
|
||||
getNodeAreaWidth(node) {
|
||||
let widthArr = []
|
||||
let loop = (node, width) => {
|
||||
if (node.children.length) {
|
||||
width += node.width / 2
|
||||
node.children.forEach(item => {
|
||||
loop(item, width)
|
||||
})
|
||||
} else {
|
||||
width += node.width
|
||||
widthArr.push(width)
|
||||
}
|
||||
}
|
||||
loop(node, 0)
|
||||
return Math.max(...widthArr)
|
||||
}
|
||||
|
||||
// 调整兄弟节点的left
|
||||
updateBrothersLeft(node, addWidth) {
|
||||
if (node.parent) {
|
||||
@@ -161,38 +149,15 @@ class CatalogOrganization extends Base {
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item === node
|
||||
})
|
||||
// 存在大于一个节点时,第一个或最后一个节点自身也需要移动,否则两边不对称
|
||||
if (
|
||||
(index === 0 || index === childrenList.length - 1) &&
|
||||
childrenList.length > 1
|
||||
) {
|
||||
let _offset = index === 0 ? -addWidth : addWidth
|
||||
node.left += _offset
|
||||
if (
|
||||
node.children &&
|
||||
node.children.length &&
|
||||
!node.hasCustomPosition()
|
||||
) {
|
||||
this.updateChildren(node.children, 'left', _offset)
|
||||
}
|
||||
}
|
||||
childrenList.forEach((item, _index) => {
|
||||
if (item.hasCustomPosition()) {
|
||||
if (item.hasCustomPosition() || _index <= index) {
|
||||
// 适配自定义位置
|
||||
return
|
||||
}
|
||||
let _offset = 0
|
||||
if (_index < index) {
|
||||
// 左边的节点往左移
|
||||
_offset = -addWidth
|
||||
} else if (_index > index) {
|
||||
// 右边的节点往右移
|
||||
_offset = addWidth
|
||||
}
|
||||
item.left += _offset
|
||||
item.left += addWidth
|
||||
// 同步更新子节点的位置
|
||||
if (item.children && item.children.length) {
|
||||
this.updateChildren(item.children, 'left', _offset)
|
||||
this.updateChildren(item.children, 'left', addWidth)
|
||||
}
|
||||
})
|
||||
// 更新父节点的位置
|
||||
|
||||
375
simple-mind-map/src/layouts/Fishbone.js
Normal file
375
simple-mind-map/src/layouts/Fishbone.js
Normal file
@@ -0,0 +1,375 @@
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun, degToRad } from '../utils'
|
||||
import { CONSTANTS } from '../utils/constant'
|
||||
import utils from './fishboneUtils'
|
||||
|
||||
// 鱼骨图
|
||||
class Fishbone extends Base {
|
||||
// 构造函数
|
||||
constructor(opt = {}) {
|
||||
super(opt)
|
||||
this.indent = 0.3
|
||||
this.childIndent = 0.5
|
||||
}
|
||||
|
||||
// 布局
|
||||
doLayout(callback) {
|
||||
let task = [
|
||||
() => {
|
||||
this.computedBaseValue()
|
||||
},
|
||||
() => {
|
||||
this.computedLeftTopValue()
|
||||
},
|
||||
() => {
|
||||
this.adjustLeftTopValue()
|
||||
},
|
||||
() => {
|
||||
callback(this.root)
|
||||
}
|
||||
]
|
||||
asyncRun(task)
|
||||
}
|
||||
|
||||
// 遍历数据创建节点、计算根节点的位置,计算根节点的子节点的top值
|
||||
computedBaseValue() {
|
||||
walk(
|
||||
this.renderer.renderTree,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex, index) => {
|
||||
// 创建节点
|
||||
let newNode = this.createNode(node, parent, isRoot, layerIndex)
|
||||
// 根节点定位在画布中心位置
|
||||
if (isRoot) {
|
||||
this.setNodeCenter(newNode)
|
||||
} else {
|
||||
// 非根节点
|
||||
// 三级及以下节点以上级方向为准
|
||||
if (parent._node.dir) {
|
||||
newNode.dir = parent._node.dir
|
||||
} else {
|
||||
// 节点生长方向
|
||||
newNode.dir =
|
||||
index % 2 === 0
|
||||
? CONSTANTS.TIMELINE_DIR.TOP
|
||||
: CONSTANTS.TIMELINE_DIR.BOTTOM
|
||||
}
|
||||
// 计算二级节点的top值
|
||||
if (parent._node.isRoot) {
|
||||
if (this.checkIsTop(newNode)) {
|
||||
newNode.top = parent._node.top - newNode.height
|
||||
} else {
|
||||
newNode.top = parent._node.top + parent._node.height
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!node.data.expand) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
null,
|
||||
true,
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
// 遍历节点树计算节点的left、top
|
||||
computedLeftTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (node.isRoot) {
|
||||
let topTotalLeft = node.left + node.width + node.height
|
||||
let bottomTotalLeft = node.left + node.width + node.height
|
||||
node.children.forEach(item => {
|
||||
if (this.checkIsTop(item)) {
|
||||
item.left = topTotalLeft
|
||||
topTotalLeft += item.width
|
||||
} else {
|
||||
item.left = bottomTotalLeft + 20
|
||||
bottomTotalLeft += item.width
|
||||
}
|
||||
})
|
||||
}
|
||||
let params = { layerIndex, node, ctx: this }
|
||||
if (this.checkIsTop(node)) {
|
||||
utils.top.computedLeftTopValue(params)
|
||||
} else {
|
||||
utils.bottom.computedLeftTopValue(params)
|
||||
}
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 调整节点left、top
|
||||
adjustLeftTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (!node.nodeData.data.expand) {
|
||||
return
|
||||
}
|
||||
let params = { node, parent, layerIndex, ctx: this }
|
||||
if (this.checkIsTop(node)) {
|
||||
utils.top.adjustLeftTopValueBefore(params)
|
||||
} else {
|
||||
utils.bottom.adjustLeftTopValueBefore(params)
|
||||
}
|
||||
},
|
||||
(node, parent) => {
|
||||
let params = { parent, node, ctx: this }
|
||||
if (this.checkIsTop(node)) {
|
||||
utils.top.adjustLeftTopValueAfter(params)
|
||||
} else {
|
||||
utils.bottom.adjustLeftTopValueAfter(params)
|
||||
}
|
||||
// 调整二级节点的子节点的left值
|
||||
if (node.isRoot) {
|
||||
let topTotalLeft = 0
|
||||
let bottomTotalLeft = 0
|
||||
node.children.forEach(item => {
|
||||
if (this.checkIsTop(item)) {
|
||||
item.left += topTotalLeft
|
||||
this.updateChildren(item.children, 'left', topTotalLeft)
|
||||
let { left, right } = this.getNodeBoundaries(item, 'h')
|
||||
topTotalLeft += right - left
|
||||
} else {
|
||||
item.left += bottomTotalLeft
|
||||
this.updateChildren(item.children, 'left', bottomTotalLeft)
|
||||
let { left, right } = this.getNodeBoundaries(item, 'h')
|
||||
bottomTotalLeft += right - left
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 递归计算节点的宽度
|
||||
getNodeAreaHeight(node) {
|
||||
let totalHeight = 0
|
||||
let loop = node => {
|
||||
totalHeight +=
|
||||
node.height +
|
||||
(this.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0)
|
||||
if (node.children.length) {
|
||||
node.children.forEach(item => {
|
||||
loop(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
loop(node)
|
||||
return totalHeight
|
||||
}
|
||||
|
||||
// 调整兄弟节点的left
|
||||
updateBrothersLeft(node) {
|
||||
let childrenList = node.children
|
||||
let totalAddWidth = 0
|
||||
childrenList.forEach(item => {
|
||||
item.left += totalAddWidth
|
||||
if (item.children && item.children.length) {
|
||||
this.updateChildren(item.children, 'left', totalAddWidth)
|
||||
}
|
||||
let { left, right } = this.getNodeBoundaries(item, 'h')
|
||||
let areaWidth = right - left
|
||||
let difference = areaWidth - item.width
|
||||
if (difference > 0) {
|
||||
totalAddWidth += difference
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 调整兄弟节点的top
|
||||
updateBrothersTop(node, addHeight) {
|
||||
if (node.parent && !node.parent.isRoot) {
|
||||
let childrenList = node.parent.children
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item === node
|
||||
})
|
||||
childrenList.forEach((item, _index) => {
|
||||
if (item.hasCustomPosition()) {
|
||||
// 适配自定义位置
|
||||
return
|
||||
}
|
||||
let _offset = 0
|
||||
// 下面的节点往下移
|
||||
if (_index > index) {
|
||||
_offset = addHeight
|
||||
}
|
||||
item.top += _offset
|
||||
// 同步更新子节点的位置
|
||||
if (item.children && item.children.length) {
|
||||
this.updateChildren(item.children, 'top', _offset)
|
||||
}
|
||||
})
|
||||
// 更新父节点的位置
|
||||
if (this.checkIsTop(node)) {
|
||||
this.updateBrothersTop(node.parent, addHeight)
|
||||
} else {
|
||||
this.updateBrothersTop(
|
||||
node.parent,
|
||||
node.layerIndex === 3 ? 0 : addHeight
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查节点是否是上方节点
|
||||
checkIsTop(node) {
|
||||
return node.dir === CONSTANTS.TIMELINE_DIR.TOP
|
||||
}
|
||||
|
||||
// 绘制连线,连接该节点到其子节点
|
||||
renderLine(node, lines, style) {
|
||||
if (node.layerIndex !== 1 && node.children.length <= 0) {
|
||||
return []
|
||||
}
|
||||
let { top, height, expandBtnSize } = node
|
||||
let len = node.children.length
|
||||
if (node.isRoot) {
|
||||
// 当前节点是根节点
|
||||
// 根节点的子节点是和根节点同一水平线排列
|
||||
let maxx = -Infinity
|
||||
node.children.forEach((item) => {
|
||||
if (item.left > maxx) {
|
||||
maxx = item.left
|
||||
}
|
||||
// 水平线段到二级节点的连线
|
||||
let nodeLineX = item.left
|
||||
let offset = node.height / 2
|
||||
let offsetX = offset / Math.tan(degToRad(this.mindMap.opt.fishboneDeg))
|
||||
let line = this.draw.path()
|
||||
if (this.checkIsTop(item)) {
|
||||
line.plot(
|
||||
`M ${nodeLineX - offsetX},${item.top + item.height + offset} L ${item.left},${
|
||||
item.top + item.height
|
||||
}`
|
||||
)
|
||||
} else {
|
||||
line.plot(
|
||||
`M ${nodeLineX - offsetX},${
|
||||
item.top - offset
|
||||
} L ${nodeLineX},${item.top}`
|
||||
)
|
||||
}
|
||||
node.style.line(line)
|
||||
node._lines.push(line)
|
||||
style && style(line, node)
|
||||
})
|
||||
// 从根节点出发的水平线
|
||||
let nodeHalfTop = node.top + node.height / 2
|
||||
let offset = node.height / 2
|
||||
let line = this.draw.path()
|
||||
line.plot(
|
||||
`M ${node.left + node.width},${nodeHalfTop} L ${
|
||||
maxx - offset / Math.tan(degToRad(this.mindMap.opt.fishboneDeg))
|
||||
},${nodeHalfTop}`
|
||||
)
|
||||
node.style.line(line)
|
||||
node._lines.push(line)
|
||||
style && style(line, node)
|
||||
} else {
|
||||
// 当前节点为非根节点
|
||||
let maxy = -Infinity
|
||||
let miny = Infinity
|
||||
let maxx = -Infinity
|
||||
let x = node.left + node.width * this.indent
|
||||
node.children.forEach((item, index) => {
|
||||
if (item.left > maxx) {
|
||||
maxx = item.left
|
||||
}
|
||||
let y = item.top + item.height / 2
|
||||
if (y > maxy) {
|
||||
maxy = y
|
||||
}
|
||||
if (y < miny) {
|
||||
miny = y
|
||||
}
|
||||
// 水平线
|
||||
if (node.layerIndex > 1) {
|
||||
let path = `M ${x},${y} L ${item.left},${y}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
}
|
||||
})
|
||||
// 斜线
|
||||
if (len >= 0) {
|
||||
let line = this.draw.path()
|
||||
expandBtnSize = len > 0 ? expandBtnSize : 0
|
||||
let lineLength = maxx - node.left - node.width * this.indent
|
||||
lineLength = Math.max(lineLength, 0)
|
||||
let params = {
|
||||
node,
|
||||
line,
|
||||
top,
|
||||
x,
|
||||
lineLength,
|
||||
height,
|
||||
expandBtnSize,
|
||||
maxy,
|
||||
miny,
|
||||
ctx: this
|
||||
}
|
||||
if (this.checkIsTop(node)) {
|
||||
utils.top.renderLine(params)
|
||||
} else {
|
||||
utils.bottom.renderLine(params)
|
||||
}
|
||||
node.style.line(line)
|
||||
node._lines.push(line)
|
||||
style && style(line, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染按钮
|
||||
renderExpandBtn(node, btn) {
|
||||
let { width, height, expandBtnSize, isRoot } = node
|
||||
if (!isRoot) {
|
||||
let { translateX, translateY } = btn.transform()
|
||||
let params = {
|
||||
node,
|
||||
btn,
|
||||
expandBtnSize,
|
||||
translateX,
|
||||
translateY,
|
||||
width,
|
||||
height
|
||||
}
|
||||
if (this.checkIsTop(node)) {
|
||||
utils.top.renderExpandBtn(params)
|
||||
} else {
|
||||
utils.bottom.renderExpandBtn(params)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建概要节点
|
||||
renderGeneralization(node, gLine, gNode) {
|
||||
let {
|
||||
top,
|
||||
bottom,
|
||||
right,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
} = this.getNodeBoundaries(node, 'h')
|
||||
let x1 = right + generalizationLineMargin
|
||||
let y1 = top
|
||||
let x2 = right + generalizationLineMargin
|
||||
let y2 = bottom
|
||||
let cx = x1 + 20
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
gLine.plot(path)
|
||||
gNode.left = right + generalizationNodeMargin
|
||||
gNode.top = top + (bottom - top - gNode.height) / 2
|
||||
}
|
||||
}
|
||||
|
||||
export default Fishbone
|
||||
369
simple-mind-map/src/layouts/FishboneBottom.js
Normal file
369
simple-mind-map/src/layouts/FishboneBottom.js
Normal file
@@ -0,0 +1,369 @@
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun } from '../utils'
|
||||
import { CONSTANTS } from '../utils/constant'
|
||||
|
||||
const degToRad = deg => {
|
||||
return (Math.PI / 180) * deg
|
||||
}
|
||||
|
||||
// 下方鱼骨图
|
||||
class Fishbone extends Base {
|
||||
// 构造函数
|
||||
constructor(opt = {}) {
|
||||
super(opt)
|
||||
}
|
||||
|
||||
// 布局
|
||||
doLayout(callback) {
|
||||
let task = [
|
||||
() => {
|
||||
this.computedBaseValue()
|
||||
},
|
||||
() => {
|
||||
this.computedLeftTopValue()
|
||||
},
|
||||
() => {
|
||||
this.adjustLeftTopValue()
|
||||
},
|
||||
() => {
|
||||
callback(this.root)
|
||||
}
|
||||
]
|
||||
asyncRun(task)
|
||||
}
|
||||
|
||||
// 遍历数据创建节点、计算根节点的位置,计算根节点的子节点的top值
|
||||
computedBaseValue() {
|
||||
walk(
|
||||
this.renderer.renderTree,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex, index) => {
|
||||
// 创建节点
|
||||
let newNode = this.createNode(node, parent, isRoot, layerIndex)
|
||||
// 根节点定位在画布中心位置
|
||||
if (isRoot) {
|
||||
this.setNodeCenter(newNode)
|
||||
} else {
|
||||
// 非根节点
|
||||
// 三级及以下节点以上级方向为准
|
||||
if (parent._node.dir) {
|
||||
newNode.dir = parent._node.dir
|
||||
} else {
|
||||
// 节点生长方向
|
||||
newNode.dir =
|
||||
index % 2 === 0
|
||||
? CONSTANTS.TIMELINE_DIR.TOP
|
||||
: CONSTANTS.TIMELINE_DIR.BOTTOM
|
||||
}
|
||||
// 计算二级节点的top值
|
||||
if (parent._node.isRoot) {
|
||||
newNode.top = parent._node.top + parent._node.height
|
||||
}
|
||||
}
|
||||
if (!node.data.expand) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
null,
|
||||
true,
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
// 遍历节点树计算节点的left、top
|
||||
computedLeftTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex, index) => {
|
||||
if (node.isRoot) {
|
||||
let totalLeft = node.left + node.width
|
||||
node.children.forEach(item => {
|
||||
item.left = totalLeft
|
||||
totalLeft += item.width
|
||||
})
|
||||
}
|
||||
if (layerIndex === 1 && node.children) {
|
||||
// 遍历二级节点的子节点
|
||||
let startLeft = node.left + node.width * 0.5
|
||||
let totalTop =
|
||||
node.top +
|
||||
node.height +
|
||||
(this.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0)
|
||||
|
||||
node.children.forEach(item => {
|
||||
item.left = startLeft
|
||||
item.top =
|
||||
totalTop +
|
||||
(this.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
totalTop +=
|
||||
item.height +
|
||||
(this.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
})
|
||||
}
|
||||
if (layerIndex > 1 && node.children) {
|
||||
// 遍历三级及以下节点的子节点
|
||||
let startLeft = node.left + node.width * 0.5
|
||||
let totalTop =
|
||||
node.top -
|
||||
(this.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0)
|
||||
node.children.forEach(item => {
|
||||
item.left = startLeft
|
||||
item.top = totalTop - item.height
|
||||
totalTop -=
|
||||
item.height +
|
||||
(this.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
})
|
||||
}
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 调整节点left、top
|
||||
adjustLeftTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (!node.nodeData.data.expand) {
|
||||
return
|
||||
}
|
||||
// 调整top
|
||||
let len = node.children.length
|
||||
// 调整三级节点的top
|
||||
// if (layerIndex === 2 && len > 0) {
|
||||
// let totalHeight = node.children.reduce((h, item) => {
|
||||
// return h + item.height
|
||||
// }, 0)
|
||||
// this.updateBrothersTop(node, totalHeight)
|
||||
// }
|
||||
if (layerIndex > 2 && len > 0) {
|
||||
let totalHeight = node.children.reduce((h, item) => {
|
||||
return (
|
||||
h +
|
||||
item.height +
|
||||
(this.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
)
|
||||
}, 0)
|
||||
this.updateBrothersTop(node, -totalHeight)
|
||||
}
|
||||
},
|
||||
(node, parent) => {
|
||||
// 将二级节点的子节点移到上方
|
||||
if (parent && parent.isRoot) {
|
||||
// 遍历二级节点的子节点
|
||||
let totalHeight = 0
|
||||
let totalHeight2 = 0
|
||||
node.children.forEach(item => {
|
||||
// 调整top
|
||||
let hasChildren = this.getNodeActChildrenLength(item) > 0
|
||||
let nodeTotalHeight = this.getNodeAreaHeight(item)
|
||||
let offset =
|
||||
hasChildren > 0
|
||||
? nodeTotalHeight -
|
||||
item.height -
|
||||
(hasChildren ? item.expandBtnSize : 0)
|
||||
: 0
|
||||
let _top = totalHeight + offset
|
||||
item.top += _top
|
||||
// 调整left
|
||||
let offsetLeft =
|
||||
(totalHeight2 + nodeTotalHeight) / Math.tan(degToRad(this.mindMap.opt.fishboneDeg))
|
||||
item.left += offsetLeft
|
||||
totalHeight += offset
|
||||
totalHeight2 += nodeTotalHeight
|
||||
// 同步更新后代节点
|
||||
this.updateChildrenPro(item.children, {
|
||||
top: _top,
|
||||
left: offsetLeft
|
||||
})
|
||||
})
|
||||
}
|
||||
// 调整二级节点的子节点的left值
|
||||
if (node.isRoot) {
|
||||
let totalLeft = 0
|
||||
node.children.forEach(item => {
|
||||
item.left += totalLeft
|
||||
this.updateChildren(item.children, 'left', totalLeft)
|
||||
let { left, right } = this.getNodeBoundaries(item, 'h')
|
||||
totalLeft += right - left
|
||||
})
|
||||
}
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 递归计算节点的宽度
|
||||
getNodeAreaHeight(node) {
|
||||
let totalHeight = 0
|
||||
let loop = node => {
|
||||
totalHeight +=
|
||||
node.height +
|
||||
(this.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0)
|
||||
if (node.children.length) {
|
||||
node.children.forEach(item => {
|
||||
loop(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
loop(node)
|
||||
return totalHeight
|
||||
}
|
||||
|
||||
// 调整兄弟节点的left
|
||||
updateBrothersLeft(node) {
|
||||
let childrenList = node.children
|
||||
let totalAddWidth = 0
|
||||
childrenList.forEach(item => {
|
||||
item.left += totalAddWidth
|
||||
if (item.children && item.children.length) {
|
||||
this.updateChildren(item.children, 'left', totalAddWidth)
|
||||
}
|
||||
// let areaWidth = this.getNodeAreaWidth(item)
|
||||
let { left, right } = this.getNodeBoundaries(item, 'h')
|
||||
let areaWidth = right - left
|
||||
let difference = areaWidth - item.width
|
||||
if (difference > 0) {
|
||||
totalAddWidth += difference
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 调整兄弟节点的top
|
||||
updateBrothersTop(node, addHeight) {
|
||||
if (node.parent && !node.parent.isRoot) {
|
||||
let childrenList = node.parent.children
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item === node
|
||||
})
|
||||
childrenList.forEach((item, _index) => {
|
||||
if (item.hasCustomPosition()) {
|
||||
// 适配自定义位置
|
||||
return
|
||||
}
|
||||
let _offset = 0
|
||||
// 下面的节点往下移
|
||||
if (_index > index) {
|
||||
_offset = addHeight
|
||||
}
|
||||
item.top += _offset
|
||||
// 同步更新子节点的位置
|
||||
if (item.children && item.children.length) {
|
||||
this.updateChildren(item.children, 'top', _offset)
|
||||
}
|
||||
})
|
||||
// 更新父节点的位置
|
||||
this.updateBrothersTop(node.parent, node.layerIndex === 3 ? 0 : addHeight)
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制连线,连接该节点到其子节点
|
||||
renderLine(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return []
|
||||
}
|
||||
let { left, top, width, height, expandBtnSize } = node
|
||||
let len = node.children.length
|
||||
if (node.isRoot) {
|
||||
// 当前节点是根节点
|
||||
let prevBother = node
|
||||
// 根节点的子节点是和根节点同一水平线排列
|
||||
node.children.forEach((item, index) => {
|
||||
let x1 = prevBother.left + prevBother.width
|
||||
let x2 = item.left
|
||||
let y = node.top + node.height / 2
|
||||
let path = `M ${x1},${y} L ${x2},${y}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
prevBother = item
|
||||
})
|
||||
} else {
|
||||
// 当前节点为非根节点
|
||||
let maxy = -Infinity
|
||||
let miny = Infinity
|
||||
let maxx = -Infinity
|
||||
let x = node.left + node.width * 0.3
|
||||
node.children.forEach((item, index) => {
|
||||
if (item.left > maxx) {
|
||||
maxx = item.left
|
||||
}
|
||||
let y = item.top + item.height / 2
|
||||
if (y > maxy) {
|
||||
maxy = y
|
||||
}
|
||||
if (y < miny) {
|
||||
miny = y
|
||||
}
|
||||
// 水平线
|
||||
if (node.layerIndex > 1) {
|
||||
let path = `M ${x},${y} L ${item.left},${y}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
}
|
||||
})
|
||||
// 竖线
|
||||
if (len > 0) {
|
||||
let line = this.draw.path()
|
||||
expandBtnSize = len > 0 ? expandBtnSize : 0
|
||||
let lineLength = maxx - node.left - node.width * 0.3
|
||||
if (node.parent && node.parent.isRoot) {
|
||||
line.plot(
|
||||
`M ${x},${top + height} L ${x + lineLength},${
|
||||
top + height + Math.tan(degToRad(this.mindMap.opt.fishboneDeg)) * lineLength
|
||||
}`
|
||||
)
|
||||
} else {
|
||||
line.plot(`M ${x},${top} L ${x},${miny}`)
|
||||
}
|
||||
node.style.line(line)
|
||||
node._lines.push(line)
|
||||
style && style(line, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染按钮
|
||||
renderExpandBtn(node, btn) {
|
||||
let { width, height, expandBtnSize, isRoot } = node
|
||||
if (!isRoot) {
|
||||
let { translateX, translateY } = btn.transform()
|
||||
if (node.parent && node.parent.isRoot) {
|
||||
btn.translate(
|
||||
width * 0.3 - expandBtnSize / 2 - translateX,
|
||||
height + expandBtnSize / 2 - translateY
|
||||
)
|
||||
} else {
|
||||
btn.translate(
|
||||
width * 0.3 - expandBtnSize / 2 - translateX,
|
||||
-expandBtnSize / 2 - translateY
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建概要节点
|
||||
renderGeneralization(node, gLine, gNode) {
|
||||
let {
|
||||
top,
|
||||
bottom,
|
||||
right,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
} = this.getNodeBoundaries(node, 'h')
|
||||
let x1 = right + generalizationLineMargin
|
||||
let y1 = top
|
||||
let x2 = right + generalizationLineMargin
|
||||
let y2 = bottom
|
||||
let cx = x1 + 20
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
gLine.plot(path)
|
||||
gNode.left = right + generalizationNodeMargin
|
||||
gNode.top = top + (bottom - top - gNode.height) / 2
|
||||
}
|
||||
}
|
||||
|
||||
export default Fishbone
|
||||
350
simple-mind-map/src/layouts/FishboneTop.js
Normal file
350
simple-mind-map/src/layouts/FishboneTop.js
Normal file
@@ -0,0 +1,350 @@
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun } from '../utils'
|
||||
import { CONSTANTS } from '../utils/constant'
|
||||
|
||||
const degToRad = deg => {
|
||||
return (Math.PI / 180) * deg
|
||||
}
|
||||
|
||||
// 上方鱼骨图
|
||||
class Fishbone extends Base {
|
||||
// 构造函数
|
||||
constructor(opt = {}) {
|
||||
super(opt)
|
||||
}
|
||||
|
||||
// 布局
|
||||
doLayout(callback) {
|
||||
let task = [
|
||||
() => {
|
||||
this.computedBaseValue()
|
||||
},
|
||||
() => {
|
||||
this.computedLeftTopValue()
|
||||
},
|
||||
() => {
|
||||
this.adjustLeftTopValue()
|
||||
},
|
||||
() => {
|
||||
callback(this.root)
|
||||
}
|
||||
]
|
||||
asyncRun(task)
|
||||
}
|
||||
|
||||
// 遍历数据创建节点、计算根节点的位置,计算根节点的子节点的top值
|
||||
computedBaseValue() {
|
||||
walk(
|
||||
this.renderer.renderTree,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex, index) => {
|
||||
// 创建节点
|
||||
let newNode = this.createNode(node, parent, isRoot, layerIndex)
|
||||
// 根节点定位在画布中心位置
|
||||
if (isRoot) {
|
||||
this.setNodeCenter(newNode)
|
||||
} else {
|
||||
// 非根节点
|
||||
// 三级及以下节点以上级方向为准
|
||||
if (parent._node.dir) {
|
||||
newNode.dir = parent._node.dir
|
||||
} else {
|
||||
// 节点生长方向
|
||||
newNode.dir =
|
||||
index % 2 === 0
|
||||
? CONSTANTS.TIMELINE_DIR.TOP
|
||||
: CONSTANTS.TIMELINE_DIR.BOTTOM
|
||||
}
|
||||
// 计算二级节点的top值
|
||||
if (parent._node.isRoot) {
|
||||
newNode.top = parent._node.top - newNode.height
|
||||
}
|
||||
}
|
||||
if (!node.data.expand) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
null,
|
||||
true,
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
// 遍历节点树计算节点的left、top
|
||||
computedLeftTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex, index) => {
|
||||
if (node.isRoot) {
|
||||
let totalLeft = node.left + node.width
|
||||
node.children.forEach(item => {
|
||||
item.left = totalLeft
|
||||
totalLeft += item.width
|
||||
})
|
||||
}
|
||||
if (layerIndex >= 1 && node.children) {
|
||||
// 遍历三级及以下节点的子节点
|
||||
let startLeft = node.left + node.width * 0.5
|
||||
let totalTop =
|
||||
node.top +
|
||||
node.height +
|
||||
(this.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0)
|
||||
node.children.forEach(item => {
|
||||
item.left = startLeft
|
||||
item.top += totalTop
|
||||
totalTop +=
|
||||
item.height +
|
||||
(this.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
})
|
||||
}
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 调整节点left、top
|
||||
adjustLeftTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (!node.nodeData.data.expand) {
|
||||
return
|
||||
}
|
||||
// 调整top
|
||||
let len = node.children.length
|
||||
// 调整三级及以下节点的top
|
||||
if (parent && !parent.isRoot && len > 0) {
|
||||
let totalHeight = node.children.reduce((h, item) => {
|
||||
return (
|
||||
h +
|
||||
item.height +
|
||||
(this.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
)
|
||||
}, 0)
|
||||
this.updateBrothersTop(node, totalHeight)
|
||||
}
|
||||
},
|
||||
(node, parent) => {
|
||||
// 将二级节点的子节点移到上方
|
||||
if (parent && parent.isRoot) {
|
||||
// 遍历二级节点的子节点
|
||||
let totalHeight = 0
|
||||
node.children.forEach(item => {
|
||||
// 调整top
|
||||
let nodeTotalHeight = this.getNodeAreaHeight(item)
|
||||
let _top = item.top
|
||||
item.top =
|
||||
node.top - (item.top - node.top) - nodeTotalHeight + node.height
|
||||
// 调整left
|
||||
let offsetLeft =
|
||||
(nodeTotalHeight + totalHeight) / Math.tan(degToRad(this.mindMap.opt.fishboneDeg))
|
||||
item.left += offsetLeft
|
||||
totalHeight += nodeTotalHeight
|
||||
// 同步更新后代节点
|
||||
this.updateChildrenPro(item.children, {
|
||||
top: item.top - _top,
|
||||
left: offsetLeft
|
||||
})
|
||||
})
|
||||
}
|
||||
// 调整二级节点的子节点的left值
|
||||
if (node.isRoot) {
|
||||
let totalLeft = 0
|
||||
node.children.forEach(item => {
|
||||
item.left += totalLeft
|
||||
this.updateChildren(item.children, 'left', totalLeft)
|
||||
let { left, right } = this.getNodeBoundaries(item, 'h')
|
||||
totalLeft += right - left
|
||||
})
|
||||
}
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 递归计算节点的宽度
|
||||
getNodeAreaHeight(node) {
|
||||
let totalHeight = 0
|
||||
let loop = node => {
|
||||
totalHeight +=
|
||||
node.height +
|
||||
(this.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0)
|
||||
if (node.children.length) {
|
||||
node.children.forEach(item => {
|
||||
loop(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
loop(node)
|
||||
return totalHeight
|
||||
}
|
||||
|
||||
// 调整兄弟节点的left
|
||||
updateBrothersLeft(node) {
|
||||
let childrenList = node.children
|
||||
let totalAddWidth = 0
|
||||
childrenList.forEach(item => {
|
||||
item.left += totalAddWidth
|
||||
if (item.children && item.children.length) {
|
||||
this.updateChildren(item.children, 'left', totalAddWidth)
|
||||
}
|
||||
// let areaWidth = this.getNodeAreaWidth(item)
|
||||
let { left, right } = this.getNodeBoundaries(item, 'h')
|
||||
let areaWidth = right - left
|
||||
let difference = areaWidth - item.width
|
||||
if (difference > 0) {
|
||||
totalAddWidth += difference
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 调整兄弟节点的top
|
||||
updateBrothersTop(node, addHeight) {
|
||||
if (node.parent && !node.parent.isRoot) {
|
||||
let childrenList = node.parent.children
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item === node
|
||||
})
|
||||
childrenList.forEach((item, _index) => {
|
||||
if (item.hasCustomPosition()) {
|
||||
// 适配自定义位置
|
||||
return
|
||||
}
|
||||
let _offset = 0
|
||||
// 下面的节点往下移
|
||||
if (_index > index) {
|
||||
_offset = addHeight
|
||||
}
|
||||
item.top += _offset
|
||||
// 同步更新子节点的位置
|
||||
if (item.children && item.children.length) {
|
||||
this.updateChildren(item.children, 'top', _offset)
|
||||
}
|
||||
})
|
||||
// 更新父节点的位置
|
||||
this.updateBrothersTop(node.parent, addHeight)
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制连线,连接该节点到其子节点
|
||||
renderLine(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return []
|
||||
}
|
||||
let { left, top, width, height, expandBtnSize } = node
|
||||
let len = node.children.length
|
||||
if (node.isRoot) {
|
||||
// 当前节点是根节点
|
||||
let prevBother = node
|
||||
// 根节点的子节点是和根节点同一水平线排列
|
||||
node.children.forEach((item, index) => {
|
||||
let x1 = prevBother.left + prevBother.width
|
||||
let x2 = item.left
|
||||
let y = node.top + node.height / 2
|
||||
let path = `M ${x1},${y} L ${x2},${y}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
prevBother = item
|
||||
})
|
||||
} else {
|
||||
// 当前节点为非根节点
|
||||
let maxy = -Infinity
|
||||
let miny = Infinity
|
||||
let maxx = -Infinity
|
||||
let x = node.left + node.width * 0.3
|
||||
node.children.forEach((item, index) => {
|
||||
if (item.left > maxx) {
|
||||
maxx = item.left
|
||||
}
|
||||
let y = item.top + item.height / 2
|
||||
if (y > maxy) {
|
||||
maxy = y
|
||||
}
|
||||
if (y < miny) {
|
||||
miny = y
|
||||
}
|
||||
// 水平线
|
||||
if (node.layerIndex > 1) {
|
||||
let path = `M ${x},${y} L ${item.left},${y}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
}
|
||||
})
|
||||
// 竖线
|
||||
if (len > 0) {
|
||||
let line = this.draw.path()
|
||||
expandBtnSize = len > 0 ? expandBtnSize : 0
|
||||
let lineLength = maxx - node.left - node.width * 0.3
|
||||
if (
|
||||
node.parent &&
|
||||
node.parent.isRoot &&
|
||||
node.dir === CONSTANTS.TIMELINE_DIR.TOP
|
||||
) {
|
||||
line.plot(
|
||||
`M ${x},${top} L ${x + lineLength},${
|
||||
top - Math.tan(degToRad(this.mindMap.opt.fishboneDeg)) * lineLength
|
||||
}`
|
||||
)
|
||||
} else {
|
||||
if (node.parent && node.parent.isRoot) {
|
||||
line.plot(
|
||||
`M ${x},${top} L ${x + lineLength},${
|
||||
top - Math.tan(degToRad(this.mindMap.opt.fishboneDeg)) * lineLength
|
||||
}`
|
||||
)
|
||||
} else {
|
||||
line.plot(`M ${x},${top + height + expandBtnSize} L ${x},${maxy}`)
|
||||
}
|
||||
}
|
||||
node.style.line(line)
|
||||
node._lines.push(line)
|
||||
style && style(line, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染按钮
|
||||
renderExpandBtn(node, btn) {
|
||||
let { width, height, expandBtnSize, isRoot } = node
|
||||
if (!isRoot) {
|
||||
let { translateX, translateY } = btn.transform()
|
||||
if (node.parent && node.parent.isRoot) {
|
||||
btn.translate(
|
||||
width * 0.3 - expandBtnSize / 2 - translateX,
|
||||
-expandBtnSize / 2 - translateY
|
||||
)
|
||||
} else {
|
||||
btn.translate(
|
||||
width * 0.3 - expandBtnSize / 2 - translateX,
|
||||
height + expandBtnSize / 2 - translateY
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建概要节点
|
||||
renderGeneralization(node, gLine, gNode) {
|
||||
let {
|
||||
top,
|
||||
bottom,
|
||||
right,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
} = this.getNodeBoundaries(node, 'h')
|
||||
let x1 = right + generalizationLineMargin
|
||||
let y1 = top
|
||||
let x2 = right + generalizationLineMargin
|
||||
let y2 = bottom
|
||||
let cx = x1 + 20
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
gLine.plot(path)
|
||||
gNode.left = right + generalizationNodeMargin
|
||||
gNode.top = top + (bottom - top - gNode.height) / 2
|
||||
}
|
||||
}
|
||||
|
||||
export default Fishbone
|
||||
338
simple-mind-map/src/layouts/Timeline.js
Normal file
338
simple-mind-map/src/layouts/Timeline.js
Normal file
@@ -0,0 +1,338 @@
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun } from '../utils'
|
||||
import { CONSTANTS } from '../utils/constant'
|
||||
|
||||
// 时间轴
|
||||
class Timeline extends Base {
|
||||
// 构造函数
|
||||
constructor(opt = {}, layout) {
|
||||
super(opt)
|
||||
this.layout = layout
|
||||
}
|
||||
|
||||
// 布局
|
||||
doLayout(callback) {
|
||||
let task = [
|
||||
() => {
|
||||
this.computedBaseValue()
|
||||
},
|
||||
() => {
|
||||
this.computedLeftTopValue()
|
||||
},
|
||||
() => {
|
||||
this.adjustLeftTopValue()
|
||||
},
|
||||
() => {
|
||||
callback(this.root)
|
||||
}
|
||||
]
|
||||
asyncRun(task)
|
||||
}
|
||||
|
||||
// 遍历数据创建节点、计算根节点的位置,计算根节点的子节点的top值
|
||||
computedBaseValue() {
|
||||
walk(
|
||||
this.renderer.renderTree,
|
||||
null,
|
||||
(cur, parent, isRoot, layerIndex, index) => {
|
||||
let newNode = this.createNode(cur, parent, isRoot, layerIndex)
|
||||
// 根节点定位在画布中心位置
|
||||
if (isRoot) {
|
||||
this.setNodeCenter(newNode)
|
||||
} else {
|
||||
// 非根节点
|
||||
// 时间轴2类型需要交替显示
|
||||
if (this.layout === CONSTANTS.LAYOUT.TIMELINE2) {
|
||||
// 三级及以下节点以上级为准
|
||||
if (parent._node.dir) {
|
||||
newNode.dir = parent._node.dir
|
||||
} else {
|
||||
// 节点生长方向
|
||||
newNode.dir =
|
||||
index % 2 === 0
|
||||
? CONSTANTS.TIMELINE_DIR.BOTTOM
|
||||
: CONSTANTS.TIMELINE_DIR.TOP
|
||||
}
|
||||
} else {
|
||||
newNode.dir = ''
|
||||
}
|
||||
if (parent._node.isRoot) {
|
||||
newNode.top =
|
||||
parent._node.top +
|
||||
(cur._node.height > parent._node.height
|
||||
? -(cur._node.height - parent._node.height) / 2
|
||||
: (parent._node.height - cur._node.height) / 2)
|
||||
}
|
||||
}
|
||||
if (!cur.data.expand) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
null,
|
||||
true,
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
// 遍历节点树计算节点的left、top
|
||||
computedLeftTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex, index) => {
|
||||
if (
|
||||
node.nodeData.data.expand &&
|
||||
node.children &&
|
||||
node.children.length
|
||||
) {
|
||||
let marginX = this.getMarginX(layerIndex + 1)
|
||||
let marginY = this.getMarginY(layerIndex + 1)
|
||||
if (isRoot) {
|
||||
let left = node.left + node.width
|
||||
let totalLeft = left + marginX
|
||||
node.children.forEach(cur => {
|
||||
cur.left = totalLeft
|
||||
totalLeft += cur.width + marginX
|
||||
})
|
||||
} else {
|
||||
let totalTop =
|
||||
node.top +
|
||||
node.height +
|
||||
marginY +
|
||||
(this.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0)
|
||||
node.children.forEach(cur => {
|
||||
cur.left = node.left + node.width * 0.5
|
||||
cur.top = totalTop
|
||||
totalTop +=
|
||||
cur.height +
|
||||
marginY +
|
||||
(this.getNodeActChildrenLength(cur) > 0 ? cur.expandBtnSize : 0)
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 调整节点left、top
|
||||
adjustLeftTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (!node.nodeData.data.expand) {
|
||||
return
|
||||
}
|
||||
// 调整left
|
||||
if (node.isRoot) {
|
||||
this.updateBrothersLeft(node)
|
||||
}
|
||||
// 调整top
|
||||
let len = node.children.length
|
||||
if (parent && !parent.isRoot && len > 0) {
|
||||
let marginY = this.getMarginY(layerIndex + 1)
|
||||
let totalHeight =
|
||||
node.children.reduce((h, item) => {
|
||||
return (
|
||||
h +
|
||||
item.height +
|
||||
(this.getNodeActChildrenLength(item) > 0
|
||||
? item.expandBtnSize
|
||||
: 0)
|
||||
)
|
||||
}, 0) +
|
||||
len * marginY
|
||||
this.updateBrothersTop(node, totalHeight)
|
||||
}
|
||||
},
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (
|
||||
parent &&
|
||||
parent.isRoot &&
|
||||
node.dir === CONSTANTS.TIMELINE_DIR.TOP
|
||||
) {
|
||||
// 遍历二级节点的子节点
|
||||
node.children.forEach(item => {
|
||||
let totalHeight = this.getNodeAreaHeight(item)
|
||||
let _top = item.top
|
||||
item.top =
|
||||
node.top - (item.top - node.top) - totalHeight + node.height
|
||||
this.updateChildren(item.children, 'top', item.top - _top)
|
||||
})
|
||||
}
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 递归计算节点的宽度
|
||||
getNodeAreaHeight(node) {
|
||||
let totalHeight = 0
|
||||
let loop = node => {
|
||||
totalHeight +=
|
||||
node.height +
|
||||
(this.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0) +
|
||||
this.getMarginY(node.layerIndex)
|
||||
if (node.children.length) {
|
||||
node.children.forEach(item => {
|
||||
loop(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
loop(node)
|
||||
return totalHeight
|
||||
}
|
||||
|
||||
// 调整兄弟节点的left
|
||||
updateBrothersLeft(node) {
|
||||
let childrenList = node.children
|
||||
let totalAddWidth = 0
|
||||
childrenList.forEach(item => {
|
||||
item.left += totalAddWidth
|
||||
if (item.children && item.children.length) {
|
||||
this.updateChildren(item.children, 'left', totalAddWidth)
|
||||
}
|
||||
// let areaWidth = this.getNodeAreaWidth(item)
|
||||
let { left, right } = this.getNodeBoundaries(item, 'h')
|
||||
let areaWidth = right - left
|
||||
let difference = areaWidth - item.width
|
||||
if (difference > 0) {
|
||||
totalAddWidth += difference
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 调整兄弟节点的top
|
||||
updateBrothersTop(node, addHeight) {
|
||||
if (node.parent && !node.parent.isRoot) {
|
||||
let childrenList = node.parent.children
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item === node
|
||||
})
|
||||
childrenList.forEach((item, _index) => {
|
||||
if (item.hasCustomPosition()) {
|
||||
// 适配自定义位置
|
||||
return
|
||||
}
|
||||
let _offset = 0
|
||||
// 下面的节点往下移
|
||||
if (_index > index) {
|
||||
_offset = addHeight
|
||||
}
|
||||
item.top += _offset
|
||||
// 同步更新子节点的位置
|
||||
if (item.children && item.children.length) {
|
||||
this.updateChildren(item.children, 'top', _offset)
|
||||
}
|
||||
})
|
||||
// 更新父节点的位置
|
||||
this.updateBrothersTop(node.parent, addHeight)
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制连线,连接该节点到其子节点
|
||||
renderLine(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return []
|
||||
}
|
||||
let { left, top, width, height, expandBtnSize } = node
|
||||
let len = node.children.length
|
||||
if (node.isRoot) {
|
||||
// 当前节点是根节点
|
||||
let prevBother = node
|
||||
// 根节点的子节点是和根节点同一水平线排列
|
||||
node.children.forEach((item, index) => {
|
||||
let x1 = prevBother.left + prevBother.width
|
||||
let x2 = item.left
|
||||
let y = node.top + node.height / 2
|
||||
let path = `M ${x1},${y} L ${x2},${y}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
prevBother = item
|
||||
})
|
||||
} else {
|
||||
// 当前节点为非根节点
|
||||
let maxy = -Infinity
|
||||
let miny = Infinity
|
||||
let x = node.left + node.width * 0.3
|
||||
node.children.forEach((item, index) => {
|
||||
let y = item.top + item.height / 2
|
||||
if (y > maxy) {
|
||||
maxy = y
|
||||
}
|
||||
if (y < miny) {
|
||||
miny = y
|
||||
}
|
||||
// 水平线
|
||||
let path = `M ${x},${y} L ${item.left},${y}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
})
|
||||
// 竖线
|
||||
if (len > 0) {
|
||||
let line = this.draw.path()
|
||||
expandBtnSize = len > 0 ? expandBtnSize : 0
|
||||
if (
|
||||
node.parent &&
|
||||
node.parent.isRoot &&
|
||||
node.dir === CONSTANTS.TIMELINE_DIR.TOP
|
||||
) {
|
||||
line.plot(`M ${x},${top} L ${x},${miny}`)
|
||||
} else {
|
||||
line.plot(`M ${x},${top + height + expandBtnSize} L ${x},${maxy}`)
|
||||
}
|
||||
node.style.line(line)
|
||||
node._lines.push(line)
|
||||
style && style(line, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染按钮
|
||||
renderExpandBtn(node, btn) {
|
||||
let { width, height, expandBtnSize, isRoot } = node
|
||||
if (!isRoot) {
|
||||
let { translateX, translateY } = btn.transform()
|
||||
if (
|
||||
node.parent &&
|
||||
node.parent.isRoot &&
|
||||
node.dir === CONSTANTS.TIMELINE_DIR.TOP
|
||||
) {
|
||||
btn.translate(
|
||||
width * 0.3 - expandBtnSize / 2 - translateX,
|
||||
-expandBtnSize / 2 - translateY
|
||||
)
|
||||
} else {
|
||||
btn.translate(
|
||||
width * 0.3 - expandBtnSize / 2 - translateX,
|
||||
height + expandBtnSize / 2 - translateY
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建概要节点
|
||||
renderGeneralization(node, gLine, gNode) {
|
||||
let {
|
||||
top,
|
||||
bottom,
|
||||
right,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
} = this.getNodeBoundaries(node, 'h')
|
||||
let x1 = right + generalizationLineMargin
|
||||
let y1 = top
|
||||
let x2 = right + generalizationLineMargin
|
||||
let y2 = bottom
|
||||
let cx = x1 + 20
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
gLine.plot(path)
|
||||
gNode.left = right + generalizationNodeMargin
|
||||
gNode.top = top + (bottom - top - gNode.height) / 2
|
||||
}
|
||||
}
|
||||
|
||||
export default Timeline
|
||||
216
simple-mind-map/src/layouts/fishboneUtils.js
Normal file
216
simple-mind-map/src/layouts/fishboneUtils.js
Normal file
@@ -0,0 +1,216 @@
|
||||
import { degToRad } from '../utils/'
|
||||
|
||||
export default {
|
||||
top: {
|
||||
renderExpandBtn({
|
||||
node,
|
||||
btn,
|
||||
expandBtnSize,
|
||||
translateX,
|
||||
translateY,
|
||||
width,
|
||||
height
|
||||
}) {
|
||||
if (node.parent && node.parent.isRoot) {
|
||||
btn.translate(
|
||||
width * 0.3 - expandBtnSize / 2 - translateX,
|
||||
-expandBtnSize / 2 - translateY
|
||||
)
|
||||
} else {
|
||||
btn.translate(
|
||||
width * 0.3 - expandBtnSize / 2 - translateX,
|
||||
height + expandBtnSize / 2 - translateY
|
||||
)
|
||||
}
|
||||
},
|
||||
renderLine({
|
||||
node,
|
||||
line,
|
||||
top,
|
||||
x,
|
||||
lineLength,
|
||||
height,
|
||||
expandBtnSize,
|
||||
maxy,
|
||||
ctx
|
||||
}) {
|
||||
if (node.parent && node.parent.isRoot) {
|
||||
line.plot(
|
||||
`M ${x},${top} L ${x + lineLength},${
|
||||
top - Math.tan(degToRad(ctx.mindMap.opt.fishboneDeg)) * lineLength
|
||||
}`
|
||||
)
|
||||
} else {
|
||||
line.plot(`M ${x},${top + height + expandBtnSize} L ${x},${maxy}`)
|
||||
}
|
||||
},
|
||||
computedLeftTopValue({ layerIndex, node, ctx }) {
|
||||
if (layerIndex >= 1 && node.children) {
|
||||
// 遍历三级及以下节点的子节点
|
||||
let startLeft = node.left + node.width * ctx.childIndent
|
||||
let totalTop =
|
||||
node.top +
|
||||
node.height +
|
||||
(ctx.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0)
|
||||
node.children.forEach(item => {
|
||||
item.left = startLeft
|
||||
item.top += totalTop
|
||||
totalTop +=
|
||||
item.height +
|
||||
(ctx.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
})
|
||||
}
|
||||
},
|
||||
adjustLeftTopValueBefore({ node, parent, ctx }) {
|
||||
// 调整top
|
||||
let len = node.children.length
|
||||
// 调整三级及以下节点的top
|
||||
if (parent && !parent.isRoot && len > 0) {
|
||||
let totalHeight = node.children.reduce((h, item) => {
|
||||
return (
|
||||
h +
|
||||
item.height +
|
||||
(ctx.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
)
|
||||
}, 0)
|
||||
ctx.updateBrothersTop(node, totalHeight)
|
||||
}
|
||||
},
|
||||
adjustLeftTopValueAfter({ parent, node, ctx }) {
|
||||
// 将二级节点的子节点移到上方
|
||||
if (parent && parent.isRoot) {
|
||||
// 遍历二级节点的子节点
|
||||
let totalHeight = node.expandBtnSize
|
||||
node.children.forEach(item => {
|
||||
// 调整top
|
||||
let nodeTotalHeight = ctx.getNodeAreaHeight(item)
|
||||
let _top = item.top
|
||||
let _left = item.left
|
||||
item.top =
|
||||
node.top - (item.top - node.top) - nodeTotalHeight + node.height
|
||||
// 调整left
|
||||
item.left = node.left + node.width * ctx.indent + (nodeTotalHeight + totalHeight) / Math.tan(degToRad(ctx.mindMap.opt.fishboneDeg))
|
||||
totalHeight += nodeTotalHeight
|
||||
// 同步更新后代节点
|
||||
ctx.updateChildrenPro(item.children, {
|
||||
top: item.top - _top,
|
||||
left: item.left - _left
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
bottom: {
|
||||
renderExpandBtn({
|
||||
node,
|
||||
btn,
|
||||
expandBtnSize,
|
||||
translateX,
|
||||
translateY,
|
||||
width,
|
||||
height
|
||||
}) {
|
||||
if (node.parent && node.parent.isRoot) {
|
||||
btn.translate(
|
||||
width * 0.3 - expandBtnSize / 2 - translateX,
|
||||
height + expandBtnSize / 2 - translateY
|
||||
)
|
||||
} else {
|
||||
btn.translate(
|
||||
width * 0.3 - expandBtnSize / 2 - translateX,
|
||||
-expandBtnSize / 2 - translateY
|
||||
)
|
||||
}
|
||||
},
|
||||
renderLine({ node, line, top, x, lineLength, height, miny, ctx }) {
|
||||
if (node.parent && node.parent.isRoot) {
|
||||
line.plot(
|
||||
`M ${x},${top + height} L ${x + lineLength},${
|
||||
top + height + Math.tan(degToRad(ctx.mindMap.opt.fishboneDeg)) * lineLength
|
||||
}`
|
||||
)
|
||||
} else {
|
||||
line.plot(`M ${x},${top} L ${x},${miny}`)
|
||||
}
|
||||
},
|
||||
computedLeftTopValue({ layerIndex, node, ctx }) {
|
||||
if (layerIndex === 1 && node.children) {
|
||||
// 遍历二级节点的子节点
|
||||
let startLeft = node.left + node.width * ctx.childIndent
|
||||
let totalTop =
|
||||
node.top +
|
||||
node.height +
|
||||
(ctx.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0)
|
||||
|
||||
node.children.forEach(item => {
|
||||
item.left = startLeft
|
||||
item.top =
|
||||
totalTop +
|
||||
(ctx.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
totalTop +=
|
||||
item.height +
|
||||
(ctx.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
})
|
||||
}
|
||||
if (layerIndex > 1 && node.children) {
|
||||
// 遍历三级及以下节点的子节点
|
||||
let startLeft = node.left + node.width * ctx.childIndent
|
||||
let totalTop =
|
||||
node.top -
|
||||
(ctx.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0)
|
||||
node.children.forEach(item => {
|
||||
item.left = startLeft
|
||||
item.top = totalTop - item.height
|
||||
totalTop -=
|
||||
item.height +
|
||||
(ctx.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
})
|
||||
}
|
||||
},
|
||||
adjustLeftTopValueBefore({ node, ctx, layerIndex }) {
|
||||
// 调整top
|
||||
let len = node.children.length
|
||||
if (layerIndex > 2 && len > 0) {
|
||||
let totalHeight = node.children.reduce((h, item) => {
|
||||
return (
|
||||
h +
|
||||
item.height +
|
||||
(ctx.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0)
|
||||
)
|
||||
}, 0)
|
||||
ctx.updateBrothersTop(node, -totalHeight)
|
||||
}
|
||||
},
|
||||
adjustLeftTopValueAfter({ parent, node, ctx }) {
|
||||
// 将二级节点的子节点移到上方
|
||||
if (parent && parent.isRoot) {
|
||||
// 遍历二级节点的子节点
|
||||
let totalHeight = 0
|
||||
let totalHeight2 = node.expandBtnSize
|
||||
node.children.forEach(item => {
|
||||
// 调整top
|
||||
let hasChildren = ctx.getNodeActChildrenLength(item) > 0
|
||||
let nodeTotalHeight = ctx.getNodeAreaHeight(item)
|
||||
let offset =
|
||||
hasChildren > 0
|
||||
? nodeTotalHeight -
|
||||
item.height -
|
||||
(hasChildren ? item.expandBtnSize : 0)
|
||||
: 0
|
||||
let _top = totalHeight + offset
|
||||
let _left = item.left
|
||||
item.top += _top
|
||||
// 调整left
|
||||
item.left = node.left + node.width * ctx.indent + (nodeTotalHeight + totalHeight2) / Math.tan(degToRad(ctx.mindMap.opt.fishboneDeg))
|
||||
totalHeight += offset
|
||||
totalHeight2 += nodeTotalHeight
|
||||
// 同步更新后代节点
|
||||
ctx.updateChildrenPro(item.children, {
|
||||
top: _top,
|
||||
left: item.left - _left
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,7 +154,10 @@ export const CONSTANTS = {
|
||||
LOGICAL_STRUCTURE: 'logicalStructure',
|
||||
MIND_MAP: 'mindMap',
|
||||
ORGANIZATION_STRUCTURE: 'organizationStructure',
|
||||
CATALOG_ORGANIZATION: 'catalogOrganization'
|
||||
CATALOG_ORGANIZATION: 'catalogOrganization',
|
||||
TIMELINE: 'timeline',
|
||||
TIMELINE2: 'timeline2',
|
||||
FISHBONE: 'fishbone'
|
||||
},
|
||||
DIR: {
|
||||
UP: 'up',
|
||||
@@ -189,6 +192,10 @@ export const CONSTANTS = {
|
||||
RIGHT: 'right',
|
||||
BOTTOM: 'bottom',
|
||||
CENTER: 'center'
|
||||
},
|
||||
TIMELINE_DIR: {
|
||||
TOP: 'top',
|
||||
BOTTOM: 'bottom'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,11 +224,26 @@ export const layoutList = [
|
||||
{
|
||||
name: '目录组织图',
|
||||
value: CONSTANTS.LAYOUT.CATALOG_ORGANIZATION,
|
||||
},
|
||||
{
|
||||
name: '时间轴',
|
||||
value: CONSTANTS.LAYOUT.TIMELINE,
|
||||
},
|
||||
{
|
||||
name: '时间轴2',
|
||||
value: CONSTANTS.LAYOUT.TIMELINE2,
|
||||
},
|
||||
{
|
||||
name: '鱼骨图',
|
||||
value: CONSTANTS.LAYOUT.FISHBONE,
|
||||
}
|
||||
]
|
||||
export const layoutValueList = [
|
||||
CONSTANTS.LAYOUT.LOGICAL_STRUCTURE,
|
||||
CONSTANTS.LAYOUT.MIND_MAP,
|
||||
CONSTANTS.LAYOUT.CATALOG_ORGANIZATION,
|
||||
CONSTANTS.LAYOUT.ORGANIZATION_STRUCTURE
|
||||
CONSTANTS.LAYOUT.ORGANIZATION_STRUCTURE,
|
||||
CONSTANTS.LAYOUT.TIMELINE,
|
||||
CONSTANTS.LAYOUT.TIMELINE2,
|
||||
CONSTANTS.LAYOUT.FISHBONE
|
||||
]
|
||||
@@ -302,4 +302,34 @@ 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
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,7 @@ function createRichTextNode() {
|
||||
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)
|
||||
@@ -100,11 +101,13 @@ function createTextNode() {
|
||||
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) {
|
||||
@@ -205,13 +208,14 @@ function createNoteNode() {
|
||||
if (!this.noteEl) {
|
||||
this.noteEl = document.createElement('div')
|
||||
this.noteEl.style.cssText = `
|
||||
position: absolute;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 5px rgb(0 0 0 / 10%);
|
||||
display: none;
|
||||
background-color: #fff;
|
||||
`
|
||||
position: absolute;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 5px rgb(0 0 0 / 10%);
|
||||
display: none;
|
||||
background-color: #fff;
|
||||
z-index: ${ this.mindMap.opt.nodeNoteTooltipZIndex }
|
||||
`
|
||||
document.body.appendChild(this.noteEl)
|
||||
}
|
||||
this.noteEl.innerText = this.nodeData.data.note
|
||||
|
||||
2
web/package-lock.json
generated
2
web/package-lock.json
generated
@@ -18450,7 +18450,6 @@
|
||||
"integrity": "sha512-VCNRiAt2P/bLo09rYt3DLe6xXUMlhJwrvU18Ddd/lYJgC7s8+wvhgYs+MTx4OiAXdu58drGwSBO9SPx7C6J82Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/core": "^7.11.0",
|
||||
"@babel/helper-compilation-targets": "^7.9.6",
|
||||
"@babel/helper-module-imports": "^7.8.3",
|
||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||
@@ -18463,7 +18462,6 @@
|
||||
"@vue/babel-plugin-jsx": "^1.0.3",
|
||||
"@vue/babel-preset-jsx": "^1.2.4",
|
||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||
"core-js": "^3.6.5",
|
||||
"core-js-compat": "^3.6.5",
|
||||
"semver": "^6.1.0"
|
||||
}
|
||||
|
||||
BIN
web/src/assets/.DS_Store
vendored
BIN
web/src/assets/.DS_Store
vendored
Binary file not shown.
BIN
web/src/assets/avatar/Think.jpg
Normal file
BIN
web/src/assets/avatar/Think.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
web/src/assets/img/fishbone.jpg
Normal file
BIN
web/src/assets/img/fishbone.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
web/src/assets/img/timeline.jpg
Normal file
BIN
web/src/assets/img/timeline.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
web/src/assets/img/timeline2.jpg
Normal file
BIN
web/src/assets/img/timeline2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
@@ -3,7 +3,10 @@ export const layoutImgMap = {
|
||||
logicalStructure: require('../assets/img/logicalStructure.jpg'),
|
||||
mindMap: require('../assets/img/mindMap.jpg'),
|
||||
organizationStructure: require('../assets/img/organizationStructure.jpg'),
|
||||
catalogOrganization: require('../assets/img/catalogOrganization.jpg')
|
||||
catalogOrganization: require('../assets/img/catalogOrganization.jpg'),
|
||||
timeline: require('../assets/img/timeline.jpg'),
|
||||
timeline2: require('../assets/img/timeline2.jpg'),
|
||||
fishbone: require('../assets/img/fishbone.jpg'),
|
||||
}
|
||||
|
||||
// 主题图片映射
|
||||
|
||||
@@ -95,7 +95,9 @@ export default {
|
||||
svgTips: 'tips: Exporting pictures in rich text mode is time-consuming',
|
||||
transformingDomToImages: 'Converting nodes: ',
|
||||
notifyTitle: 'Info',
|
||||
notifyMessage: 'If the download is not triggered, check whether it is blocked by the browser'
|
||||
notifyMessage: 'If the download is not triggered, check whether it is blocked by the browser',
|
||||
paddingX: 'Padding x',
|
||||
paddingY: 'Padding y'
|
||||
},
|
||||
fullscreen: {
|
||||
fullscreenShow: 'Full screen show',
|
||||
|
||||
@@ -95,7 +95,9 @@ export default {
|
||||
svgTips: 'tips:富文本模式导出图片非常耗时',
|
||||
transformingDomToImages: '正在转换节点:',
|
||||
notifyTitle: '消息',
|
||||
notifyMessage: '如果没有触发下载,请检查是否被浏览器拦截了'
|
||||
notifyMessage: '如果没有触发下载,请检查是否被浏览器拦截了',
|
||||
paddingX: '水平内边距',
|
||||
paddingY: '垂直内边距'
|
||||
},
|
||||
fullscreen: {
|
||||
fullscreenShow: '全屏查看',
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -1,8 +1,36 @@
|
||||
# Changelog
|
||||
|
||||
## 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.
|
||||
|
||||
## 0.5.4
|
||||
|
||||
New: 1.Add new themes.
|
||||
New: 1.Add new themes. 2.Added timeline and fishbone structure.
|
||||
|
||||
Fix: 1.Fix the conflict issue between node right-click and canvas right-click. 2.Fix the bug that the line segment is not hidden when dragging nodes such as organizational chart and directory organization chart.
|
||||
|
||||
optimization: 1.Optimize the layout of organizational chart. 2.Optimize the layout of the directory organization chart.
|
||||
|
||||
## 0.5.4-fix.1
|
||||
|
||||
optimization: 1.Optimize fishbone layout.
|
||||
|
||||
## 0.5.3
|
||||
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Changelog</h1>
|
||||
<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>
|
||||
<p>New: 1.Add new themes.</p>
|
||||
<p>New: 1.Add new themes. 2.Added timeline and fishbone structure.</p>
|
||||
<p>Fix: 1.Fix the conflict issue between node right-click and canvas right-click. 2.Fix the bug that the line segment is not hidden when dragging nodes such as organizational chart and directory organization chart.</p>
|
||||
<p>optimization: 1.Optimize the layout of organizational chart. 2.Optimize the layout of the directory organization chart.</p>
|
||||
<h2>0.5.4-fix.1</h2>
|
||||
<p>optimization: 1.Optimize fishbone layout.</p>
|
||||
<h2>0.5.3</h2>
|
||||
<p>Fix: 1.Fixed the issue of setting the text style when multiple nodes were selected in rich text mode, which would change the text of all selected nodes to the text of the last selected node.</p>
|
||||
<p>New: 1.Support setting the position of the initial central node.</p>
|
||||
|
||||
@@ -26,7 +26,8 @@ const mindMap = new MindMap({
|
||||
| -------------------------------- | ------- | ---------------- | ------------------------------------------------------------ | -------- |
|
||||
| el | Element | | Container element, must be a DOM element | Yes |
|
||||
| data | Object | {} | Mind map data, refer to: https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js | |
|
||||
| layout | String | logicalStructure | Layout type, options: logicalStructure (logical structure diagram), mindMap (mind map), catalogOrganization (catalog organization diagram), organizationStructure (organization structure diagram) | |
|
||||
| layout | String | logicalStructure | Layout type, options: logicalStructure (logical structure diagram), mindMap (mind map), catalogOrganization (catalog organization diagram), organizationStructure (organization structure diagram)、timeline(v0.5.4+, timeline)、timeline2(v0.5.4+, up down alternating timeline)、fishbone(v0.5.4+, fishbone diagram) | |
|
||||
| fishboneDeg(v0.5.4+) | Number | 45 | Set the diagonal angle of the fishbone structure diagram | |
|
||||
| theme | String | default | Theme, options: default, classic, minions, pinkGrape, mint, gold, vitalityOrange, greenLeaf, dark2, skyGreen, classic2, classic3, classic4(v0.2.0+), classicGreen, classicBlue, blueSky, brainImpairedPink, dark, earthYellow, freshGreen, freshRed, romanticPurple, simpleBlack(v0.5.4+), courseGreen(v0.5.4+), coffee(v0.5.4+), redSpirit(v0.5.4+), blackHumour(v0.5.4+), lateNightOffice(v0.5.4+), blackGold(v0.5.4+) | |
|
||||
| themeConfig | Object | {} | Theme configuration, will be merged with the selected theme, available fields refer to: https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/default.js | |
|
||||
| scaleRatio | Number | 0.1 | The incremental scaling ratio | |
|
||||
@@ -52,6 +53,12 @@ const mindMap = new MindMap({
|
||||
| enableNodeTransitionMove(v0.5.1+) | Boolean | true | Whether to enable node animation transition | |
|
||||
| nodeTransitionMoveDuration(v0.5.1+) | Number | 300 | If node animation transition is enabled, the transition time can be set using this attribute, in milliseconds | |
|
||||
| initRootNodePosition(v0.5.3+) | Array | null | The position of the initial root node can be passed as an array, default is `['center', 'center']`, Represents the root node at the center of the canvas, In addition to `center`, keywords can also be set to `left`, `top`, `right`, and `bottom`, In addition to passing keywords, each item in the array can also pass a number representing a specific pixel, Can pass a percentage string, such as `['40%', '60%']`, Represents a horizontal position at `40%` of the canvas width, and a vertical position at `60%` of the canvas height | |
|
||||
| exportPaddingX(v0.5.5+) | Number | 10 | Horizontal padding of graphics when exporting PNG, SVG, and PDF | |
|
||||
| exportPaddingY(v0.5.5+) | Number | 10 | Vertical padding of graphics when exporting PNG, SVG, and PDF | |
|
||||
| nodeTextEditZIndex(v0.5.5+) | Number | 3000 | | z-index of node text edit box elements |
|
||||
| nodeNoteTooltipZIndex(v0.5.5+) | Number | 3000 | z-index of floating layer elements in node comments | |
|
||||
| isEndNodeTextEditOnClickOuter(v0.5.5+) | Boolean | true | Whether to end the editing status of node text when clicking on an area outside the canvas | |
|
||||
| maxHistoryCount(v0.5.6+) | Number | 1000 | | Maximum number of history records |
|
||||
|
||||
### Watermark config
|
||||
|
||||
|
||||
@@ -46,7 +46,14 @@
|
||||
<td>layout</td>
|
||||
<td>String</td>
|
||||
<td>logicalStructure</td>
|
||||
<td>Layout type, options: logicalStructure (logical structure diagram), mindMap (mind map), catalogOrganization (catalog organization diagram), organizationStructure (organization structure diagram)</td>
|
||||
<td>Layout type, options: logicalStructure (logical structure diagram), mindMap (mind map), catalogOrganization (catalog organization diagram), organizationStructure (organization structure diagram)、timeline(v0.5.4+, timeline)、timeline2(v0.5.4+, up down alternating timeline)、fishbone(v0.5.4+, fishbone diagram)</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>fishboneDeg(v0.5.4+)</td>
|
||||
<td>Number</td>
|
||||
<td>45</td>
|
||||
<td>Set the diagonal angle of the fishbone structure diagram</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -224,6 +231,48 @@
|
||||
<td>The position of the initial root node can be passed as an array, default is <code>['center', 'center']</code>, Represents the root node at the center of the canvas, In addition to <code>center</code>, keywords can also be set to <code>left</code>, <code>top</code>, <code>right</code>, and <code>bottom</code>, In addition to passing keywords, each item in the array can also pass a number representing a specific pixel, Can pass a percentage string, such as <code>['40%', '60%']</code>, Represents a horizontal position at <code>40%</code> of the canvas width, and a vertical position at <code>60%</code> of the canvas height</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>exportPaddingX(v0.5.5+)</td>
|
||||
<td>Number</td>
|
||||
<td>10</td>
|
||||
<td>Horizontal padding of graphics when exporting PNG, SVG, and PDF</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>exportPaddingY(v0.5.5+)</td>
|
||||
<td>Number</td>
|
||||
<td>10</td>
|
||||
<td>Vertical padding of graphics when exporting PNG, SVG, and PDF</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>nodeTextEditZIndex(v0.5.5+)</td>
|
||||
<td>Number</td>
|
||||
<td>3000</td>
|
||||
<td></td>
|
||||
<td>z-index of node text edit box elements</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>nodeNoteTooltipZIndex(v0.5.5+)</td>
|
||||
<td>Number</td>
|
||||
<td>3000</td>
|
||||
<td>z-index of floating layer elements in node comments</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>isEndNodeTextEditOnClickOuter(v0.5.5+)</td>
|
||||
<td>Boolean</td>
|
||||
<td>true</td>
|
||||
<td>Whether to end the editing status of node text when clicking on an area outside the canvas</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>maxHistoryCount(v0.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>
|
||||
|
||||
1
web/src/pages/Doc/en/deploy/index.md
Normal file
1
web/src/pages/Doc/en/deploy/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# Deploy
|
||||
@@ -7,8 +7,8 @@
|
||||
## Features
|
||||
|
||||
- [x] Plugin architecture. In addition to core functions, other functions are provided as plugins, which can be used as needed to reduce the overall volume
|
||||
- [x] Supports four types of structures: logical structure diagrams, mind maps,
|
||||
organizational structure diagrams, and directory organization diagrams
|
||||
- [x] Supports six types of structures: logical structure diagrams, mind maps,
|
||||
organizational structure diagrams, directory organization diagrams, timeline, and fishbone diagrams
|
||||
- [x] Built-in multiple themes and allows for highly customized styles, and support register new themes
|
||||
- [x] Supports shortcuts
|
||||
- [x] Node content supports images, icons, hyperlinks, notes, tags, and
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
<h2>Features</h2>
|
||||
<ul>
|
||||
<li><input type="checkbox" id="checkbox18" checked="true" /><label for="checkbox18">Plugin architecture. In addition to core functions, other functions are provided as plugins, which can be used as needed to reduce the overall volume</label></li>
|
||||
<li><input type="checkbox" id="checkbox19" checked="true" /><label for="checkbox19">Supports four types of structures: logical structure diagrams, mind maps,</label>
|
||||
organizational structure diagrams, and directory organization diagrams</li>
|
||||
<li><input type="checkbox" id="checkbox19" checked="true" /><label for="checkbox19">Supports six types of structures: logical structure diagrams, mind maps,</label>
|
||||
organizational structure diagrams, directory organization diagrams, timeline, and fishbone diagrams</li>
|
||||
<li><input type="checkbox" id="checkbox20" checked="true" /><label for="checkbox20">Built-in multiple themes and allows for highly customized styles, and support register new themes</label></li>
|
||||
<li><input type="checkbox" id="checkbox21" checked="true" /><label for="checkbox21">Supports shortcuts</label></li>
|
||||
<li><input type="checkbox" id="checkbox22" checked="true" /><label for="checkbox22">Node content supports images, icons, hyperlinks, notes, tags, and</label>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,8 +1,36 @@
|
||||
# Changelog
|
||||
|
||||
## 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.添加新主题。
|
||||
新增:1.添加新主题。 2.新增时间轴和鱼骨结构。
|
||||
|
||||
修复:1.修复节点右键和画布右键的冲突问题。 2.修复组织结构图、目录组织图等节点拖拽时存在线段未隐藏的bug。
|
||||
|
||||
优化:1.优化组织结构图布局。2.优化目录组织图布局。
|
||||
|
||||
## 0.5.4-fix.1
|
||||
|
||||
优化:1.优化鱼骨图布局。
|
||||
|
||||
## 0.5.3
|
||||
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Changelog</h1>
|
||||
<h2>0.5.6</h2>
|
||||
<p>修复:1.修复短时间快速多次渲染时节点位置错乱的问题。 2.修复节点正在编辑中时拖动画布导致编辑框和节点分离的问题。</p>
|
||||
<p>新增:1.添加最大历史记录数限制。</p>
|
||||
<h2>0.5.5</h2>
|
||||
<p>新增:1.支持配置导出为png、svg、pdf时的内边距。 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.添加新主题。</p>
|
||||
<p>新增:1.添加新主题。 2.新增时间轴和鱼骨结构。</p>
|
||||
<p>修复:1.修复节点右键和画布右键的冲突问题。 2.修复组织结构图、目录组织图等节点拖拽时存在线段未隐藏的bug。</p>
|
||||
<p>优化:1.优化组织结构图布局。2.优化目录组织图布局。</p>
|
||||
<h2>0.5.4-fix.1</h2>
|
||||
<p>优化:1.优化鱼骨图布局。</p>
|
||||
<h2>0.5.3</h2>
|
||||
<p>修复:1.修复富文本模式下,如果选择了多个节点时设置文本样式,会将所有多选节点的文本改成最后一个多选节点的文本的问题。</p>
|
||||
<p>新增:1.支持设置初始中心节点的位置。</p>
|
||||
|
||||
@@ -26,7 +26,8 @@ const mindMap = new MindMap({
|
||||
| -------------------------------- | ------- | ---------------- | ------------------------------------------------------------ | -------- |
|
||||
| el | Element | | 容器元素,必须为DOM元素 | 是 |
|
||||
| data | Object | {} | 思维导图数据,可参考:[https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js) | |
|
||||
| layout | String | logicalStructure | 布局类型,可选列表:logicalStructure(逻辑结构图)、mindMap(思维导图)、catalogOrganization(目录组织图)、organizationStructure(组织结构图) | |
|
||||
| layout | String | logicalStructure | 布局类型,可选列表:logicalStructure(逻辑结构图)、mindMap(思维导图)、catalogOrganization(目录组织图)、organizationStructure(组织结构图)、timeline(v0.5.4+,时间轴)、timeline2(v0.5.4+,上下交替型时间轴)、fishbone(v0.5.4+,鱼骨图) | |
|
||||
| fishboneDeg(v0.5.4+) | Number | 45 | 设置鱼骨结构图的斜线角度 | |
|
||||
| theme | String | default | 主题,可选列表:default(默认)、classic(脑图经典)、minions(小黄人)、pinkGrape(粉红葡萄)、mint(薄荷)、gold(金色vip)、vitalityOrange(活力橙)、greenLeaf(绿叶)、dark2(暗色2)、skyGreen(天清绿)、classic2(脑图经典2)、classic3(脑图经典3)、classic4(脑图经典4,v0.2.0+)、classicGreen(经典绿)、classicBlue(经典蓝)、blueSky(天空蓝)、brainImpairedPink(脑残粉)、dark(暗色)、earthYellow(泥土黄)、freshGreen(清新绿)、freshRed(清新红)、romanticPurple(浪漫紫)、simpleBlack(v0.5.4+简约黑)、courseGreen(v0.5.4+课程绿)、coffee(v0.5.4+咖啡)、redSpirit(v0.5.4+红色精神)、blackHumour(v0.5.4+黑色幽默)、lateNightOffice(v0.5.4+深夜办公室)、blackGold(v0.5.4+黑金) | |
|
||||
| themeConfig | Object | {} | 主题配置,会和所选择的主题进行合并,可用字段可参考:[https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/default.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/default.js) | |
|
||||
| scaleRatio | Number | 0.1 | 放大缩小的增量比例 | |
|
||||
@@ -52,6 +53,12 @@ const mindMap = new MindMap({
|
||||
| enableNodeTransitionMove(v0.5.1+) | Boolean | true | 是否开启节点动画过渡 | |
|
||||
| nodeTransitionMoveDuration(v0.5.1+) | Number | 300 | 如果开启节点动画过渡,可以通过该属性设置过渡的时间,单位ms | |
|
||||
| initRootNodePosition(v0.5.3+) | Array | null | 初始根节点的位置,可传一个数组,默认为`['center', 'center']`,代表根节点处于画布中心位置,除了`center`,关键词还可以设置`left`、`top`、`right`、`bottom`,除了可以传关键词,数组的每项还可以传递一个数字,代表具体的像素,可以传递一个百分比字符串,比如`['40%', '60%']`,代表水平位置在画布宽度的`40%`的位置,垂直位置在画布高度的`60%`的位置 | |
|
||||
| exportPaddingX(v0.5.5+) | Number | 10 | 导出png、svg、pdf时的图形水平内边距 | |
|
||||
| exportPaddingY(v0.5.5+) | Number | 10 | 导出png、svg、pdf时的图形垂直内边距 | |
|
||||
| nodeTextEditZIndex(v0.5.5+) | Number | 3000 | 节点文本编辑框元素的z-index | |
|
||||
| nodeNoteTooltipZIndex(v0.5.5+) | Number | 3000 | 节点备注浮层元素的z-index | |
|
||||
| isEndNodeTextEditOnClickOuter(v0.5.5+) | Boolean | true | 是否在点击了画布外的区域时结束节点文本的编辑状态 | |
|
||||
| maxHistoryCount(v0.5.6+) | Number | 1000 | 最大历史记录数 | |
|
||||
|
||||
### 水印配置
|
||||
|
||||
|
||||
@@ -46,7 +46,14 @@
|
||||
<td>layout</td>
|
||||
<td>String</td>
|
||||
<td>logicalStructure</td>
|
||||
<td>布局类型,可选列表:logicalStructure(逻辑结构图)、mindMap(思维导图)、catalogOrganization(目录组织图)、organizationStructure(组织结构图)</td>
|
||||
<td>布局类型,可选列表:logicalStructure(逻辑结构图)、mindMap(思维导图)、catalogOrganization(目录组织图)、organizationStructure(组织结构图)、timeline(v0.5.4+,时间轴)、timeline2(v0.5.4+,上下交替型时间轴)、fishbone(v0.5.4+,鱼骨图)</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>fishboneDeg(v0.5.4+)</td>
|
||||
<td>Number</td>
|
||||
<td>45</td>
|
||||
<td>设置鱼骨结构图的斜线角度</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -224,6 +231,48 @@
|
||||
<td>初始根节点的位置,可传一个数组,默认为<code>['center', 'center']</code>,代表根节点处于画布中心位置,除了<code>center</code>,关键词还可以设置<code>left</code>、<code>top</code>、<code>right</code>、<code>bottom</code>,除了可以传关键词,数组的每项还可以传递一个数字,代表具体的像素,可以传递一个百分比字符串,比如<code>['40%', '60%']</code>,代表水平位置在画布宽度的<code>40%</code>的位置,垂直位置在画布高度的<code>60%</code>的位置</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>exportPaddingX(v0.5.5+)</td>
|
||||
<td>Number</td>
|
||||
<td>10</td>
|
||||
<td>导出png、svg、pdf时的图形水平内边距</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>exportPaddingY(v0.5.5+)</td>
|
||||
<td>Number</td>
|
||||
<td>10</td>
|
||||
<td>导出png、svg、pdf时的图形垂直内边距</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>nodeTextEditZIndex(v0.5.5+)</td>
|
||||
<td>Number</td>
|
||||
<td>3000</td>
|
||||
<td>节点文本编辑框元素的z-index</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>nodeNoteTooltipZIndex(v0.5.5+)</td>
|
||||
<td>Number</td>
|
||||
<td>3000</td>
|
||||
<td>节点备注浮层元素的z-index</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>isEndNodeTextEditOnClickOuter(v0.5.5+)</td>
|
||||
<td>Boolean</td>
|
||||
<td>true</td>
|
||||
<td>是否在点击了画布外的区域时结束节点文本的编辑状态</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>maxHistoryCount(v0.5.6+)</td>
|
||||
<td>Number</td>
|
||||
<td>1000</td>
|
||||
<td>最大历史记录数</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>水印配置</h3>
|
||||
|
||||
@@ -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`时通过选项指定使用的结构:
|
||||
|
||||
|
||||
@@ -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(时间轴2)、fishbone(鱼骨图)。</p>
|
||||
<p>可以在实例化<code>simple-mind-map</code>时通过选项指定使用的结构:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">new</span> MindMap({
|
||||
<span class="hljs-comment">// ...</span>
|
||||
|
||||
@@ -1,3 +1,124 @@
|
||||
# 如何渲染一个右键菜单
|
||||
|
||||
编写中。。。
|
||||
右键菜单可以方便的完成一些功能,大体上分两种,一是在画布上点击右键,二是在节点上点击右键,两者的功能肯定是不一样的,甚至根节点和其他节点功能上也有些不同,比如根节点不能添加同级节点,也不能被删除等等。
|
||||
|
||||
右键菜单的UI界面需要你自行开发,可以设置成绝对定位或固定定位,然后让它显示在鼠标右键点击的位置即可。
|
||||
|
||||
## 右键菜单的显示和隐藏
|
||||
|
||||
首先监听`node_contextmenu`事件在右键点击节点时显示菜单:
|
||||
|
||||
```js
|
||||
// 当前右键点击的类型
|
||||
const type = ref('')
|
||||
// 如果点击的节点,那么代表被点击的节点
|
||||
const currentNode = shallowRef(null)
|
||||
// 菜单显示的位置
|
||||
const left = ref(0)
|
||||
const top = ref(0)
|
||||
// 是否显示菜单
|
||||
const show = ref(false)
|
||||
|
||||
mindMap.on('node_contextmenu', (e, node) => {
|
||||
type.value = 'node'
|
||||
left.value = e.clientX + 10
|
||||
top.value = e.clientY + 10
|
||||
show.value = true
|
||||
currentNode.value = node
|
||||
})
|
||||
```
|
||||
|
||||
你可以根据当前点击的节点来判断一些操作是否可用。比如根节点不能删除,不能插入同级节点,又比如同级第一个节点不能再被往上移,同级最后一个节点不能被往下移。
|
||||
|
||||
对于画布的处理会比较麻烦,不能直接监听`contextmenu`事件,因为会和右键多选节点冲突,所以需要结合`mousedown`事件和`mouseup`事件来处理。
|
||||
|
||||
```js
|
||||
// 记录鼠标右键按下的位置
|
||||
const mousedownX = ref(0)
|
||||
const mousedownY = ref(0)
|
||||
const isMousedown = ref(false)
|
||||
|
||||
mindMap.on('svg_mousedown', (e) => {
|
||||
// 如果不是右键点击直接返回
|
||||
if (e.which !== 3) {
|
||||
return
|
||||
}
|
||||
mousedownX.value = e.clientX
|
||||
mousedownY.value = e.clientY
|
||||
isMousedown.value = true
|
||||
})
|
||||
|
||||
mindMap.on('mouseup', (e) => {
|
||||
if (!isMousedown.value) {
|
||||
return
|
||||
}
|
||||
isMousedown.value = false
|
||||
// 如果鼠标松开和按下的距离大于3,则不认为是点击事件
|
||||
if (
|
||||
Math.abs(mousedownX.value - e.clientX) > 3 ||
|
||||
Math.abs(mousedownX.value - e.clientY) > 3
|
||||
) {
|
||||
hide()
|
||||
return
|
||||
}
|
||||
type.value = 'svg'
|
||||
left.value = e.clientX + 10
|
||||
top.value = e.clientY + 10
|
||||
show.value = true
|
||||
})
|
||||
```
|
||||
|
||||
很简单,其实就是判断鼠标按下和松开的距离是否很小,是的话就认为是点击事件,否则应该是鼠标拖动。
|
||||
|
||||
右键菜单显示了,肯定就需要隐藏,当左键点击了画布、左键点击了节点、左键点击了展开收起按钮时都需要隐藏右键菜单。
|
||||
|
||||
```js
|
||||
const hide = () => {
|
||||
show.value = false
|
||||
left.value = 0
|
||||
top.value = 0
|
||||
type.value = ''
|
||||
}
|
||||
mindMap.on('node_click', hide)
|
||||
mindMap.on('draw_click', hide)
|
||||
mindMap.on('expand_btn_click', hide)
|
||||
```
|
||||
|
||||
## 复制、剪切、粘贴的实现
|
||||
|
||||
接下来介绍一下复制、剪切、粘贴的实现。
|
||||
|
||||
一般来说你的右键菜单中肯定也会添加这三个按钮,另外快捷键操作也是必不可少的,但是这三个快捷键是没有内置的,所以你需要自己注册一下:
|
||||
|
||||
```js
|
||||
mindMap.keyCommand.addShortcut('Control+c', copy)
|
||||
mindMap.keyCommand.addShortcut('Control+v', paste)
|
||||
mindMap.keyCommand.addShortcut('Control+x', cut)
|
||||
```
|
||||
|
||||
如需删除调用`removeShortcut`方法即可。
|
||||
|
||||
接下来实现一下这三个方法:
|
||||
|
||||
```js
|
||||
// 保存复制/剪切的节点的数据,后续可以原来粘贴
|
||||
let copyData = null
|
||||
|
||||
const copy = () => {
|
||||
copyData = mindMap.renderer.copyNode()
|
||||
}
|
||||
|
||||
const cut = () => {
|
||||
mindMap.execCommand('CUT_NODE', _copyData => {
|
||||
copyData = _copyData
|
||||
})
|
||||
}
|
||||
|
||||
const paste = () => {
|
||||
mindMap.execCommand('PASTE_NODE', copyData)
|
||||
}
|
||||
```
|
||||
|
||||
## 完整示例
|
||||
|
||||
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFV1tv1EYU/itTo2o3JfEG0Re2SQQFHngIRUAlohpFjj2bNXg9lj3ObhoioagSSUkCakFKC6G0BKhaEWhLuWQJ/Jm1d3nqX+iZi2/rkNBKVR9WWp/bd+Zc5pyZU464rjoTYKWqjPiGZ7kU+ZgG7pjmWA2XeBTNIQ/XBhFxxkngUGwOIr+u2zZpnsY1NI9qHmmgElgoJRrjlmOO665gaYoPZBsPNYA61NBdTdEchDTHxhQxGpMcRU5g25qjOZUKCre/DZdWwmt/vL2x2V14GV5pd7//qvtbO7xzVXMM4vgU0VkXgxI4Vi6VBoTWg4Xozu1Evvf1Avz/69Xy24V7nZeLnfa93o8/9376tU8gNmgEnocdepKYzG56wDLzSwD0rt0OV25Ga6+7G1ug39le6W5vxvo2rlHp0DCISy8JO1lMAxPR2uPw+gNhQpiLRf06aUrZmm77WEJuPgm3b759dTe6e0XEI1pe6ry4WoBvkMDHJmk65wpOJKyJAsvyIaWCWcTuvFkPH62FGyvh4rNKuPRLuHgliRr8iW4+iVY2Ib7h9dVu+1F47XGnfT9c/SFav9/9fa339KlIsEHc2WM61TMZlgEHBhDLA2h0DM2xikBZYVkYKiTFxB72VMZj2SmDd/MZMwELe85KrIpb2DhKGg3dMculo5+fnTz52bHjpUE0mcIkOn3oiYjgzucxXd2nrEr2Rj115MzZ4zFubDNvrG7xisvZYsWgzuh2wDg8JYLOiiyhDwsa1FiBBM2R0ErQlhwu6d9yBivTf7gZ921ZuoHtKjKJETSgMdRpTI/bmP39dPYEHE1qHiUO1S0He6WBQaFlwhGraVw1hRE0JUMSZIpblJE1Jbr7UjYjvxl4xKUxJmjULduEOmDCX6Q2+sztiNKP1Nla7m497AfLA+4Aej7lZeX+Jw/iv5IW61mORU8Twq+wU8S3qEUc0CyxooHqKxmQOkjTeS4+P/CJuITTwr2IZ2XdqrppnqnDRQ7NBZ0DGfaIvd+QJQzlq9H3VZoBJd4u/0irxaACynQyDhKnXHLgcNCccJIWhVoMQLCMBxEjZxso3wGMC11Q6CCsGrYFUTmH9qMDO7RTzJ/I8HOtSb1AdmZmfCRcBstOzW6PfKjZSfyZ6cnkaubHyB4gmWedFytsaGRn4a2n0er93psb4a07MvM10Fabdcuoow9GR9HBgWwNejDNPR7IpHbSaVGMRZ/ERDEaEjSdHX3xECfO5pudl1sM3MJJmfMfFIztdYKd0DMXZRI+MTqj9dfhq8vhN8vJ9Ow9X+8+aIcbDztbqwfZEFv8DiLd29zovNiCeItId7audtrPUjdTj8Z1Wlf1Kb9ciORQGskBNIYOokuXdlOLw5uqTXA1oZOLAhsUbPjtEpV82UOF/YdV/666Fh1qW8ZFSDXzOdf5TML09ObuErjlwt0wOUWdfjmGOVIReypsqPBBMSyXOsXwhdCIac0gw9Z9f1RTpMVjuEE0hbOlgGWm3GR+gchIBbhZwdiSvHDG4cLRFDQzZNWAyGICX1WfztoYvud4mKtiE9yPSi67xSCwVb4GCgIkKvYEIGLXY4tiqYUGljdWKqrRd/lzAoyAF4d5nDjLnQVFsbZlD7TboQpGAphWY2Ln+7c2+K0PVsQymLcyUsllbY9g8Dp+/1iMRTee9f58Hi1djm4v7YGbsuN/ORH45NnlEofl40ZT1Ip40ch1ScV+QzV8X1OSmapmai/u4aZl0noVWmr4Qy6HkJtMaQ8DojWDOYM3M/vt66/R2FSqCJcJsQMqFEWjV9Gw/OLFF38U4evYmq6D+MfDw24rRt4Z96Nky9W9aQtwY6suzG/LmY4Jieuwridp2dPrL4cAD7eq6NChQ5I0pRsXpz3YV80q2ler1frhDmRcLiKyQkjeFIHnE68K6Bbbf2IluER4ZpVBReSVvUzVCz5x4CHMVTXJgLwmO52mwDtXLHJqBf6qHmzUVgOzEhia8kjTh5fKBdCQO9kOb1+hWywgpiV9m1fm/wbZvoo9" />
|
||||
@@ -1,8 +1,104 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>如何渲染一个右键菜单</h1>
|
||||
<p>编写中。。。</p>
|
||||
<p>右键菜单可以方便的完成一些功能,大体上分两种,一是在画布上点击右键,二是在节点上点击右键,两者的功能肯定是不一样的,甚至根节点和其他节点功能上也有些不同,比如根节点不能添加同级节点,也不能被删除等等。</p>
|
||||
<p>右键菜单的UI界面需要你自行开发,可以设置成绝对定位或固定定位,然后让它显示在鼠标右键点击的位置即可。</p>
|
||||
<h2>右键菜单的显示和隐藏</h2>
|
||||
<p>首先监听<code>node_contextmenu</code>事件在右键点击节点时显示菜单:</p>
|
||||
<pre class="hljs"><code><span class="hljs-comment">// 当前右键点击的类型</span>
|
||||
<span class="hljs-keyword">const</span> type = ref(<span class="hljs-string">''</span>)
|
||||
<span class="hljs-comment">// 如果点击的节点,那么代表被点击的节点</span>
|
||||
<span class="hljs-keyword">const</span> currentNode = shallowRef(<span class="hljs-literal">null</span>)
|
||||
<span class="hljs-comment">// 菜单显示的位置</span>
|
||||
<span class="hljs-keyword">const</span> left = ref(<span class="hljs-number">0</span>)
|
||||
<span class="hljs-keyword">const</span> top = ref(<span class="hljs-number">0</span>)
|
||||
<span class="hljs-comment">// 是否显示菜单</span>
|
||||
<span class="hljs-keyword">const</span> show = ref(<span class="hljs-literal">false</span>)
|
||||
|
||||
mindMap.on(<span class="hljs-string">'node_contextmenu'</span>, <span class="hljs-function">(<span class="hljs-params">e, node</span>) =></span> {
|
||||
type.value = <span class="hljs-string">'node'</span>
|
||||
left.value = e.clientX + <span class="hljs-number">10</span>
|
||||
top.value = e.clientY + <span class="hljs-number">10</span>
|
||||
show.value = <span class="hljs-literal">true</span>
|
||||
currentNode.value = node
|
||||
})
|
||||
</code></pre>
|
||||
<p>你可以根据当前点击的节点来判断一些操作是否可用。比如根节点不能删除,不能插入同级节点,又比如同级第一个节点不能再被往上移,同级最后一个节点不能被往下移。</p>
|
||||
<p>对于画布的处理会比较麻烦,不能直接监听<code>contextmenu</code>事件,因为会和右键多选节点冲突,所以需要结合<code>mousedown</code>事件和<code>mouseup</code>事件来处理。</p>
|
||||
<pre class="hljs"><code><span class="hljs-comment">// 记录鼠标右键按下的位置</span>
|
||||
<span class="hljs-keyword">const</span> mousedownX = ref(<span class="hljs-number">0</span>)
|
||||
<span class="hljs-keyword">const</span> mousedownY = ref(<span class="hljs-number">0</span>)
|
||||
<span class="hljs-keyword">const</span> isMousedown = ref(<span class="hljs-literal">false</span>)
|
||||
|
||||
mindMap.on(<span class="hljs-string">'svg_mousedown'</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =></span> {
|
||||
<span class="hljs-comment">// 如果不是右键点击直接返回</span>
|
||||
<span class="hljs-keyword">if</span> (e.which !== <span class="hljs-number">3</span>) {
|
||||
<span class="hljs-keyword">return</span>
|
||||
}
|
||||
mousedownX.value = e.clientX
|
||||
mousedownY.value = e.clientY
|
||||
isMousedown.value = <span class="hljs-literal">true</span>
|
||||
})
|
||||
|
||||
mindMap.on(<span class="hljs-string">'mouseup'</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =></span> {
|
||||
<span class="hljs-keyword">if</span> (!isMousedown.value) {
|
||||
<span class="hljs-keyword">return</span>
|
||||
}
|
||||
isMousedown.value = <span class="hljs-literal">false</span>
|
||||
<span class="hljs-comment">// 如果鼠标松开和按下的距离大于3,则不认为是点击事件</span>
|
||||
<span class="hljs-keyword">if</span> (
|
||||
<span class="hljs-built_in">Math</span>.abs(mousedownX.value - e.clientX) > <span class="hljs-number">3</span> ||
|
||||
<span class="hljs-built_in">Math</span>.abs(mousedownX.value - e.clientY) > <span class="hljs-number">3</span>
|
||||
) {
|
||||
hide()
|
||||
<span class="hljs-keyword">return</span>
|
||||
}
|
||||
type.value = <span class="hljs-string">'svg'</span>
|
||||
left.value = e.clientX + <span class="hljs-number">10</span>
|
||||
top.value = e.clientY + <span class="hljs-number">10</span>
|
||||
show.value = <span class="hljs-literal">true</span>
|
||||
})
|
||||
</code></pre>
|
||||
<p>很简单,其实就是判断鼠标按下和松开的距离是否很小,是的话就认为是点击事件,否则应该是鼠标拖动。</p>
|
||||
<p>右键菜单显示了,肯定就需要隐藏,当左键点击了画布、左键点击了节点、左键点击了展开收起按钮时都需要隐藏右键菜单。</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">const</span> hide = <span class="hljs-function">() =></span> {
|
||||
show.value = <span class="hljs-literal">false</span>
|
||||
left.value = <span class="hljs-number">0</span>
|
||||
top.value = <span class="hljs-number">0</span>
|
||||
type.value = <span class="hljs-string">''</span>
|
||||
}
|
||||
mindMap.on(<span class="hljs-string">'node_click'</span>, hide)
|
||||
mindMap.on(<span class="hljs-string">'draw_click'</span>, hide)
|
||||
mindMap.on(<span class="hljs-string">'expand_btn_click'</span>, hide)
|
||||
</code></pre>
|
||||
<h2>复制、剪切、粘贴的实现</h2>
|
||||
<p>接下来介绍一下复制、剪切、粘贴的实现。</p>
|
||||
<p>一般来说你的右键菜单中肯定也会添加这三个按钮,另外快捷键操作也是必不可少的,但是这三个快捷键是没有内置的,所以你需要自己注册一下:</p>
|
||||
<pre class="hljs"><code>mindMap.keyCommand.addShortcut(<span class="hljs-string">'Control+c'</span>, copy)
|
||||
mindMap.keyCommand.addShortcut(<span class="hljs-string">'Control+v'</span>, paste)
|
||||
mindMap.keyCommand.addShortcut(<span class="hljs-string">'Control+x'</span>, cut)
|
||||
</code></pre>
|
||||
<p>如需删除调用<code>removeShortcut</code>方法即可。</p>
|
||||
<p>接下来实现一下这三个方法:</p>
|
||||
<pre class="hljs"><code><span class="hljs-comment">// 保存复制/剪切的节点的数据,后续可以原来粘贴</span>
|
||||
<span class="hljs-keyword">let</span> copyData = <span class="hljs-literal">null</span>
|
||||
|
||||
<span class="hljs-keyword">const</span> copy = <span class="hljs-function">() =></span> {
|
||||
copyData = mindMap.renderer.copyNode()
|
||||
}
|
||||
|
||||
<span class="hljs-keyword">const</span> cut = <span class="hljs-function">() =></span> {
|
||||
mindMap.execCommand(<span class="hljs-string">'CUT_NODE'</span>, <span class="hljs-function"><span class="hljs-params">_copyData</span> =></span> {
|
||||
copyData = _copyData
|
||||
})
|
||||
}
|
||||
|
||||
<span class="hljs-keyword">const</span> paste = <span class="hljs-function">() =></span> {
|
||||
mindMap.execCommand(<span class="hljs-string">'PASTE_NODE'</span>, copyData)
|
||||
}
|
||||
</code></pre>
|
||||
<h2>完整示例</h2>
|
||||
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFV1tv1EYU/itTo2o3JfEG0Re2SQQFHngIRUAlohpFjj2bNXg9lj3ObhoioagSSUkCakFKC6G0BKhaEWhLuWQJ/Jm1d3nqX+iZi2/rkNBKVR9WWp/bd+Zc5pyZU464rjoTYKWqjPiGZ7kU+ZgG7pjmWA2XeBTNIQ/XBhFxxkngUGwOIr+u2zZpnsY1NI9qHmmgElgoJRrjlmOO665gaYoPZBsPNYA61NBdTdEchDTHxhQxGpMcRU5g25qjOZUKCre/DZdWwmt/vL2x2V14GV5pd7//qvtbO7xzVXMM4vgU0VkXgxI4Vi6VBoTWg4Xozu1Evvf1Avz/69Xy24V7nZeLnfa93o8/9376tU8gNmgEnocdepKYzG56wDLzSwD0rt0OV25Ga6+7G1ug39le6W5vxvo2rlHp0DCISy8JO1lMAxPR2uPw+gNhQpiLRf06aUrZmm77WEJuPgm3b759dTe6e0XEI1pe6ry4WoBvkMDHJmk65wpOJKyJAsvyIaWCWcTuvFkPH62FGyvh4rNKuPRLuHgliRr8iW4+iVY2Ib7h9dVu+1F47XGnfT9c/SFav9/9fa339KlIsEHc2WM61TMZlgEHBhDLA2h0DM2xikBZYVkYKiTFxB72VMZj2SmDd/MZMwELe85KrIpb2DhKGg3dMculo5+fnTz52bHjpUE0mcIkOn3oiYjgzucxXd2nrEr2Rj115MzZ4zFubDNvrG7xisvZYsWgzuh2wDg8JYLOiiyhDwsa1FiBBM2R0ErQlhwu6d9yBivTf7gZ921ZuoHtKjKJETSgMdRpTI/bmP39dPYEHE1qHiUO1S0He6WBQaFlwhGraVw1hRE0JUMSZIpblJE1Jbr7UjYjvxl4xKUxJmjULduEOmDCX6Q2+sztiNKP1Nla7m497AfLA+4Aej7lZeX+Jw/iv5IW61mORU8Twq+wU8S3qEUc0CyxooHqKxmQOkjTeS4+P/CJuITTwr2IZ2XdqrppnqnDRQ7NBZ0DGfaIvd+QJQzlq9H3VZoBJd4u/0irxaACynQyDhKnXHLgcNCccJIWhVoMQLCMBxEjZxso3wGMC11Q6CCsGrYFUTmH9qMDO7RTzJ/I8HOtSb1AdmZmfCRcBstOzW6PfKjZSfyZ6cnkaubHyB4gmWedFytsaGRn4a2n0er93psb4a07MvM10Fabdcuoow9GR9HBgWwNejDNPR7IpHbSaVGMRZ/ERDEaEjSdHX3xECfO5pudl1sM3MJJmfMfFIztdYKd0DMXZRI+MTqj9dfhq8vhN8vJ9Ow9X+8+aIcbDztbqwfZEFv8DiLd29zovNiCeItId7audtrPUjdTj8Z1Wlf1Kb9ciORQGskBNIYOokuXdlOLw5uqTXA1oZOLAhsUbPjtEpV82UOF/YdV/666Fh1qW8ZFSDXzOdf5TML09ObuErjlwt0wOUWdfjmGOVIReypsqPBBMSyXOsXwhdCIac0gw9Z9f1RTpMVjuEE0hbOlgGWm3GR+gchIBbhZwdiSvHDG4cLRFDQzZNWAyGICX1WfztoYvud4mKtiE9yPSi67xSCwVb4GCgIkKvYEIGLXY4tiqYUGljdWKqrRd/lzAoyAF4d5nDjLnQVFsbZlD7TboQpGAphWY2Ln+7c2+K0PVsQymLcyUsllbY9g8Dp+/1iMRTee9f58Hi1djm4v7YGbsuN/ORH45NnlEofl40ZT1Ip40ch1ScV+QzV8X1OSmapmai/u4aZl0noVWmr4Qy6HkJtMaQ8DojWDOYM3M/vt66/R2FSqCJcJsQMqFEWjV9Gw/OLFF38U4evYmq6D+MfDw24rRt4Z96Nky9W9aQtwY6suzG/LmY4Jieuwridp2dPrL4cAD7eq6NChQ5I0pRsXpz3YV80q2ler1frhDmRcLiKyQkjeFIHnE68K6Bbbf2IluER4ZpVBReSVvUzVCz5x4CHMVTXJgLwmO52mwDtXLHJqBf6qHmzUVgOzEhia8kjTh5fKBdCQO9kOb1+hWywgpiV9m1fm/wbZvoo9" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -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=" />
|
||||
@@ -1,8 +1,69 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>如何渲染富文本的悬浮工具栏</h1>
|
||||
<p>编写中。。。</p>
|
||||
|
||||
<blockquote>
|
||||
<p>要支持节点富文本编辑需要使用富文本插件</p>
|
||||
</blockquote>
|
||||
<p>如果开启了节点富文本编辑,那么可以对节点内的部分文本应用样式,一般当选中文本时上方会出现一个工具栏,有加粗、斜体、改变颜色等等的按钮。</p>
|
||||
<p>首先要监听<code>rich_text_selection_change</code>事件,也就是选中文本的事件:</p>
|
||||
<pre class="hljs"><code>mindMap.on(<span class="hljs-string">'rich_text_selection_change'</span>, <span class="hljs-function">(<span class="hljs-params">hasRange, rect, formatInfo</span>) =></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">'px'</span>
|
||||
<span class="hljs-keyword">let</span> top = rect.top - <span class="hljs-number">60</span> + <span class="hljs-string">'px'</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">'宋体, SimSun, Songti SC'</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">'px'</span>
|
||||
})
|
||||
</code></pre>
|
||||
<h3>设置文字颜色</h3>
|
||||
<pre class="hljs"><code>mindMap.richText.formatText({
|
||||
<span class="hljs-attr">color</span>: <span class="hljs-string">'#fff'</span>
|
||||
})
|
||||
</code></pre>
|
||||
<h3>设置文字背景颜色</h3>
|
||||
<pre class="hljs"><code>mindMap.richText.formatText({
|
||||
<span class="hljs-attr">background</span>: <span class="hljs-string">'#fff'</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>
|
||||
|
||||
|
||||
@@ -1,3 +1,245 @@
|
||||
# 导入和导出
|
||||
|
||||
编写中。。。
|
||||
## 导出
|
||||
|
||||
> 要使用导出功能需要使用导出插件。
|
||||
|
||||
目前支持导出为`.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, domToImage = false, plusCssText = '')
|
||||
```
|
||||
|
||||
如果开启了节点富文本编辑功能,那么可以通过`domToImage`参数控制导出的`svg`中是否保留`html`结构,还是转换成图片形式,同样,如果传了`true`,导出会非常耗时,建议导出为`svg`时`domToImage`传`false`。
|
||||
|
||||
如果`domToImage`传的是`false`,也就是`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==" />
|
||||
@@ -1,8 +1,182 @@
|
||||
<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>导出为smm、json</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">'smm'</span>, <span class="hljs-literal">true</span>, <span class="hljs-string">'文件名'</span>, <span class="hljs-literal">true</span>)
|
||||
mindMap.export(<span class="hljs-string">'json'</span>, <span class="hljs-literal">true</span>, <span class="hljs-string">'文件名'</span>, <span class="hljs-literal">false</span>)
|
||||
</code></pre>
|
||||
<h3>导出为png、pdf</h3>
|
||||
<p>导出这两种文件很简单:</p>
|
||||
<pre class="hljs"><code>mindMap.export(<span class="hljs-string">'png'</span>, <span class="hljs-literal">true</span>, <span class="hljs-string">'文件名'</span>)
|
||||
mindMap.export(<span class="hljs-string">'pdf'</span>, <span class="hljs-literal">true</span>, <span class="hljs-string">'文件名'</span>)
|
||||
</code></pre>
|
||||
<p>如果开启了节点富文本编辑,那么导出会非常慢,因为需要挨个转换节点,所以如果导出操作很频繁的话建议关闭节点非文本编辑功能。</p>
|
||||
<h3>导出为svg</h3>
|
||||
<p>导出为<code>svg</code>可以传递的参数如下:</p>
|
||||
<pre class="hljs"><code>mindMap.export(type, isDownload, fileName, domToImage = <span class="hljs-literal">false</span>, plusCssText = <span class="hljs-string">''</span>)
|
||||
</code></pre>
|
||||
<p>如果开启了节点富文本编辑功能,那么可以通过<code>domToImage</code>参数控制导出的<code>svg</code>中是否保留<code>html</code>结构,还是转换成图片形式,同样,如果传了<code>true</code>,导出会非常耗时,建议导出为<code>svg</code>时<code>domToImage</code>传<code>false</code>。</p>
|
||||
<p>如果<code>domToImage</code>传的是<code>false</code>,也就是<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">'svg'</span>,
|
||||
<span class="hljs-literal">true</span>,
|
||||
<span class="hljs-string">'文件名'</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">'md'</span>, <span class="hljs-literal">true</span>, <span class="hljs-string">'文件名'</span>)
|
||||
</code></pre>
|
||||
<h2>导入</h2>
|
||||
<p>目前支持从<code>.smm</code>、<code>.json</code>、<code>.xmind</code>、<code>.xlsx</code>、<code>.md</code>格式的文件导入。</p>
|
||||
<h3>导入smm、json</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">'json数据'</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">'simple-mind-map/src/parse/xmind.js'</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">'xlsx'</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> =></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> =></span> {
|
||||
<span class="hljs-keyword">const</span> reader = <span class="hljs-keyword">new</span> FileReader()
|
||||
reader.onload = <span class="hljs-function">() =></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) => {
|
||||
<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 <= <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> =></span> {
|
||||
<span class="hljs-keyword">if</span> (arr.length > 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> =></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 < 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 < 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>) =></span> {
|
||||
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = arr.length - <span class="hljs-number">1</span>; i >= <span class="hljs-number">0</span>; i--) {
|
||||
<span class="hljs-keyword">if</span> (row >= 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 < 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 < 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">'xlsx文件对象'</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">'simple-mind-map/src/parse/markdown.js'</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">'md文件内容'</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>
|
||||
|
||||
|
||||
@@ -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=" />
|
||||
@@ -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">'data_change'</span>, <span class="hljs-function"><span class="hljs-params">data</span> =></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">'view_data_change'</span>, <span class="hljs-function"><span class="hljs-params">data</span> =></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>
|
||||
|
||||
|
||||
66
web/src/pages/Doc/zh/deploy/index.md
Normal file
66
web/src/pages/Doc/zh/deploy/index.md
Normal 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
|
||||
|
||||
编写中。。。
|
||||
55
web/src/pages/Doc/zh/deploy/index.vue
Normal file
55
web/src/pages/Doc/zh/deploy/index.vue
Normal 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 && node ../copy.js</code>中的<code> && 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">'history'</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>
|
||||
@@ -7,7 +7,7 @@
|
||||
## 特性
|
||||
|
||||
- [x] 插件化架构,除核心功能外,其他功能作为插件提供,按需使用,减小整体体积
|
||||
- [x] 支持逻辑结构图、思维导图、组织结构图、目录组织图四种结构
|
||||
- [x] 支持逻辑结构图、思维导图、组织结构图、目录组织图、时间轴、鱼骨图六种结构
|
||||
- [x] 内置多种主题,允许高度自定义样式,支持注册新主题
|
||||
- [x] 支持快捷键
|
||||
- [x] 节点内容支持图片、图标、超链接、备注、标签、概要
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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`文件内容
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -111,10 +111,11 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.showNewFeatureInfo()
|
||||
// this.showNewFeatureInfo()
|
||||
this.getData()
|
||||
this.init()
|
||||
this.$bus.$on('execCommand', this.execCommand)
|
||||
this.$bus.$on('paddingChange', this.onPaddingChange)
|
||||
this.$bus.$on('export', this.export)
|
||||
this.$bus.$on('setData', this.setData)
|
||||
this.$bus.$on('startTextEdit', () => {
|
||||
@@ -276,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)
|
||||
@@ -305,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)
|
||||
@@ -374,6 +378,11 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
// 修改导出内边距
|
||||
onPaddingChange(data) {
|
||||
this.mindMap.updateConfig(data)
|
||||
},
|
||||
|
||||
// 显示新特性提示
|
||||
showNewFeatureInfo() {
|
||||
let showed = localStorage.getItem('SIMPLE_MIND_MAP_NEW_FEATURE_TIP_1')
|
||||
|
||||
@@ -30,6 +30,22 @@
|
||||
>{{ $t('export.domToImage') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
<div class="paddingInputBox" v-show="['svg', 'png', 'pdf'].includes(exportType)">
|
||||
<span class="name">{{ $t('export.paddingX') }}</span>
|
||||
<el-input
|
||||
style="width: 100px"
|
||||
v-model="paddingX"
|
||||
size="mini"
|
||||
@change="onPaddingChange"
|
||||
></el-input>
|
||||
<span class="name" style="margin-left: 10px;">{{ $t('export.paddingY') }}</span>
|
||||
<el-input
|
||||
style="width: 100px"
|
||||
v-model="paddingY"
|
||||
size="mini"
|
||||
@change="onPaddingChange"
|
||||
></el-input>
|
||||
</div>
|
||||
<div class="downloadTypeList">
|
||||
<div
|
||||
class="downloadTypeItem"
|
||||
@@ -77,7 +93,9 @@ export default {
|
||||
widthConfig: true,
|
||||
domToImage: false,
|
||||
loading: false,
|
||||
loadingText: ''
|
||||
loadingText: '',
|
||||
paddingX: 10,
|
||||
paddingY: 10
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -102,6 +120,13 @@ export default {
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
onPaddingChange() {
|
||||
this.$bus.$emit('paddingChange', {
|
||||
exportPaddingX: Number(this.paddingX),
|
||||
exportPaddingY: Number(this.paddingY)
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-22 22:08:11
|
||||
@@ -163,6 +188,14 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.paddingInputBox {
|
||||
margin-bottom: 10px;
|
||||
|
||||
.name {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.tip {
|
||||
margin-top: 10px;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user