Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b04469649a | ||
|
|
ee57dbdcc5 | ||
|
|
bca744f6fc | ||
|
|
442408dacf | ||
|
|
7297f0a082 | ||
|
|
f0d90941fb | ||
|
|
830962c044 | ||
|
|
eeec71dc65 | ||
|
|
9bd87a22f3 | ||
|
|
88c9f22a15 | ||
|
|
7380fc60d5 | ||
|
|
a178b99d73 | ||
|
|
af9ee04aaf | ||
|
|
01dc98f1f8 | ||
|
|
cbd07246bd | ||
|
|
5bb23ca738 | ||
|
|
0886ba7698 | ||
|
|
a65cffa58b | ||
|
|
02e2d432dd | ||
|
|
c8d23cab40 | ||
|
|
244ced83bc | ||
|
|
5c9c3d7934 | ||
|
|
4ca6713675 | ||
|
|
3d18404fd6 | ||
|
|
98dda26bf8 | ||
|
|
cd4c5ecd83 | ||
|
|
fdc0017ccb | ||
|
|
20a5c12bbb | ||
|
|
4eacec125e | ||
|
|
34322ba6d1 | ||
|
|
8210151a7b | ||
|
|
1e00088608 | ||
|
|
ee59b8002a | ||
|
|
b2ca5d0fba | ||
|
|
470604e567 | ||
|
|
f5f665ec0a | ||
|
|
5e865a4e33 | ||
|
|
38ad33b604 | ||
|
|
e1b4146171 | ||
|
|
65151f4b0a | ||
|
|
942706fb63 | ||
|
|
5f4492d4b7 | ||
|
|
ace1f62a40 | ||
|
|
706c88c7d5 | ||
|
|
4512fb16eb | ||
|
|
e446ff12e7 | ||
|
|
4318646abe | ||
|
|
2cbfe4f0e7 | ||
|
|
b7910c4665 | ||
|
|
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 |
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2021-2023 The MindMap Team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
17
README.md
@@ -19,6 +19,12 @@
|
||||
|
||||
在线地址:[https://wanglin2.github.io/mind-map/](https://wanglin2.github.io/mind-map/)
|
||||
|
||||
另外也提供了客户端可供下载使用,支持`Windows`、`Mac`及`Linux`,下载地址:
|
||||
|
||||
Github:[releases](https://github.com/wanglin2/mind-map/releases)。
|
||||
|
||||
百度云盘:[地址](https://pan.baidu.com/s/1huasEbKsGNH2Af68dvWiOg?pwd=3bp3)。
|
||||
|
||||
# 特性
|
||||
|
||||
- [x] 插件化架构,除核心功能外,其他功能作为插件提供,按需使用,减小打包体积
|
||||
@@ -84,4 +90,13 @@ MIT
|
||||
|
||||
# 微信交流群
|
||||
|
||||

|
||||
<img src="./qrcode.jpg" style="width: 300px" />
|
||||
|
||||
# 请作者喝杯咖啡
|
||||
|
||||
> 厚椰乳一盒 + 纯牛奶半盒 + 冰块 + 咖啡液 = 生椰拿铁 yyds
|
||||
|
||||
<p>
|
||||
<img src="./web/src/assets/img/alipay.jpg" style="width: 300px" />
|
||||
<img src="./web/src/assets/img/wechat.jpg" style="width: 300px" />
|
||||
</p>
|
||||
BIN
qrcode.jpg
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
@@ -9,9 +9,11 @@ import AssociativeLine from './src/AssociativeLine'
|
||||
import RichText from './src/RichText'
|
||||
import xmind from './src/parse/xmind.js'
|
||||
import markdown from './src/parse/markdown.js'
|
||||
import icons from './src/svg/icons.js'
|
||||
|
||||
MindMap.xmind = xmind
|
||||
MindMap.markdown = markdown
|
||||
MindMap.iconList = icons.nodeIconList
|
||||
|
||||
MindMap
|
||||
.usePlugin(MiniMap)
|
||||
|
||||
@@ -10,7 +10,7 @@ import BatchExecution from './src/BatchExecution'
|
||||
import { layoutValueList, CONSTANTS } from './src/utils/constant'
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
import { simpleDeepClone } from './src/utils'
|
||||
import defaultTheme from './src/themes/default'
|
||||
import defaultTheme, { checkIsNodeSizeIndependenceConfig } from './src/themes/default'
|
||||
|
||||
// 默认选项配置
|
||||
const defaultOpt = {
|
||||
@@ -92,7 +92,37 @@ 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,
|
||||
// 是否一直显示节点的展开收起按钮,默认为鼠标移上去和激活时才显示
|
||||
alwaysShowExpandBtn: false,
|
||||
// 扩展节点可插入的图标
|
||||
iconList: [
|
||||
// {
|
||||
// name: '',// 分组名称
|
||||
// type: '',// 分组的值
|
||||
// list: [// 分组下的图标列表
|
||||
// {
|
||||
// name: '',// 图标名称
|
||||
// icon:''// 图标,可以传svg或图片
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
],
|
||||
// 节点最大缓存数量
|
||||
maxNodeCacheCount: 1000,
|
||||
// 关联线默认文字
|
||||
defaultAssociativeLineText: '关联'
|
||||
}
|
||||
|
||||
// 思维导图
|
||||
@@ -182,12 +212,12 @@ class MindMap {
|
||||
}
|
||||
|
||||
// 重新渲染
|
||||
reRender(callback) {
|
||||
reRender(callback, source = '') {
|
||||
this.batchExecution.push('render', () => {
|
||||
this.draw.clear()
|
||||
this.initTheme()
|
||||
this.renderer.reRender = true
|
||||
this.renderer.render(callback)
|
||||
this.renderer.render(callback, source)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -237,7 +267,9 @@ class MindMap {
|
||||
// 设置主题配置
|
||||
setThemeConfig(config) {
|
||||
this.opt.themeConfig = config
|
||||
this.render(null, CONSTANTS.CHANGE_THEME)
|
||||
// 检查改变的是否是节点大小无关的主题属性
|
||||
let res = checkIsNodeSizeIndependenceConfig(config)
|
||||
this.render(null, res ? '' : CONSTANTS.CHANGE_THEME)
|
||||
}
|
||||
|
||||
// 获取自定义主题配置
|
||||
@@ -272,6 +304,7 @@ class MindMap {
|
||||
layout = CONSTANTS.LAYOUT.LOGICAL_STRUCTURE
|
||||
}
|
||||
this.opt.layout = layout
|
||||
this.view.reset()
|
||||
this.renderer.setLayout()
|
||||
this.render()
|
||||
}
|
||||
@@ -286,8 +319,12 @@ class MindMap {
|
||||
this.execCommand('CLEAR_ACTIVE_NODE')
|
||||
this.command.clearHistory()
|
||||
this.command.addHistory()
|
||||
this.renderer.renderTree = data
|
||||
this.reRender()
|
||||
if (this.richText) {
|
||||
this.renderer.renderTree = this.richText.handleSetData(data)
|
||||
} else {
|
||||
this.renderer.renderTree = data
|
||||
}
|
||||
this.reRender(() => {}, CONSTANTS.SET_DATA)
|
||||
}
|
||||
|
||||
// 动态设置思维导图数据,包括节点数据、布局、主题、视图
|
||||
@@ -359,7 +396,7 @@ class MindMap {
|
||||
}
|
||||
|
||||
// 获取svg数据
|
||||
getSvgData() {
|
||||
getSvgData({ paddingX = 0, paddingY = 0 } = {}) {
|
||||
const svg = this.svg
|
||||
const draw = this.draw
|
||||
// 保存原始信息
|
||||
@@ -371,6 +408,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.11",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
|
||||
@@ -3,13 +3,13 @@ import { v4 as uuid } from 'uuid'
|
||||
import {
|
||||
getAssociativeLineTargetIndex,
|
||||
computeCubicBezierPathPoints,
|
||||
joinCubicBezierPath,
|
||||
cubicBezierPath,
|
||||
getNodePoint,
|
||||
computeNodePoints,
|
||||
getNodeLinePath,
|
||||
getDefaultControlPointOffsets
|
||||
getNodeLinePath
|
||||
} from './utils/associativeLineUtils'
|
||||
import associativeLineControlsMethods from './utils/associativeLineControls'
|
||||
import associativeLineTextMethods from './utils/associativeLineText'
|
||||
|
||||
// 关联线类
|
||||
class AssociativeLine {
|
||||
@@ -46,6 +46,14 @@ class AssociativeLine {
|
||||
}
|
||||
// 节流一下,不然很卡
|
||||
this.checkOverlapNode = throttle(this.checkOverlapNode, 100, this)
|
||||
// 控制点相关方法
|
||||
Object.keys(associativeLineControlsMethods).forEach(item => {
|
||||
this[item] = associativeLineControlsMethods[item].bind(this)
|
||||
})
|
||||
// 关联线文字相关方法
|
||||
Object.keys(associativeLineTextMethods).forEach(item => {
|
||||
this[item] = associativeLineTextMethods[item].bind(this)
|
||||
})
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
@@ -89,6 +97,8 @@ class AssociativeLine {
|
||||
window.addEventListener('mouseup', e => {
|
||||
this.onControlPointMouseup(e)
|
||||
})
|
||||
// 缩放事件
|
||||
this.mindMap.on('scale', this.onScale)
|
||||
}
|
||||
|
||||
// 创建箭头
|
||||
@@ -177,36 +187,46 @@ class AssociativeLine {
|
||||
.stroke({ width: associativeLineActiveWidth, color: 'transparent' })
|
||||
.fill({ color: 'none' })
|
||||
clickPath.plot(pathStr)
|
||||
// 文字
|
||||
let text = this.createText({ path, clickPath, node, toNode, startPoint, endPoint, controlPoints })
|
||||
// 点击事件
|
||||
clickPath.click(e => {
|
||||
e.stopPropagation()
|
||||
// 如果当前存在激活节点,那么取消激活节点
|
||||
if (this.mindMap.renderer.activeNodeList.length > 0) {
|
||||
this.clearActiveNodes()
|
||||
} else {
|
||||
// 否则清除当前的关联线的激活状态,如果有的话
|
||||
this.clearActiveLine()
|
||||
// 保存当前激活的关联线信息
|
||||
this.activeLine = [path, clickPath, node, toNode]
|
||||
// 让不可见的点击线显示
|
||||
clickPath.stroke({ color: associativeLineActiveColor })
|
||||
// 渲染控制点和连线
|
||||
this.renderControls(
|
||||
startPoint,
|
||||
endPoint,
|
||||
controlPoints[0],
|
||||
controlPoints[1]
|
||||
)
|
||||
this.mindMap.emit(
|
||||
'associative_line_click',
|
||||
path,
|
||||
clickPath,
|
||||
node,
|
||||
toNode
|
||||
)
|
||||
}
|
||||
this.setActiveLine({ path, clickPath, text, node, toNode, startPoint, endPoint, controlPoints })
|
||||
})
|
||||
this.lineList.push([path, clickPath, node, toNode])
|
||||
// 渲染关联线文字
|
||||
this.renderText(this.getText(node, toNode), path, text)
|
||||
this.lineList.push([path, clickPath, text, node, toNode])
|
||||
}
|
||||
|
||||
// 激活某根关联线
|
||||
setActiveLine({ path, clickPath, text, node, toNode, startPoint, endPoint, controlPoints }) {
|
||||
let {
|
||||
associativeLineActiveColor
|
||||
} = this.mindMap.themeConfig
|
||||
// 如果当前存在激活节点,那么取消激活节点
|
||||
if (this.mindMap.renderer.activeNodeList.length > 0) {
|
||||
this.clearActiveNodes()
|
||||
} else {
|
||||
// 否则清除当前的关联线的激活状态,如果有的话
|
||||
this.clearActiveLine()
|
||||
// 保存当前激活的关联线信息
|
||||
this.activeLine = [path, clickPath, text, node, toNode]
|
||||
// 让不可见的点击线显示
|
||||
clickPath.stroke({ color: associativeLineActiveColor })
|
||||
// 如果没有输入过关联线文字,那么显示默认文字
|
||||
if (!this.getText(node, toNode)) {
|
||||
this.renderText(this.mindMap.opt.defaultAssociativeLineText, path, text)
|
||||
}
|
||||
// 渲染控制点和连线
|
||||
this.renderControls(
|
||||
startPoint,
|
||||
endPoint,
|
||||
controlPoints[0],
|
||||
controlPoints[1]
|
||||
)
|
||||
this.mindMap.emit('associative_line_click', path, clickPath, node, toNode)
|
||||
}
|
||||
}
|
||||
|
||||
// 移除所有连接线
|
||||
@@ -214,6 +234,7 @@ class AssociativeLine {
|
||||
this.lineList.forEach(line => {
|
||||
line[0].remove()
|
||||
line[1].remove()
|
||||
line[2].remove()
|
||||
})
|
||||
this.lineList = []
|
||||
}
|
||||
@@ -253,7 +274,8 @@ class AssociativeLine {
|
||||
updateCreatingLine(e) {
|
||||
let { x, y } = this.getTransformedEventPos(e)
|
||||
let startPoint = getNodePoint(this.creatingStartNode)
|
||||
let pathStr = cubicBezierPath(startPoint.x, startPoint.y, x, y)
|
||||
let offsetX = x > startPoint.x ? -10 : 10
|
||||
let pathStr = cubicBezierPath(startPoint.x, startPoint.y, x + offsetX, y)
|
||||
this.creatingLine.plot(pathStr)
|
||||
this.checkOverlapNode(x, y)
|
||||
}
|
||||
@@ -349,20 +371,33 @@ class AssociativeLine {
|
||||
// 删除连接线
|
||||
removeLine() {
|
||||
if (!this.activeLine) return
|
||||
let [, , node, toNode] = this.activeLine
|
||||
let [, , , node, toNode] = this.activeLine
|
||||
this.removeControls()
|
||||
let { associativeLineTargets, associativeLineTargetControlOffsets } =
|
||||
let { associativeLineTargets, associativeLineTargetControlOffsets, associativeLineText } =
|
||||
node.nodeData.data
|
||||
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
|
||||
// 更新关联线文本数据
|
||||
let newAssociativeLineText = {}
|
||||
if (associativeLineText) {
|
||||
Object.keys(associativeLineText).forEach((item) => {
|
||||
if (item !== toNode.nodeData.data.id) {
|
||||
newAssociativeLineText[item] = associativeLineText[item]
|
||||
}
|
||||
})
|
||||
}
|
||||
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
||||
// 目标
|
||||
associativeLineTargets: associativeLineTargets.filter((_, index) => {
|
||||
return index !== targetIndex
|
||||
}),
|
||||
// 偏移量
|
||||
associativeLineTargetControlOffsets: associativeLineTargetControlOffsets
|
||||
? associativeLineTargetControlOffsets.filter((_, index) => {
|
||||
return index !== targetIndex
|
||||
})
|
||||
: []
|
||||
: [],
|
||||
// 文本
|
||||
associativeLineText: newAssociativeLineText
|
||||
})
|
||||
}
|
||||
|
||||
@@ -376,9 +411,16 @@ class AssociativeLine {
|
||||
// 清除激活的线
|
||||
clearActiveLine() {
|
||||
if (this.activeLine) {
|
||||
this.activeLine[1].stroke({
|
||||
let [, clickPath, text, node, toNode] = this.activeLine
|
||||
clickPath.stroke({
|
||||
color: 'transparent'
|
||||
})
|
||||
// 隐藏关联线文本编辑框
|
||||
this.hideEditTextBox()
|
||||
// 如果当前关联线没有文字,则清空文字节点
|
||||
if (!this.getText(node, toNode)) {
|
||||
text.clear()
|
||||
}
|
||||
this.activeLine = null
|
||||
this.removeControls()
|
||||
}
|
||||
@@ -391,6 +433,7 @@ class AssociativeLine {
|
||||
this.lineList.forEach(line => {
|
||||
line[0].hide()
|
||||
line[1].hide()
|
||||
line[2].hide()
|
||||
})
|
||||
this.hideControls()
|
||||
}
|
||||
@@ -401,225 +444,11 @@ class AssociativeLine {
|
||||
this.lineList.forEach(line => {
|
||||
line[0].show()
|
||||
line[1].show()
|
||||
line[2].show()
|
||||
})
|
||||
this.showControls()
|
||||
this.isNodeDragging = false
|
||||
}
|
||||
|
||||
// 创建控制点、连线节点
|
||||
createControlNodes() {
|
||||
let { associativeLineActiveColor } = this.mindMap.themeConfig
|
||||
// 连线
|
||||
this.controlLine1 = this.draw
|
||||
.line()
|
||||
.stroke({ color: associativeLineActiveColor, width: 2 })
|
||||
this.controlLine2 = this.draw
|
||||
.line()
|
||||
.stroke({ color: associativeLineActiveColor, width: 2 })
|
||||
// 控制点
|
||||
this.controlPoint1 = this.createOneControlNode('controlPoint1')
|
||||
this.controlPoint2 = this.createOneControlNode('controlPoint2')
|
||||
}
|
||||
|
||||
// 创建控制点
|
||||
createOneControlNode(pointKey) {
|
||||
let { associativeLineActiveColor } = this.mindMap.themeConfig
|
||||
return this.draw
|
||||
.circle(this.controlPointDiameter)
|
||||
.stroke({ color: associativeLineActiveColor })
|
||||
.fill({ color: '#fff' })
|
||||
.click(e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
.mousedown(e => {
|
||||
this.onControlPointMousedown(e, pointKey)
|
||||
})
|
||||
}
|
||||
|
||||
// 控制点的鼠标按下事件
|
||||
onControlPointMousedown(e, pointKey) {
|
||||
e.stopPropagation()
|
||||
this.isControlPointMousedown = true
|
||||
this.mousedownControlPointKey = pointKey
|
||||
}
|
||||
|
||||
// 控制点的鼠标移动事件
|
||||
onControlPointMousemove(e) {
|
||||
if (
|
||||
!this.isControlPointMousedown ||
|
||||
!this.mousedownControlPointKey ||
|
||||
!this[this.mousedownControlPointKey]
|
||||
)
|
||||
return
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
let radius = this.controlPointDiameter / 2
|
||||
// 转换鼠标当前的位置
|
||||
let { x, y } = this.getTransformedEventPos(e)
|
||||
this.controlPointMousemoveState.pos = {
|
||||
x,
|
||||
y
|
||||
}
|
||||
// 更新当前拖拽的控制点的位置
|
||||
this[this.mousedownControlPointKey].x(x - radius).y(y - radius)
|
||||
let [path, clickPath, node, toNode] = this.activeLine
|
||||
let [startPoint, endPoint] = computeNodePoints(node, toNode)
|
||||
this.controlPointMousemoveState.startPoint = startPoint
|
||||
this.controlPointMousemoveState.endPoint = endPoint
|
||||
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
|
||||
this.controlPointMousemoveState.targetIndex = targetIndex
|
||||
let offsets = []
|
||||
let associativeLineTargetControlOffsets = node.nodeData.data.associativeLineTargetControlOffsets
|
||||
if (!associativeLineTargetControlOffsets) {
|
||||
// 兼容0.4.5版本,没有associativeLineTargetControlOffsets的情况
|
||||
offsets = getDefaultControlPointOffsets(startPoint, endPoint)
|
||||
} else {
|
||||
offsets = associativeLineTargetControlOffsets[targetIndex]
|
||||
}
|
||||
let point1 = null
|
||||
let point2 = null
|
||||
// 拖拽的是控制点1
|
||||
if (this.mousedownControlPointKey === 'controlPoint1') {
|
||||
point1 = {
|
||||
x,
|
||||
y
|
||||
}
|
||||
point2 = {
|
||||
x: endPoint.x + offsets[1].x,
|
||||
y: endPoint.y + offsets[1].y
|
||||
}
|
||||
// 更新控制点1的连线
|
||||
this.controlLine1.plot(startPoint.x, startPoint.y, point1.x, point1.y)
|
||||
} else {
|
||||
// 拖拽的是控制点2
|
||||
point1 = {
|
||||
x: startPoint.x + offsets[0].x,
|
||||
y: startPoint.y + offsets[0].y
|
||||
}
|
||||
point2 = {
|
||||
x,
|
||||
y
|
||||
}
|
||||
// 更新控制点2的连线
|
||||
this.controlLine2.plot(endPoint.x, endPoint.y, point2.x, point2.y)
|
||||
}
|
||||
// 更新关联线
|
||||
let pathStr = joinCubicBezierPath(startPoint, endPoint, point1, point2)
|
||||
path.plot(pathStr)
|
||||
clickPath.plot(pathStr)
|
||||
}
|
||||
|
||||
// 控制点的鼠标移动事件
|
||||
onControlPointMouseup(e) {
|
||||
if (!this.isControlPointMousedown) return
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
let { pos, startPoint, endPoint, targetIndex } =
|
||||
this.controlPointMousemoveState
|
||||
let [, , node] = this.activeLine
|
||||
let offsetList = []
|
||||
let associativeLineTargetControlOffsets = node.nodeData.data.associativeLineTargetControlOffsets
|
||||
if (!associativeLineTargetControlOffsets) {
|
||||
// 兼容0.4.5版本,没有associativeLineTargetControlOffsets的情况
|
||||
offsetList[targetIndex] = getDefaultControlPointOffsets(startPoint, endPoint)
|
||||
} else {
|
||||
offsetList = associativeLineTargetControlOffsets
|
||||
}
|
||||
let offset1 = null
|
||||
let offset2 = null
|
||||
if (this.mousedownControlPointKey === 'controlPoint1') {
|
||||
// 更新控制点1数据
|
||||
offset1 = {
|
||||
x: pos.x - startPoint.x,
|
||||
y: pos.y - startPoint.y
|
||||
}
|
||||
offset2 = offsetList[targetIndex][1]
|
||||
} else {
|
||||
// 更新控制点2数据
|
||||
offset1 = offsetList[targetIndex][0]
|
||||
offset2 = {
|
||||
x: pos.x - endPoint.x,
|
||||
y: pos.y - endPoint.y
|
||||
}
|
||||
}
|
||||
offsetList[targetIndex] = [offset1, offset2]
|
||||
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
||||
associativeLineTargetControlOffsets: offsetList
|
||||
})
|
||||
// 这里要加个setTimeout0是因为draw_click事件比mouseup事件触发的晚,所以重置isControlPointMousedown需要等draw_click事件触发完以后
|
||||
setTimeout(() => {
|
||||
this.resetControlPoint()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
// 复位控制点移动
|
||||
resetControlPoint() {
|
||||
this.isControlPointMousedown = false
|
||||
this.mousedownControlPointKey = ''
|
||||
this.controlPointMousemoveState = {
|
||||
pos: null,
|
||||
startPoint: null,
|
||||
endPoint: null,
|
||||
targetIndex: ''
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染控制点
|
||||
renderControls(startPoint, endPoint, point1, point2) {
|
||||
if (!this.controlLine1) {
|
||||
this.createControlNodes()
|
||||
}
|
||||
let radius = this.controlPointDiameter / 2
|
||||
// 控制点和起终点的连线
|
||||
this.controlLine1.plot(startPoint.x, startPoint.y, point1.x, point1.y)
|
||||
this.controlLine2.plot(endPoint.x, endPoint.y, point2.x, point2.y)
|
||||
// 控制点
|
||||
this.controlPoint1.x(point1.x - radius).y(point1.y - radius)
|
||||
this.controlPoint2.x(point2.x - radius).y(point2.y - radius)
|
||||
}
|
||||
|
||||
// 删除控制点
|
||||
removeControls() {
|
||||
if (!this.controlLine1) return
|
||||
;[
|
||||
this.controlLine1,
|
||||
this.controlLine2,
|
||||
this.controlPoint1,
|
||||
this.controlPoint2
|
||||
].forEach(item => {
|
||||
item.remove()
|
||||
})
|
||||
this.controlLine1 = null
|
||||
this.controlLine2 = null
|
||||
this.controlPoint1 = null
|
||||
this.controlPoint2 = null
|
||||
}
|
||||
|
||||
// 隐藏控制点
|
||||
hideControls() {
|
||||
if (!this.controlLine1) return
|
||||
;[
|
||||
this.controlLine1,
|
||||
this.controlLine2,
|
||||
this.controlPoint1,
|
||||
this.controlPoint2
|
||||
].forEach(item => {
|
||||
item.hide()
|
||||
})
|
||||
}
|
||||
|
||||
// 显示控制点
|
||||
showControls() {
|
||||
if (!this.controlLine1) return
|
||||
;[
|
||||
this.controlLine1,
|
||||
this.controlLine2,
|
||||
this.controlPoint1,
|
||||
this.controlPoint2
|
||||
].forEach(item => {
|
||||
item.show()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
AssociativeLine.instanceName = 'associativeLine'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { imgToDataUrl, downloadFile } from './utils'
|
||||
import { imgToDataUrl, downloadFile, readBlob } from './utils'
|
||||
import JsPDF from 'jspdf'
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
import drawBackgroundImageToCanvas from './utils/simulateCSSBackgroundInCanvas'
|
||||
@@ -27,8 +27,12 @@ class Export {
|
||||
}
|
||||
|
||||
// 获取svg数据
|
||||
async getSvgData(domToImage) {
|
||||
let { svg, svgHTML } = this.mindMap.getSvgData()
|
||||
async 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 => {
|
||||
@@ -40,24 +44,14 @@ class Export {
|
||||
if (imageList.length > 0) {
|
||||
svgHTML = svg.svg()
|
||||
}
|
||||
// 如果开启了富文本编辑,需要把svg中的dom元素转换成图片
|
||||
let nodeWithDomToImg = null
|
||||
if (domToImage && this.mindMap.richText) {
|
||||
let res = await this.mindMap.richText.handleSvgDomElements(svg)
|
||||
if (res) {
|
||||
nodeWithDomToImg = res.svg
|
||||
svgHTML = res.svgHTML
|
||||
}
|
||||
}
|
||||
return {
|
||||
node: svg,
|
||||
str: svgHTML,
|
||||
nodeWithDomToImg
|
||||
str: svgHTML
|
||||
}
|
||||
}
|
||||
|
||||
// svg转png
|
||||
svgToPng(svgSrc) {
|
||||
svgToPng(svgSrc, transparent) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
// 跨域图片需要添加这个属性,否则画布被污染了无法导出图片
|
||||
@@ -69,7 +63,9 @@ class Export {
|
||||
canvas.height = img.height + this.exportPadding * 2
|
||||
let ctx = canvas.getContext('2d')
|
||||
// 绘制背景
|
||||
await this.drawBackgroundToCanvas(ctx, canvas.width, canvas.height)
|
||||
if (!transparent) {
|
||||
await this.drawBackgroundToCanvas(ctx, canvas.width, canvas.height)
|
||||
}
|
||||
// 图片绘制到canvas里
|
||||
ctx.drawImage(
|
||||
img,
|
||||
@@ -131,23 +127,50 @@ class Export {
|
||||
})
|
||||
}
|
||||
|
||||
// 在svg上绘制思维导图背景
|
||||
drawBackgroundToSvg(svg) {
|
||||
return new Promise(async resolve => {
|
||||
let {
|
||||
backgroundColor = '#fff',
|
||||
backgroundImage,
|
||||
backgroundRepeat = 'repeat'
|
||||
} = this.mindMap.themeConfig
|
||||
// 背景颜色
|
||||
svg.css('background-color', backgroundColor)
|
||||
// 背景图片
|
||||
if (backgroundImage && backgroundImage !== 'none') {
|
||||
let imgDataUrl = await imgToDataUrl(backgroundImage)
|
||||
svg.css('background-image', `url(${imgDataUrl})`)
|
||||
svg.css('background-repeat', backgroundRepeat)
|
||||
resolve()
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 导出为png
|
||||
/**
|
||||
* 方法1.把svg的图片都转化成data:url格式,再转换
|
||||
* 方法2.把svg的图片提取出来再挨个绘制到canvas里,最后一起转换
|
||||
*/
|
||||
async png() {
|
||||
let { str } = await this.getSvgData(true)
|
||||
async png(name, transparent = false) {
|
||||
let { node, str } = await this.getSvgData()
|
||||
// 如果开启了富文本,则使用htmltocanvas转换为图片
|
||||
if (this.mindMap.richText) {
|
||||
let res = await this.mindMap.richText.handleExportPng(node.node)
|
||||
let imgDataUrl = await this.svgToPng(res, transparent)
|
||||
return imgDataUrl
|
||||
}
|
||||
// 转换成blob数据
|
||||
let blob = new Blob([str], {
|
||||
type: 'image/svg+xml'
|
||||
})
|
||||
// 转换成data:url数据
|
||||
let svgUrl = URL.createObjectURL(blob)
|
||||
let svgUrl = await readBlob(blob)
|
||||
// 绘制到canvas上
|
||||
let imgDataUrl = await this.svgToPng(svgUrl)
|
||||
URL.revokeObjectURL(svgUrl)
|
||||
return imgDataUrl
|
||||
let res = await this.svgToPng(svgUrl, transparent)
|
||||
return res
|
||||
}
|
||||
|
||||
// 导出为pdf
|
||||
@@ -182,38 +205,13 @@ class Export {
|
||||
image.src = img
|
||||
}
|
||||
|
||||
// 在svg上绘制思维导图背景
|
||||
drawBackgroundToSvg(svg) {
|
||||
return new Promise(async resolve => {
|
||||
let {
|
||||
backgroundColor = '#fff',
|
||||
backgroundImage,
|
||||
backgroundRepeat = 'repeat'
|
||||
} = this.mindMap.themeConfig
|
||||
// 背景颜色
|
||||
svg.css('background-color', backgroundColor)
|
||||
// 背景图片
|
||||
if (backgroundImage && backgroundImage !== 'none') {
|
||||
let imgDataUrl = await imgToDataUrl(backgroundImage)
|
||||
svg.css('background-image', `url(${imgDataUrl})`)
|
||||
svg.css('background-repeat', backgroundRepeat)
|
||||
resolve()
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 导出为svg
|
||||
// domToImage:是否将svg中的dom节点转换成图片的形式
|
||||
// plusCssText:附加的css样式,如果svg中存在dom节点,想要设置一些针对节点的样式可以通过这个参数传入
|
||||
async svg(name, domToImage = false, plusCssText) {
|
||||
let { node, nodeWithDomToImg } = await this.getSvgData(domToImage)
|
||||
async svg(name, plusCssText) {
|
||||
let { node } = await this.getSvgData()
|
||||
// 开启了节点富文本编辑
|
||||
if (this.mindMap.richText) {
|
||||
if (domToImage) {
|
||||
node = nodeWithDomToImg
|
||||
} else if (plusCssText) {
|
||||
if (plusCssText) {
|
||||
let foreignObjectList = node.find('foreignObject')
|
||||
if (foreignObjectList.length > 0) {
|
||||
foreignObjectList[0].add(SVG(`<style>${plusCssText}</style>`))
|
||||
@@ -227,28 +225,32 @@ class Export {
|
||||
let blob = new Blob([str], {
|
||||
type: 'image/svg+xml'
|
||||
})
|
||||
return URL.createObjectURL(blob)
|
||||
let res = await readBlob(blob)
|
||||
return res
|
||||
}
|
||||
|
||||
// 导出为json
|
||||
json(name, withConfig = true) {
|
||||
async json(name, withConfig = true) {
|
||||
let data = this.mindMap.getData(withConfig)
|
||||
let str = JSON.stringify(data)
|
||||
let blob = new Blob([str])
|
||||
return URL.createObjectURL(blob)
|
||||
let res = await readBlob(blob)
|
||||
return res
|
||||
}
|
||||
|
||||
// 专有文件,其实就是json文件
|
||||
smm(name, withConfig) {
|
||||
return this.json(name, withConfig)
|
||||
async smm(name, withConfig) {
|
||||
let res = await this.json(name, withConfig)
|
||||
return res
|
||||
}
|
||||
|
||||
// markdown文件
|
||||
md() {
|
||||
async md() {
|
||||
let data = this.mindMap.getData()
|
||||
let content = transformToMarkdown(data)
|
||||
let blob = new Blob([content])
|
||||
return URL.createObjectURL(blob)
|
||||
let res = await readBlob(blob)
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ export default class KeyCommand {
|
||||
if (this.mindMap.richText && this.mindMap.richText.showTextEdit) {
|
||||
return
|
||||
}
|
||||
if (this.mindMap.renderer.textEdit.showTextEdit) {
|
||||
if (this.mindMap.renderer.textEdit.showTextEdit || (this.mindMap.associativeLine && this.mindMap.associativeLine.showTextEdit)) {
|
||||
return
|
||||
}
|
||||
this.isInSvg = false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Style from './Style'
|
||||
import Shape from './Shape'
|
||||
import { asyncRun } from './utils'
|
||||
import { G } from '@svgdotjs/svg.js'
|
||||
import { G, Rect } from '@svgdotjs/svg.js'
|
||||
import nodeGeneralizationMethods from './utils/nodeGeneralization'
|
||||
import nodeExpandBtnMethods from './utils/nodeExpandBtn'
|
||||
import nodeCommandWrapsMethods from './utils/nodeCommandWraps'
|
||||
@@ -67,12 +67,16 @@ class Node {
|
||||
this._noteData = null
|
||||
this.noteEl = null
|
||||
this._expandBtn = null
|
||||
this._lastExpandBtnType = null
|
||||
this._showExpandBtn = false
|
||||
this._openExpandNode = null
|
||||
this._closeExpandNode = null
|
||||
this._fillExpandNode = null
|
||||
this._lines = []
|
||||
this._generalizationLine = null
|
||||
this._generalizationNode = null
|
||||
this._unVisibleRectRegionNode = null
|
||||
this._isMouseenter = false
|
||||
// 尺寸信息
|
||||
this._rectInfo = {
|
||||
imgContentWidth: 0,
|
||||
@@ -241,13 +245,23 @@ class Node {
|
||||
layout() {
|
||||
// 清除之前的内容
|
||||
this.group.clear()
|
||||
let { width, textContentItemMargin } = this
|
||||
let { width, height, textContentItemMargin } = this
|
||||
let { paddingY } = this.getPaddingVale()
|
||||
paddingY += this.shapePadding.paddingY
|
||||
// 节点形状
|
||||
this.shapeNode = this.shapeInstance.createShape()
|
||||
this.group.add(this.shapeNode)
|
||||
this.updateNodeShape()
|
||||
// 渲染一个隐藏的矩形区域,用来触发展开收起按钮的显示
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
if (!this._unVisibleRectRegionNode) {
|
||||
this._unVisibleRectRegionNode = new Rect()
|
||||
}
|
||||
this._unVisibleRectRegionNode.fill({
|
||||
color: 'transparent'
|
||||
}).size(this.expandBtnSize, height).x(width).y(0)
|
||||
this.group.add(this._unVisibleRectRegionNode)
|
||||
}
|
||||
// 概要节点添加一个带所属节点id的类名
|
||||
if (this.isGeneralization && this.generalizationBelongNode) {
|
||||
this.group.addClass('generalization_' + this.generalizationBelongNode.uid)
|
||||
@@ -373,9 +387,14 @@ class Node {
|
||||
this.mindMap.emit('node_mouseup', this, e)
|
||||
})
|
||||
this.group.on('mouseenter', e => {
|
||||
this._isMouseenter = true
|
||||
// 显示展开收起按钮
|
||||
this.showExpandBtn()
|
||||
this.mindMap.emit('node_mouseenter', this, e)
|
||||
})
|
||||
this.group.on('mouseleave', e => {
|
||||
this._isMouseenter = false
|
||||
this.hideExpandBtn()
|
||||
this.mindMap.emit('node_mouseleave', this, e)
|
||||
})
|
||||
// 双击事件
|
||||
@@ -389,7 +408,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()
|
||||
@@ -423,19 +442,31 @@ class Node {
|
||||
if (!this.group) {
|
||||
return
|
||||
}
|
||||
let { enableNodeTransitionMove, nodeTransitionMoveDuration } =
|
||||
let { enableNodeTransitionMove, nodeTransitionMoveDuration, alwaysShowExpandBtn } =
|
||||
this.mindMap.opt
|
||||
// 需要移除展开收缩按钮
|
||||
if (this._expandBtn && this.nodeData.children.length <= 0) {
|
||||
this.removeExpandBtn()
|
||||
if (alwaysShowExpandBtn) {
|
||||
// 需要移除展开收缩按钮
|
||||
if (this._expandBtn && this.nodeData.children.length <= 0) {
|
||||
this.removeExpandBtn()
|
||||
} else {
|
||||
// 更新展开收起按钮
|
||||
this.renderExpandBtn()
|
||||
}
|
||||
} else {
|
||||
// 更新展开收起按钮
|
||||
this.renderExpandBtn()
|
||||
let { isActive, expand } = this.nodeData.data
|
||||
// 展开状态且非激活状态,且当前鼠标不在它上面,才隐藏
|
||||
if (expand && !isActive && !this._isMouseenter) {
|
||||
this.hideExpandBtn()
|
||||
} else {
|
||||
this.showExpandBtn()
|
||||
}
|
||||
}
|
||||
// 更新概要
|
||||
this.renderGeneralization()
|
||||
// 更新节点位置
|
||||
let t = this.group.transform()
|
||||
// 如果节点位置没有变化,则返回
|
||||
if (this.left === t.translateX && this.top === t.translateY) return
|
||||
if (!isLayout && enableNodeTransitionMove) {
|
||||
this.group
|
||||
.animate(nodeTransitionMoveDuration)
|
||||
@@ -727,9 +758,10 @@ class Node {
|
||||
|
||||
// 获取padding值
|
||||
getPaddingVale() {
|
||||
let { isActive }= this.nodeData.data
|
||||
return {
|
||||
paddingX: this.getStyle('paddingX', true, this.nodeData.data.isActive),
|
||||
paddingY: this.getStyle('paddingY', true, this.nodeData.data.isActive)
|
||||
paddingX: this.getStyle('paddingX', true, isActive),
|
||||
paddingY: this.getStyle('paddingY', true, isActive)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -250,6 +250,7 @@ class Render {
|
||||
|
||||
// 渲染
|
||||
render(callback = () => {}, source) {
|
||||
let t = Date.now()
|
||||
// 如果当前还没有渲染完毕,不再触发渲染
|
||||
if (this.isRendering) {
|
||||
// 等待当前渲染完毕后再进行一次渲染
|
||||
@@ -265,7 +266,6 @@ class Render {
|
||||
// 重新渲染需要清除激活状态
|
||||
if (this.reRender) {
|
||||
this.clearActive()
|
||||
this.layout.clearNodePool()
|
||||
}
|
||||
// 计算布局
|
||||
this.layout.doLayout(root => {
|
||||
@@ -281,13 +281,30 @@ class Render {
|
||||
// 更新根节点
|
||||
this.root = root
|
||||
// 渲染节点
|
||||
this.root.render(() => {
|
||||
const onEnd = () => {
|
||||
this.isRendering = false
|
||||
this.mindMap.emit('node_tree_render_end')
|
||||
callback && callback()
|
||||
if (this.hasWaitRendering) {
|
||||
this.hasWaitRendering = false
|
||||
this.render(callback, source)
|
||||
} else {
|
||||
// 触发一次保存,因为修改了渲染树的数据
|
||||
if (this.mindMap.richText && [CONSTANTS.CHANGE_THEME, CONSTANTS.SET_DATA].includes(source)) {
|
||||
this.mindMap.command.addHistory()
|
||||
}
|
||||
}
|
||||
}
|
||||
let { enableNodeTransitionMove, nodeTransitionMoveDuration } =
|
||||
this.mindMap.opt
|
||||
this.root.render(() => {
|
||||
let dur = Date.now() - t
|
||||
if (enableNodeTransitionMove && dur <= nodeTransitionMoveDuration) {
|
||||
setTimeout(() => {
|
||||
onEnd()
|
||||
}, nodeTransitionMoveDuration - dur);
|
||||
} else {
|
||||
onEnd()
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -353,6 +370,8 @@ class Render {
|
||||
if (!node.nodeData.data.isActive) {
|
||||
node.nodeData.data.isActive = true
|
||||
this.addActiveNode(node)
|
||||
// 激活节点需要显示展开收起按钮
|
||||
node.showExpandBtn()
|
||||
setTimeout(() => {
|
||||
node.updateNodeShape()
|
||||
}, 0)
|
||||
@@ -722,6 +741,12 @@ class Render {
|
||||
this.setNodeData(node, {
|
||||
isActive: active
|
||||
})
|
||||
// 切换激活状态,需要切换展开收起按钮的显隐
|
||||
if (active) {
|
||||
node.showExpandBtn()
|
||||
} else {
|
||||
node.hideExpandBtn()
|
||||
}
|
||||
node.updateNodeShape()
|
||||
}
|
||||
|
||||
@@ -736,14 +761,14 @@ class Render {
|
||||
item.render()
|
||||
})
|
||||
node.renderLine()
|
||||
node.updateExpandBtnNode()
|
||||
// node.updateExpandBtnNode()
|
||||
} else {
|
||||
// 收缩
|
||||
node.children.forEach(item => {
|
||||
item.remove()
|
||||
})
|
||||
node.removeLine()
|
||||
node.updateExpandBtnNode()
|
||||
// node.updateExpandBtnNode()
|
||||
}
|
||||
this.mindMap.render()
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import Quill from 'quill'
|
||||
import 'quill/dist/quill.snow.css'
|
||||
import './css/quill.css'
|
||||
import html2canvas from 'html2canvas'
|
||||
import { Image as SvgImage } from '@svgdotjs/svg.js'
|
||||
import { walk } from './utils'
|
||||
import { walk, getTextFromHtml } from './utils'
|
||||
import { CONSTANTS } from './utils/constant'
|
||||
|
||||
let extended = false
|
||||
@@ -41,8 +39,49 @@ class RichText {
|
||||
this.range = null
|
||||
this.lastRange = null
|
||||
this.node = null
|
||||
this.styleEl = null
|
||||
this.cacheEditingText = ''
|
||||
this.initOpt()
|
||||
this.extendQuill()
|
||||
this.appendCss()
|
||||
|
||||
// 处理数据,转成富文本格式
|
||||
if (this.mindMap.opt.data) {
|
||||
this.mindMap.opt.data = this.handleSetData(this.mindMap.opt.data)
|
||||
}
|
||||
}
|
||||
|
||||
// 插入样式
|
||||
appendCss() {
|
||||
let cssText = `
|
||||
.ql-editor {
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
height: auto;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.ql-container {
|
||||
height: auto;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.ql-container.ql-snow {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.smm-richtext-node-wrap p {
|
||||
font-family: auto;
|
||||
}
|
||||
|
||||
.smm-richtext-node-edit-wrap p {
|
||||
font-family: auto;
|
||||
}
|
||||
`
|
||||
this.styleEl = document.createElement('style')
|
||||
this.styleEl.type = 'text/css'
|
||||
this.styleEl.innerHTML = cssText
|
||||
document.head.appendChild(this.styleEl)
|
||||
}
|
||||
|
||||
// 处理选项参数
|
||||
@@ -96,36 +135,46 @@ class RichText {
|
||||
if (!rect) rect = node._textData.node.node.getBoundingClientRect()
|
||||
this.mindMap.emit('before_show_text_edit')
|
||||
this.mindMap.renderer.textEdit.registerTmpShortcut()
|
||||
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;`
|
||||
document.body.appendChild(this.textEditNode)
|
||||
}
|
||||
// 原始宽高
|
||||
let g = node._textData.node
|
||||
let originWidth = g.attr('data-width')
|
||||
let originHeight = g.attr('data-height')
|
||||
// 缩放值
|
||||
let scaleX = rect.width / originWidth
|
||||
let scaleY = rect.height / originHeight
|
||||
// 内边距
|
||||
const paddingX = 6
|
||||
const paddingY = 4
|
||||
if (!this.textEditNode) {
|
||||
this.textEditNode = document.createElement('div')
|
||||
this.textEditNode.classList.add('smm-richtext-node-edit-wrap')
|
||||
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;box-shadow: 0 0 20px rgba(0,0,0,.5);outline: none; word-break: break-all;padding: ${paddingY}px ${paddingX}px;`
|
||||
this.textEditNode.addEventListener('click', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
document.body.appendChild(this.textEditNode)
|
||||
}
|
||||
// 使用节点的填充色,否则如果节点颜色是白色的话编辑时看不见
|
||||
let bgColor = node.style.merge('fillColor')
|
||||
this.textEditNode.style.marginLeft = `-${paddingX * scaleX}px`
|
||||
this.textEditNode.style.marginTop = `-${paddingY * scaleY}px`
|
||||
this.textEditNode.style.zIndex = this.mindMap.opt.nodeTextEditZIndex
|
||||
this.textEditNode.style.backgroundColor = bgColor === 'transparent' ? '#fff' : bgColor
|
||||
this.textEditNode.style.minWidth = originWidth + 'px'
|
||||
this.textEditNode.style.minWidth = originWidth + paddingX * 2 + 'px'
|
||||
this.textEditNode.style.minHeight = originHeight + 'px'
|
||||
this.textEditNode.style.left =
|
||||
rect.left + (rect.width - originWidth) / 2 + 'px'
|
||||
this.textEditNode.style.top =
|
||||
rect.top + (rect.height - originHeight) / 2 + 'px'
|
||||
this.textEditNode.style.left = rect.left + 'px'
|
||||
this.textEditNode.style.top = rect.top + 'px'
|
||||
this.textEditNode.style.display = 'block'
|
||||
this.textEditNode.style.maxWidth = this.mindMap.opt.textAutoWrapWidth + 'px'
|
||||
this.textEditNode.style.transform = `scale(${rect.width / originWidth}, ${
|
||||
rect.height / originHeight
|
||||
})`
|
||||
this.textEditNode.style.maxWidth = this.mindMap.opt.textAutoWrapWidth + paddingX * 2 + 'px'
|
||||
this.textEditNode.style.transform = `scale(${scaleX}, ${scaleY})`
|
||||
this.textEditNode.style.transformOrigin = 'left top'
|
||||
if (!node.nodeData.data.richText) {
|
||||
// 还不是富文本的情况
|
||||
let text = node.nodeData.data.text.split(/\n/gim).join('<br>')
|
||||
let html = `<p>${text}</p>`
|
||||
this.textEditNode.innerHTML = html
|
||||
this.textEditNode.innerHTML = this.cacheEditingText || html
|
||||
} else {
|
||||
this.textEditNode.innerHTML = node.nodeData.data.text
|
||||
this.textEditNode.innerHTML = this.cacheEditingText || node.nodeData.data.text
|
||||
}
|
||||
this.initQuillEditor()
|
||||
document.querySelector('.ql-editor').style.minHeight = originHeight + 'px'
|
||||
@@ -135,6 +184,7 @@ class RichText {
|
||||
// 如果是非富文本的情况,需要手动应用文本样式
|
||||
this.setTextStyleIfNotRichText(node)
|
||||
}
|
||||
this.cacheEditingText = ''
|
||||
}
|
||||
|
||||
// 如果是非富文本的情况,需要手动应用文本样式
|
||||
@@ -151,14 +201,19 @@ class RichText {
|
||||
this.formatAllText(style)
|
||||
}
|
||||
|
||||
// 获取当前正在编辑的内容
|
||||
getEditText() {
|
||||
let html = this.quill.container.firstChild.innerHTML
|
||||
// 去除最后的空行
|
||||
return html.replace(/<p><br><\/p>$/, '')
|
||||
}
|
||||
|
||||
// 隐藏文本编辑控件,即完成编辑
|
||||
hideEditText(nodes) {
|
||||
if (!this.showTextEdit) {
|
||||
return
|
||||
}
|
||||
let html = this.quill.container.firstChild.innerHTML
|
||||
// 去除最后的空行
|
||||
html = html.replace(/<p><br><\/p>$/, '')
|
||||
let html = this.getEditText()
|
||||
let list = nodes && nodes.length > 0 ? nodes : this.mindMap.renderer.activeNodeList
|
||||
list.forEach(node => {
|
||||
this.mindMap.execCommand('SET_NODE_TEXT', node, html, true)
|
||||
@@ -349,94 +404,40 @@ class RichText {
|
||||
return data
|
||||
}
|
||||
|
||||
// 将svg中嵌入的dom元素转换成图片
|
||||
async _handleSvgDomElements(svg) {
|
||||
svg = svg.clone()
|
||||
let foreignObjectList = svg.find('foreignObject')
|
||||
let task = foreignObjectList.map(async item => {
|
||||
let clone = item.first().node.cloneNode(true)
|
||||
let div = document.createElement('div')
|
||||
div.style.cssText = `position: fixed; left: -999999px;`
|
||||
div.appendChild(clone)
|
||||
this.mindMap.el.appendChild(div)
|
||||
let canvas = await html2canvas(clone, {
|
||||
backgroundColor: null
|
||||
})
|
||||
this.mindMap.el.removeChild(div)
|
||||
let imgNode = new SvgImage()
|
||||
.load(canvas.toDataURL())
|
||||
.size(canvas.width, canvas.height)
|
||||
item.replace(imgNode)
|
||||
})
|
||||
await Promise.all(task)
|
||||
return {
|
||||
svg: svg,
|
||||
svgHTML: svg.svg()
|
||||
// 处理导出为图片
|
||||
async handleExportPng(node) {
|
||||
let el = document.createElement('div')
|
||||
el.style.position = 'absolute'
|
||||
el.style.left = '-9999999px'
|
||||
el.appendChild(node)
|
||||
this.mindMap.el.appendChild(el)
|
||||
// 遍历所有节点,将它们的margin和padding设为0
|
||||
let walk = (root) => {
|
||||
root.style.margin = 0
|
||||
root.style.padding = 0
|
||||
if (root.hasChildNodes()) {
|
||||
Array.from(root.children).forEach((item) => {
|
||||
walk(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 将svg中嵌入的dom元素转换成图片
|
||||
handleSvgDomElements(svg) {
|
||||
return new Promise((resolve, reject) => {
|
||||
svg = svg.clone()
|
||||
let foreignObjectList = svg.find('foreignObject')
|
||||
let index = 0
|
||||
let len = foreignObjectList.length
|
||||
let transform = async () => {
|
||||
this.mindMap.emit('transforming-dom-to-images', index, len)
|
||||
try {
|
||||
let item = foreignObjectList[index++]
|
||||
let parent = item.parent()
|
||||
let clone = item.first().node.cloneNode(true)
|
||||
let div = document.createElement('div')
|
||||
div.style.cssText = `position: fixed; left: -999999px;`
|
||||
div.appendChild(clone)
|
||||
this.mindMap.el.appendChild(div)
|
||||
let canvas = await html2canvas(clone, {
|
||||
backgroundColor: null
|
||||
})
|
||||
// 优先使用原始宽高,因为当设备的window.devicePixelRatio不为1时,html2canvas输出的图片会更大
|
||||
let imgNodeWidth = parent.attr('data-width') || canvas.width
|
||||
let imgNodeHeight = parent.attr('data-height') || canvas.height
|
||||
this.mindMap.el.removeChild(div)
|
||||
let imgNode = new SvgImage()
|
||||
.load(canvas.toDataURL())
|
||||
.size(imgNodeWidth, imgNodeHeight)
|
||||
.x((parent ? parent.attr('data-offsetx') : 0) || 0)
|
||||
item.replace(imgNode)
|
||||
if (index <= len - 1) {
|
||||
setTimeout(() => {
|
||||
transform()
|
||||
}, 0)
|
||||
} else {
|
||||
resolve({
|
||||
svg: svg,
|
||||
svgHTML: svg.svg()
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
}
|
||||
if (len > 0) {
|
||||
transform()
|
||||
} else {
|
||||
resolve(null)
|
||||
}
|
||||
walk(node)
|
||||
let canvas = await html2canvas(el, {
|
||||
backgroundColor: null
|
||||
})
|
||||
this.mindMap.el.removeChild(el)
|
||||
return canvas.toDataURL()
|
||||
}
|
||||
|
||||
// 将所有节点转换成非富文本节点
|
||||
transformAllNodesToNormalNode() {
|
||||
let div = document.createElement('div')
|
||||
walk(
|
||||
this.mindMap.renderer.renderTree,
|
||||
null,
|
||||
node => {
|
||||
if (node.data.richText) {
|
||||
node.data.richText = false
|
||||
div.innerHTML = node.data.text
|
||||
node.data.text = div.textContent
|
||||
node.data.text = getTextFromHtml(node.data.text)
|
||||
// delete node.data.uid
|
||||
}
|
||||
},
|
||||
@@ -451,9 +452,27 @@ class RichText {
|
||||
this.mindMap.render(null, CONSTANTS.TRANSFORM_TO_NORMAL_NODE)
|
||||
}
|
||||
|
||||
// 处理导入数据
|
||||
handleSetData(data) {
|
||||
let walk = (root) => {
|
||||
if (!root.data.richText) {
|
||||
root.data.richText = true
|
||||
root.data.resetRichText = true
|
||||
}
|
||||
if (root.children && root.children.length > 0) {
|
||||
Array.from(root.children).forEach((item) => {
|
||||
walk(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
walk(data)
|
||||
return data
|
||||
}
|
||||
|
||||
// 插件被移除前做的事情
|
||||
beforePluginRemove() {
|
||||
this.transformAllNodesToNormalNode()
|
||||
document.head.removeChild(this.styleEl)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
// })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -109,6 +109,18 @@ class Style {
|
||||
})
|
||||
}
|
||||
|
||||
// 生成内联样式
|
||||
createStyleText() {
|
||||
return `
|
||||
color: ${this.merge('color')};
|
||||
font-family: ${this.merge('fontFamily')};
|
||||
font-size: ${this.merge('fontSize') + 'px'};
|
||||
font-weight: ${this.merge('fontWeight')};
|
||||
font-style: ${this.merge('fontStyle')};
|
||||
text-decoration: ${this.merge('textDecoration')}
|
||||
`
|
||||
}
|
||||
|
||||
// 获取文本样式
|
||||
getTextFontStyle() {
|
||||
return {
|
||||
@@ -120,11 +132,11 @@ class Style {
|
||||
}
|
||||
|
||||
// html文字节点
|
||||
domText(node, fontSizeScale = 1, textLines) {
|
||||
domText(node, fontSizeScale = 1, isMultiLine) {
|
||||
node.style.fontFamily = this.merge('fontFamily')
|
||||
node.style.fontSize = this.merge('fontSize') * fontSizeScale + 'px'
|
||||
node.style.fontWeight = this.merge('fontWeight') || 'normal'
|
||||
node.style.lineHeight = textLines === 1 ? 'normal' : this.merge('lineHeight')
|
||||
node.style.lineHeight = !isMultiLine ? 'normal' : this.merge('lineHeight')
|
||||
node.style.fontStyle = this.merge('fontStyle')
|
||||
}
|
||||
|
||||
|
||||
@@ -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,18 @@ 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)
|
||||
node.style.domText(this.textEditNode, scale, textLines.length)
|
||||
let textLines = (this.cacheEditingText || node.nodeData.data.text).split(/\n/gim)
|
||||
let isMultiLine = node._textData.node.attr('data-ismultiLine') === 'true'
|
||||
node.style.domText(this.textEditNode, scale, isMultiLine)
|
||||
this.textEditNode.style.zIndex = this.mindMap.opt.nodeTextEditZIndex
|
||||
this.textEditNode.innerHTML = textLines.join('<br>')
|
||||
this.textEditNode.style.minWidth = rect.width + 10 + 'px'
|
||||
this.textEditNode.style.minHeight = rect.height + 6 + 'px'
|
||||
@@ -87,12 +120,15 @@ export default class TextEdit {
|
||||
this.textEditNode.style.top = rect.top + 'px'
|
||||
this.textEditNode.style.display = 'block'
|
||||
this.textEditNode.style.maxWidth = this.mindMap.opt.textAutoWrapWidth * scale + 'px'
|
||||
if (textLines.length > 1 && lineHeight !== 1) {
|
||||
this.textEditNode.style.transform = `translateY(${-((lineHeight * fontSize - fontSize) / 2 - 2) * scale}px)`
|
||||
if (isMultiLine && lineHeight !== 1) {
|
||||
this.textEditNode.style.transform = `translateY(${-((lineHeight * fontSize - fontSize) / 2) * scale}px)`
|
||||
}
|
||||
this.showTextEdit = true
|
||||
// 选中文本
|
||||
this.selectNodeText()
|
||||
if (!this.cacheEditingText) {
|
||||
this.selectNodeText()
|
||||
}
|
||||
this.cacheEditingText = ''
|
||||
}
|
||||
|
||||
// 选中文本
|
||||
@@ -104,8 +140,14 @@ export default class TextEdit {
|
||||
selection.addRange(range)
|
||||
}
|
||||
|
||||
// 获取当前正在编辑的内容
|
||||
getEditText() {
|
||||
return getStrWithBrFromHtml(this.textEditNode.innerHTML)
|
||||
}
|
||||
|
||||
// 隐藏文本编辑框
|
||||
hideEditTextBox() {
|
||||
this.currentNode = null
|
||||
if (this.mindMap.richText) {
|
||||
return this.mindMap.richText.hideEditText()
|
||||
}
|
||||
@@ -113,7 +155,7 @@ export default class TextEdit {
|
||||
return
|
||||
}
|
||||
this.renderer.activeNodeList.forEach(node => {
|
||||
let str = getStrWithBrFromHtml(this.textEditNode.innerHTML)
|
||||
let str = this.getEditText()
|
||||
this.mindMap.execCommand('SET_NODE_TEXT', node, str)
|
||||
if (node.isGeneralization) {
|
||||
// 概要节点
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import Node from '../Node'
|
||||
import { CONSTANTS, initRootNodePositionMap } from '../utils/constant'
|
||||
import Lru from '../utils/Lru'
|
||||
|
||||
// 布局基类
|
||||
class Base {
|
||||
@@ -13,8 +14,7 @@ class Base {
|
||||
this.draw = this.mindMap.draw
|
||||
// 根节点
|
||||
this.root = null
|
||||
// 保存所有uid和节点,用于复用
|
||||
this.nodePool = {}
|
||||
this.lru = new Lru(this.mindMap.opt.maxNodeCacheCount)
|
||||
}
|
||||
|
||||
// 计算节点位置
|
||||
@@ -40,16 +40,7 @@ class Base {
|
||||
// 记录本次渲染时的节点
|
||||
this.renderer.nodeCache[uid] = node
|
||||
// 记录所有渲染时的节点
|
||||
this.nodePool[uid] = node
|
||||
// 如果总缓存数量达到1000,直接清空
|
||||
if (Object.keys(this.nodePool).length > 1000) {
|
||||
this.clearNodePool()
|
||||
}
|
||||
}
|
||||
|
||||
// 清空节点存储池
|
||||
clearNodePool() {
|
||||
this.nodePool = {}
|
||||
this.lru.add(uid, node)
|
||||
}
|
||||
|
||||
// 检查当前来源是否需要重新计算节点大小
|
||||
@@ -72,9 +63,9 @@ class Base {
|
||||
newNode.getSize()
|
||||
newNode.needLayout = true
|
||||
}
|
||||
} else if (this.nodePool[data.data.uid] && !this.renderer.reRender) {
|
||||
} else if (this.lru.has(data.data.uid) && !this.renderer.reRender) {
|
||||
// 数据上没有保存节点引用,但是通过uid找到了缓存的节点,也可以复用
|
||||
newNode = this.nodePool[data.data.uid]
|
||||
newNode = this.lru.get(data.data.uid)
|
||||
// 保存该节点上一次的数据
|
||||
let lastData = JSON.stringify(newNode.nodeData.data)
|
||||
newNode.reset()
|
||||
|
||||
@@ -199,6 +199,9 @@ class CatalogOrganization extends Base {
|
||||
return []
|
||||
}
|
||||
let { left, top, width, height, expandBtnSize } = node
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
let len = node.children.length
|
||||
let marginX = this.getMarginX(node.layerIndex + 1)
|
||||
if (node.isRoot) {
|
||||
|
||||
@@ -8,6 +8,8 @@ class Fishbone extends Base {
|
||||
// 构造函数
|
||||
constructor(opt = {}) {
|
||||
super(opt)
|
||||
this.indent = 0.3
|
||||
this.childIndent = 0.5
|
||||
}
|
||||
|
||||
// 布局
|
||||
@@ -229,6 +231,9 @@ class Fishbone extends Base {
|
||||
return []
|
||||
}
|
||||
let { top, height, expandBtnSize } = node
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
let len = node.children.length
|
||||
if (node.isRoot) {
|
||||
// 当前节点是根节点
|
||||
@@ -239,21 +244,21 @@ class Fishbone extends Base {
|
||||
maxx = item.left
|
||||
}
|
||||
// 水平线段到二级节点的连线
|
||||
let nodeLineX = item.left + item.width * 0.3
|
||||
let offset = item.height + node.height / 2
|
||||
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 + offset} L ${nodeLineX},${
|
||||
item.top
|
||||
`M ${nodeLineX - offsetX},${item.top + item.height + offset} L ${item.left},${
|
||||
item.top + item.height
|
||||
}`
|
||||
)
|
||||
} else {
|
||||
line.plot(
|
||||
`M ${nodeLineX - offsetX},${
|
||||
item.top + item.height - offset
|
||||
} L ${nodeLineX},${item.top + item.height}`
|
||||
item.top - offset
|
||||
} L ${nodeLineX},${item.top}`
|
||||
)
|
||||
}
|
||||
node.style.line(line)
|
||||
@@ -277,7 +282,7 @@ class Fishbone extends Base {
|
||||
let maxy = -Infinity
|
||||
let miny = Infinity
|
||||
let maxx = -Infinity
|
||||
let x = node.left + node.width * 0.3
|
||||
let x = node.left + node.width * this.indent
|
||||
node.children.forEach((item, index) => {
|
||||
if (item.left > maxx) {
|
||||
maxx = item.left
|
||||
@@ -300,7 +305,7 @@ class Fishbone extends Base {
|
||||
if (len >= 0) {
|
||||
let line = this.draw.path()
|
||||
expandBtnSize = len > 0 ? expandBtnSize : 0
|
||||
let lineLength = maxx - node.left - node.width * 0.3
|
||||
let lineLength = maxx - node.left - node.width * this.indent
|
||||
lineLength = Math.max(lineLength, 0)
|
||||
let params = {
|
||||
node,
|
||||
|
||||
@@ -159,6 +159,9 @@ class LogicalStructure extends Base {
|
||||
return []
|
||||
}
|
||||
let { left, top, width, height, expandBtnSize } = node
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
let marginX = this.getMarginX(node.layerIndex + 1)
|
||||
let s1 = (marginX - expandBtnSize) * 0.6
|
||||
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
|
||||
@@ -188,6 +191,9 @@ class LogicalStructure extends Base {
|
||||
return []
|
||||
}
|
||||
let { left, top, width, height, expandBtnSize } = node
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
|
||||
node.children.forEach((item, index) => {
|
||||
let x1 =
|
||||
@@ -213,6 +219,9 @@ class LogicalStructure extends Base {
|
||||
return []
|
||||
}
|
||||
let { left, top, width, height, expandBtnSize } = node
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
|
||||
node.children.forEach((item, index) => {
|
||||
let x1 =
|
||||
@@ -245,9 +254,15 @@ class LogicalStructure extends Base {
|
||||
let nodeUseLineStyleOffset = this.mindMap.themeConfig.nodeUseLineStyle
|
||||
? height / 2
|
||||
: 0
|
||||
// 位置没有变化则返回
|
||||
let _x = width
|
||||
let _y = height / 2 + nodeUseLineStyleOffset
|
||||
if (_x === translateX && _y === translateY) {
|
||||
return
|
||||
}
|
||||
btn.translate(
|
||||
width - translateX,
|
||||
height / 2 - translateY + nodeUseLineStyleOffset
|
||||
_x - translateX,
|
||||
_y - translateY
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -198,6 +198,9 @@ class MindMap extends Base {
|
||||
return []
|
||||
}
|
||||
let { left, top, width, height, expandBtnSize } = node
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
let marginX = this.getMarginX(node.layerIndex + 1)
|
||||
let s1 = (marginX - expandBtnSize) * 0.6
|
||||
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
|
||||
@@ -235,6 +238,9 @@ class MindMap extends Base {
|
||||
return []
|
||||
}
|
||||
let { left, top, width, height, expandBtnSize } = node
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
|
||||
node.children.forEach((item, index) => {
|
||||
let x1 =
|
||||
@@ -269,6 +275,9 @@ class MindMap extends Base {
|
||||
return []
|
||||
}
|
||||
let { left, top, width, height, expandBtnSize } = node
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
|
||||
node.children.forEach((item, index) => {
|
||||
let x1 =
|
||||
@@ -276,7 +285,7 @@ class MindMap extends Base {
|
||||
? left + width / 2
|
||||
: item.dir === 'left'
|
||||
? left - expandBtnSize
|
||||
: left + width + 20
|
||||
: left + width + expandBtnSize
|
||||
let y1 = top + height / 2
|
||||
let x2 = item.dir === 'left' ? item.left + item.width : item.left
|
||||
let y2 = item.top + item.height / 2
|
||||
@@ -310,8 +319,14 @@ class MindMap extends Base {
|
||||
let nodeUseLineStyleOffset = this.mindMap.themeConfig.nodeUseLineStyle
|
||||
? height / 2
|
||||
: 0
|
||||
let x = (node.dir === 'left' ? 0 - expandBtnSize : width) - translateX
|
||||
let y = height / 2 - translateY + nodeUseLineStyleOffset
|
||||
// 位置没有变化则返回
|
||||
let _x = (node.dir === 'left' ? 0 - expandBtnSize : width)
|
||||
let _y = height / 2 + nodeUseLineStyleOffset
|
||||
if (_x === translateX && _y === translateY) {
|
||||
return
|
||||
}
|
||||
let x = _x - translateX
|
||||
let y = _y - translateY
|
||||
btn.translate(x, y)
|
||||
}
|
||||
|
||||
|
||||
@@ -179,6 +179,9 @@ class OrganizationStructure extends Base {
|
||||
return []
|
||||
}
|
||||
let { left, top, width, height, expandBtnSize, isRoot } = node
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
let x1 = left + width / 2
|
||||
let y1 = top + height
|
||||
let marginX = this.getMarginX(node.layerIndex + 1)
|
||||
|
||||
@@ -238,6 +238,9 @@ class Timeline extends Base {
|
||||
return []
|
||||
}
|
||||
let { left, top, width, height, expandBtnSize } = node
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
let len = node.children.length
|
||||
if (node.isRoot) {
|
||||
// 当前节点是根节点
|
||||
|
||||
@@ -47,7 +47,7 @@ export default {
|
||||
computedLeftTopValue({ layerIndex, node, ctx }) {
|
||||
if (layerIndex >= 1 && node.children) {
|
||||
// 遍历三级及以下节点的子节点
|
||||
let startLeft = node.left + node.width * 0.5
|
||||
let startLeft = node.left + node.width * ctx.childIndent
|
||||
let totalTop =
|
||||
node.top +
|
||||
node.height +
|
||||
@@ -80,22 +80,21 @@ export default {
|
||||
// 将二级节点的子节点移到上方
|
||||
if (parent && parent.isRoot) {
|
||||
// 遍历二级节点的子节点
|
||||
let totalHeight = 0
|
||||
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
|
||||
let offsetLeft =
|
||||
(nodeTotalHeight + totalHeight) / Math.tan(degToRad(ctx.mindMap.opt.fishboneDeg))
|
||||
item.left += offsetLeft
|
||||
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: offsetLeft
|
||||
left: item.left - _left
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -137,7 +136,7 @@ export default {
|
||||
computedLeftTopValue({ layerIndex, node, ctx }) {
|
||||
if (layerIndex === 1 && node.children) {
|
||||
// 遍历二级节点的子节点
|
||||
let startLeft = node.left + node.width * 0.5
|
||||
let startLeft = node.left + node.width * ctx.childIndent
|
||||
let totalTop =
|
||||
node.top +
|
||||
node.height +
|
||||
@@ -155,7 +154,7 @@ export default {
|
||||
}
|
||||
if (layerIndex > 1 && node.children) {
|
||||
// 遍历三级及以下节点的子节点
|
||||
let startLeft = node.left + node.width * 0.5
|
||||
let startLeft = node.left + node.width * ctx.childIndent
|
||||
let totalTop =
|
||||
node.top -
|
||||
(ctx.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0)
|
||||
@@ -187,7 +186,7 @@ export default {
|
||||
if (parent && parent.isRoot) {
|
||||
// 遍历二级节点的子节点
|
||||
let totalHeight = 0
|
||||
let totalHeight2 = 0
|
||||
let totalHeight2 = node.expandBtnSize
|
||||
node.children.forEach(item => {
|
||||
// 调整top
|
||||
let hasChildren = ctx.getNodeActChildrenLength(item) > 0
|
||||
@@ -199,17 +198,16 @@ export default {
|
||||
(hasChildren ? item.expandBtnSize : 0)
|
||||
: 0
|
||||
let _top = totalHeight + offset
|
||||
let _left = item.left
|
||||
item.top += _top
|
||||
// 调整left
|
||||
let offsetLeft =
|
||||
(totalHeight2 + nodeTotalHeight) / Math.tan(degToRad(ctx.mindMap.opt.fishboneDeg))
|
||||
item.left += offsetLeft
|
||||
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: offsetLeft
|
||||
left: item.left - _left
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -279,9 +279,9 @@ export const nodeIconList = [
|
||||
]
|
||||
|
||||
// 获取nodeIconList icon内容
|
||||
const getNodeIconListIcon = name => {
|
||||
const getNodeIconListIcon = (name, extendIconList = []) => {
|
||||
let arr = name.split('_')
|
||||
let typeData = nodeIconList.find(item => {
|
||||
let typeData = [...nodeIconList, ...extendIconList].find(item => {
|
||||
return item.type === arr[0]
|
||||
})
|
||||
return typeData.list.find(item => {
|
||||
|
||||
57
simple-mind-map/src/themes/autumn.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import defaultTheme from './default'
|
||||
import merge from 'deepmerge'
|
||||
|
||||
// 秋天
|
||||
export default merge(defaultTheme, {
|
||||
// 背景颜色
|
||||
backgroundColor: '#fff2df',
|
||||
// 连线的颜色
|
||||
lineColor: '#b0bc47',
|
||||
lineWidth: 3,
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 3,
|
||||
// 概要连线的颜色
|
||||
generalizationLineColor: '#b0bc47',
|
||||
// 根节点样式
|
||||
root: {
|
||||
fillColor: '#e68112',
|
||||
color: '#fff',
|
||||
borderColor: '#e68112',
|
||||
borderWidth: 0,
|
||||
fontSize: 24,
|
||||
active: {
|
||||
borderColor: '#b0bc47',
|
||||
borderWidth: 3
|
||||
}
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
fillColor: '#ffd683',
|
||||
color: '#8c5416',
|
||||
borderColor: '#b0bc47',
|
||||
borderWidth: 2,
|
||||
fontSize: 18,
|
||||
active: {
|
||||
borderColor: '#e68112'
|
||||
}
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
fontSize: 14,
|
||||
color: '#8c5416',
|
||||
active: {
|
||||
borderColor: '#b0bc47'
|
||||
}
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
fontSize: 14,
|
||||
fillColor: '#ffd683',
|
||||
borderColor: '#b0bc47',
|
||||
borderWidth: 2,
|
||||
color: '#8c5416',
|
||||
active: {
|
||||
borderColor: '#e68112'
|
||||
}
|
||||
}
|
||||
})
|
||||
57
simple-mind-map/src/themes/avocado.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import defaultTheme from './default'
|
||||
import merge from 'deepmerge'
|
||||
|
||||
// 牛油果
|
||||
export default merge(defaultTheme, {
|
||||
// 背景颜色
|
||||
backgroundColor: '#e6f1de',
|
||||
// 连线的颜色
|
||||
lineColor: '#f5ffad',
|
||||
lineWidth: 4,
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 3,
|
||||
// 概要连线的颜色
|
||||
generalizationLineColor: '#749336',
|
||||
// 根节点样式
|
||||
root: {
|
||||
fillColor: '#94c143',
|
||||
color: '#fff',
|
||||
borderColor: '#94c143',
|
||||
borderWidth: 0,
|
||||
fontSize: 24,
|
||||
active: {
|
||||
borderColor: '#749336',
|
||||
borderWidth: 3
|
||||
}
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
fillColor: '#cee498',
|
||||
color: '#749336',
|
||||
borderColor: '#aec668',
|
||||
borderWidth: 2,
|
||||
fontSize: 18,
|
||||
active: {
|
||||
borderColor: '#749336'
|
||||
}
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
fontSize: 14,
|
||||
color: '#749336',
|
||||
active: {
|
||||
borderColor: '#749336'
|
||||
}
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
fontSize: 14,
|
||||
fillColor: '#cee498',
|
||||
borderColor: '#aec668',
|
||||
borderWidth: 2,
|
||||
color: '#749336',
|
||||
active: {
|
||||
borderColor: '#749336'
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -34,6 +34,14 @@ export default {
|
||||
associativeLineActiveWidth: 8,
|
||||
// 关联线激活状态的颜色
|
||||
associativeLineActiveColor: 'rgba(2, 167, 240, 1)',
|
||||
// 关联线文字颜色
|
||||
associativeLineTextColor: 'rgb(51, 51, 51)',
|
||||
// 关联线文字大小
|
||||
associativeLineTextFontSize: 14,
|
||||
// 关联线文字行高
|
||||
associativeLineTextLineHeight: 1.2,
|
||||
// 关联线文字字体
|
||||
associativeLineTextFontFamily: '微软雅黑, Microsoft YaHei',
|
||||
// 背景颜色
|
||||
backgroundColor: '#fafafa',
|
||||
// 背景图片
|
||||
@@ -148,4 +156,38 @@ export const supportActiveStyle = [
|
||||
'borderRadius'
|
||||
]
|
||||
|
||||
// 检测主题配置是否是节点大小无关的
|
||||
const nodeSizeIndependenceList = [
|
||||
'lineWidth',
|
||||
'lineColor',
|
||||
'lineDasharray',
|
||||
'lineStyle',
|
||||
'generalizationLineWidth',
|
||||
'generalizationLineColor',
|
||||
'associativeLineWidth',
|
||||
'associativeLineColor',
|
||||
'associativeLineActiveWidth',
|
||||
'associativeLineActiveColor',
|
||||
'associativeLineTextColor',
|
||||
'associativeLineTextFontSize',
|
||||
'associativeLineTextLineHeight',
|
||||
'associativeLineTextFontFamily',
|
||||
'backgroundColor',
|
||||
'backgroundImage',
|
||||
'backgroundRepeat',
|
||||
'backgroundPosition',
|
||||
'backgroundSize'
|
||||
]
|
||||
export const checkIsNodeSizeIndependenceConfig = (config) => {
|
||||
let keys = Object.keys(config)
|
||||
for(let i = 0; i < keys.length; i++) {
|
||||
if (!nodeSizeIndependenceList.find((item) => {
|
||||
return item === keys[i]
|
||||
})) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export const lineStyleProps = ['lineColor', 'lineDasharray', 'lineWidth']
|
||||
|
||||
@@ -27,6 +27,9 @@ import redSpirit from './redSpirit'
|
||||
import blackHumour from './blackHumour'
|
||||
import lateNightOffice from './lateNightOffice'
|
||||
import blackGold from './blackGold'
|
||||
import avocado from './avocado'
|
||||
import autumn from './autumn'
|
||||
import orangeJuice from './orangeJuice'
|
||||
|
||||
export default {
|
||||
default: defaultTheme,
|
||||
@@ -57,5 +60,8 @@ export default {
|
||||
redSpirit,
|
||||
blackHumour,
|
||||
lateNightOffice,
|
||||
blackGold
|
||||
blackGold,
|
||||
avocado,
|
||||
autumn,
|
||||
orangeJuice
|
||||
}
|
||||
|
||||
57
simple-mind-map/src/themes/orangeJuice.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import defaultTheme from './default'
|
||||
import merge from 'deepmerge'
|
||||
|
||||
// 橙汁
|
||||
export default merge(defaultTheme, {
|
||||
// 背景颜色
|
||||
backgroundColor: '#070616',
|
||||
// 连线的颜色
|
||||
lineColor: '#fff',
|
||||
lineWidth: 3,
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 3,
|
||||
// 概要连线的颜色
|
||||
generalizationLineColor: '#fff',
|
||||
// 根节点样式
|
||||
root: {
|
||||
fillColor: '#ff6811',
|
||||
color: '#110501',
|
||||
borderColor: '#ff6811',
|
||||
borderWidth: 0,
|
||||
fontSize: 24,
|
||||
active: {
|
||||
borderColor: '#a9a4a9',
|
||||
borderWidth: 3
|
||||
}
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
fillColor: '#070616',
|
||||
color: '#a9a4a9',
|
||||
borderColor: '#ff6811',
|
||||
borderWidth: 2,
|
||||
fontSize: 18,
|
||||
active: {
|
||||
borderColor: '#110501'
|
||||
}
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
fontSize: 14,
|
||||
color: '#a9a4a9',
|
||||
active: {
|
||||
borderColor: '#ff6811'
|
||||
}
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
fontSize: 14,
|
||||
fillColor: '',
|
||||
borderColor: '#ff6811',
|
||||
borderWidth: 2,
|
||||
color: '#a9a4a9',
|
||||
active: {
|
||||
borderColor: '#110501'
|
||||
}
|
||||
}
|
||||
})
|
||||
39
simple-mind-map/src/utils/Lru.js
Normal file
@@ -0,0 +1,39 @@
|
||||
// LRU缓存类
|
||||
export default class CRU {
|
||||
constructor(max) {
|
||||
this.max = max || 1000
|
||||
this.size = 0
|
||||
this.pool = new Map()
|
||||
}
|
||||
|
||||
add(key, value) {
|
||||
// 如果该key是否已经存在,则先删除
|
||||
this.delete(key)
|
||||
this.pool.set(key, value)
|
||||
this.size++
|
||||
// 如果数量超出最大值,则删除最早的
|
||||
if (this.size > this.max) {
|
||||
let keys = this.pool.keys()
|
||||
let last = keys.next()
|
||||
this.delete(last.value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
delete(key) {
|
||||
if (this.pool.has(key)) {
|
||||
this.pool.delete(key)
|
||||
this.size--
|
||||
}
|
||||
}
|
||||
|
||||
has(key) {
|
||||
return this.pool.has(key)
|
||||
}
|
||||
|
||||
get(key) {
|
||||
if (this.pool.has(key)) {
|
||||
return this.pool.get(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
241
simple-mind-map/src/utils/associativeLineControls.js
Normal file
@@ -0,0 +1,241 @@
|
||||
import {
|
||||
getAssociativeLineTargetIndex,
|
||||
joinCubicBezierPath,
|
||||
computeNodePoints,
|
||||
getDefaultControlPointOffsets
|
||||
} from './associativeLineUtils'
|
||||
|
||||
// 创建控制点、连线节点
|
||||
function createControlNodes() {
|
||||
let { associativeLineActiveColor } = this.mindMap.themeConfig
|
||||
// 连线
|
||||
this.controlLine1 = this.draw
|
||||
.line()
|
||||
.stroke({ color: associativeLineActiveColor, width: 2 })
|
||||
this.controlLine2 = this.draw
|
||||
.line()
|
||||
.stroke({ color: associativeLineActiveColor, width: 2 })
|
||||
// 控制点
|
||||
this.controlPoint1 = this.createOneControlNode('controlPoint1')
|
||||
this.controlPoint2 = this.createOneControlNode('controlPoint2')
|
||||
}
|
||||
|
||||
// 创建控制点
|
||||
function createOneControlNode(pointKey) {
|
||||
let { associativeLineActiveColor } = this.mindMap.themeConfig
|
||||
return this.draw
|
||||
.circle(this.controlPointDiameter)
|
||||
.stroke({ color: associativeLineActiveColor })
|
||||
.fill({ color: '#fff' })
|
||||
.click(e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
.mousedown(e => {
|
||||
this.onControlPointMousedown(e, pointKey)
|
||||
})
|
||||
}
|
||||
|
||||
// 控制点的鼠标按下事件
|
||||
function onControlPointMousedown(e, pointKey) {
|
||||
e.stopPropagation()
|
||||
this.isControlPointMousedown = true
|
||||
this.mousedownControlPointKey = pointKey
|
||||
}
|
||||
|
||||
// 控制点的鼠标移动事件
|
||||
function onControlPointMousemove(e) {
|
||||
if (
|
||||
!this.isControlPointMousedown ||
|
||||
!this.mousedownControlPointKey ||
|
||||
!this[this.mousedownControlPointKey]
|
||||
)
|
||||
return
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
let radius = this.controlPointDiameter / 2
|
||||
// 转换鼠标当前的位置
|
||||
let { x, y } = this.getTransformedEventPos(e)
|
||||
this.controlPointMousemoveState.pos = {
|
||||
x,
|
||||
y
|
||||
}
|
||||
// 更新当前拖拽的控制点的位置
|
||||
this[this.mousedownControlPointKey].x(x - radius).y(y - radius)
|
||||
let [path, clickPath, text, node, toNode] = this.activeLine
|
||||
let [startPoint, endPoint] = computeNodePoints(node, toNode)
|
||||
this.controlPointMousemoveState.startPoint = startPoint
|
||||
this.controlPointMousemoveState.endPoint = endPoint
|
||||
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
|
||||
this.controlPointMousemoveState.targetIndex = targetIndex
|
||||
let offsets = []
|
||||
let associativeLineTargetControlOffsets =
|
||||
node.nodeData.data.associativeLineTargetControlOffsets
|
||||
if (!associativeLineTargetControlOffsets) {
|
||||
// 兼容0.4.5版本,没有associativeLineTargetControlOffsets的情况
|
||||
offsets = getDefaultControlPointOffsets(startPoint, endPoint)
|
||||
} else {
|
||||
offsets = associativeLineTargetControlOffsets[targetIndex]
|
||||
}
|
||||
let point1 = null
|
||||
let point2 = null
|
||||
// 拖拽的是控制点1
|
||||
if (this.mousedownControlPointKey === 'controlPoint1') {
|
||||
point1 = {
|
||||
x,
|
||||
y
|
||||
}
|
||||
point2 = {
|
||||
x: endPoint.x + offsets[1].x,
|
||||
y: endPoint.y + offsets[1].y
|
||||
}
|
||||
// 更新控制点1的连线
|
||||
this.controlLine1.plot(startPoint.x, startPoint.y, point1.x, point1.y)
|
||||
} else {
|
||||
// 拖拽的是控制点2
|
||||
point1 = {
|
||||
x: startPoint.x + offsets[0].x,
|
||||
y: startPoint.y + offsets[0].y
|
||||
}
|
||||
point2 = {
|
||||
x,
|
||||
y
|
||||
}
|
||||
// 更新控制点2的连线
|
||||
this.controlLine2.plot(endPoint.x, endPoint.y, point2.x, point2.y)
|
||||
}
|
||||
// 更新关联线
|
||||
let pathStr = joinCubicBezierPath(startPoint, endPoint, point1, point2)
|
||||
path.plot(pathStr)
|
||||
clickPath.plot(pathStr)
|
||||
this.updateTextPos(path, text)
|
||||
this.updateTextEditBoxPos(text)
|
||||
}
|
||||
|
||||
// 控制点的鼠标移动事件
|
||||
function onControlPointMouseup(e) {
|
||||
if (!this.isControlPointMousedown) return
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
let { pos, startPoint, endPoint, targetIndex } =
|
||||
this.controlPointMousemoveState
|
||||
let [, , , node] = this.activeLine
|
||||
let offsetList = []
|
||||
let associativeLineTargetControlOffsets =
|
||||
node.nodeData.data.associativeLineTargetControlOffsets
|
||||
if (!associativeLineTargetControlOffsets) {
|
||||
// 兼容0.4.5版本,没有associativeLineTargetControlOffsets的情况
|
||||
offsetList[targetIndex] = getDefaultControlPointOffsets(
|
||||
startPoint,
|
||||
endPoint
|
||||
)
|
||||
} else {
|
||||
offsetList = associativeLineTargetControlOffsets
|
||||
}
|
||||
let offset1 = null
|
||||
let offset2 = null
|
||||
if (this.mousedownControlPointKey === 'controlPoint1') {
|
||||
// 更新控制点1数据
|
||||
offset1 = {
|
||||
x: pos.x - startPoint.x,
|
||||
y: pos.y - startPoint.y
|
||||
}
|
||||
offset2 = offsetList[targetIndex][1]
|
||||
} else {
|
||||
// 更新控制点2数据
|
||||
offset1 = offsetList[targetIndex][0]
|
||||
offset2 = {
|
||||
x: pos.x - endPoint.x,
|
||||
y: pos.y - endPoint.y
|
||||
}
|
||||
}
|
||||
offsetList[targetIndex] = [offset1, offset2]
|
||||
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
||||
associativeLineTargetControlOffsets: offsetList
|
||||
})
|
||||
// 这里要加个setTimeout0是因为draw_click事件比mouseup事件触发的晚,所以重置isControlPointMousedown需要等draw_click事件触发完以后
|
||||
setTimeout(() => {
|
||||
this.resetControlPoint()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
// 复位控制点移动
|
||||
function resetControlPoint() {
|
||||
this.isControlPointMousedown = false
|
||||
this.mousedownControlPointKey = ''
|
||||
this.controlPointMousemoveState = {
|
||||
pos: null,
|
||||
startPoint: null,
|
||||
endPoint: null,
|
||||
targetIndex: ''
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染控制点
|
||||
function renderControls(startPoint, endPoint, point1, point2) {
|
||||
if (!this.controlLine1) {
|
||||
this.createControlNodes()
|
||||
}
|
||||
let radius = this.controlPointDiameter / 2
|
||||
// 控制点和起终点的连线
|
||||
this.controlLine1.plot(startPoint.x, startPoint.y, point1.x, point1.y)
|
||||
this.controlLine2.plot(endPoint.x, endPoint.y, point2.x, point2.y)
|
||||
// 控制点
|
||||
this.controlPoint1.x(point1.x - radius).y(point1.y - radius)
|
||||
this.controlPoint2.x(point2.x - radius).y(point2.y - radius)
|
||||
}
|
||||
|
||||
// 删除控制点
|
||||
function removeControls() {
|
||||
if (!this.controlLine1) return
|
||||
;[
|
||||
this.controlLine1,
|
||||
this.controlLine2,
|
||||
this.controlPoint1,
|
||||
this.controlPoint2
|
||||
].forEach(item => {
|
||||
item.remove()
|
||||
})
|
||||
this.controlLine1 = null
|
||||
this.controlLine2 = null
|
||||
this.controlPoint1 = null
|
||||
this.controlPoint2 = null
|
||||
}
|
||||
|
||||
// 隐藏控制点
|
||||
function hideControls() {
|
||||
if (!this.controlLine1) return
|
||||
;[
|
||||
this.controlLine1,
|
||||
this.controlLine2,
|
||||
this.controlPoint1,
|
||||
this.controlPoint2
|
||||
].forEach(item => {
|
||||
item.hide()
|
||||
})
|
||||
}
|
||||
|
||||
// 显示控制点
|
||||
function showControls() {
|
||||
if (!this.controlLine1) return
|
||||
;[
|
||||
this.controlLine1,
|
||||
this.controlLine2,
|
||||
this.controlPoint1,
|
||||
this.controlPoint2
|
||||
].forEach(item => {
|
||||
item.show()
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
createControlNodes,
|
||||
createOneControlNode,
|
||||
onControlPointMousedown,
|
||||
onControlPointMousemove,
|
||||
onControlPointMouseup,
|
||||
resetControlPoint,
|
||||
renderControls,
|
||||
removeControls,
|
||||
hideControls,
|
||||
showControls
|
||||
}
|
||||
167
simple-mind-map/src/utils/associativeLineText.js
Normal file
@@ -0,0 +1,167 @@
|
||||
import { Text } from '@svgdotjs/svg.js'
|
||||
import { getStrWithBrFromHtml } from './index'
|
||||
|
||||
// 创建文字节点
|
||||
function createText(data) {
|
||||
let g = this.draw.group()
|
||||
const setActive = () => {
|
||||
if (
|
||||
!this.activeLine ||
|
||||
this.activeLine[3] !== data.node ||
|
||||
this.activeLine[4] !== data.toNode
|
||||
) {
|
||||
this.setActiveLine({
|
||||
...data,
|
||||
text: g
|
||||
})
|
||||
}
|
||||
}
|
||||
g.click(e => {
|
||||
e.stopPropagation()
|
||||
setActive()
|
||||
})
|
||||
g.on('dblclick', e => {
|
||||
e.stopPropagation()
|
||||
setActive()
|
||||
if (!this.activeLine) return
|
||||
this.showEditTextBox(g)
|
||||
})
|
||||
return g
|
||||
}
|
||||
|
||||
// 显示文本编辑框
|
||||
function showEditTextBox(g) {
|
||||
this.mindMap.emit('before_show_text_edit')
|
||||
// 注册回车快捷键
|
||||
this.mindMap.keyCommand.addShortcut('Enter', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
|
||||
if (!this.textEditNode) {
|
||||
this.textEditNode = document.createElement('div')
|
||||
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: 3px 5px;margin-left: -5px;margin-top: -3px;outline: none; word-break: break-all;`
|
||||
this.textEditNode.setAttribute('contenteditable', true)
|
||||
this.textEditNode.addEventListener('keyup', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
this.textEditNode.addEventListener('click', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
document.body.appendChild(this.textEditNode)
|
||||
}
|
||||
let {
|
||||
associativeLineTextFontSize,
|
||||
associativeLineTextFontFamily,
|
||||
associativeLineTextLineHeight
|
||||
} = this.mindMap.themeConfig
|
||||
let scale = this.mindMap.view.scale
|
||||
let [, , , node, toNode] = this.activeLine
|
||||
let textLines = (
|
||||
this.getText(node, toNode) || this.mindMap.opt.defaultAssociativeLineText
|
||||
).split(/\n/gim)
|
||||
this.textEditNode.style.fontFamily = associativeLineTextFontFamily
|
||||
this.textEditNode.style.fontSize = associativeLineTextFontSize * scale + 'px'
|
||||
this.textEditNode.style.lineHeight = textLines.length > 1 ? associativeLineTextLineHeight : 'normal'
|
||||
this.textEditNode.style.zIndex = this.mindMap.opt.nodeTextEditZIndex
|
||||
this.textEditNode.innerHTML = textLines.join('<br>')
|
||||
this.textEditNode.style.display = 'block'
|
||||
this.updateTextEditBoxPos(g)
|
||||
this.showTextEdit = true
|
||||
}
|
||||
|
||||
// 处理画布缩放
|
||||
function onScale() {
|
||||
this.hideEditTextBox()
|
||||
}
|
||||
|
||||
// 更新文本编辑框位置
|
||||
function updateTextEditBoxPos(g) {
|
||||
let rect = g.node.getBoundingClientRect()
|
||||
this.textEditNode.style.minWidth = rect.width + 10 + 'px'
|
||||
this.textEditNode.style.minHeight = rect.height + 6 + 'px'
|
||||
this.textEditNode.style.left = rect.left + 'px'
|
||||
this.textEditNode.style.top = rect.top + 'px'
|
||||
}
|
||||
|
||||
// 隐藏文本编辑框
|
||||
function hideEditTextBox() {
|
||||
if (!this.showTextEdit) {
|
||||
return
|
||||
}
|
||||
let [path, , text, node, toNode] = this.activeLine
|
||||
let str = getStrWithBrFromHtml(this.textEditNode.innerHTML)
|
||||
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
||||
associativeLineText: {
|
||||
...(node.nodeData.data.associativeLineText || {}),
|
||||
[toNode.nodeData.data.id]: str
|
||||
}
|
||||
})
|
||||
this.textEditNode.style.display = 'none'
|
||||
this.textEditNode.innerHTML = ''
|
||||
this.showTextEdit = false
|
||||
this.renderText(str, path, text)
|
||||
this.mindMap.emit('hide_text_edit')
|
||||
}
|
||||
|
||||
// 获取某根关联线的文字
|
||||
function getText(node, toNode) {
|
||||
let obj = node.nodeData.data.associativeLineText
|
||||
if (!obj) {
|
||||
return ''
|
||||
}
|
||||
return obj[toNode.nodeData.data.id] || ''
|
||||
}
|
||||
|
||||
// 渲染关联线文字
|
||||
function renderText(str, path, text) {
|
||||
if (!str) return
|
||||
let { associativeLineTextFontSize, associativeLineTextLineHeight } =
|
||||
this.mindMap.themeConfig
|
||||
text.clear()
|
||||
let textArr = str.split(/\n/gim)
|
||||
textArr.forEach((item, index) => {
|
||||
let node = new Text().text(item)
|
||||
node.y(associativeLineTextFontSize * associativeLineTextLineHeight * index)
|
||||
this.styleText(node)
|
||||
text.add(node)
|
||||
})
|
||||
updateTextPos(path, text)
|
||||
}
|
||||
|
||||
// 给文本设置样式
|
||||
function styleText(node) {
|
||||
let {
|
||||
associativeLineTextColor,
|
||||
associativeLineTextFontSize,
|
||||
associativeLineTextFontFamily
|
||||
} = this.mindMap.themeConfig
|
||||
node
|
||||
.fill({
|
||||
color: associativeLineTextColor
|
||||
})
|
||||
.css({
|
||||
'font-family': associativeLineTextFontFamily,
|
||||
'font-size': associativeLineTextFontSize
|
||||
})
|
||||
}
|
||||
|
||||
// 更新关联线文字位置
|
||||
function updateTextPos(path, text) {
|
||||
let pathLength = path.length()
|
||||
let centerPoint = path.pointAt(pathLength / 2)
|
||||
let { width: textWidth, height: textHeight } = text.bbox()
|
||||
text.x(centerPoint.x - textWidth / 2)
|
||||
text.y(centerPoint.y - textHeight / 2)
|
||||
}
|
||||
|
||||
export default {
|
||||
getText,
|
||||
createText,
|
||||
styleText,
|
||||
onScale,
|
||||
showEditTextBox,
|
||||
hideEditTextBox,
|
||||
updateTextEditBoxPos,
|
||||
renderText,
|
||||
updateTextPos
|
||||
}
|
||||
@@ -28,34 +28,6 @@ export const themeList = [
|
||||
name: '默认',
|
||||
value: 'default',
|
||||
},
|
||||
{
|
||||
name: '脑图经典',
|
||||
value: 'classic',
|
||||
},
|
||||
{
|
||||
name: '小黄人',
|
||||
value: 'minions',
|
||||
},
|
||||
{
|
||||
name: '粉红葡萄',
|
||||
value: 'pinkGrape',
|
||||
},
|
||||
{
|
||||
name: '薄荷',
|
||||
value: 'mint',
|
||||
},
|
||||
{
|
||||
name: '金色vip',
|
||||
value: 'gold',
|
||||
},
|
||||
{
|
||||
name: '活力橙',
|
||||
value: 'vitalityOrange',
|
||||
},
|
||||
{
|
||||
name: '绿叶',
|
||||
value: 'greenLeaf',
|
||||
},
|
||||
{
|
||||
name: '暗色2',
|
||||
value: 'dark2',
|
||||
@@ -72,10 +44,6 @@ export const themeList = [
|
||||
name: '脑图经典3',
|
||||
value: 'classic3',
|
||||
},
|
||||
{
|
||||
name: '脑图经典4',
|
||||
value: 'classic4',
|
||||
},
|
||||
{
|
||||
name: '经典绿',
|
||||
value: 'classicGreen',
|
||||
@@ -112,6 +80,38 @@ export const themeList = [
|
||||
name: '浪漫紫',
|
||||
value: 'romanticPurple',
|
||||
},
|
||||
{
|
||||
name: '粉红葡萄',
|
||||
value: 'pinkGrape',
|
||||
},
|
||||
{
|
||||
name: '薄荷',
|
||||
value: 'mint',
|
||||
},
|
||||
{
|
||||
name: '金色vip',
|
||||
value: 'gold',
|
||||
},
|
||||
{
|
||||
name: '活力橙',
|
||||
value: 'vitalityOrange',
|
||||
},
|
||||
{
|
||||
name: '绿叶',
|
||||
value: 'greenLeaf',
|
||||
},
|
||||
{
|
||||
name: '脑图经典',
|
||||
value: 'classic',
|
||||
},
|
||||
{
|
||||
name: '脑图经典4',
|
||||
value: 'classic4',
|
||||
},
|
||||
{
|
||||
name: '小黄人',
|
||||
value: 'minions',
|
||||
},
|
||||
{
|
||||
name: '简约黑',
|
||||
value: 'simpleBlack',
|
||||
@@ -139,12 +139,25 @@ export const themeList = [
|
||||
{
|
||||
name: '黑金',
|
||||
value: 'blackGold',
|
||||
},
|
||||
{
|
||||
name: '牛油果',
|
||||
value: 'avocado',
|
||||
},
|
||||
{
|
||||
name: '秋天',
|
||||
value: 'autumn',
|
||||
},
|
||||
{
|
||||
name: '橙汁',
|
||||
value: 'orangeJuice',
|
||||
}
|
||||
]
|
||||
|
||||
// 常量
|
||||
export const CONSTANTS = {
|
||||
CHANGE_THEME: 'changeTheme',
|
||||
SET_DATA: 'setData',
|
||||
TRANSFORM_TO_NORMAL_NODE: 'transformAllNodesToNormalNode',
|
||||
MODE: {
|
||||
READONLY: 'readonly',
|
||||
|
||||
@@ -302,4 +302,58 @@ export const nextTick = function (fn, ctx) {
|
||||
pending = true
|
||||
timerFunc(handle, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查节点是否超出画布
|
||||
export const checkNodeOuter = (mindMap, node) => {
|
||||
let elRect = mindMap.elRect
|
||||
let { scaleX, scaleY, translateX, translateY } = mindMap.draw.transform()
|
||||
let { left, top, width, height } = node
|
||||
let right = (left + width) * scaleX + translateX
|
||||
let bottom = (top + height) * scaleY + translateY
|
||||
left = left * scaleX + translateX
|
||||
top = top * scaleY + translateY
|
||||
let offsetLeft = 0
|
||||
let offsetTop = 0
|
||||
if (left < 0) {
|
||||
offsetLeft = -left
|
||||
}
|
||||
if (right > elRect.width) {
|
||||
offsetLeft = -(right - elRect.width)
|
||||
}
|
||||
if (top < 0) {
|
||||
offsetTop = -top
|
||||
}
|
||||
if (bottom > elRect.height) {
|
||||
offsetTop = -(bottom - elRect.height)
|
||||
}
|
||||
return {
|
||||
isOuter: offsetLeft !== 0 || offsetTop !== 0,
|
||||
offsetLeft,
|
||||
offsetTop
|
||||
}
|
||||
}
|
||||
|
||||
// 提取html字符串里的纯文本
|
||||
let getTextFromHtmlEl = null
|
||||
export const getTextFromHtml = (html) => {
|
||||
if (!getTextFromHtmlEl) {
|
||||
getTextFromHtmlEl = document.createElement('div')
|
||||
}
|
||||
getTextFromHtmlEl.innerHTML = html
|
||||
return getTextFromHtmlEl.textContent
|
||||
}
|
||||
|
||||
// 将blob转成data:url
|
||||
export const readBlob = (blob) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let reader = new FileReader()
|
||||
reader.onload = (evt) => {
|
||||
resolve(evt.target.result)
|
||||
}
|
||||
reader.onerror = (err) => {
|
||||
reject(err)
|
||||
}
|
||||
reader.readAsDataURL(blob)
|
||||
})
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { measureText, resizeImgSize } from '../utils'
|
||||
import { measureText, resizeImgSize, getTextFromHtml } from '../utils'
|
||||
import { Image, SVG, A, G, Rect, Text, ForeignObject } from '@svgdotjs/svg.js'
|
||||
import iconsSvg from '../svg/icons'
|
||||
import { CONSTANTS } from './constant'
|
||||
|
||||
// 创建图片节点
|
||||
function createImgNode() {
|
||||
@@ -41,8 +42,18 @@ function createIconNode() {
|
||||
}
|
||||
let iconSize = this.mindMap.themeConfig.iconSize
|
||||
return _data.icon.map(item => {
|
||||
let src = iconsSvg.getNodeIconListIcon(item, this.mindMap.opt.iconList || [])
|
||||
let node = null
|
||||
// svg图标
|
||||
if (/^<svg/.test(src)) {
|
||||
node = SVG(src)
|
||||
} else {
|
||||
// 图片图标
|
||||
node = new Image().load(src)
|
||||
}
|
||||
node.size(iconSize, iconSize)
|
||||
return {
|
||||
node: SVG(iconsSvg.getNodeIconListIcon(item)).size(iconSize, iconSize),
|
||||
node,
|
||||
width: iconSize,
|
||||
height: iconSize
|
||||
}
|
||||
@@ -52,11 +63,18 @@ function createIconNode() {
|
||||
// 创建富文本节点
|
||||
function createRichTextNode() {
|
||||
let g = new G()
|
||||
// 重新设置富文本节点内容
|
||||
if (this.nodeData.data.resetRichText || [CONSTANTS.CHANGE_THEME].includes(this.mindMap.renderer.renderSource)) {
|
||||
delete this.nodeData.data.resetRichText
|
||||
let text = getTextFromHtml(this.nodeData.data.text)
|
||||
this.nodeData.data.text = `<p><span style="${this.style.createStyleText()}">${text}</span></p>`
|
||||
}
|
||||
let html = `<div>${this.nodeData.data.text}</div>`
|
||||
let div = document.createElement('div')
|
||||
div.innerHTML = html
|
||||
div.style.cssText = `position: fixed; left: -999999px;`
|
||||
let el = div.children[0]
|
||||
el.classList.add('smm-richtext-node-wrap')
|
||||
el.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml')
|
||||
el.style.maxWidth = this.mindMap.opt.textAutoWrapWidth + 'px'
|
||||
this.mindMap.el.appendChild(div)
|
||||
@@ -95,21 +113,27 @@ function createTextNode() {
|
||||
let textStyle = this.style.getTextFontStyle()
|
||||
let textArr = this.nodeData.data.text.split(/\n/gim)
|
||||
let maxWidth = this.mindMap.opt.textAutoWrapWidth
|
||||
let isMultiLine = false
|
||||
textArr.forEach((item, index) => {
|
||||
let arr = item.split('')
|
||||
let lines = []
|
||||
let line = []
|
||||
while (arr.length) {
|
||||
line.push(arr.shift())
|
||||
let text = line.join('')
|
||||
if (measureText(text, textStyle).width >= maxWidth) {
|
||||
lines.push(text)
|
||||
line = []
|
||||
let str = arr.shift()
|
||||
let text = [...line, str].join('')
|
||||
if (measureText(text, textStyle).width <= maxWidth) {
|
||||
line.push(str)
|
||||
} else {
|
||||
lines.push(line.join(''))
|
||||
line = [str]
|
||||
}
|
||||
}
|
||||
if (line.length > 0) {
|
||||
lines.push(line.join(''))
|
||||
}
|
||||
if (lines.length > 1) {
|
||||
isMultiLine = true
|
||||
}
|
||||
textArr[index] = lines.join('\n')
|
||||
})
|
||||
textArr = textArr.join('\n').split(/\n/gim)
|
||||
@@ -124,6 +148,7 @@ function createTextNode() {
|
||||
height = Math.ceil(height)
|
||||
g.attr('data-width', width)
|
||||
g.attr('data-height', height)
|
||||
g.attr('data-ismultiLine', isMultiLine || textArr.length > 1)
|
||||
return {
|
||||
node: g,
|
||||
width,
|
||||
@@ -205,13 +230,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
|
||||
|
||||
@@ -32,15 +32,20 @@ function createExpandNodeContent() {
|
||||
|
||||
// 创建或更新展开收缩按钮内容
|
||||
function updateExpandBtnNode() {
|
||||
let { expand } = this.nodeData.data
|
||||
// 如果本次和上次的展开状态一样则返回
|
||||
if (expand === this._lastExpandBtnType) return
|
||||
if (this._expandBtn) {
|
||||
this._expandBtn.clear()
|
||||
}
|
||||
this.createExpandNodeContent()
|
||||
let node
|
||||
if (this.nodeData.data.expand === false) {
|
||||
if (expand === false) {
|
||||
node = this._openExpandNode
|
||||
this._lastExpandBtnType = false
|
||||
} else {
|
||||
node = this._closeExpandNode
|
||||
this._lastExpandBtnType = true
|
||||
}
|
||||
if (this._expandBtn) this._expandBtn.add(this._fillExpandNode).add(node)
|
||||
}
|
||||
@@ -93,14 +98,36 @@ function renderExpandBtn() {
|
||||
})
|
||||
this.group.add(this._expandBtn)
|
||||
}
|
||||
this._showExpandBtn = true
|
||||
this.updateExpandBtnNode()
|
||||
this.updateExpandBtnPos()
|
||||
}
|
||||
|
||||
// 移除展开收缩按钮
|
||||
function removeExpandBtn() {
|
||||
if (this._expandBtn) {
|
||||
if (this._expandBtn && this._showExpandBtn) {
|
||||
this._expandBtn.remove()
|
||||
this._showExpandBtn = false
|
||||
}
|
||||
}
|
||||
|
||||
// 显示展开收起按钮
|
||||
function showExpandBtn() {
|
||||
if (this.mindMap.opt.alwaysShowExpandBtn) return
|
||||
setTimeout(() => {
|
||||
this.renderExpandBtn()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
// 隐藏展开收起按钮
|
||||
function hideExpandBtn() {
|
||||
if (this.mindMap.opt.alwaysShowExpandBtn || this._isMouseenter) return
|
||||
// 非激活状态且展开状态鼠标移出才隐藏按钮
|
||||
let { isActive, expand } = this.nodeData.data
|
||||
if (!isActive && expand) {
|
||||
setTimeout(() => {
|
||||
this.removeExpandBtn()
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,5 +136,7 @@ export default {
|
||||
updateExpandBtnNode,
|
||||
updateExpandBtnPos,
|
||||
renderExpandBtn,
|
||||
removeExpandBtn
|
||||
removeExpandBtn,
|
||||
showExpandBtn,
|
||||
hideExpandBtn
|
||||
}
|
||||
|
||||
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/avatar/Think.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
web/src/assets/img/autumn.jpg
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
web/src/assets/img/avocado.jpg
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
web/src/assets/img/iconList.jpg
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
web/src/assets/img/orangeJuice.jpg
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
@@ -40,5 +40,8 @@ export const themeMap = {
|
||||
blackHumour: require('../assets/img/blackHumour.jpg'),
|
||||
lateNightOffice: require('../assets/img/lateNightOffice.jpg'),
|
||||
blackGold: require('../assets/img/blackGold.jpg'),
|
||||
autumn: require('../assets/img/autumn.jpg'),
|
||||
avocado: require('../assets/img/avocado.jpg'),
|
||||
orangeJuice: require('../assets/img/orangeJuice.jpg'),
|
||||
}
|
||||
|
||||
64
web/src/config/icon.js
Normal file
@@ -90,12 +90,14 @@ export default {
|
||||
pdfFile: 'pdf file',
|
||||
markdownFile: 'markdown file',
|
||||
tips: 'tips: .smm and .json file can be import',
|
||||
domToImage: 'Whether to convert rich text nodes in svg into pictures',
|
||||
isTransparent: 'Background is transparent',
|
||||
pngTips: 'tips: Exporting pictures in rich text mode is time-consuming. It is recommended to export to svg format',
|
||||
svgTips: 'tips: Exporting pictures in rich text mode is time-consuming',
|
||||
transformingDomToImages: 'Converting nodes: ',
|
||||
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',
|
||||
|
||||
@@ -90,12 +90,14 @@ export default {
|
||||
pdfFile: 'pdf文件',
|
||||
markdownFile: 'markdown文件',
|
||||
tips: 'tips:.smm和.json文件可用于导入',
|
||||
domToImage: '是否将svg中富文本节点转换成图片',
|
||||
isTransparent: '背景是否透明',
|
||||
pngTips: 'tips:富文本模式导出图片非常耗时,建议导出为svg格式',
|
||||
svgTips: 'tips:富文本模式导出图片非常耗时',
|
||||
transformingDomToImages: '正在转换节点:',
|
||||
notifyTitle: '消息',
|
||||
notifyMessage: '如果没有触发下载,请检查是否被浏览器拦截了'
|
||||
notifyMessage: '如果没有触发下载,请检查是否被浏览器拦截了',
|
||||
paddingX: '水平内边距',
|
||||
paddingY: '垂直内边距'
|
||||
},
|
||||
fullscreen: {
|
||||
fullscreenShow: '全屏查看',
|
||||
|
||||
@@ -10,8 +10,8 @@ let langList = [
|
||||
path: 'en'
|
||||
}
|
||||
]
|
||||
let StartList = ['introduction', 'start', 'translate', 'changelog']
|
||||
let CourseList = new Array(18).fill(0).map((_, index) => {
|
||||
let StartList = ['introduction', 'start', 'deploy', 'client', 'translate', 'changelog']
|
||||
let CourseList = new Array(19).fill(0).map((_, index) => {
|
||||
return 'course' + (index + 1)
|
||||
})
|
||||
let APIList = [
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
> The function of adjusting associated line control points is supported from v0.4.6+
|
||||
|
||||
This plugin is used to support the addition of associative lines.
|
||||
> Relevance support for text editing starting from v0.5.11+
|
||||
|
||||
The plugin is currently not fully functional, and does not support adding text to association lines.
|
||||
This plugin is used to support the addition of associative lines.
|
||||
|
||||
## Register
|
||||
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
<blockquote>
|
||||
<p>The function of adjusting associated line control points is supported from v0.4.6+</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>Relevance support for text editing starting from v0.5.11+</p>
|
||||
</blockquote>
|
||||
<p>This plugin is used to support the addition of associative lines.</p>
|
||||
<p>The plugin is currently not fully functional, and does not support adding text to association lines.</p>
|
||||
<h2>Register</h2>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> MindMap <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map'</span>
|
||||
<span class="hljs-keyword">import</span> AssociativeLine <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/AssociativeLine.js'</span>
|
||||
|
||||
@@ -1,5 +1,63 @@
|
||||
# Changelog
|
||||
|
||||
## 0.5.11
|
||||
|
||||
New: Supports associative text editing.
|
||||
|
||||
optimization: Optimizing theme configuration updates, changing configurations that do not involve node size does not trigger node recalculation.
|
||||
|
||||
## 0.5.10
|
||||
|
||||
New: Optimize node reuse logic using LRU caching algorithm.
|
||||
|
||||
## 0.5.10-fix.1
|
||||
|
||||
Fix: Fix the issue of import errors.
|
||||
|
||||
## 0.5.10-fix.2
|
||||
|
||||
Fix: Fixed the issue of switching themes and importing data without triggering data changes in rich text mode.
|
||||
|
||||
New: Add three new themes.
|
||||
|
||||
## 0.5.9
|
||||
|
||||
Change: Unified export method format, using `FileReader` instead of `URL.createObjectURL` to convert `blob` data.
|
||||
|
||||
## 0.5.8
|
||||
|
||||
optimization: 1.The position setting is not triggered when the node position does not change. 2.The unfolding and folding status does not change and does not trigger button updates.
|
||||
|
||||
New: 1.The default setting is to move the mouse over the node to display the expand and collapse buttons. 2.Support the list of icons that can be inserted into extended nodes.
|
||||
|
||||
## 0.5.7
|
||||
|
||||
Breaking change:In rich text mode, exporting png has been changed to using html2canvas to convert the entire svg, greatly improving the export speed. However, html2canvas has a bug where the text color inline with the dom node in the foreignObject element cannot be recognized. Therefore, the text color of the exported node is fixed. However, compared to the previously unavailable state of the export, it can at least be exported quickly and smoothly.
|
||||
|
||||
optimization: Optimize the rich text node editing experience.
|
||||
|
||||
New: In rich text mode, importing data, initializing data, and switching theme scene node styles support following theme changes.
|
||||
|
||||
## 0.5.6
|
||||
|
||||
Fix: 1.Fix the issue of node position disorder during fast and multiple renderings in a short period of time. 2.Fix the issue of dragging the canvas while the node is being edited, causing the edit box and node to separate.
|
||||
|
||||
New: 1.Add a maximum history limit.
|
||||
|
||||
## 0.5.5-fix.1
|
||||
|
||||
Fix: 1.Fix the issue where the edit box is also outside the canvas when editing nodes outside the canvas. 2.After modifying the structure, reset the transformation to prevent the problem of sudden position changes during the first drag after switching the structure during scaling.
|
||||
|
||||
optimization: 1.When multiple nodes are selected, as long as there is a cross between the node and the selection area, it is considered selected.
|
||||
|
||||
## 0.5.5-fix.2
|
||||
|
||||
Fix: 1.Fix mini map error.
|
||||
|
||||
## 0.5.5
|
||||
|
||||
New: 1.Supports configuring the padding when exporting to PNG, SVG, or PDF. 2.Support the configuration of z-index for node text editing boxes and node comment floating layer elements. 3.Support clicking on areas outside the canvas to end node editing status.
|
||||
|
||||
## 0.5.4
|
||||
|
||||
New: 1.Add new themes. 2.Added timeline and fishbone structure.
|
||||
@@ -8,6 +66,10 @@ Fix: 1.Fix the conflict issue between node right-click and canvas right-click. 2
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
@@ -1,10 +1,41 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Changelog</h1>
|
||||
<h2>0.5.11</h2>
|
||||
<p>New: Supports associative text editing.</p>
|
||||
<p>optimization: Optimizing theme configuration updates, changing configurations that do not involve node size does not trigger node recalculation.</p>
|
||||
<h2>0.5.10</h2>
|
||||
<p>New: Optimize node reuse logic using LRU caching algorithm.</p>
|
||||
<h2>0.5.10-fix.1</h2>
|
||||
<p>Fix: Fix the issue of import errors.</p>
|
||||
<h2>0.5.10-fix.2</h2>
|
||||
<p>Fix: Fixed the issue of switching themes and importing data without triggering data changes in rich text mode.</p>
|
||||
<p>New: Add three new themes.</p>
|
||||
<h2>0.5.9</h2>
|
||||
<p>Change: Unified export method format, using <code>FileReader</code> instead of <code>URL.createObjectURL</code> to convert <code>blob</code> data.</p>
|
||||
<h2>0.5.8</h2>
|
||||
<p>optimization: 1.The position setting is not triggered when the node position does not change. 2.The unfolding and folding status does not change and does not trigger button updates.</p>
|
||||
<p>New: 1.The default setting is to move the mouse over the node to display the expand and collapse buttons. 2.Support the list of icons that can be inserted into extended nodes.</p>
|
||||
<h2>0.5.7</h2>
|
||||
<p>Breaking change:In rich text mode, exporting png has been changed to using html2canvas to convert the entire svg, greatly improving the export speed. However, html2canvas has a bug where the text color inline with the dom node in the foreignObject element cannot be recognized. Therefore, the text color of the exported node is fixed. However, compared to the previously unavailable state of the export, it can at least be exported quickly and smoothly.</p>
|
||||
<p>optimization: Optimize the rich text node editing experience.</p>
|
||||
<p>New: In rich text mode, importing data, initializing data, and switching theme scene node styles support following theme changes.</p>
|
||||
<h2>0.5.6</h2>
|
||||
<p>Fix: 1.Fix the issue of node position disorder during fast and multiple renderings in a short period of time. 2.Fix the issue of dragging the canvas while the node is being edited, causing the edit box and node to separate.</p>
|
||||
<p>New: 1.Add a maximum history limit.</p>
|
||||
<h2>0.5.5-fix.1</h2>
|
||||
<p>Fix: 1.Fix the issue where the edit box is also outside the canvas when editing nodes outside the canvas. 2.After modifying the structure, reset the transformation to prevent the problem of sudden position changes during the first drag after switching the structure during scaling.</p>
|
||||
<p>optimization: 1.When multiple nodes are selected, as long as there is a cross between the node and the selection area, it is considered selected.</p>
|
||||
<h2>0.5.5-fix.2</h2>
|
||||
<p>Fix: 1.Fix mini map error.</p>
|
||||
<h2>0.5.5</h2>
|
||||
<p>New: 1.Supports configuring the padding when exporting to PNG, SVG, or PDF. 2.Support the configuration of z-index for node text editing boxes and node comment floating layer elements. 3.Support clicking on areas outside the canvas to end node editing status.</p>
|
||||
<h2>0.5.4</h2>
|
||||
<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>
|
||||
|
||||
@@ -28,7 +28,7 @@ const mindMap = new MindMap({
|
||||
| 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)、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+) | |
|
||||
| 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+)、、avocado(v.5.10-fix.2+)、autumn(v.5.10-fix.2+)、orangeJuice(v.5.10-fix.2+) | |
|
||||
| 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 | |
|
||||
| maxTag | Number | 5 | The maximum number of tags displayed in the node, any additional tags will be discarded | |
|
||||
@@ -53,6 +53,15 @@ 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 |
|
||||
| alwaysShowExpandBtn(v0.5.8+) | Boolean | false | Whether to always display the expand and collapse buttons of nodes, which are only displayed when the mouse is moved up and activated by default | |
|
||||
| iconList(v0.5.8+) | Array | [] | The icons that can be inserted into the extension node, and each item in the array is an object. Please refer to the "Icon Configuration" table below for the detailed structure of the object | |
|
||||
| maxNodeCacheCount(v0.5.10+) | Number | 1000 | The maximum number of cached nodes. To optimize performance, an internal node cache pool is maintained to reuse nodes. This attribute allows you to specify the maximum number of caches in the pool | |
|
||||
|
||||
### Watermark config
|
||||
|
||||
@@ -64,6 +73,14 @@ const mindMap = new MindMap({
|
||||
| angle | Number | 30 | Tilt angle of watermark, range: [0, 90] |
|
||||
| textStyle | Object | {color: '#999', opacity: 0.5, fontSize: 14} | Watermark text style |
|
||||
|
||||
### Icon Configuration
|
||||
|
||||
| Field Name | Type | Default Value | Description |
|
||||
| ----------- | ------ | ------------------------------------------- | ------------------------------------------------------------ |
|
||||
| name | String | | The name of the icon group |
|
||||
| type | String | | Values for icon grouping |
|
||||
| list | Array | | A list of icons under grouping, with each item in the array being an object, `{ name: '', icon: '' }`,`name`represents the name of the icon, `icon`represents the icon, Can be an `svg` icon, such as `<svg ...><path></path></svg>`, also can be a image `url`, or `base64` icon, such as `data:image/png;base64,...` |
|
||||
|
||||
## Static methods
|
||||
|
||||
### defineTheme(name, config)
|
||||
@@ -123,10 +140,14 @@ List of all currently registered plugins.
|
||||
|
||||
## Instance methods
|
||||
|
||||
### getSvgData()
|
||||
### getSvgData({ paddingX = 0, paddingY = 0 })
|
||||
|
||||
> v0.3.0+
|
||||
|
||||
`paddingX`: Padding x
|
||||
|
||||
`paddingY`: Padding y
|
||||
|
||||
Get the `svg` data and return an object. The detailed structure is as follows:
|
||||
|
||||
```js
|
||||
@@ -349,6 +370,18 @@ smm (essentially also json)
|
||||
`fileName`: (v0.1.6+) the name of the exported file, default is `思维导图` (mind
|
||||
map).
|
||||
|
||||
If it is exported as `png`, the fourth parameter can be passed:
|
||||
|
||||
`transparent`: v0.5.7+, `Boolean`, default is `false`, Specify whether the background of the exported image is transparent
|
||||
|
||||
If it is exported as `svg`, the fourth parameter can be passed:
|
||||
|
||||
`plusCssText`: Additional `CSS` style. If there is a `dom` node in `svg`, you can pass in some styles specific to the node through this parameter
|
||||
|
||||
If it is exported as `json` or `smm`, the fourth parameter can be passed:
|
||||
|
||||
`withConfig`: `Boolean`, default is `true`, Specify whether the exported data includes configuration data, otherwise only pure node tree data will be exported
|
||||
|
||||
### toPos(x, y)
|
||||
|
||||
> v0.1.5+
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<td>theme</td>
|
||||
<td>String</td>
|
||||
<td>default</td>
|
||||
<td>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+)</td>
|
||||
<td>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+)、、avocado(v.5.10-fix.2+)、autumn(v.5.10-fix.2+)、orangeJuice(v.5.10-fix.2+)</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -231,6 +231,69 @@
|
||||
<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>
|
||||
<tr>
|
||||
<td>alwaysShowExpandBtn(v0.5.8+)</td>
|
||||
<td>Boolean</td>
|
||||
<td>false</td>
|
||||
<td>Whether to always display the expand and collapse buttons of nodes, which are only displayed when the mouse is moved up and activated by default</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>iconList(v0.5.8+)</td>
|
||||
<td>Array</td>
|
||||
<td>[]</td>
|
||||
<td>The icons that can be inserted into the extension node, and each item in the array is an object. Please refer to the "Icon Configuration" table below for the detailed structure of the object</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>maxNodeCacheCount(v0.5.10+)</td>
|
||||
<td>Number</td>
|
||||
<td>1000</td>
|
||||
<td>The maximum number of cached nodes. To optimize performance, an internal node cache pool is maintained to reuse nodes. This attribute allows you to specify the maximum number of caches in the pool</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Watermark config</h3>
|
||||
@@ -276,6 +339,37 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Icon Configuration</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field Name</th>
|
||||
<th>Type</th>
|
||||
<th>Default Value</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>name</td>
|
||||
<td>String</td>
|
||||
<td></td>
|
||||
<td>The name of the icon group</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>type</td>
|
||||
<td>String</td>
|
||||
<td></td>
|
||||
<td>Values for icon grouping</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>list</td>
|
||||
<td>Array</td>
|
||||
<td></td>
|
||||
<td>A list of icons under grouping, with each item in the array being an object, <code>{ name: '', icon: '' }</code>,<code>name</code>represents the name of the icon, <code>icon</code>represents the icon, Can be an <code>svg</code> icon, such as <code><svg ...><path></path></svg></code>, also can be a image <code>url</code>, or <code>base64</code> icon, such as <code>data:image/png;base64,...</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h2>Static methods</h2>
|
||||
<h3>defineTheme(name, config)</h3>
|
||||
<blockquote>
|
||||
@@ -319,10 +413,12 @@ mindMap.setTheme(<span class="hljs-string">'Theme name'</span>)
|
||||
</blockquote>
|
||||
<p>List of all currently registered plugins.</p>
|
||||
<h2>Instance methods</h2>
|
||||
<h3>getSvgData()</h3>
|
||||
<h3>getSvgData({ paddingX = 0, paddingY = 0 })</h3>
|
||||
<blockquote>
|
||||
<p>v0.3.0+</p>
|
||||
</blockquote>
|
||||
<p><code>paddingX</code>: Padding x</p>
|
||||
<p><code>paddingY</code>: Padding y</p>
|
||||
<p>Get the <code>svg</code> data and return an object. The detailed structure is as follows:</p>
|
||||
<pre class="hljs"><code>{
|
||||
svg, <span class="hljs-comment">// Element, the overall svg element of the mind map graphics, including: svg (canvas container), g (actual mind map group)</span>
|
||||
@@ -772,6 +868,12 @@ smm (essentially also json)</p>
|
||||
<code>false</code></p>
|
||||
<p><code>fileName</code>: (v0.1.6+) the name of the exported file, default is <code>思维导图</code> (mind
|
||||
map).</p>
|
||||
<p>If it is exported as <code>png</code>, the fourth parameter can be passed:</p>
|
||||
<p><code>transparent</code>: v0.5.7+, <code>Boolean</code>, default is <code>false</code>, Specify whether the background of the exported image is transparent</p>
|
||||
<p>If it is exported as <code>svg</code>, the fourth parameter can be passed:</p>
|
||||
<p><code>plusCssText</code>: Additional <code>CSS</code> style. If there is a <code>dom</code> node in <code>svg</code>, you can pass in some styles specific to the node through this parameter</p>
|
||||
<p>If it is exported as <code>json</code> or <code>smm</code>, the fourth parameter can be passed:</p>
|
||||
<p><code>withConfig</code>: <code>Boolean</code>, default is <code>true</code>, Specify whether the exported data includes configuration data, otherwise only pure node tree data will be exported</p>
|
||||
<h3>toPos(x, y)</h3>
|
||||
<blockquote>
|
||||
<p>v0.1.5+</p>
|
||||
|
||||
1
web/src/pages/Doc/en/deploy/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# Deploy
|
||||
@@ -15,17 +15,40 @@ After registration and instantiation of `MindMap`, the instance can be obtained
|
||||
|
||||
## Methods
|
||||
|
||||
### png()
|
||||
All exported methods are asynchronous and return an instance of `Promise`. You can use the `then` method to obtain data, or use the `async await` function to obtain:
|
||||
|
||||
Exports as `png`, an async method that returns image data, `data:url` data which
|
||||
can be downloaded or displayed.
|
||||
```js
|
||||
mindMap.doExport.png().then((data) => {
|
||||
// ...
|
||||
})
|
||||
|
||||
### svg(name, domToImage = false, plusCssText)
|
||||
const export = async () => {
|
||||
let data = await mindMap.doExport.png()
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The returned data is in the format of `data:URL`. You can create an `a` tag to trigger the download:
|
||||
|
||||
```js
|
||||
let a = document.createElement('a')
|
||||
a.href = 'xxx.png'// .png、.svg、.pdf、.md、.json、.smm
|
||||
a.download = 'xxx'
|
||||
a.click()
|
||||
```
|
||||
|
||||
### png(name, transparent = false)
|
||||
|
||||
- `name`: Name, optional
|
||||
|
||||
- `transparent`: v0.5.7+, Specify whether the background of the exported image is transparent
|
||||
|
||||
Exports as `png`.
|
||||
|
||||
### svg(name, plusCssText)
|
||||
|
||||
- `name`:`svg` title
|
||||
|
||||
- `domToImage`:v0.4.0+, When node rich text editing is enabled, you can use this parameter to specify whether to convert the `dom` node in the `svg` into a picture
|
||||
|
||||
- `plusCssText`:v0.4.0+, When node rich text editing is enabled and `domToImage` passes `false`, additional `css` styles can be added. If there is a `dom` node in `svg`, you can set some styles for the node through this parameter, such as:
|
||||
|
||||
```js
|
||||
@@ -40,22 +63,7 @@ svg(
|
||||
)
|
||||
```
|
||||
|
||||
Exports as `svg`, an async method that returns `svg` data, `data:url` data which
|
||||
can be downloaded or displayed.
|
||||
|
||||
### getSvgData(domToImage)
|
||||
|
||||
- `domToImage`:v0.4.0+, If node rich text is enabled, you can use this parameter to specify whether to convert the `DOM` node embedded in `svg` into a picture.
|
||||
|
||||
Gets `svg` data, an async method that returns an object:
|
||||
|
||||
```js
|
||||
{
|
||||
node; // svg object
|
||||
str; // svg string, if rich text editing is enabled and domToImage is set to true, the dom node in the svg character returned by this value will be converted into the form of an image
|
||||
nodeWithDomToImg// v0.4.0+,The svg object after the DOM node is converted to an image has a value only when rich text editing is enabled and domToImage is set to true, otherwise null
|
||||
}
|
||||
```
|
||||
Exports as `svg`.
|
||||
|
||||
### pdf(name)
|
||||
|
||||
@@ -63,7 +71,7 @@ Gets `svg` data, an async method that returns an object:
|
||||
|
||||
`name`:File name
|
||||
|
||||
Export as `pdf`
|
||||
Export as `pdf`. Unlike other export methods, this method does not return data and directly triggers the download.
|
||||
|
||||
### json(name, withConfig)
|
||||
|
||||
@@ -71,4 +79,21 @@ Export as `pdf`
|
||||
|
||||
`withConfig``:Boolean`, default `true`, Whether the data contains configuration, otherwise it is pure mind map node data
|
||||
|
||||
Return `json` data, `data:url` type, you can download it yourself
|
||||
Return `json` data.
|
||||
|
||||
### md()
|
||||
|
||||
> v0.4.7+
|
||||
|
||||
Export as `markdown` file.
|
||||
|
||||
### getSvgData()
|
||||
|
||||
Gets `svg` data, an async method that returns an object:
|
||||
|
||||
```js
|
||||
{
|
||||
node // svg node
|
||||
str // svg string
|
||||
}
|
||||
```
|
||||
@@ -10,18 +10,38 @@ MindMap.usePlugin(Export)
|
||||
</code></pre>
|
||||
<p>After registration and instantiation of <code>MindMap</code>, the instance can be obtained through <code>mindMap.doExport</code>.</p>
|
||||
<h2>Methods</h2>
|
||||
<h3>png()</h3>
|
||||
<p>Exports as <code>png</code>, an async method that returns image data, <code>data:url</code> data which
|
||||
can be downloaded or displayed.</p>
|
||||
<h3>svg(name, domToImage = false, plusCssText)</h3>
|
||||
<p>All exported methods are asynchronous and return an instance of <code>Promise</code>. You can use the <code>then</code> method to obtain data, or use the <code>async await</code> function to obtain:</p>
|
||||
<pre class="hljs"><code>mindMap.doExport.png().then(<span class="hljs-function">(<span class="hljs-params">data</span>) =></span> {
|
||||
<span class="hljs-comment">// ...</span>
|
||||
})
|
||||
|
||||
<span class="hljs-keyword">const</span> <span class="hljs-keyword">export</span> = <span class="hljs-keyword">async</span> () => {
|
||||
<span class="hljs-keyword">let</span> data = <span class="hljs-keyword">await</span> mindMap.doExport.png()
|
||||
<span class="hljs-comment">// ...</span>
|
||||
}
|
||||
</code></pre>
|
||||
<p>The returned data is in the format of <code>data:URL</code>. You can create an <code>a</code> tag to trigger the download:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">let</span> a = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'a'</span>)
|
||||
a.href = <span class="hljs-string">'xxx.png'</span><span class="hljs-comment">// .png、.svg、.pdf、.md、.json、.smm</span>
|
||||
a.download = <span class="hljs-string">'xxx'</span>
|
||||
a.click()
|
||||
</code></pre>
|
||||
<h3>png(name, transparent = false)</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<p><code>name</code>: Name, optional</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>transparent</code>: v0.5.7+, Specify whether the background of the exported image is transparent</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>Exports as <code>png</code>.</p>
|
||||
<h3>svg(name, plusCssText)</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<p><code>name</code>:<code>svg</code> title</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>domToImage</code>:v0.4.0+, When node rich text editing is enabled, you can use this parameter to specify whether to convert the <code>dom</code> node in the <code>svg</code> into a picture</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>plusCssText</code>:v0.4.0+, When node rich text editing is enabled and <code>domToImage</code> passes <code>false</code>, additional <code>css</code> styles can be added. If there is a <code>dom</code> node in <code>svg</code>, you can set some styles for the node through this parameter, such as:</p>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -35,29 +55,29 @@ can be downloaded or displayed.</p>
|
||||
}`</span>
|
||||
)
|
||||
</code></pre>
|
||||
<p>Exports as <code>svg</code>, an async method that returns <code>svg</code> data, <code>data:url</code> data which
|
||||
can be downloaded or displayed.</p>
|
||||
<h3>getSvgData(domToImage)</h3>
|
||||
<ul>
|
||||
<li><code>domToImage</code>:v0.4.0+, If node rich text is enabled, you can use this parameter to specify whether to convert the <code>DOM</code> node embedded in <code>svg</code> into a picture.</li>
|
||||
</ul>
|
||||
<p>Gets <code>svg</code> data, an async method that returns an object:</p>
|
||||
<pre class="hljs"><code>{
|
||||
node; <span class="hljs-comment">// svg object</span>
|
||||
str; <span class="hljs-comment">// svg string, if rich text editing is enabled and domToImage is set to true, the dom node in the svg character returned by this value will be converted into the form of an image</span>
|
||||
nodeWithDomToImg<span class="hljs-comment">// v0.4.0+,The svg object after the DOM node is converted to an image has a value only when rich text editing is enabled and domToImage is set to true, otherwise null</span>
|
||||
}
|
||||
</code></pre>
|
||||
<p>Exports as <code>svg</code>.</p>
|
||||
<h3>pdf(name)</h3>
|
||||
<blockquote>
|
||||
<p>v0.2.1+</p>
|
||||
</blockquote>
|
||||
<p><code>name</code>:File name</p>
|
||||
<p>Export as <code>pdf</code></p>
|
||||
<p>Export as <code>pdf</code>. Unlike other export methods, this method does not return data and directly triggers the download.</p>
|
||||
<h3>json(name, withConfig)</h3>
|
||||
<p><code>name</code>:It is temporarily useless, just pass an empty string</p>
|
||||
<p><code>withConfig``:Boolean</code>, default <code>true</code>, Whether the data contains configuration, otherwise it is pure mind map node data</p>
|
||||
<p>Return <code>json</code> data, <code>data:url</code> type, you can download it yourself</p>
|
||||
<p>Return <code>json</code> data.</p>
|
||||
<h3>md()</h3>
|
||||
<blockquote>
|
||||
<p>v0.4.7+</p>
|
||||
</blockquote>
|
||||
<p>Export as <code>markdown</code> file.</p>
|
||||
<h3>getSvgData()</h3>
|
||||
<p>Gets <code>svg</code> data, an async method that returns an object:</p>
|
||||
<pre class="hljs"><code>{
|
||||
node <span class="hljs-comment">// svg node</span>
|
||||
str <span class="hljs-comment">// svg string</span>
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
`simple-mind-map` is a simple and powerful web mind map library, not dependent on any specific framework. Can help you quickly develop mind mapping products.
|
||||
|
||||
> If you just want to use mind mapping, you can also use the demo of this project as a regular online mind mapping tool. Click on the 【Online Demo】 in the upper right corner to start using it.
|
||||
>
|
||||
> Additionally, a client is provided for download, support `Windows`、`Mac` and `Linux`, [Click here to learn more](/mind-map/#/doc/zh/client)。
|
||||
|
||||
## Features
|
||||
|
||||
@@ -97,6 +99,18 @@ These open-source mind maps are also good, each with its own characteristics, bu
|
||||
|
||||
In summary, in open-source mind maps, it is difficult to find a better choice than `simple-mind-map`. Of course, `simple-mind-map` is far from being the best, and it also has many shortcomings, as you saw in the previous [special note]. However, `simple-mind-map` has always been in a fast iteration process, and we welcome you to join and improve it together.
|
||||
|
||||
## Browser Compatibility
|
||||
|
||||
We recommend using the latest version of the `Chrome` browser.
|
||||
|
||||
Limited testing situation:
|
||||
|
||||
Normal operation: `360` extreme speed browser(v13.5.2036.0)、`opera` browser(v71.0.3770.284)。
|
||||
|
||||
Abnormal operation: `Firefox`(v98.0.2), Node content cannot be displayed in rich text mode. If you want to support the `Firefox` browser, please use non rich text mode.
|
||||
|
||||
Unsupported: `IE` browser.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://opensource.org/licenses/MIT)
|
||||
|
||||
@@ -4,24 +4,25 @@
|
||||
<p><code>simple-mind-map</code> is a simple and powerful web mind map library, not dependent on any specific framework. Can help you quickly develop mind mapping products.</p>
|
||||
<blockquote>
|
||||
<p>If you just want to use mind mapping, you can also use the demo of this project as a regular online mind mapping tool. Click on the 【Online Demo】 in the upper right corner to start using it.</p>
|
||||
<p>Additionally, a client is provided for download, support <code>Windows</code>、<code>Mac</code> and <code>Linux</code>, <a href="/mind-map/#/doc/zh/client">Click here to learn more</a>。</p>
|
||||
</blockquote>
|
||||
<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 six types of structures: logical structure diagrams, mind maps,</label>
|
||||
<li><input type="checkbox" id="checkbox90" checked="true" /><label for="checkbox90">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="checkbox91" checked="true" /><label for="checkbox91">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>
|
||||
<li><input type="checkbox" id="checkbox92" checked="true" /><label for="checkbox92">Built-in multiple themes and allows for highly customized styles, and support register new themes</label></li>
|
||||
<li><input type="checkbox" id="checkbox93" checked="true" /><label for="checkbox93">Supports shortcuts</label></li>
|
||||
<li><input type="checkbox" id="checkbox94" checked="true" /><label for="checkbox94">Node content supports images, icons, hyperlinks, notes, tags, and</label>
|
||||
summaries</li>
|
||||
<li><input type="checkbox" id="checkbox23" checked="true" /><label for="checkbox23">Supports forward and backward navigation</label></li>
|
||||
<li><input type="checkbox" id="checkbox24" checked="true" /><label for="checkbox24">Supports dragging and scaling</label></li>
|
||||
<li><input type="checkbox" id="checkbox25" checked="true" /><label for="checkbox25">Supports right-click and Ctrl + left-click to select multiple items</label></li>
|
||||
<li><input type="checkbox" id="checkbox26" checked="true" /><label for="checkbox26">Supports free dragging and dragging to adjust nodes</label></li>
|
||||
<li><input type="checkbox" id="checkbox27" checked="true" /><label for="checkbox27">Supports various node shapes</label></li>
|
||||
<li><input type="checkbox" id="checkbox28" checked="true" /><label for="checkbox28">Supports export to json, png, svg, pdf markdown, and import from json, xmind, markdown</label></li>
|
||||
<li><input type="checkbox" id="checkbox29" checked="true" /><label for="checkbox29">Supports mini map、support watermark</label></li>
|
||||
<li><input type="checkbox" id="checkbox30" checked="true" /><label for="checkbox30">Supports associative lines</label></li>
|
||||
<li><input type="checkbox" id="checkbox95" checked="true" /><label for="checkbox95">Supports forward and backward navigation</label></li>
|
||||
<li><input type="checkbox" id="checkbox96" checked="true" /><label for="checkbox96">Supports dragging and scaling</label></li>
|
||||
<li><input type="checkbox" id="checkbox97" checked="true" /><label for="checkbox97">Supports right-click and Ctrl + left-click to select multiple items</label></li>
|
||||
<li><input type="checkbox" id="checkbox98" checked="true" /><label for="checkbox98">Supports free dragging and dragging to adjust nodes</label></li>
|
||||
<li><input type="checkbox" id="checkbox99" checked="true" /><label for="checkbox99">Supports various node shapes</label></li>
|
||||
<li><input type="checkbox" id="checkbox100" checked="true" /><label for="checkbox100">Supports export to json, png, svg, pdf markdown, and import from json, xmind, markdown</label></li>
|
||||
<li><input type="checkbox" id="checkbox101" checked="true" /><label for="checkbox101">Supports mini map、support watermark</label></li>
|
||||
<li><input type="checkbox" id="checkbox102" checked="true" /><label for="checkbox102">Supports associative lines</label></li>
|
||||
</ul>
|
||||
<h2>Repository Catalog Introduction</h2>
|
||||
<p>1.<code>simple-mind-map</code></p>
|
||||
@@ -31,16 +32,16 @@ frameworks such as Vue and React, or without a framework.</p>
|
||||
<p>This is an online mind map built using the <code>simple-mind-map</code> library and based
|
||||
on <code>Vue2.x</code> and <code>ElementUI</code>. Features include:</p>
|
||||
<ul>
|
||||
<li><input type="checkbox" id="checkbox31" checked="true" /><label for="checkbox31">Toolbar, which supports inserting and deleting nodes, and editing node</label>
|
||||
<li><input type="checkbox" id="checkbox103" checked="true" /><label for="checkbox103">Toolbar, which supports inserting and deleting nodes, and editing node</label>
|
||||
images, icons, hyperlinks, notes, tags, and summaries</li>
|
||||
<li><input type="checkbox" id="checkbox32" checked="true" /><label for="checkbox32">Sidebar, with panels for basic style settings, node style settings,</label>
|
||||
<li><input type="checkbox" id="checkbox104" checked="true" /><label for="checkbox104">Sidebar, with panels for basic style settings, node style settings,</label>
|
||||
outline, theme selection, and structure selection</li>
|
||||
<li><input type="checkbox" id="checkbox33" checked="true" /><label for="checkbox33">Import and export functionality; data is saved in the browser's local</label>
|
||||
<li><input type="checkbox" id="checkbox105" checked="true" /><label for="checkbox105">Import and export functionality; data is saved in the browser's local</label>
|
||||
storage by default, but it also supports creating, opening, and editing
|
||||
local files on the computer directly</li>
|
||||
<li><input type="checkbox" id="checkbox34" checked="true" /><label for="checkbox34">Right-click menu, which supports operations such as expanding, collapsing,</label>
|
||||
<li><input type="checkbox" id="checkbox106" checked="true" /><label for="checkbox106">Right-click menu, which supports operations such as expanding, collapsing,</label>
|
||||
and organizing layout</li>
|
||||
<li><input type="checkbox" id="checkbox35" checked="true" /><label for="checkbox35">Bottom bar, which supports node and word count statistics, switching</label>
|
||||
<li><input type="checkbox" id="checkbox107" checked="true" /><label for="checkbox107">Bottom bar, which supports node and word count statistics, switching</label>
|
||||
between edit and read-only modes, zooming in and out, and switching to
|
||||
full screen, support mini map</li>
|
||||
</ul>
|
||||
@@ -69,6 +70,12 @@ full screen, support mini map</li>
|
||||
<p>3.<a href="https://github.com/hizzgdev/jsmind">jsmind</a>、<a href="https://github.com/ssshooter/mind-elixir-core">Mind-elixir</a>、<a href="https://github.com/ondras/my-mind">my-mind</a>、<a href="https://github.com/awehook/blink-mind">blink-mind</a>、<a href="https://github.com/luvsic3/remind">remind</a>、<a href="https://github.com/hellowuxin/vue3-mindmap">vue3-mindmap</a>、<a href="https://github.com/zyascend/ZMindMap">ZMindMap</a>...</p>
|
||||
<p>These open-source mind maps are also good, each with its own characteristics, but they also have certain drawbacks, such as stopping updates, average interface aesthetics, less functionality, relying on a certain framework, and so on.</p>
|
||||
<p>In summary, in open-source mind maps, it is difficult to find a better choice than <code>simple-mind-map</code>. Of course, <code>simple-mind-map</code> is far from being the best, and it also has many shortcomings, as you saw in the previous [special note]. However, <code>simple-mind-map</code> has always been in a fast iteration process, and we welcome you to join and improve it together.</p>
|
||||
<h2>Browser Compatibility</h2>
|
||||
<p>We recommend using the latest version of the <code>Chrome</code> browser.</p>
|
||||
<p>Limited testing situation:</p>
|
||||
<p>Normal operation: <code>360</code> extreme speed browser(v13.5.2036.0)、<code>opera</code> browser(v71.0.3770.284)。</p>
|
||||
<p>Abnormal operation: <code>Firefox</code>(v98.0.2), Node content cannot be displayed in rich text mode. If you want to support the <code>Firefox</code> browser, please use non rich text mode.</p>
|
||||
<p>Unsupported: <code>IE</code> browser.</p>
|
||||
<h2>License</h2>
|
||||
<p><a href="https://opensource.org/licenses/MIT">MIT</a></p>
|
||||
<h2>Invite the author to a cup of coffee</h2>
|
||||
|
||||
@@ -17,7 +17,7 @@ If you are using the file in the format of `umd`, you can obtain it in the follo
|
||||
```
|
||||
|
||||
```js
|
||||
MindMap.markdown
|
||||
simpleMindMap.markdown
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<p>If you are using the file in the format of <code>umd</code>, you can obtain it in the following way:</p>
|
||||
<pre class="hljs"><code><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"simple-mind-map/dist/simpleMindMap.umd.min.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
|
||||
</code></pre>
|
||||
<pre class="hljs"><code>MindMap.markdown
|
||||
<pre class="hljs"><code>simpleMindMap.markdown
|
||||
</code></pre>
|
||||
<h2>Methods</h2>
|
||||
<h3>transformToMarkdown(data)</h3>
|
||||
|
||||
@@ -10,9 +10,11 @@ By default, node editing can only uniformly apply styles to all text in the node
|
||||
|
||||
The principle of this plugin is to use [Quill](https://github.com/quilljs/quill) editor implements rich text editing, and then uses the edited `DOM` node directly as the text data of the node, and embeds the `DOM` node through the `svg` `foreignObject` tag during rendering.
|
||||
|
||||
This also caused a problem, that is, the function of exporting as a picture was affected, The original principle of exporting `svg` as an image is very simple, Get the `svg` string, and then create the `blob` data of the `type=image/svg+xml` type. Then use the `URL.createObjectURL` method to generate the `data:url` data. Then create a `Image` tag, use the `data:url` as the `src` of the image, and finally draw the image on the `canvas` object for export, However, after testing, when the `DOM` node is embedded in the `svg`, this method of export will cause errors, and after trying many ways, the perfect export effect cannot be achieved, The current method is to traverse the `foreignObject` node in `svg`, using [html2canvas](https://github.com/niklasvh/html2canvas) Convert the `DOM` node in the `foreignObject` node into an image and then replace the `foreignObject` node. This method can work, but it is very time-consuming. Because the `html2canvas` conversion takes a long time, it takes about 2 seconds to convert a node. This leads to the more nodes, the slower the conversion time. Therefore, it is recommended not to use this plugin if you cannot tolerate the long time of export.
|
||||
> The following prompts exist in versions prior to v0.5.6:
|
||||
>
|
||||
> This also caused a problem, that is, the function of exporting as a picture was affected, The original principle of exporting `svg` as an image is very simple, Get the `svg` string, and then create the `blob` data of the `type=image/svg+xml` type. Then use the `URL.createObjectURL` method to generate the `data:url` data. Then create a `Image` tag, use the `data:url` as the `src` of the image, and finally draw the image on the `canvas` object for export, However, after testing, when the `DOM` node is embedded in the `svg`, this method of export will cause errors, and after trying many ways, the perfect export effect cannot be achieved, The current method is to traverse the `foreignObject` node in `svg`, using [html2canvas](https://github.com/niklasvh/html2canvas) Convert the `DOM` node in the `foreignObject` node into an image and then replace the `foreignObject` node. This method can work, but it is very time-consuming. Because the `html2canvas` conversion takes a long time, it takes about 2 seconds to convert a node. This leads to the more nodes, the slower the conversion time. Therefore, it is recommended not to use this plugin if you cannot tolerate the long time of export.
|
||||
|
||||
If you have a better way, please leave a message.
|
||||
The version of `v0.5.7+` directly uses `html2canvas` to convert the entire `svg`, which is no longer an issue with speed. However, there is currently a bug where the color of the node does not take effect after export.
|
||||
|
||||
## Register
|
||||
|
||||
|
||||
@@ -10,8 +10,11 @@
|
||||
<p>This plugin provides the ability to edit rich text of nodes, and takes effect after registration.</p>
|
||||
<p>By default, node editing can only uniformly apply styles to all text in the node. This plugin can support rich text editing effects. Currently, it supports bold, italic, underline, strikethrough, font, font size, color, and backgroundColor. Underline and line height are not supported.</p>
|
||||
<p>The principle of this plugin is to use <a href="https://github.com/quilljs/quill">Quill</a> editor implements rich text editing, and then uses the edited <code>DOM</code> node directly as the text data of the node, and embeds the <code>DOM</code> node through the <code>svg</code> <code>foreignObject</code> tag during rendering.</p>
|
||||
<blockquote>
|
||||
<p>The following prompts exist in versions prior to v0.5.6:</p>
|
||||
<p>This also caused a problem, that is, the function of exporting as a picture was affected, The original principle of exporting <code>svg</code> as an image is very simple, Get the <code>svg</code> string, and then create the <code>blob</code> data of the <code>type=image/svg+xml</code> type. Then use the <code>URL.createObjectURL</code> method to generate the <code>data:url</code> data. Then create a <code>Image</code> tag, use the <code>data:url</code> as the <code>src</code> of the image, and finally draw the image on the <code>canvas</code> object for export, However, after testing, when the <code>DOM</code> node is embedded in the <code>svg</code>, this method of export will cause errors, and after trying many ways, the perfect export effect cannot be achieved, The current method is to traverse the <code>foreignObject</code> node in <code>svg</code>, using <a href="https://github.com/niklasvh/html2canvas">html2canvas</a> Convert the <code>DOM</code> node in the <code>foreignObject</code> node into an image and then replace the <code>foreignObject</code> node. This method can work, but it is very time-consuming. Because the <code>html2canvas</code> conversion takes a long time, it takes about 2 seconds to convert a node. This leads to the more nodes, the slower the conversion time. Therefore, it is recommended not to use this plugin if you cannot tolerate the long time of export.</p>
|
||||
<p>If you have a better way, please leave a message.</p>
|
||||
</blockquote>
|
||||
<p>The version of <code>v0.5.7+</code> directly uses <code>html2canvas</code> to convert the entire <code>svg</code>, which is no longer an issue with speed. However, there is currently a bug where the color of the node does not take effect after export.</p>
|
||||
<h2>Register</h2>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> MindMap <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map'</span>
|
||||
<span class="hljs-keyword">import</span> RichText <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/RichText.js'</span>
|
||||
|
||||
@@ -91,6 +91,7 @@ If you only use library, you don't need to read this section.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/wanglin2/mind-map.git
|
||||
cd mind-map
|
||||
cd simple-mind-map
|
||||
npm i
|
||||
npm link
|
||||
|
||||
@@ -64,6 +64,7 @@ compile this dependency:</p>
|
||||
<p>If you only use library, you don't need to read this section.</p>
|
||||
<h3>Local Development</h3>
|
||||
<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
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
# Participate in translation
|
||||
|
||||
Thanks for the first version English translation provided by [Emircan ERKUL](https://github.com/emircanerkul).
|
||||
> Thanks for the first version English translation provided by [Emircan ERKUL](https://github.com/emircanerkul).
|
||||
>
|
||||
> Due to limited energy, most translations currently use machine translation, so accuracy is inevitably problematic.
|
||||
>
|
||||
> At present, the 【Course】 section is not translated. If you are interested, please join us.
|
||||
|
||||
If you want to participate in the translation of this document, you can clone this repository first.
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Participate in translation</h1>
|
||||
<blockquote>
|
||||
<p>Thanks for the first version English translation provided by <a href="https://github.com/emircanerkul">Emircan ERKUL</a>.</p>
|
||||
<p>Due to limited energy, most translations currently use machine translation, so accuracy is inevitably problematic.</p>
|
||||
<p>At present, the 【Course】 section is not translated. If you are interested, please join us.</p>
|
||||
</blockquote>
|
||||
<p>If you want to participate in the translation of this document, you can clone this repository first.</p>
|
||||
<p>The translated documents are in the <code>/web/src/pages/Doc/</code> directory, and currently support English(<code>en</code>) and Simplified Chinese(<code>zh</code>).</p>
|
||||
<p>If you are adding a new language type, you can create a new directory under the <code>/web/src/pages/Doc/</code> directory, Then create a folder for each chapter, You can also directly copy all chapter directories under the existing language directory for translation, Note that you only need to write the <code>index.md</code> file, The <code>index.vue</code> file under the chapter directory is automatically generated by the script according to <code>index.md</code>.</p>
|
||||
|
||||
@@ -129,6 +129,16 @@ Measure the width and height of the text, return value:
|
||||
{ width, height }
|
||||
```
|
||||
|
||||
#### getTextFromHtml(html)
|
||||
|
||||
Extract plain text content from an HTML string.
|
||||
|
||||
#### readBlob(blob)
|
||||
|
||||
> v0.5.9+
|
||||
|
||||
Convert `blob` data to `data:url` data.
|
||||
|
||||
## Simulate CSS background in Canvas
|
||||
|
||||
Import:
|
||||
@@ -157,4 +167,50 @@ drawBackgroundImageToCanvas(ctx, width, height, img, {
|
||||
// success
|
||||
}
|
||||
})
|
||||
```
|
||||
```
|
||||
|
||||
## LRU cache class
|
||||
|
||||
> v0.5.10+
|
||||
|
||||
Import:
|
||||
|
||||
```js
|
||||
import Lru from 'simple-mind-map/src/utils/Lru.js'
|
||||
```
|
||||
|
||||
### Constructor
|
||||
|
||||
```js
|
||||
let lru = new Lru(max)
|
||||
```
|
||||
|
||||
`max`: Specify the maximum number of caches.
|
||||
|
||||
### Instance properties
|
||||
|
||||
#### size
|
||||
|
||||
The current number of caches.
|
||||
|
||||
#### pool
|
||||
|
||||
Get cache pool.
|
||||
|
||||
### Instance methods
|
||||
|
||||
#### add(key, value)
|
||||
|
||||
Add cache.
|
||||
|
||||
#### delete(key)
|
||||
|
||||
Delete cache.
|
||||
|
||||
#### has(key)
|
||||
|
||||
Check if a cache exists.
|
||||
|
||||
#### get(key)
|
||||
|
||||
Gets the value of a cache.
|
||||
@@ -82,6 +82,13 @@ and copying the <code>data</code> of the data object, example:</p>
|
||||
<p>Measure the width and height of the text, return value:</p>
|
||||
<pre class="hljs"><code>{ width, height }
|
||||
</code></pre>
|
||||
<h4>getTextFromHtml(html)</h4>
|
||||
<p>Extract plain text content from an HTML string.</p>
|
||||
<h4>readBlob(blob)</h4>
|
||||
<blockquote>
|
||||
<p>v0.5.9+</p>
|
||||
</blockquote>
|
||||
<p>Convert <code>blob</code> data to <code>data:url</code> data.</p>
|
||||
<h2>Simulate CSS background in Canvas</h2>
|
||||
<p>Import:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> drawBackgroundImageToCanvas <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/utils/simulateCSSBackgroundInCanvas'</span>
|
||||
@@ -105,6 +112,31 @@ drawBackgroundImageToCanvas(ctx, width, height, img, {
|
||||
}
|
||||
})
|
||||
</code></pre>
|
||||
<h2>LRU cache class</h2>
|
||||
<blockquote>
|
||||
<p>v0.5.10+</p>
|
||||
</blockquote>
|
||||
<p>Import:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> Lru <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/utils/Lru.js'</span>
|
||||
</code></pre>
|
||||
<h3>Constructor</h3>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">let</span> lru = <span class="hljs-keyword">new</span> Lru(max)
|
||||
</code></pre>
|
||||
<p><code>max</code>: Specify the maximum number of caches.</p>
|
||||
<h3>Instance properties</h3>
|
||||
<h4>size</h4>
|
||||
<p>The current number of caches.</p>
|
||||
<h4>pool</h4>
|
||||
<p>Get cache pool.</p>
|
||||
<h3>Instance methods</h3>
|
||||
<h4>add(key, value)</h4>
|
||||
<p>Add cache.</p>
|
||||
<h4>delete(key)</h4>
|
||||
<p>Delete cache.</p>
|
||||
<h4>has(key)</h4>
|
||||
<p>Check if a cache exists.</p>
|
||||
<h4>get(key)</h4>
|
||||
<p>Gets the value of a cache.</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -17,16 +17,15 @@ If you are using the file in the format of `umd`, you can obtain it in the follo
|
||||
```
|
||||
|
||||
```js
|
||||
MindMap.xmind
|
||||
simpleMindMap.xmind
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
### 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
|
||||
|
||||
@@ -11,21 +11,19 @@
|
||||
<p>If you are using the file in the format of <code>umd</code>, you can obtain it in the following way:</p>
|
||||
<pre class="hljs"><code><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"simple-mind-map/dist/simpleMindMap.umd.min.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
|
||||
</code></pre>
|
||||
<pre class="hljs"><code>MindMap.xmind
|
||||
<pre class="hljs"><code>simpleMindMap.xmind
|
||||
</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>
|
||||
|
||||
@@ -25,6 +25,7 @@ export default [
|
||||
{ path: 'course16', title: '如何渲染富文本的悬浮工具栏' },
|
||||
{ path: 'course17', title: '导入和导出' },
|
||||
{ path: 'course18', title: '如何持久化数据' },
|
||||
{ path: 'course19', title: '插入和扩展节点图标' },
|
||||
{ path: 'doExport', title: 'Export 插件' },
|
||||
{ path: 'drag', title: 'Drag插件' },
|
||||
{ path: 'introduction', title: '简介' },
|
||||
@@ -41,7 +42,9 @@ export default [
|
||||
{ path: 'utils', title: '内置工具方法' },
|
||||
{ path: 'view', title: 'View实例' },
|
||||
{ path: 'watermark', title: 'Watermark插件' },
|
||||
{ path: 'xmind', title: 'XMind解析' }
|
||||
{ path: 'xmind', title: 'XMind解析' },
|
||||
{ path: 'deploy', title: '部署' },
|
||||
{ path: 'client', title: '客户端' }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -68,7 +71,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' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
> 调整关联线控制点的功能从v0.4.6+开始支持
|
||||
|
||||
该插件用于支持添加关联线。
|
||||
> 关联性支持文本编辑从v0.5.11+开始支持
|
||||
|
||||
该插件目前功能还不完善,不支持在关联线上添加文字。
|
||||
该插件用于支持添加关联线。
|
||||
|
||||
## 注册
|
||||
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
<blockquote>
|
||||
<p>调整关联线控制点的功能从v0.4.6+开始支持</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>关联性支持文本编辑从v0.5.11+开始支持</p>
|
||||
</blockquote>
|
||||
<p>该插件用于支持添加关联线。</p>
|
||||
<p>该插件目前功能还不完善,不支持在关联线上添加文字。</p>
|
||||
<h2>注册</h2>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> MindMap <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map'</span>
|
||||
<span class="hljs-keyword">import</span> AssociativeLine <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/AssociativeLine.js'</span>
|
||||
|
||||
@@ -1,5 +1,63 @@
|
||||
# Changelog
|
||||
|
||||
## 0.5.11
|
||||
|
||||
新增:支持关联性文本编辑。
|
||||
|
||||
优化:优化主题配置更新,改变不涉及节点大小的配置不触发节点重新计算。
|
||||
|
||||
## 0.5.10
|
||||
|
||||
新增:使用LRU缓存算法优化节点复用逻辑。
|
||||
|
||||
## 0.5.10-fix.1
|
||||
|
||||
修复:修复导入出错的问题。
|
||||
|
||||
## 0.5.10-fix.2
|
||||
|
||||
修复:修复富文本模式下,切换主题、导入数据后没有触发数据改变的问题。
|
||||
|
||||
新增:新增三种主题。
|
||||
|
||||
## 0.5.9
|
||||
|
||||
修改:统一导出方法的格式,使用`FileReader`代替`URL.createObjectURL`转换`blob`数据。
|
||||
|
||||
## 0.5.8
|
||||
|
||||
优化:1.节点位置没有变化不触发位置设置。 2.展开收起状态没有变化不触发按钮更新。
|
||||
|
||||
新增:1.默认改为鼠标移上节点才显示展开收起按钮。 2.支持扩展节点可插入的图标列表。
|
||||
|
||||
## 0.5.7
|
||||
|
||||
破坏性更新:富文本模式下导出png改为使用html2canvas转换整个svg,大幅提高导出速度,不过html2canvas存在一个bug,foreignObject元素中的dom节点内联的文字颜色无法识别,所以导出节点的文字颜色是固定的,不过相对于之前的导出基本不可用状态,目前至少能快速顺利的导出。
|
||||
|
||||
优化:优化富文本节点编辑体验。
|
||||
|
||||
新增:富文本模式下,导入数据、初始化数据、切换主题场景节点样式支持跟随主题变化。
|
||||
|
||||
## 0.5.6
|
||||
|
||||
修复:1.修复短时间快速多次渲染时节点位置错乱的问题。 2.修复节点正在编辑中时拖动画布导致编辑框和节点分离的问题。
|
||||
|
||||
新增:1.添加最大历史记录数限制。
|
||||
|
||||
## 0.5.5
|
||||
|
||||
新增:1.支持配置导出为png、svg、pdf时的内边距。 2.支持配置节点文本编辑框、节点备注浮层元素的z-index。 3.支持点击画布外的区域结束节点编辑状态。
|
||||
|
||||
## 0.5.5-fix.1
|
||||
|
||||
修复:1.修复节点在画布外编辑时编辑框也在画布外的问题。 2.修改结构后复位变换,防止存在缩放时切换结构后第一次拖动时会发生位置突变的问题。
|
||||
|
||||
优化:1.节点多选时只要节点和选区存在交叉即认为被选中。
|
||||
|
||||
## 0.5.5-fix.2
|
||||
|
||||
修复:1.修复小地图报错。
|
||||
|
||||
## 0.5.4
|
||||
|
||||
新增:1.添加新主题。 2.新增时间轴和鱼骨结构。
|
||||
@@ -8,6 +66,10 @@
|
||||
|
||||
优化:1.优化组织结构图布局。2.优化目录组织图布局。
|
||||
|
||||
## 0.5.4-fix.1
|
||||
|
||||
优化:1.优化鱼骨图布局。
|
||||
|
||||
## 0.5.3
|
||||
|
||||
修复:1.修复富文本模式下,如果选择了多个节点时设置文本样式,会将所有多选节点的文本改成最后一个多选节点的文本的问题。
|
||||
|
||||
@@ -1,10 +1,41 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Changelog</h1>
|
||||
<h2>0.5.11</h2>
|
||||
<p>新增:支持关联性文本编辑。</p>
|
||||
<p>优化:优化主题配置更新,改变不涉及节点大小的配置不触发节点重新计算。</p>
|
||||
<h2>0.5.10</h2>
|
||||
<p>新增:使用LRU缓存算法优化节点复用逻辑。</p>
|
||||
<h2>0.5.10-fix.1</h2>
|
||||
<p>修复:修复导入出错的问题。</p>
|
||||
<h2>0.5.10-fix.2</h2>
|
||||
<p>修复:修复富文本模式下,切换主题、导入数据后没有触发数据改变的问题。</p>
|
||||
<p>新增:新增三种主题。</p>
|
||||
<h2>0.5.9</h2>
|
||||
<p>修改:统一导出方法的格式,使用<code>FileReader</code>代替<code>URL.createObjectURL</code>转换<code>blob</code>数据。</p>
|
||||
<h2>0.5.8</h2>
|
||||
<p>优化:1.节点位置没有变化不触发位置设置。 2.展开收起状态没有变化不触发按钮更新。</p>
|
||||
<p>新增:1.默认改为鼠标移上节点才显示展开收起按钮。 2.支持扩展节点可插入的图标列表。</p>
|
||||
<h2>0.5.7</h2>
|
||||
<p>破坏性更新:富文本模式下导出png改为使用html2canvas转换整个svg,大幅提高导出速度,不过html2canvas存在一个bug,foreignObject元素中的dom节点内联的文字颜色无法识别,所以导出节点的文字颜色是固定的,不过相对于之前的导出基本不可用状态,目前至少能快速顺利的导出。</p>
|
||||
<p>优化:优化富文本节点编辑体验。</p>
|
||||
<p>新增:富文本模式下,导入数据、初始化数据、切换主题场景节点样式支持跟随主题变化。</p>
|
||||
<h2>0.5.6</h2>
|
||||
<p>修复:1.修复短时间快速多次渲染时节点位置错乱的问题。 2.修复节点正在编辑中时拖动画布导致编辑框和节点分离的问题。</p>
|
||||
<p>新增:1.添加最大历史记录数限制。</p>
|
||||
<h2>0.5.5</h2>
|
||||
<p>新增:1.支持配置导出为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.添加新主题。 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>
|
||||
|
||||
80
web/src/pages/Doc/zh/client/index.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# 客户端
|
||||
|
||||
本项目也提供了客户端版本,使用[Electron](https://www.electronjs.org/)开发。支持`Windows`、`Mac`及`Linux`。
|
||||
|
||||
目前功能比较简单:
|
||||
|
||||
1.支持新建、打开文件进行编辑;
|
||||
|
||||
2.支持查看最近编辑文件列表;
|
||||
|
||||
3.支持文件的复制、删除、重命名;
|
||||
|
||||
## 下载
|
||||
|
||||
你可以直接下载对应的客户端安装使用,提供了两个下载地址:
|
||||
|
||||
Github:[releases](https://github.com/wanglin2/mind-map/releases)。
|
||||
|
||||
百度云盘:[地址](https://pan.baidu.com/s/1huasEbKsGNH2Af68dvWiOg?pwd=3bp3)。
|
||||
|
||||
## 开发
|
||||
|
||||
如果有需要,你也可以进行二次开发。
|
||||
|
||||
### clone
|
||||
|
||||
```bash
|
||||
git clone https://github.com/wanglin2/mind-map.git
|
||||
cd mind-map
|
||||
git checkout electron
|
||||
```
|
||||
|
||||
### 启动服务
|
||||
|
||||
在项目根目录下执行:
|
||||
|
||||
```bash
|
||||
cd simple-mind-map
|
||||
npm i
|
||||
npm link
|
||||
cd ..
|
||||
cd web
|
||||
npm i
|
||||
npm link simple-mind-map
|
||||
npm run electron:serve
|
||||
```
|
||||
|
||||
### 打包客户端
|
||||
|
||||
你至少需要两台电脑,一台`Windows`和一台`Mac`。
|
||||
|
||||
打包`Windows`应用:
|
||||
|
||||
```bash
|
||||
npm run electron:build-win
|
||||
```
|
||||
|
||||
打包`Mac`应用:
|
||||
|
||||
```bash
|
||||
npm run electron:build-mac
|
||||
```
|
||||
|
||||
打包`Linux`应用:
|
||||
|
||||
```bash
|
||||
npm run electron:build-linux
|
||||
```
|
||||
|
||||
打包全部应用:
|
||||
|
||||
```bash
|
||||
npm run electron:build-all
|
||||
```
|
||||
|
||||
根据你的电脑系统自动打包:
|
||||
|
||||
```bash
|
||||
npm run electron:build
|
||||
```
|
||||
60
web/src/pages/Doc/zh/client/index.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>客户端</h1>
|
||||
<p>本项目也提供了客户端版本,使用<a href="https://www.electronjs.org/">Electron</a>开发。支持<code>Windows</code>、<code>Mac</code>及<code>Linux</code>。</p>
|
||||
<p>目前功能比较简单:</p>
|
||||
<p>1.支持新建、打开文件进行编辑;</p>
|
||||
<p>2.支持查看最近编辑文件列表;</p>
|
||||
<p>3.支持文件的复制、删除、重命名;</p>
|
||||
<h2>下载</h2>
|
||||
<p>你可以直接下载对应的客户端安装使用,提供了两个下载地址:</p>
|
||||
<p>Github:<a href="https://github.com/wanglin2/mind-map/releases">releases</a>。</p>
|
||||
<p>百度云盘:<a href="https://pan.baidu.com/s/1huasEbKsGNH2Af68dvWiOg?pwd=3bp3">地址</a>。</p>
|
||||
<h2>开发</h2>
|
||||
<p>如果有需要,你也可以进行二次开发。</p>
|
||||
<h3>clone</h3>
|
||||
<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
|
||||
git checkout electron
|
||||
</code></pre>
|
||||
<h3>启动服务</h3>
|
||||
<p>在项目根目录下执行:</p>
|
||||
<pre class="hljs"><code><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
|
||||
npm run electron:serve
|
||||
</code></pre>
|
||||
<h3>打包客户端</h3>
|
||||
<p>你至少需要两台电脑,一台<code>Windows</code>和一台<code>Mac</code>。</p>
|
||||
<p>打包<code>Windows</code>应用:</p>
|
||||
<pre class="hljs"><code>npm run electron:build-win
|
||||
</code></pre>
|
||||
<p>打包<code>Mac</code>应用:</p>
|
||||
<pre class="hljs"><code>npm run electron:build-mac
|
||||
</code></pre>
|
||||
<p>打包<code>Linux</code>应用:</p>
|
||||
<pre class="hljs"><code>npm run electron:build-linux
|
||||
</code></pre>
|
||||
<p>打包全部应用:</p>
|
||||
<pre class="hljs"><code>npm run electron:build-all
|
||||
</code></pre>
|
||||
<p>根据你的电脑系统自动打包:</p>
|
||||
<pre class="hljs"><code>npm run electron:build
|
||||
</code></pre>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -28,7 +28,7 @@ const mindMap = new MindMap({
|
||||
| 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(组织结构图)、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+黑金) | |
|
||||
| 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+黑金)、avocado(v.5.10-fix.2+牛油果)、autumn(v.5.10-fix.2+秋天)、orangeJuice(v.5.10-fix.2+橙汁) | |
|
||||
| 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 | 放大缩小的增量比例 | |
|
||||
| maxTag | Number | 5 | 节点里最多显示的标签数量,多余的会被丢弃 | |
|
||||
@@ -53,6 +53,15 @@ 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 | 最大历史记录数 | |
|
||||
| alwaysShowExpandBtn(v0.5.8+) | Boolean | false | 是否一直显示节点的展开收起按钮,默认为鼠标移上去和激活时才显示 | |
|
||||
| iconList(v0.5.8+) | Array | [] | 扩展节点可插入的图标,数组的每一项为一个对象,对象详细结构请参考下方【图标配置】表格 | |
|
||||
| maxNodeCacheCount(v0.5.10+) | Number | 1000 | 节点最大缓存数量。为了优化性能,内部会维护一个节点缓存池,用来复用节点,通过该属性可以指定池的最大缓存数量 | |
|
||||
|
||||
### 水印配置
|
||||
|
||||
@@ -64,7 +73,13 @@ const mindMap = new MindMap({
|
||||
| angle | Number | 30 | 水印的倾斜角度,范围:[0, 90] |
|
||||
| textStyle | Object | {color: '#999', opacity: 0.5, fontSize: 14} | 水印文字样式 |
|
||||
|
||||
### 图标配置
|
||||
|
||||
| 字段名称 | 类型 | 默认值 | 描述 |
|
||||
| ----------- | ------ | ------------------------------------------- | ------------------------------------ |
|
||||
| name | String | | 图标分组的名称 |
|
||||
| type | String | | 图标分组的值 |
|
||||
| list | Array | | 分组下的图标列表,数组的每一项为一个对象,`{ name: '', icon: '' }`,`name`代表图标的名称,`icon`代表图标,可以是`svg`图标,比如`<svg ...><path></path></svg>`,也可以是图片`url`,或者是`base64`图标,比如`data:image/png;base64,...` |
|
||||
|
||||
## 静态方法
|
||||
|
||||
@@ -123,10 +138,14 @@ mindMap.setTheme('主题名称')
|
||||
|
||||
## 实例方法
|
||||
|
||||
### getSvgData()
|
||||
### getSvgData({ paddingX = 0, paddingY = 0 })
|
||||
|
||||
> v0.3.0+
|
||||
|
||||
`paddingX`:水平内边距
|
||||
|
||||
`paddingY`:垂直内边距
|
||||
|
||||
获取`svg`数据,返回一个对象,详细结构如下:
|
||||
|
||||
```js
|
||||
@@ -338,6 +357,18 @@ mindMap.updateConfig({
|
||||
|
||||
`fileName`:(v0.1.6+)导出文件的名称,默认为`思维导图`
|
||||
|
||||
如果是导出为`png`,那么可以传递第四个参数:
|
||||
|
||||
`transparent`:v0.5.7+, `Boolean`,默认为`false`,指定导出图片的背景是否是透明的
|
||||
|
||||
如果是导出为`svg`,那么可以传递第四个参数:
|
||||
|
||||
`plusCssText`:附加的`css`样式,如果`svg`中存在`dom`节点,想要设置一些针对节点的样式可以通过这个参数传入
|
||||
|
||||
如果是导出为`json`或`smm`,那么可以传递第四个参数:
|
||||
|
||||
`withConfig`:`Boolean`,默认为`true`,指定导出的数据中是否包含配置数据,否则只导出纯节点树数据
|
||||
|
||||
### toPos(x, y)
|
||||
|
||||
> v0.1.5+
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<td>theme</td>
|
||||
<td>String</td>
|
||||
<td>default</td>
|
||||
<td>主题,可选列表: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+黑金)</td>
|
||||
<td>主题,可选列表: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+黑金)、avocado(v.5.10-fix.2+牛油果)、autumn(v.5.10-fix.2+秋天)、orangeJuice(v.5.10-fix.2+橙汁)</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -231,6 +231,69 @@
|
||||
<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>
|
||||
<tr>
|
||||
<td>alwaysShowExpandBtn(v0.5.8+)</td>
|
||||
<td>Boolean</td>
|
||||
<td>false</td>
|
||||
<td>是否一直显示节点的展开收起按钮,默认为鼠标移上去和激活时才显示</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>iconList(v0.5.8+)</td>
|
||||
<td>Array</td>
|
||||
<td>[]</td>
|
||||
<td>扩展节点可插入的图标,数组的每一项为一个对象,对象详细结构请参考下方【图标配置】表格</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>maxNodeCacheCount(v0.5.10+)</td>
|
||||
<td>Number</td>
|
||||
<td>1000</td>
|
||||
<td>节点最大缓存数量。为了优化性能,内部会维护一个节点缓存池,用来复用节点,通过该属性可以指定池的最大缓存数量</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>水印配置</h3>
|
||||
@@ -276,6 +339,37 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>图标配置</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>字段名称</th>
|
||||
<th>类型</th>
|
||||
<th>默认值</th>
|
||||
<th>描述</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>name</td>
|
||||
<td>String</td>
|
||||
<td></td>
|
||||
<td>图标分组的名称</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>type</td>
|
||||
<td>String</td>
|
||||
<td></td>
|
||||
<td>图标分组的值</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>list</td>
|
||||
<td>Array</td>
|
||||
<td></td>
|
||||
<td>分组下的图标列表,数组的每一项为一个对象,<code>{ name: '', icon: '' }</code>,<code>name</code>代表图标的名称,<code>icon</code>代表图标,可以是<code>svg</code>图标,比如<code><svg ...><path></path></svg></code>,也可以是图片<code>url</code>,或者是<code>base64</code>图标,比如<code>data:image/png;base64,...</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h2>静态方法</h2>
|
||||
<h3>defineTheme(name, config)</h3>
|
||||
<blockquote>
|
||||
@@ -319,10 +413,12 @@ mindMap.setTheme(<span class="hljs-string">'主题名称'</span>)
|
||||
</blockquote>
|
||||
<p>当前注册的所有插件列表。</p>
|
||||
<h2>实例方法</h2>
|
||||
<h3>getSvgData()</h3>
|
||||
<h3>getSvgData({ paddingX = 0, paddingY = 0 })</h3>
|
||||
<blockquote>
|
||||
<p>v0.3.0+</p>
|
||||
</blockquote>
|
||||
<p><code>paddingX</code>:水平内边距</p>
|
||||
<p><code>paddingY</code>:垂直内边距</p>
|
||||
<p>获取<code>svg</code>数据,返回一个对象,详细结构如下:</p>
|
||||
<pre class="hljs"><code>{
|
||||
svg, <span class="hljs-comment">// Element,思维导图图形的整体svg元素,包括:svg(画布容器)、g(实际的思维导图组)</span>
|
||||
@@ -761,6 +857,12 @@ mindMap.setTheme(<span class="hljs-string">'主题名称'</span>)
|
||||
<p><code>type</code>:要导出的类型,可选值:png、svg、json、pdf(v0.2.1+)、smm(本质也是json)</p>
|
||||
<p><code>isDownload</code>:是否需要直接触发下载,布尔值,默认为<code>false</code></p>
|
||||
<p><code>fileName</code>:(v0.1.6+)导出文件的名称,默认为<code>思维导图</code></p>
|
||||
<p>如果是导出为<code>png</code>,那么可以传递第四个参数:</p>
|
||||
<p><code>transparent</code>:v0.5.7+, <code>Boolean</code>,默认为<code>false</code>,指定导出图片的背景是否是透明的</p>
|
||||
<p>如果是导出为<code>svg</code>,那么可以传递第四个参数:</p>
|
||||
<p><code>plusCssText</code>:附加的<code>css</code>样式,如果<code>svg</code>中存在<code>dom</code>节点,想要设置一些针对节点的样式可以通过这个参数传入</p>
|
||||
<p>如果是导出为<code>json</code>或<code>smm</code>,那么可以传递第四个参数:</p>
|
||||
<p><code>withConfig</code>:<code>Boolean</code>,默认为<code>true</code>,指定导出的数据中是否包含配置数据,否则只导出纯节点树数据</p>
|
||||
<h3>toPos(x, y)</h3>
|
||||
<blockquote>
|
||||
<p>v0.1.5+</p>
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
}
|
||||
```
|
||||
|
||||
`icon`目前只能使用内置的图标,完整图标可以在[icons.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js)文件中查看。
|
||||
`icon`可以使用内置的图标,完整图标可以在[icons.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js)文件中查看。也可以扩展图标,参考[扩展图标](https://wanglin2.github.io/mind-map/#/doc/zh/course19/%E6%89%A9%E5%B1%95%E5%9B%BE%E6%A0%87)。
|
||||
|
||||
创建实例时还支持传递其他很多选项参数,完整选项列表可以在[实例化选项](https://wanglin2.github.io/mind-map/#/doc/zh/constructor/%E5%AE%9E%E4%BE%8B%E5%8C%96%E9%80%89%E9%A1%B9)查看。
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<span class="hljs-attr">children</span>: []<span class="hljs-comment">// 子节点</span>
|
||||
}
|
||||
</code></pre>
|
||||
<p><code>icon</code>目前只能使用内置的图标,完整图标可以在<a href="https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js">icons.js</a>文件中查看。</p>
|
||||
<p><code>icon</code>可以使用内置的图标,完整图标可以在<a href="https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js">icons.js</a>文件中查看。也可以扩展图标,参考<a href="https://wanglin2.github.io/mind-map/#/doc/zh/course19/%E6%89%A9%E5%B1%95%E5%9B%BE%E6%A0%87">扩展图标</a>。</p>
|
||||
<p>创建实例时还支持传递其他很多选项参数,完整选项列表可以在<a href="https://wanglin2.github.io/mind-map/#/doc/zh/constructor/%E5%AE%9E%E4%BE%8B%E5%8C%96%E9%80%89%E9%A1%B9">实例化选项</a>查看。</p>
|
||||
<p>这样得到的思维导图可以通过鼠标和快捷键进行操作,比如单击某个节点可以激活它,双击某个节点可以编辑节点文本,按下<code>Tab</code>键会给当前激活的节点添加一个子节点,按下<code>Enter</code>键会给当前激活的节点添加一个兄弟节点等等,完整的快捷键列表可以参考<a href="https://github.com/wanglin2/mind-map/blob/main/web/src/config/zh.js#L246">快捷键列表</a>。</p>
|
||||
<p>当然有些功能还是需要UI界面的,比如图标列表、编辑超链接等等,需要注意的是<code>simple-mind-map</code>库并不包含UI界面,所以需要你自己开发,然后通过<code>simple-mind-map</code>提供的相关API来操作,本教程的其他章节会向你介绍如何使用。</p>
|
||||
|
||||
@@ -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,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=" />
|
||||
@@ -4,7 +4,66 @@
|
||||
<blockquote>
|
||||
<p>要支持节点富文本编辑需要使用富文本插件</p>
|
||||
</blockquote>
|
||||
|
||||
<p>如果开启了节点富文本编辑,那么可以对节点内的部分文本应用样式,一般当选中文本时上方会出现一个工具栏,有加粗、斜体、改变颜色等等的按钮。</p>
|
||||
<p>首先要监听<code>rich_text_selection_change</code>事件,也就是选中文本的事件:</p>
|
||||
<pre class="hljs"><code>mindMap.on(<span class="hljs-string">'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,251 @@
|
||||
# 导入和导出
|
||||
|
||||
编写中。。。
|
||||
## 导出
|
||||
|
||||
> 要使用导出功能需要使用导出插件。
|
||||
|
||||
目前支持导出为`.smm`、`.json`、`.svg`、`.png`、`.pdf`、`.md`文件。
|
||||
|
||||
`.smm`是`simple-mind-map`自己定义的一种文件,其实就是`json`文件,换了一个扩展名而已。
|
||||
|
||||
导出直接调用`export`方法即可:
|
||||
|
||||
```js
|
||||
mindMap.export(type, isDownload, fileName, ...)
|
||||
```
|
||||
|
||||
`type`:文件类型
|
||||
|
||||
`isDownload`:传`true`会触发下载,`false`则不会,函数会返回导出文件的数据,`data:url`格式,你可以自行下载,`pdf`不支持该参数,默认会直接下载。
|
||||
|
||||
`fileName`:下载的文件名称
|
||||
|
||||
### 导出为smm、json
|
||||
|
||||
这两种文件的导出是一样的:
|
||||
|
||||
```js
|
||||
mindMap.export(type, isDownload, fileName, withConfig)
|
||||
```
|
||||
|
||||
`withConfig`指定导出的数据中是否要包含节点数据外的配置数据,比如使用的布局、主题等,传`true`,导出的结构如下:
|
||||
|
||||
```js
|
||||
{
|
||||
layout,
|
||||
root,
|
||||
theme: {
|
||||
template,
|
||||
config
|
||||
},
|
||||
view
|
||||
}
|
||||
```
|
||||
|
||||
如果传`false`,导出的数据只有`root`部分,也就是纯节点树。
|
||||
|
||||
示例:
|
||||
|
||||
```js
|
||||
mindMap.export('smm', true, '文件名', true)
|
||||
mindMap.export('json', true, '文件名', false)
|
||||
```
|
||||
|
||||
### 导出为png、pdf
|
||||
|
||||
导出这两种文件很简单:
|
||||
|
||||
```js
|
||||
mindMap.export('png', true, '文件名')
|
||||
mindMap.export('pdf', true, '文件名')
|
||||
```
|
||||
|
||||
### 导出为svg
|
||||
|
||||
导出为`svg`可以传递的参数如下:
|
||||
|
||||
```js
|
||||
mindMap.export(type, isDownload, fileName, plusCssText = '')
|
||||
```
|
||||
|
||||
如果开启了节点富文本编辑,也就是`svg`中会存在节点的`html`结构,这就又存在一个问题,因为浏览器对每个元素默认会设置一些样式,影响最大的就是`margin`和`padding`,这就有可能会导致节点中的文字错位,所以可以通过`plusCssText`参数传入`css`样式:
|
||||
|
||||
```js
|
||||
mindMap.export(
|
||||
'svg',
|
||||
true,
|
||||
'文件名',
|
||||
false,
|
||||
`* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}`
|
||||
)
|
||||
```
|
||||
|
||||
### 导出为md
|
||||
|
||||
导出为`markdown`文件只要传递默认的三个参数即可:
|
||||
|
||||
```js
|
||||
mindMap.export('md', true, '文件名')
|
||||
```
|
||||
|
||||
## 导入
|
||||
|
||||
目前支持从`.smm`、`.json`、`.xmind`、`.xlsx`、`.md`格式的文件导入。
|
||||
|
||||
### 导入smm、json
|
||||
|
||||
这两个文件导入很简单,直接读取文件内容,转成对象,然后调用相关方法渲染到画布即可。
|
||||
|
||||
因为导出这两种类型时可以选择是否包含配置数据,所以导入的时候调用的方法也是不一样的:
|
||||
|
||||
```js
|
||||
let data = JSON.parse('json数据')
|
||||
// 如果数据中存在root属性,那么代表是包含配置的完整数据,则使用setFullData方法导入数据
|
||||
if (data.root) {
|
||||
mindMap.setFullData(data)
|
||||
} else {
|
||||
// 否则使用setData方法导入
|
||||
mindMap.setData(data)
|
||||
}
|
||||
// 导入数据后有可能新数据渲染在可视区域外了,所以为了更好的体验,可以复位一下视图的变换
|
||||
mindMap.view.reset()
|
||||
```
|
||||
|
||||
### 导入xmind
|
||||
|
||||
要导入`xmind`文件,需要引入`xmind`的解析方法:
|
||||
|
||||
```js
|
||||
import xmind from 'simple-mind-map/src/parse/xmind.js'
|
||||
```
|
||||
|
||||
如果使用的是`umd`文件,可以这样获取:
|
||||
|
||||
```js
|
||||
MindMap.xmind
|
||||
```
|
||||
|
||||
如果你是通过`input type=file`等方式获取到的`File`文件对象,那么可以直接传递给`parseXmindFile`方法解析,注意返回的是一个`Promise`实例,会返回解析后的节点树数据,使用`setData`方法渲染到画布即可。
|
||||
|
||||
```js
|
||||
let data = await xmind.parseXmindFile(file)
|
||||
mindMap.setData(data)
|
||||
```
|
||||
|
||||
`.xmind`文件本质上是一个压缩包,改成`zip`后缀可以解压缩,里面存在一个`content.json`文件,如果你自己解析出了这个文件,那么可以把这个文件内容传递给这个`transformXmind`方法进行转换:
|
||||
|
||||
```js
|
||||
let data = await xmind.transformXmind(fileContent)
|
||||
mindMap.setData(data)
|
||||
```
|
||||
|
||||
另外如果导入的是`xmind8`版本的数据,需要使用`transformOldXmind`方法。
|
||||
|
||||
### 导入xlsx
|
||||
|
||||
这个文件的导入没有内置方法,需要你自己开发,以下是一个使用`xlsx`库的方式:
|
||||
|
||||
```js
|
||||
import { read, utils } from 'xlsx'
|
||||
|
||||
// 文件转buffer
|
||||
export const fileToBuffer = file => {
|
||||
return new Promise(r => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = () => {
|
||||
r(reader.result)
|
||||
}
|
||||
reader.readAsArrayBuffer(file)
|
||||
})
|
||||
}
|
||||
|
||||
// File文件对象
|
||||
const transformXLSXToJson = async (file) => {
|
||||
const wb = read(await fileToBuffer(file))
|
||||
const data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], {
|
||||
header: 1
|
||||
})
|
||||
if (data.length <= 0) {
|
||||
return
|
||||
}
|
||||
let max = 0
|
||||
data.forEach(arr => {
|
||||
if (arr.length > max) {
|
||||
max = arr.length
|
||||
}
|
||||
})
|
||||
let layers = []
|
||||
let walk = layer => {
|
||||
if (!layers[layer]) {
|
||||
layers[layer] = []
|
||||
}
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (data[i][layer]) {
|
||||
let node = {
|
||||
data: {
|
||||
text: data[i][layer]
|
||||
},
|
||||
children: [],
|
||||
_row: i
|
||||
}
|
||||
layers[layer].push(node)
|
||||
}
|
||||
}
|
||||
if (layer < max - 1) {
|
||||
walk(layer + 1)
|
||||
}
|
||||
}
|
||||
walk(0)
|
||||
let getParent = (arr, row) => {
|
||||
for (let i = arr.length - 1; i >= 0; i--) {
|
||||
if (row >= arr[i]._row) {
|
||||
return arr[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = 1; i < layers.length; i++) {
|
||||
let arr = layers[i]
|
||||
for (let j = 0; j < arr.length; j++) {
|
||||
let item = arr[j]
|
||||
let parent = getParent(layers[i - 1], item._row)
|
||||
if (parent) {
|
||||
parent.children.push(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return layers[0][0]
|
||||
}
|
||||
|
||||
let data = transformXLSXToJson('xlsx文件对象')
|
||||
mindMap.setData(data)
|
||||
```
|
||||
|
||||
### 导入md
|
||||
|
||||
要导入`markdown`文件需要引入相应的解析方法:
|
||||
|
||||
```js
|
||||
import markdown from 'simple-mind-map/src/parse/markdown.js'
|
||||
```
|
||||
|
||||
如果使用的是umd格式的文件,那么可以通过如下方式获取:
|
||||
|
||||
```js
|
||||
MindMap.markdown
|
||||
```
|
||||
|
||||
获取到`md`文件的内容后调用`transformMarkdownTo`方法转换即可,返回一个`Promise`实例:
|
||||
|
||||
```js
|
||||
let data = await markdown.transformMarkdownTo('md文件内容')
|
||||
mindMap.setData(data)
|
||||
```
|
||||
|
||||
### 完整示例
|
||||
|
||||
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFV81u20YQfpUN24JUoVAK0JMrB3YbB0gApYGTQ4EwhzW5kuiQu8TuypJhC0iDJkHaBsglPfXQQ4Oil7intnb7NvVf36KzfxRNUXUuQQ3bImdnvm925tsf7XnrRRHujIm34vVEzNNCIkHkuLge0TQvGJdoD3EyaCNG+2xMJUnaSIxwlrHJJhmgGRpwliMfEPwyop/SpI8LMxR5AswZuZqD9WqOi8iLKEIRzYhEyqY8VxEdZ1lEI9rpoJODv06evjn9/vnxn79FNGZUSGC/RYuxBMeAtNDqdbSnQBTEIM0ImEkoMR8SGap38aD70I1zMoThThSFgcjz/ami3N8WjO5PMzHdz5PWhx3lmw5QcAWcQ0mEDBRKSHFOWi1DhRDOCJeBf37w+z+PX5x++0sIcH8//ipUWOpTI+sHwFWfeWLm4LcMAIe6cpgiQjNHWKalQCCRpeQjTJOM3MtzPagBZ4hkgpQwmv5ShC+V13IMyPxSiI1pTLKlEPnlOfSrCUQUfk3bf/r67NWzalUvSqAsgRKBBljQwSbBCeFKTWSCbpaGQFPNHUIOH+viPpmaLOvDjGYMJ0pUO6A4y4GQ5Lvu0VAmWGLwun3vizthgbkgAQQ4HXIixpm0rTfdlrsFYQMTdmV1Fflsa5vE0i/ro1lGnE30BDY4ZzzwTRVOnj09eXt4+sOL84MDJygrJPUDa/YGwAYK247OUIxlPIIFo3AqHKqcDHqTsaEdswHNDdHCamqF1hLMH4tdGtdbUilWpVR4gtNyfzDIpnIaS3Vs3pCGSS2ZUvOEGmYC8m6aiFb0solA+NnrN3WsHPNHCZs0SrS/tCjvS6eG7N3UerEFbh6h5JiKAeN531rus/9Q83uRm9n2X/96+vKtK6jlUSteU5XzU8tJWULOmCzJ7HESQthNOE/q4tEb1aJrzU39c6M7KZmouROpelPN9PnR8R9HapNyqZKpOvvWxW2wqXxVrprLYRmHwFdBfhv6MyZt5Fb3q5dgGmBIsImnoMM6zV2qjjXNYqZUp4GYJhYHXx7nwSKGFabVSGArRrIVlLB4nBMqQ9DERkbU42e7t5LAt5GfMypxSgn3W20Tpeq6MhdD5ClD5FVMxixB5Moceac/Hp5/8+TsyaG5KGipWDDlGI/SLOGEKucHc4waXCNLnen46Luzo5/rZBcJG0j11WLR73/KwD1am4tLaSo3YW3cYQm5y0QqU0Yh0s/IQIIu/BhaB216qN1nrU9BFiCNXsfcAeH2By+SwMUNSwJvCPWSdAfFGRZiNfJsu2+QnEWeHrYOaTIfLcUALr0OjFYdHZJkLNvCysUMRrKX6pueOixhXG13kYfWtBHe7UWw9Ae4rbGUsObW4iyNH4FLdSWC34XV2usY78ujYYFVg2E51WLnU3JPvU6lYvAq5G5mirdmb8aRF3bMddhtwETkYSxE5EEH1K0YobBSXKeeSZrI0Qq61u1+pP0QKsqecgKM6Q7RA1oO6u+DehMc1DwQb8G+PJYmUJ0RA7mCuvZNsmL+skg/IulwBO6fdLvF1DE3835cbrlwlKTA61ALnCQpHTpDmXpoNfGOGV9zGdiky3cABD3rHnhtz3RAfQHRd0v4vqPhIzsAHSjXauTB1xmzQMMOPIYcdso0J6pZV7fgeibg8N2GCLvWGr7imNjFVqsod+55s38BRuykJA==" />
|
||||
@@ -1,8 +1,185 @@
|
||||
<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>
|
||||
<pre class="hljs"><code>mindMap.export(type, isDownload, fileName, ...)
|
||||
</code></pre>
|
||||
<p><code>type</code>:文件类型</p>
|
||||
<p><code>isDownload</code>:传<code>true</code>会触发下载,<code>false</code>则不会,函数会返回导出文件的数据,<code>data:url</code>格式,你可以自行下载,<code>pdf</code>不支持该参数,默认会直接下载。</p>
|
||||
<p><code>fileName</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>
|
||||
<h3>导出为svg</h3>
|
||||
<p>导出为<code>svg</code>可以传递的参数如下:</p>
|
||||
<pre class="hljs"><code>mindMap.export(type, isDownload, fileName, plusCssText = <span class="hljs-string">''</span>)
|
||||
</code></pre>
|
||||
<p>如果开启了节点富文本编辑,也就是<code>svg</code>中会存在节点的<code>html</code>结构,这就又存在一个问题,因为浏览器对每个元素默认会设置一些样式,影响最大的就是<code>margin</code>和<code>padding</code>,这就有可能会导致节点中的文字错位,所以可以通过<code>plusCssText</code>参数传入<code>css</code>样式:</p>
|
||||
<pre class="hljs"><code>mindMap.export(
|
||||
<span class="hljs-string">'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>
|
||||
|
||||
|
||||
74
web/src/pages/Doc/zh/course19/index.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# 插入和扩展节点图标
|
||||
|
||||
## 插入图标
|
||||
|
||||
`simple-mind-map`内置了一些图标:
|
||||
|
||||
<img src="../../../../assets/img/iconList.jpg" />
|
||||
|
||||
你可以通过如下方式获取:
|
||||
|
||||
```js
|
||||
import { nodeIconList } from 'simple-mind-map/src/svg/icons'
|
||||
```
|
||||
|
||||
如果你使用的是`umd`格式的文件,可以通过如下方式获取(v0.5.8+):
|
||||
|
||||
```js
|
||||
simpleMindMap.iconList
|
||||
```
|
||||
|
||||
结构如下:
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
name: '优先级图标',
|
||||
type: 'priority',
|
||||
list: [
|
||||
{
|
||||
name: '1',
|
||||
icon: `<svg viewBox="0 0 1024 1024"><path d="M512.042667 1024C229.248 1024 0 794.794667 0 511.957333 0 229.205333 229.248 0 512.042667 0 794.752 0 1024 229.205333 1024 511.957333 1024 794.794667 794.752 1024 512.042667 1024z" fill="#E93B30"></path><path d="M580.309333 256h-75.52c-10.666667 29.824-30.165333 55.765333-58.709333 78.165333-28.416 22.314667-54.869333 37.418667-79.146667 45.397334v84.608a320 320 0 0 0 120.234667-70.698667v352.085333H580.266667V256z" fill="#FFFFFF"></path></svg>`
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
你可以通过UI界面展示出来,要给一个节点设置图标,可以使用`node.setIcon([])`方法,要获取节点当前的图标,可以使用`node.getData('icon')`方法。
|
||||
|
||||
一个图标由`type_name`构成,比如前面的图标要插入节点,那么图标的值为`priority_1`。
|
||||
|
||||
通常一个分组的图标只允许插入一个,所以存在一个替换的逻辑,完整示例如下:
|
||||
|
||||
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFVltvG0UU/ivDIrRrcNZ2xZOxowLtQySCUF+9UbTZHdtDd2dWO7NJrNRSqYooVSpQkVBLkAjQFIRU9ZFeBP0zsWP+Befs7M0bV2qe+mBr51y+78y5zMyB8XEU2bsJNbpGT3oxixSRVCXRusNZGIlYkQMS02GTCL4pEq6o3yRy7AaB2LtGh2RKhrEIiQkIZuGxybi/6UZa5RgSxAFdC0G6FrqRYzicEIcHVBGUoWWf8CQIHO5wT3CpiOsptks/Fz6VoCv5rMFWQ3t6SRxTrjbA/jMGHn0y2Cr9YQuoAamlJhFtEu6GtEH66+QAyRHgOp2AGrXkA2Jum/CPRqhutcj895vzX05m//ww+/be4u6ts1vP5w+ezr5/PHvyYPbzn4unJ7Ojf+fH3+RgsA26D3C1qOwhKDZQZzFFw4KfQEpVEnOipf0+RoOKKewu5dc8szsPZ3eO/3v4iAEgatiQWJrrHXBa6zRyvDqxjALmUW3bJJ0UdkpoIGnugSSwH2R4NP/xid6q3lW21fuHi8dfzb+7P/v65Oyn24Xq9NnN02d/nb28DUINhRnARKY7vUgWlvKQxqwsKEVj0N5Ks4Kg2lInRpdmOQTYweLV0eLXw/nRq/m937QZ5qmMqJar12ar8ICMNbEkGely4iqpm//9cnb3OC/OKuQokWOrgpTWAf8qHW7vukFC7aGIr7re2OIgq+QIl3bWz9bAtu0aBQ6Ezg/gOrwYU8sq+70yZnQvH08rI6BBl/jCS0IAtUdUXQ0ofn4y2fAtM/P8VHDlMk5js9HUXr6r3G6ZEsdAgWNURFqs6L5CsWPMj5/rSdIHQJqODAwNvTELfNgYGg9KjBrcSpY60+mLw7MXf9TJlglXkML5scruLUWQf2ay3I9xpq4JobBzvhCSKSY4eJoBHSqzSUwPSgdl2krNp42P9FlbtIANTWRiS23rBgQXC3rKjUeybJcV3QmtgzaDThYODtj5Fg4oH6kxWSftyrDh6TBksUxDRpy6G0z7a8YHrAtPbM0rkHvLxHkzG+TGjSJh9QFdfTmUA4jDAr9eS994cNfBAo6gKHAVhRUhPZ/tEi9wpew7Rpa7KzQUjpGqMwPml9piRMCk1wJt1TBHUkIEOy6a5KH2dhKl4Ka67MERdB1M8lE3o5iJmKkJVrVjNsAnl2x3ei3tdkGYS8swly4EI0YxlbIajZZcMJoSJo8mg6lHUyYx/+q1KjWCpVSTQJfrcvbycAy7pZ8b2SFnUxnanpSOUUyCXSln3jJ7zFfjLum02++ldoRExWzFFBihY1NF2j/4e7de9hyqdHR3pAgSpR1xDoaqS9rZSomoXJynH1M2GoP5h+12tJ8zr+Z9P2cOYUIZ8Oaokev7jI9yQRG6nXXhG0bcySPIgi7WAAgTlNbAaBq6AvjAs7+UgsN7MoV3MgVUoDgzHQOei/qgtFvwacdwY7GQYrHWdmKxJ2kMII6RnXkrnpDa93yp0SuLbWpM/wfhELfG" />
|
||||
|
||||
## 扩展图标
|
||||
|
||||
> v0.5.8+
|
||||
|
||||
除了使用内置的图标外,你也可以扩展图标,实例化`simple-mind-map`时传入你要扩展的图标列表即可:
|
||||
|
||||
```js
|
||||
new MindMap({
|
||||
// ...
|
||||
iconList: [
|
||||
{
|
||||
name: '',// 分组名称
|
||||
type: '',// 分组的值
|
||||
list: [// 分组下的图标列表
|
||||
{
|
||||
name: '',// 图标名称
|
||||
icon:''// 图标,可以传svg或图片
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
然后和插入内置图标一样,通过`setIcon`方法插入图标的`key`即可。
|
||||
|
||||
完整示例:
|
||||
|
||||
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFWGmP28YZ/iusikLaai2SklarddZBqINaXdSxkijRMgIeI5ISL/HSkRhIg7RxUgcJUiDNUaBOGydBgDT91lxt/sxqvf3Uv9AZUpfXG6D+VAKUZt7jeY95h3yHr0Qoy0r4Hojcjpw6oq1aLuYA17NeHBqqbpm2i72C2WB0iJlG3fQMF0iHmKPwmmbO2mCE3cdGtqljUYgQ3WrUVUOq81bIGkYcSNbALR1Sb+m8NYwMDQwbGhpwMURDkncww9O0oTE0RNNwXIwXXdUHjCkBB/J29mJ37x2EmqJn28Bwy1C+pkKNO9jdezt9GALiQGrMXVjgEDN4HRxgd17EXkHGEcAELCAbcbE4Fn05Cn+REGLjOHb519cu//x49c8/rN565+rt15+8/t3lh9+s3vt89fWHqz99efXN49Un/7p89OYGDIYB5hDumleJEWSUES+mukDf2sdgSl3PNrCQeucO8gYx7sPoAvuhndWDj1YPHv37o89UCIg46giLhbZ+AZVukQcbvOuGHUtTRRDKHmJkAHsfA5oDNhrICIwHWfjs8oOvw1DDqNahvv/w6vPfXL77/uq3j598/MaWdfHtaxfffvXkhzcgMYRCGUCJDCJ9niw8lYfAZzcGl+LgLnEvyAoCDSXDxIRL87QLMIKrnz65+vTh5Sc/Xb7zl1AM5Wnn0bVc/Wy2thowY4doSdZGn07cXuou//HD6u1Hm8W5CdnyHCW2hxSsA/rZq/CEz2seSIxMu8iLSsyAtL0coWliXc+xu4lE4poJtCHC/EDcobHdprHYrt73thmYbbZnbG0AaLcxyRQ9HYImZOAWNYCGuUVZikXXmnnTcHnVAHb04DDUkniXv71LyTCCCMPIHikku2DuIvIwcvnou3AnhQ+AIB1rMCQoKqomwcCQ8N0dxjW4G61ct3Tx/cMn339x3djTBm8wCp8fN8n9nzzYDNe0jZ5qqG7bNF1UOU3TUV3VNKBmVAMjN3qIRUW4dHCZ7m3E11Wyl9M9x9ED7zYWvXrzq9XfPr747q3Vg9/BTRU9RAUeDFfvvfPki7/vFNAOgQqi57imvieH9uFrP+7ktNDkln/x7e+3j5DVgz9effrlfhaupXLtFRnihzrX/NiEdjsa1KGq8zLALUN+QeAdkEkfqr1coz0jqiXZpODFnHeVYleGo/IM/uTreaqO/rkzu4gGVK4k5TrdIkXVSs08PldyLUSll92JlKeqx42zxgRJ6d3ivNvuzi1xVJlwTRxd6XjwX9E5P6AdxUcBfe+i2Z6Px3EfjRmDu84OLv9sjvv0fLQcpba0Csdusfj2JBjzrUlgD2JONaaWdpCjskRXGt1ZgUk2u92leJwkc5yG+/0UlfVzTt5Xx06qnqJzVLpBAD/t4ay3pAGKMUVX2kW6CxjbTdGuby6pluKVkvJgQJW5Ja9YsyqOnzSFaXbRKDNJ0yBOVFuo9vMDKZOZFNqTcRxvVh0OTy6mJh8/toUzu641sy0RP+qSydSZQHL82DNBRwLVpD7uulZprh8vCLIRXwqNaVy1VboJ6OyZLGR53HPjDYbsGs75vDLq+EnJj6f8bLzW7bFJlW1ycMGcsi3C6uOEdonhBCqZUut8kof/bM5ynU5fHOh+cjAuEkS1TGaso6mc8jM8KYKl62gFQXKWaWlxMu9lCTfjtLM0f0JqjSbOxgnF8liBbDbJXjw3dUd0Lz1b6BVTLfbsrJ5TG3p3ztTPVUYis7zMafVOp26O+wW7waq1Ml9ihXPdwPtdUYlXuJ5RqPHVNshMASNONOfI0Vvputsde0d8iZOc6Xl3kunn6Kl2rlkWl8TPMhm2uGCklioJShwcLclzyT1yxv1W2WDZFguYsTjvg2m8xJLVo4Zla7n++ARYaH7cXxD+jD9v8YKiF7RMtcTXVC0v2AKlqzOLHRdwI10s6dW2kqa7vVadcZnzHnD6VZniOEdvK8UqlaP1+sAsF1nbK7GjE2CaQoFuZZqCPOjZXrlnn+XnFLWYNUp6bk5Q+Zo84wZp3aMqQqbE2mqJ9Wdkql4O7lmjOk23eY9qc32Y81lBoNJ8GeIBxpl0bY/ILP3RGF+QM6pGTNs1Xe3V8uq8LC9IcbZIxUv6sbK77TmZomrBPcvXpmlOaFCD7LRJOHjzrFwb4B2hOKvhmX7ab+aP0qNmnhmlYRwoliAev0oUxgKhdcyy5rJ8PFexOe2sXNQ9uWeiuNJJ+AiQxRpRUAZemRPkutCZjVEtuecmVWTNepEdpZNJn3FYhLHoVGWVJSvF6VKhlcmAr1aQTo4atxr0NDd38nlG6BkSRxT5yszvi5TPNmg1rWieyFD6bM5wXUagZh2+lOziAiV7I73o86kcpI2lTD45h7Eu27ljhRmYRc5PZjOcwvqLIz4VX5Yq9nE1FSd8lanVhFJhcmYCSk3xZUIqnJzBR5mfZzpmekCcVx1Yu3ZRbRc0l6On6nxG8ifS2aKrqCxv+fPBSKXq5kl5QE9rbc2gW0S33y7leJPgeK87pRtEW2Ytt8TWe9KcJHJjtqyU2ONatSBXOmpy7JRrlDQBA5aQ5DbfcbI1hZ+me3TS6VNMAddr0Od0tiWp6bYVr5fbA6/ICbjZHDfmR7hTN8bj1GzeEJs0vsyLo6kCDJ91UmQPp0qMLwOizR4vPPSAo4oa3Zmcey09n49u3wr/+fHh6t1vLn54fPHjI8eXLx98AOlP3gq68mfeo9s36ZYWzO8fvBAeR7ZdUgL2WVHUdb0c9mjwrRqDbRdvy86uo7qhgYPdFZK5S67toB702S5PA4bsKtiLGLHXj6IGeqTaTvBWRzjX1WBD/DMdJpTeaqLurQDfh7EoejdGD7BXX932FNd72JvPT7seFfWT8D7Fw0MhPA7CCezSLY13AZxh2Kmk+pio8Y5zZxhZ564AdHMYCdhrAVXacbddJBQ5xSF3X3CD5JqmJvBIZOPqqeC5LjzMvSTCLn0CRTbdcNSyVdNW3QVqfMjoAdTZUF4mT/FQ7Tlhkk/DJJ8LxpRt4Dj73oSU5/RmB7PxZg3zPN5serSNL+H8GU9267AZneJ7ywynjrvQwhV/aX2+H0YSeHioXx8lEsDRE6LjDCPbzZTYq4hN1c1UyVVuYyRB/CqQwzBr28HaAFqERR8wghJE9y+vV84GaqfIC46peW6oiLbSCHaexHrmmtZu8qx5BaiyAsXTBGHNN5ZvtvvrjWUdbnIV2t2gWrwkqYa8IWxdT6wL+X/0mNx4sHZ6O4eAcBMGaxA5jIQrgD6jJMaOacCvNgH8cM2AK7A9mQwj8KNMeBxJ4HCYsOG5UNUBWqxbgm3OHGBDkGFkfVS44UNNqPvsUiOttW/3I/f/Cx/8M9s=" />
|
||||
66
web/src/pages/Doc/zh/course19/index.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>插入和扩展节点图标</h1>
|
||||
<h2>插入图标</h2>
|
||||
<p><code>simple-mind-map</code>内置了一些图标:</p>
|
||||
<img src="../../../../assets/img/iconList.jpg" />
|
||||
<p>你可以通过如下方式获取:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> { nodeIconList } <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/svg/icons'</span>
|
||||
</code></pre>
|
||||
<p>如果你使用的是<code>umd</code>格式的文件,可以通过如下方式获取(v0.5.8+):</p>
|
||||
<pre class="hljs"><code>simpleMindMap.iconList
|
||||
</code></pre>
|
||||
<p>结构如下:</p>
|
||||
<pre class="hljs"><code>[
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'优先级图标'</span>,
|
||||
<span class="hljs-attr">type</span>: <span class="hljs-string">'priority'</span>,
|
||||
<span class="hljs-attr">list</span>: [
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'1'</span>,
|
||||
<span class="hljs-attr">icon</span>: <span class="hljs-string">`<svg viewBox="0 0 1024 1024"><path d="M512.042667 1024C229.248 1024 0 794.794667 0 511.957333 0 229.205333 229.248 0 512.042667 0 794.752 0 1024 229.205333 1024 511.957333 1024 794.794667 794.752 1024 512.042667 1024z" fill="#E93B30"></path><path d="M580.309333 256h-75.52c-10.666667 29.824-30.165333 55.765333-58.709333 78.165333-28.416 22.314667-54.869333 37.418667-79.146667 45.397334v84.608a320 320 0 0 0 120.234667-70.698667v352.085333H580.266667V256z" fill="#FFFFFF"></path></svg>`</span>
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
</code></pre>
|
||||
<p>你可以通过UI界面展示出来,要给一个节点设置图标,可以使用<code>node.setIcon([])</code>方法,要获取节点当前的图标,可以使用<code>node.getData('icon')</code>方法。</p>
|
||||
<p>一个图标由<code>type_name</code>构成,比如前面的图标要插入节点,那么图标的值为<code>priority_1</code>。</p>
|
||||
<p>通常一个分组的图标只允许插入一个,所以存在一个替换的逻辑,完整示例如下:</p>
|
||||
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFVltvG0UU/ivDIrRrcNZ2xZOxowLtQySCUF+9UbTZHdtDd2dWO7NJrNRSqYooVSpQkVBLkAjQFIRU9ZFeBP0zsWP+Befs7M0bV2qe+mBr51y+78y5zMyB8XEU2bsJNbpGT3oxixSRVCXRusNZGIlYkQMS02GTCL4pEq6o3yRy7AaB2LtGh2RKhrEIiQkIZuGxybi/6UZa5RgSxAFdC0G6FrqRYzicEIcHVBGUoWWf8CQIHO5wT3CpiOsptks/Fz6VoCv5rMFWQ3t6SRxTrjbA/jMGHn0y2Cr9YQuoAamlJhFtEu6GtEH66+QAyRHgOp2AGrXkA2Jum/CPRqhutcj895vzX05m//ww+/be4u6ts1vP5w+ezr5/PHvyYPbzn4unJ7Ojf+fH3+RgsA26D3C1qOwhKDZQZzFFw4KfQEpVEnOipf0+RoOKKewu5dc8szsPZ3eO/3v4iAEgatiQWJrrHXBa6zRyvDqxjALmUW3bJJ0UdkpoIGnugSSwH2R4NP/xid6q3lW21fuHi8dfzb+7P/v65Oyn24Xq9NnN02d/nb28DUINhRnARKY7vUgWlvKQxqwsKEVj0N5Ks4Kg2lInRpdmOQTYweLV0eLXw/nRq/m937QZ5qmMqJar12ar8ICMNbEkGely4iqpm//9cnb3OC/OKuQokWOrgpTWAf8qHW7vukFC7aGIr7re2OIgq+QIl3bWz9bAtu0aBQ6Ezg/gOrwYU8sq+70yZnQvH08rI6BBl/jCS0IAtUdUXQ0ofn4y2fAtM/P8VHDlMk5js9HUXr6r3G6ZEsdAgWNURFqs6L5CsWPMj5/rSdIHQJqODAwNvTELfNgYGg9KjBrcSpY60+mLw7MXf9TJlglXkML5scruLUWQf2ay3I9xpq4JobBzvhCSKSY4eJoBHSqzSUwPSgdl2krNp42P9FlbtIANTWRiS23rBgQXC3rKjUeybJcV3QmtgzaDThYODtj5Fg4oH6kxWSftyrDh6TBksUxDRpy6G0z7a8YHrAtPbM0rkHvLxHkzG+TGjSJh9QFdfTmUA4jDAr9eS994cNfBAo6gKHAVhRUhPZ/tEi9wpew7Rpa7KzQUjpGqMwPml9piRMCk1wJt1TBHUkIEOy6a5KH2dhKl4Ka67MERdB1M8lE3o5iJmKkJVrVjNsAnl2x3ei3tdkGYS8swly4EI0YxlbIajZZcMJoSJo8mg6lHUyYx/+q1KjWCpVSTQJfrcvbycAy7pZ8b2SFnUxnanpSOUUyCXSln3jJ7zFfjLum02++ldoRExWzFFBihY1NF2j/4e7de9hyqdHR3pAgSpR1xDoaqS9rZSomoXJynH1M2GoP5h+12tJ8zr+Z9P2cOYUIZ8Oaokev7jI9yQRG6nXXhG0bcySPIgi7WAAgTlNbAaBq6AvjAs7+UgsN7MoV3MgVUoDgzHQOei/qgtFvwacdwY7GQYrHWdmKxJ2kMII6RnXkrnpDa93yp0SuLbWpM/wfhELfG" />
|
||||
<h2>扩展图标</h2>
|
||||
<blockquote>
|
||||
<p>v0.5.8+</p>
|
||||
</blockquote>
|
||||
<p>除了使用内置的图标外,你也可以扩展图标,实例化<code>simple-mind-map</code>时传入你要扩展的图标列表即可:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">new</span> MindMap({
|
||||
<span class="hljs-comment">// ...</span>
|
||||
<span class="hljs-attr">iconList</span>: [
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">''</span>,<span class="hljs-comment">// 分组名称</span>
|
||||
<span class="hljs-attr">type</span>: <span class="hljs-string">''</span>,<span class="hljs-comment">// 分组的值</span>
|
||||
<span class="hljs-attr">list</span>: [<span class="hljs-comment">// 分组下的图标列表</span>
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">''</span>,<span class="hljs-comment">// 图标名称</span>
|
||||
<span class="hljs-attr">icon</span>:<span class="hljs-string">''</span><span class="hljs-comment">// 图标,可以传svg或图片</span>
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
</code></pre>
|
||||
<p>然后和插入内置图标一样,通过<code>setIcon</code>方法插入图标的<code>key</code>即可。</p>
|
||||
<p>完整示例:</p>
|
||||
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFWGmP28YZ/iusikLaai2SklarddZBqINaXdSxkijRMgIeI5ISL/HSkRhIg7RxUgcJUiDNUaBOGydBgDT91lxt/sxqvf3Uv9AZUpfXG6D+VAKUZt7jeY95h3yHr0Qoy0r4Hojcjpw6oq1aLuYA17NeHBqqbpm2i72C2WB0iJlG3fQMF0iHmKPwmmbO2mCE3cdGtqljUYgQ3WrUVUOq81bIGkYcSNbALR1Sb+m8NYwMDQwbGhpwMURDkncww9O0oTE0RNNwXIwXXdUHjCkBB/J29mJ37x2EmqJn28Bwy1C+pkKNO9jdezt9GALiQGrMXVjgEDN4HRxgd17EXkHGEcAELCAbcbE4Fn05Cn+REGLjOHb519cu//x49c8/rN565+rt15+8/t3lh9+s3vt89fWHqz99efXN49Un/7p89OYGDIYB5hDumleJEWSUES+mukDf2sdgSl3PNrCQeucO8gYx7sPoAvuhndWDj1YPHv37o89UCIg46giLhbZ+AZVukQcbvOuGHUtTRRDKHmJkAHsfA5oDNhrICIwHWfjs8oOvw1DDqNahvv/w6vPfXL77/uq3j598/MaWdfHtaxfffvXkhzcgMYRCGUCJDCJ9niw8lYfAZzcGl+LgLnEvyAoCDSXDxIRL87QLMIKrnz65+vTh5Sc/Xb7zl1AM5Wnn0bVc/Wy2thowY4doSdZGn07cXuou//HD6u1Hm8W5CdnyHCW2hxSsA/rZq/CEz2seSIxMu8iLSsyAtL0coWliXc+xu4lE4poJtCHC/EDcobHdprHYrt73thmYbbZnbG0AaLcxyRQ9HYImZOAWNYCGuUVZikXXmnnTcHnVAHb04DDUkniXv71LyTCCCMPIHikku2DuIvIwcvnou3AnhQ+AIB1rMCQoKqomwcCQ8N0dxjW4G61ct3Tx/cMn339x3djTBm8wCp8fN8n9nzzYDNe0jZ5qqG7bNF1UOU3TUV3VNKBmVAMjN3qIRUW4dHCZ7m3E11Wyl9M9x9ED7zYWvXrzq9XfPr747q3Vg9/BTRU9RAUeDFfvvfPki7/vFNAOgQqi57imvieH9uFrP+7ktNDkln/x7e+3j5DVgz9effrlfhaupXLtFRnihzrX/NiEdjsa1KGq8zLALUN+QeAdkEkfqr1coz0jqiXZpODFnHeVYleGo/IM/uTreaqO/rkzu4gGVK4k5TrdIkXVSs08PldyLUSll92JlKeqx42zxgRJ6d3ivNvuzi1xVJlwTRxd6XjwX9E5P6AdxUcBfe+i2Z6Px3EfjRmDu84OLv9sjvv0fLQcpba0Csdusfj2JBjzrUlgD2JONaaWdpCjskRXGt1ZgUk2u92leJwkc5yG+/0UlfVzTt5Xx06qnqJzVLpBAD/t4ay3pAGKMUVX2kW6CxjbTdGuby6pluKVkvJgQJW5Ja9YsyqOnzSFaXbRKDNJ0yBOVFuo9vMDKZOZFNqTcRxvVh0OTy6mJh8/toUzu641sy0RP+qSydSZQHL82DNBRwLVpD7uulZprh8vCLIRXwqNaVy1VboJ6OyZLGR53HPjDYbsGs75vDLq+EnJj6f8bLzW7bFJlW1ycMGcsi3C6uOEdonhBCqZUut8kof/bM5ynU5fHOh+cjAuEkS1TGaso6mc8jM8KYKl62gFQXKWaWlxMu9lCTfjtLM0f0JqjSbOxgnF8liBbDbJXjw3dUd0Lz1b6BVTLfbsrJ5TG3p3ztTPVUYis7zMafVOp26O+wW7waq1Ml9ihXPdwPtdUYlXuJ5RqPHVNshMASNONOfI0Vvputsde0d8iZOc6Xl3kunn6Kl2rlkWl8TPMhm2uGCklioJShwcLclzyT1yxv1W2WDZFguYsTjvg2m8xJLVo4Zla7n++ARYaH7cXxD+jD9v8YKiF7RMtcTXVC0v2AKlqzOLHRdwI10s6dW2kqa7vVadcZnzHnD6VZniOEdvK8UqlaP1+sAsF1nbK7GjE2CaQoFuZZqCPOjZXrlnn+XnFLWYNUp6bk5Q+Zo84wZp3aMqQqbE2mqJ9Wdkql4O7lmjOk23eY9qc32Y81lBoNJ8GeIBxpl0bY/ILP3RGF+QM6pGTNs1Xe3V8uq8LC9IcbZIxUv6sbK77TmZomrBPcvXpmlOaFCD7LRJOHjzrFwb4B2hOKvhmX7ab+aP0qNmnhmlYRwoliAev0oUxgKhdcyy5rJ8PFexOe2sXNQ9uWeiuNJJ+AiQxRpRUAZemRPkutCZjVEtuecmVWTNepEdpZNJn3FYhLHoVGWVJSvF6VKhlcmAr1aQTo4atxr0NDd38nlG6BkSRxT5yszvi5TPNmg1rWieyFD6bM5wXUagZh2+lOziAiV7I73o86kcpI2lTD45h7Eu27ljhRmYRc5PZjOcwvqLIz4VX5Yq9nE1FSd8lanVhFJhcmYCSk3xZUIqnJzBR5mfZzpmekCcVx1Yu3ZRbRc0l6On6nxG8ifS2aKrqCxv+fPBSKXq5kl5QE9rbc2gW0S33y7leJPgeK87pRtEW2Ytt8TWe9KcJHJjtqyU2ONatSBXOmpy7JRrlDQBA5aQ5DbfcbI1hZ+me3TS6VNMAddr0Od0tiWp6bYVr5fbA6/ICbjZHDfmR7hTN8bj1GzeEJs0vsyLo6kCDJ91UmQPp0qMLwOizR4vPPSAo4oa3Zmcey09n49u3wr/+fHh6t1vLn54fPHjI8eXLx98AOlP3gq68mfeo9s36ZYWzO8fvBAeR7ZdUgL2WVHUdb0c9mjwrRqDbRdvy86uo7qhgYPdFZK5S67toB702S5PA4bsKtiLGLHXj6IGeqTaTvBWRzjX1WBD/DMdJpTeaqLurQDfh7EoejdGD7BXX932FNd72JvPT7seFfWT8D7Fw0MhPA7CCezSLY13AZxh2Kmk+pio8Y5zZxhZ564AdHMYCdhrAVXacbddJBQ5xSF3X3CD5JqmJvBIZOPqqeC5LjzMvSTCLn0CRTbdcNSyVdNW3QVqfMjoAdTZUF4mT/FQ7Tlhkk/DJJ8LxpRt4Dj73oSU5/RmB7PxZg3zPN5serSNL+H8GU9267AZneJ7ywynjrvQwhV/aX2+H0YSeHioXx8lEsDRE6LjDCPbzZTYq4hN1c1UyVVuYyRB/CqQwzBr28HaAFqERR8wghJE9y+vV84GaqfIC46peW6oiLbSCHaexHrmmtZu8qx5BaiyAsXTBGHNN5ZvtvvrjWUdbnIV2t2gWrwkqYa8IWxdT6wL+X/0mNx4sHZ6O4eAcBMGaxA5jIQrgD6jJMaOacCvNgH8cM2AK7A9mQwj8KNMeBxJ4HCYsOG5UNUBWqxbgm3OHGBDkGFkfVS44UNNqPvsUiOttW/3I/f/Cx/8M9s=" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
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
@@ -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>
|
||||
@@ -15,16 +15,40 @@ MindMap.usePlugin(Export)
|
||||
|
||||
## 方法
|
||||
|
||||
### png()
|
||||
所有导出的方法都是异步方法,返回一个`Promise`实例,你可以使用`then`方法获取数据,或者使用`async await`函数获取:
|
||||
|
||||
导出为`png`,异步方法,返回图片数据,`data:url`数据,可以自行下载或显示
|
||||
```js
|
||||
mindMap.doExport.png().then((data) => {
|
||||
// ...
|
||||
})
|
||||
|
||||
### svg(name, domToImage = false, plusCssText)
|
||||
const export = async () => {
|
||||
let data = await mindMap.doExport.png()
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
返回的数据为`data:url`格式的,你可以创建一个`a`标签来触发下载:
|
||||
|
||||
```js
|
||||
let a = document.createElement('a')
|
||||
a.href = 'xxx.png'// .png、.svg、.pdf、.md、.json、.smm
|
||||
a.download = 'xxx'
|
||||
a.click()
|
||||
```
|
||||
|
||||
### png(name, transparent = false)
|
||||
|
||||
- `name`:名称,可不传
|
||||
|
||||
- `transparent`:v0.5.7+,指定导出图片的背景是否是透明的
|
||||
|
||||
导出为`png`。
|
||||
|
||||
### svg(name, plusCssText)
|
||||
|
||||
- `name`:`svg`标题
|
||||
|
||||
- `domToImage`:v0.4.0+,当开启了节点富文本编辑,可以通过该参数指定是否将`svg`中的`dom`节点转换成图片的形式
|
||||
|
||||
- `plusCssText`:v0.4.0+,当开启了节点富文本编辑,且`domToImage`传了`false`时,可以添加附加的`css`样式,如果`svg`中存在`dom`节点,想要设置一些针对节点的样式可以通过这个参数传入,比如:
|
||||
|
||||
```js
|
||||
@@ -39,21 +63,7 @@ svg(
|
||||
)
|
||||
```
|
||||
|
||||
导出为`svg`,异步方法,返回`svg`数据,`data:url`数据,可以自行下载或显示
|
||||
|
||||
### getSvgData(domToImage)
|
||||
|
||||
- `domToImage`:v0.4.0+,如果开启了节点富文本,则可以通过该参数指定是否要将`svg`中嵌入的`DOM`节点转换为图片。
|
||||
|
||||
获取`svg`数据,异步方法,返回一个对象:
|
||||
|
||||
```js
|
||||
{
|
||||
node// svg对象
|
||||
str// svg字符串,如果开启了富文本编辑且domToImage设为true,那么该值返回的svg字符内的dom节点会被转换成图片的形式
|
||||
nodeWithDomToImg// v0.4.0+,DOM节点转换为图片后的svg对象,只有当开启了富文本编辑且domToImage设为true才有值,否则为null
|
||||
}
|
||||
```
|
||||
导出为`svg`。
|
||||
|
||||
### pdf(name)
|
||||
|
||||
@@ -61,7 +71,7 @@ svg(
|
||||
|
||||
`name`:文件名称
|
||||
|
||||
导出为`pdf`
|
||||
导出为`pdf`,和其他导出方法不一样,这个方法不会返回数据,会直接触发下载。
|
||||
|
||||
### json(name, withConfig)
|
||||
|
||||
@@ -69,4 +79,25 @@ svg(
|
||||
|
||||
`withConfig``:Boolean`, 默认为`true`,数据中是否包含配置,否则为纯思维导图节点数据
|
||||
|
||||
返回`json`数据,`data:url`数据,可以自行下载
|
||||
返回`json`数据。
|
||||
|
||||
### smm(name, withConfig)
|
||||
|
||||
`simple-mind-map`自定义的文件格式,其实就是`json`,和`json`方法返回的数据一模一样。
|
||||
|
||||
### md()
|
||||
|
||||
> v0.4.7+
|
||||
|
||||
导出`markdown`文件。
|
||||
|
||||
### getSvgData()
|
||||
|
||||
获取`svg`数据,异步方法,返回一个对象:
|
||||
|
||||
```js
|
||||
{
|
||||
node// svg节点
|
||||
str// svg字符串
|
||||
}
|
||||
```
|
||||
@@ -10,17 +10,38 @@ MindMap.usePlugin(Export)
|
||||
</code></pre>
|
||||
<p>注册完且实例化<code>MindMap</code>后可通过<code>mindMap.doExport</code>获取到该实例。</p>
|
||||
<h2>方法</h2>
|
||||
<h3>png()</h3>
|
||||
<p>导出为<code>png</code>,异步方法,返回图片数据,<code>data:url</code>数据,可以自行下载或显示</p>
|
||||
<h3>svg(name, domToImage = false, plusCssText)</h3>
|
||||
<p>所有导出的方法都是异步方法,返回一个<code>Promise</code>实例,你可以使用<code>then</code>方法获取数据,或者使用<code>async await</code>函数获取:</p>
|
||||
<pre class="hljs"><code>mindMap.doExport.png().then(<span class="hljs-function">(<span class="hljs-params">data</span>) =></span> {
|
||||
<span class="hljs-comment">// ...</span>
|
||||
})
|
||||
|
||||
<span class="hljs-keyword">const</span> <span class="hljs-keyword">export</span> = <span class="hljs-keyword">async</span> () => {
|
||||
<span class="hljs-keyword">let</span> data = <span class="hljs-keyword">await</span> mindMap.doExport.png()
|
||||
<span class="hljs-comment">// ...</span>
|
||||
}
|
||||
</code></pre>
|
||||
<p>返回的数据为<code>data:url</code>格式的,你可以创建一个<code>a</code>标签来触发下载:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">let</span> a = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'a'</span>)
|
||||
a.href = <span class="hljs-string">'xxx.png'</span><span class="hljs-comment">// .png、.svg、.pdf、.md、.json、.smm</span>
|
||||
a.download = <span class="hljs-string">'xxx'</span>
|
||||
a.click()
|
||||
</code></pre>
|
||||
<h3>png(name, transparent = false)</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<p><code>name</code>:名称,可不传</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>transparent</code>:v0.5.7+,指定导出图片的背景是否是透明的</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>导出为<code>png</code>。</p>
|
||||
<h3>svg(name, plusCssText)</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<p><code>name</code>:<code>svg</code>标题</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>domToImage</code>:v0.4.0+,当开启了节点富文本编辑,可以通过该参数指定是否将<code>svg</code>中的<code>dom</code>节点转换成图片的形式</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>plusCssText</code>:v0.4.0+,当开启了节点富文本编辑,且<code>domToImage</code>传了<code>false</code>时,可以添加附加的<code>css</code>样式,如果<code>svg</code>中存在<code>dom</code>节点,想要设置一些针对节点的样式可以通过这个参数传入,比如:</p>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -34,28 +55,31 @@ MindMap.usePlugin(Export)
|
||||
}`</span>
|
||||
)
|
||||
</code></pre>
|
||||
<p>导出为<code>svg</code>,异步方法,返回<code>svg</code>数据,<code>data:url</code>数据,可以自行下载或显示</p>
|
||||
<h3>getSvgData(domToImage)</h3>
|
||||
<ul>
|
||||
<li><code>domToImage</code>:v0.4.0+,如果开启了节点富文本,则可以通过该参数指定是否要将<code>svg</code>中嵌入的<code>DOM</code>节点转换为图片。</li>
|
||||
</ul>
|
||||
<p>获取<code>svg</code>数据,异步方法,返回一个对象:</p>
|
||||
<pre class="hljs"><code>{
|
||||
node<span class="hljs-comment">// svg对象</span>
|
||||
str<span class="hljs-comment">// svg字符串,如果开启了富文本编辑且domToImage设为true,那么该值返回的svg字符内的dom节点会被转换成图片的形式</span>
|
||||
nodeWithDomToImg<span class="hljs-comment">// v0.4.0+,DOM节点转换为图片后的svg对象,只有当开启了富文本编辑且domToImage设为true才有值,否则为null</span>
|
||||
}
|
||||
</code></pre>
|
||||
<p>导出为<code>svg</code>。</p>
|
||||
<h3>pdf(name)</h3>
|
||||
<blockquote>
|
||||
<p>v0.2.1+</p>
|
||||
</blockquote>
|
||||
<p><code>name</code>:文件名称</p>
|
||||
<p>导出为<code>pdf</code></p>
|
||||
<p>导出为<code>pdf</code>,和其他导出方法不一样,这个方法不会返回数据,会直接触发下载。</p>
|
||||
<h3>json(name, withConfig)</h3>
|
||||
<p><code>name</code>:暂时没有用处,传空字符串即可</p>
|
||||
<p><code>withConfig``:Boolean</code>, 默认为<code>true</code>,数据中是否包含配置,否则为纯思维导图节点数据</p>
|
||||
<p>返回<code>json</code>数据,<code>data:url</code>数据,可以自行下载</p>
|
||||
<p>返回<code>json</code>数据。</p>
|
||||
<h3>smm(name, withConfig)</h3>
|
||||
<p><code>simple-mind-map</code>自定义的文件格式,其实就是<code>json</code>,和<code>json</code>方法返回的数据一模一样。</p>
|
||||
<h3>md()</h3>
|
||||
<blockquote>
|
||||
<p>v0.4.7+</p>
|
||||
</blockquote>
|
||||
<p>导出<code>markdown</code>文件。</p>
|
||||
<h3>getSvgData()</h3>
|
||||
<p>获取<code>svg</code>数据,异步方法,返回一个对象:</p>
|
||||
<pre class="hljs"><code>{
|
||||
node<span class="hljs-comment">// svg节点</span>
|
||||
str<span class="hljs-comment">// svg字符串</span>
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||