mirror of
https://github.com/wanglin2/mind-map.git
synced 2026-02-24 18:38:26 +08:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b6263acb4 | ||
|
|
187c940e56 | ||
|
|
4b59bec01c | ||
|
|
9427ee550c | ||
|
|
14975e117c | ||
|
|
d6254c0cc2 | ||
|
|
4a81ce9cc2 | ||
|
|
d17191c890 | ||
|
|
ff56fe3e68 | ||
|
|
6bdcec0fca | ||
|
|
27885aabe7 | ||
|
|
e345037f9b | ||
|
|
da5290e649 | ||
|
|
516676b484 | ||
|
|
20a8934da9 | ||
|
|
0075c44b29 | ||
|
|
ec7a8cdd43 | ||
|
|
1629bb7ccf | ||
|
|
35bff6ab57 | ||
|
|
ef9d8b0ea4 | ||
|
|
5d4c5703bb | ||
|
|
11bb519db8 | ||
|
|
1952280003 | ||
|
|
c845a0b7fa | ||
|
|
f1748e7e42 | ||
|
|
49063d257b | ||
|
|
803c83ac4f | ||
|
|
0bdf9f3add | ||
|
|
10ed3d4f7c | ||
|
|
8fc7f7d32c | ||
|
|
27a0efa4e0 | ||
|
|
7d227e901a | ||
|
|
080d7489e7 | ||
|
|
b11bd5a7ef | ||
|
|
80ae38d295 | ||
|
|
a4b7915196 | ||
|
|
4d4f1b993e | ||
|
|
b814bd35ca | ||
|
|
c57361a360 |
31
README.md
31
README.md
@@ -11,13 +11,13 @@
|
||||
|
||||
本项目包含两部分:
|
||||
|
||||
1.一个js思维导图库,不依赖任何框架,你可以使用它来快速完成Web思维导图产品的开发。
|
||||
1.一个 js 思维导图库,不依赖任何框架,你可以使用它来快速完成 Web 思维导图产品的开发。
|
||||
|
||||
开发文档:[https://wanglin2.github.io/mind-map/#/doc/zh/](https://wanglin2.github.io/mind-map/#/doc/zh/)
|
||||
开发文档:[https://wanglin2.github.io/mind-map/#/doc/zh/](https://wanglin2.github.io/mind-map/#/doc/zh/)。
|
||||
|
||||
2.一个Web思维导图,基于思维导图库、Vue2.x、ElementUI开发,可以操作电脑本地文件,所以你可以直接把它当做一个在线版思维导图应用使用,如果觉得github的响应速度慢,你也可以部署到你的服务器上。
|
||||
2.一个 Web 思维导图,基于思维导图库、Vue2.x、ElementUI 开发,可以操作电脑本地文件,所以你可以直接把它当做一个在线版思维导图应用使用,如果觉得 github 的响应速度慢,你也可以部署到你的服务器上。
|
||||
|
||||
在线地址:[https://wanglin2.github.io/mind-map/](https://wanglin2.github.io/mind-map/)
|
||||
在线地址:[https://wanglin2.github.io/mind-map/](https://wanglin2.github.io/mind-map/)。
|
||||
|
||||
另外也提供了客户端可供下载使用,支持`Windows`、`Mac`及`Linux`,下载地址:
|
||||
|
||||
@@ -28,18 +28,15 @@ Github:[releases](https://github.com/wanglin2/mind-map/releases)。
|
||||
# 特性
|
||||
|
||||
- [x] 插件化架构,除核心功能外,其他功能作为插件提供,按需使用,减小打包体积
|
||||
- [x] 支持逻辑结构图、思维导图、组织结构图、目录组织图、时间轴、鱼骨图六种结构
|
||||
- [x] 支持逻辑结构图、思维导图、组织结构图、目录组织图、时间轴(横向、竖向)、鱼骨图等结构
|
||||
- [x] 内置多种主题,允许高度自定义样式,支持注册新主题
|
||||
- [x] 支持快捷键
|
||||
- [x] 节点内容支持图片、图标、超链接、备注、标签、概要
|
||||
- [x] 支持前进后退
|
||||
- [x] 支持拖动、缩放
|
||||
- [x] 支持右键和Ctrl+左键两种多选方式
|
||||
- [x] 支持节点自由拖拽、拖拽调整
|
||||
- [x] 支持多种节点形状
|
||||
- [x] 支持导出为`json`、`png`、`svg`、`pdf`、`markdown`,支持从`json`、`xmind`、`markdown`导入
|
||||
- [x] 支持小地图、支持水印
|
||||
- [x] 支持关联线
|
||||
- [x] 节点内容支持文本(普通文本、富文本)、图片、图标、超链接、备注、标签、概要
|
||||
- [x] 节点支持拖拽(拖拽移动、自由调整)、多种节点形状,支持使用 DDM 完全自定义节点内容
|
||||
- [x] 支持画布拖动、缩放
|
||||
- [x] 支持鼠标按键拖动选择和Ctrl+左键两种多选节点方式
|
||||
- [x] 支持导出为`json`、`png`、`svg`、`pdf`、`markdown`、`xmind`,支持从`json`、`xmind`、`markdown`导入
|
||||
- [x] 支持快捷键、前进后退、关联线、搜索替换、小地图、水印
|
||||
- [x] 提供丰富的配置,满足各种场景各种使用习惯
|
||||
|
||||
# 安装
|
||||
|
||||
@@ -152,4 +149,8 @@ const mindMap = new MindMap({
|
||||
<img src="./web/src/assets/avatar/千帆.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>千帆</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/才镇.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>才镇</span>
|
||||
</span>
|
||||
</p>
|
||||
70
index.html
70
index.html
File diff suppressed because one or more lines are too long
BIN
qrcode.jpg
BIN
qrcode.jpg
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 182 KiB |
@@ -2,6 +2,7 @@ import MindMap from './index'
|
||||
import MiniMap from './src/plugins/MiniMap.js'
|
||||
import Watermark from './src/plugins/Watermark.js'
|
||||
import KeyboardNavigation from './src/plugins/KeyboardNavigation.js'
|
||||
import ExportXMind from './src/plugins/ExportXMind.js'
|
||||
import ExportPDF from './src/plugins/ExportPDF.js'
|
||||
import Export from './src/plugins/Export.js'
|
||||
import Drag from './src/plugins/Drag.js'
|
||||
@@ -11,6 +12,7 @@ import RichText from './src/plugins/RichText'
|
||||
import NodeImgAdjust from './src/plugins/NodeImgAdjust.js'
|
||||
import TouchEvent from './src/plugins/TouchEvent.js'
|
||||
import Search from './src/plugins/Search.js'
|
||||
import Painter from './src/plugins/Painter.js'
|
||||
import xmind from './src/parse/xmind.js'
|
||||
import markdown from './src/parse/markdown.js'
|
||||
import icons from './src/svg/icons.js'
|
||||
@@ -30,6 +32,7 @@ MindMap
|
||||
.usePlugin(Watermark)
|
||||
.usePlugin(Drag)
|
||||
.usePlugin(KeyboardNavigation)
|
||||
.usePlugin(ExportXMind)
|
||||
.usePlugin(ExportPDF)
|
||||
.usePlugin(Export)
|
||||
.usePlugin(Select)
|
||||
@@ -38,5 +41,6 @@ MindMap
|
||||
.usePlugin(TouchEvent)
|
||||
.usePlugin(NodeImgAdjust)
|
||||
.usePlugin(Search)
|
||||
.usePlugin(Painter)
|
||||
|
||||
export default MindMap
|
||||
@@ -164,6 +164,7 @@ class MindMap {
|
||||
this.renderer.clearAllActive()
|
||||
this.opt.theme = theme
|
||||
this.render(null, CONSTANTS.CHANGE_THEME)
|
||||
this.emit('view_theme_change', theme)
|
||||
}
|
||||
|
||||
// 获取当前主题
|
||||
|
||||
4
simple-mind-map/package-lock.json
generated
4
simple-mind-map/package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.6.7",
|
||||
"version": "0.6.11-fix.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "0.6.7",
|
||||
"version": "0.6.11-fix.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@svgdotjs/svg.js": "^3.0.16",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.6.11",
|
||||
"version": "0.6.12",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
|
||||
@@ -323,7 +323,9 @@ export const nodeDataNoStylePropList = [
|
||||
'richText',
|
||||
'resetRichText',
|
||||
'uid',
|
||||
'activeStyle'
|
||||
'activeStyle',
|
||||
'associativeLineTargets',
|
||||
'associativeLineTargetControlOffsets'
|
||||
]
|
||||
|
||||
// 数据缓存
|
||||
|
||||
@@ -122,5 +122,9 @@ export const defaultOpt = {
|
||||
// 是否开启自定义节点内容
|
||||
isUseCustomNodeContent: false,
|
||||
// 自定义返回节点内容的方法
|
||||
customCreateNodeContent: null
|
||||
customCreateNodeContent: null,
|
||||
// 指定内部一些元素(节点文本编辑元素、节点备注显示元素、关联线文本编辑元素、节点图片调整按钮元素)添加到的位置,默认添加到document.body下
|
||||
customInnerElsAppendTo: null,
|
||||
// 拖拽元素时,指示元素新位置的块的最大高度
|
||||
nodeDragPlaceholderMaxSize: 20
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ class Event extends EventEmitter {
|
||||
this.mindMap = opt.mindMap
|
||||
this.isLeftMousedown = false
|
||||
this.isRightMousedown = false
|
||||
this.isMiddleMousedown = false
|
||||
this.mousedownPos = {
|
||||
x: 0,
|
||||
y: 0
|
||||
@@ -92,6 +93,8 @@ class Event extends EventEmitter {
|
||||
this.isLeftMousedown = true
|
||||
} else if (e.which === 3) {
|
||||
this.isRightMousedown = true
|
||||
} else if (e.which === 2) {
|
||||
this.isMiddleMousedown = true
|
||||
}
|
||||
this.mousedownPos.x = e.clientX
|
||||
this.mousedownPos.y = e.clientY
|
||||
@@ -107,9 +110,10 @@ class Event extends EventEmitter {
|
||||
this.mousemoveOffset.y = e.clientY - this.mousedownPos.y
|
||||
this.emit('mousemove', e, this)
|
||||
if (
|
||||
useLeftKeySelectionRightKeyDrag
|
||||
this.isMiddleMousedown ||
|
||||
(useLeftKeySelectionRightKeyDrag
|
||||
? this.isRightMousedown
|
||||
: this.isLeftMousedown
|
||||
: this.isLeftMousedown)
|
||||
) {
|
||||
e.preventDefault()
|
||||
this.emit('drag', e, this)
|
||||
@@ -120,6 +124,7 @@ class Event extends EventEmitter {
|
||||
onMouseup(e) {
|
||||
this.isLeftMousedown = false
|
||||
this.isRightMousedown = false
|
||||
this.isMiddleMousedown = false
|
||||
this.emit('mouseup', e, this)
|
||||
}
|
||||
|
||||
|
||||
@@ -153,9 +153,12 @@ class Render {
|
||||
// 剪切节点
|
||||
this.cutNode = this.cutNode.bind(this)
|
||||
this.mindMap.command.add('CUT_NODE', this.cutNode)
|
||||
// 修改节点样式
|
||||
// 修改节点单个样式
|
||||
this.setNodeStyle = this.setNodeStyle.bind(this)
|
||||
this.mindMap.command.add('SET_NODE_STYLE', this.setNodeStyle)
|
||||
// 修改节点多个样式
|
||||
this.setNodeStyles = this.setNodeStyles.bind(this)
|
||||
this.mindMap.command.add('SET_NODE_STYLES', this.setNodeStyles)
|
||||
// 切换节点是否激活
|
||||
this.setNodeActive = this.setNodeActive.bind(this)
|
||||
this.mindMap.command.add('SET_NODE_ACTIVE', this.setNodeActive)
|
||||
@@ -859,6 +862,42 @@ class Render {
|
||||
}
|
||||
}
|
||||
|
||||
// 设置节点多个样式
|
||||
setNodeStyles(node, style, isActive) {
|
||||
let data = {}
|
||||
if (isActive) {
|
||||
data = {
|
||||
activeStyle: {
|
||||
...(node.nodeData.data.activeStyle || {}),
|
||||
...style
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data = style
|
||||
}
|
||||
// 如果开启了富文本,则需要应用到富文本上
|
||||
if (this.mindMap.richText) {
|
||||
let config = this.mindMap.richText.normalStyleToRichTextStyle(style)
|
||||
if (Object.keys(config).length > 0) {
|
||||
this.mindMap.richText.showEditText(node)
|
||||
this.mindMap.richText.formatAllText(config)
|
||||
this.mindMap.richText.hideEditText([node])
|
||||
}
|
||||
}
|
||||
this.setNodeDataRender(node, data)
|
||||
// 更新了连线的样式
|
||||
let props = Object.keys(style)
|
||||
let hasLineStyleProps = false
|
||||
props.forEach((key) => {
|
||||
if (lineStyleProps.includes(key)) {
|
||||
hasLineStyleProps = true
|
||||
}
|
||||
})
|
||||
if (hasLineStyleProps) {
|
||||
;(node.parent || node).renderLine(true)
|
||||
}
|
||||
}
|
||||
|
||||
// 设置节点是否激活
|
||||
setNodeActive(node, active) {
|
||||
this.setNodeData(node, {
|
||||
@@ -1132,6 +1171,8 @@ class Render {
|
||||
node.generalizationBelongNode.updateGeneralization()
|
||||
}
|
||||
if (!notRender) this.mindMap.render()
|
||||
} else {
|
||||
this.mindMap.emit('node_tree_render_end')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ export default class TextEdit {
|
||||
this.textEditNode = null
|
||||
// 隐藏的文本输入框
|
||||
this.hiddenInputEl = null
|
||||
// 节点激活时默认聚焦到隐藏输入框
|
||||
this.enableFocus = true
|
||||
// 文本编辑框是否显示
|
||||
this.showTextEdit = false
|
||||
// 如果编辑过程中缩放画布了,那么缓存当前编辑的内容
|
||||
@@ -97,7 +99,17 @@ export default class TextEdit {
|
||||
|
||||
// 让隐藏的文本输入框聚焦
|
||||
focusHiddenInput() {
|
||||
if (this.hiddenInputEl) this.hiddenInputEl.focus()
|
||||
if (this.hiddenInputEl && this.enableFocus) this.hiddenInputEl.focus()
|
||||
}
|
||||
|
||||
// 关闭默认聚焦
|
||||
stopFocusOnNodeActive() {
|
||||
this.enableFocus = false
|
||||
}
|
||||
|
||||
// 开启默认聚焦
|
||||
openFocusOnNodeActive() {
|
||||
this.enableFocus = true
|
||||
}
|
||||
|
||||
// 注册临时快捷键
|
||||
@@ -167,7 +179,11 @@ export default class TextEdit {
|
||||
this.textEditNode.addEventListener('click', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
document.body.appendChild(this.textEditNode)
|
||||
this.textEditNode.addEventListener('mousedown', (e) => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body
|
||||
targetNode.appendChild(this.textEditNode)
|
||||
}
|
||||
let scale = this.mindMap.view.scale
|
||||
let lineHeight = node.style.merge('lineHeight')
|
||||
|
||||
@@ -385,10 +385,10 @@ class Node {
|
||||
this.active(e)
|
||||
})
|
||||
this.group.on('mousedown', e => {
|
||||
if (this.isRoot && e.which === 3) {
|
||||
if (this.isRoot && e.which === 3 && !this.mindMap.opt.readonly) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
if (!this.isRoot) {
|
||||
if (!this.isRoot && !this.mindMap.opt.readonly) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
// 多选和取消多选
|
||||
@@ -414,7 +414,7 @@ class Node {
|
||||
this.mindMap.emit('node_mousedown', this, e)
|
||||
})
|
||||
this.group.on('mouseup', e => {
|
||||
if (!this.isRoot) {
|
||||
if (!this.isRoot && !this.mindMap.opt.readonly) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
this.mindMap.emit('node_mouseup', this, e)
|
||||
|
||||
@@ -43,6 +43,11 @@ function setStyle(prop, value, isActive) {
|
||||
this.mindMap.execCommand('SET_NODE_STYLE', this, prop, value, isActive)
|
||||
}
|
||||
|
||||
// 修改多个样式
|
||||
function setStyles(style, isActive) {
|
||||
this.mindMap.execCommand('SET_NODE_STYLES', this, style, isActive)
|
||||
}
|
||||
|
||||
export default {
|
||||
setData,
|
||||
setText,
|
||||
@@ -52,5 +57,6 @@ export default {
|
||||
setNote,
|
||||
setTag,
|
||||
setShape,
|
||||
setStyle
|
||||
setStyle,
|
||||
setStyles
|
||||
}
|
||||
|
||||
@@ -268,7 +268,7 @@ function createNoteNode() {
|
||||
if (!this.noteEl) {
|
||||
this.noteEl = document.createElement('div')
|
||||
this.noteEl.style.cssText = `
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 5px rgb(0 0 0 / 10%);
|
||||
@@ -276,7 +276,8 @@ function createNoteNode() {
|
||||
background-color: #fff;
|
||||
z-index: ${ this.mindMap.opt.nodeNoteTooltipZIndex }
|
||||
`
|
||||
document.body.appendChild(this.noteEl)
|
||||
const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body
|
||||
targetNode.appendChild(this.noteEl)
|
||||
}
|
||||
this.noteEl.innerText = this.nodeData.data.note
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ class LogicalStructure extends Base {
|
||||
let nodeUseLineStylePath = nodeUseLineStyle
|
||||
? ` L ${item.left + item.width},${y2}`
|
||||
: ''
|
||||
if (node.isRoot) {
|
||||
if (node.isRoot && !this.mindMap.themeConfig.rootLineKeepSameInCurve) {
|
||||
path = this.quadraticCurvePath(x1, y1, x2, y2) + nodeUseLineStylePath
|
||||
} else {
|
||||
path = this.cubicBezierPath(x1, y1, x2, y2) + nodeUseLineStylePath
|
||||
|
||||
@@ -315,7 +315,7 @@ class MindMap extends Base {
|
||||
nodeUseLineStylePath = ` L ${item.left + item.width},${y2}`
|
||||
}
|
||||
}
|
||||
if (node.isRoot) {
|
||||
if (node.isRoot && !this.mindMap.themeConfig.rootLineKeepSameInCurve) {
|
||||
path = this.quadraticCurvePath(x1, y1, x2, y2) + nodeUseLineStylePath
|
||||
} else {
|
||||
path = this.cubicBezierPath(x1, y1, x2, y2) + nodeUseLineStylePath
|
||||
|
||||
@@ -123,6 +123,7 @@ const transformOldXmind = content => {
|
||||
let elements = data.elements
|
||||
let root = null
|
||||
let getRoot = arr => {
|
||||
if (!arr) return
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (!root && arr[i].name === 'topic') {
|
||||
root = arr[i]
|
||||
@@ -142,9 +143,10 @@ const transformOldXmind = content => {
|
||||
}
|
||||
let walk = (node, newNode) => {
|
||||
let nodeElements = node.elements
|
||||
let nodeTitle = getItemByName(nodeElements, 'title')
|
||||
newNode.data = {
|
||||
// 节点内容
|
||||
text: getItemByName(nodeElements, 'title').elements[0].text
|
||||
text: nodeTitle && nodeTitle.elements && nodeTitle.elements[0].text
|
||||
}
|
||||
try {
|
||||
// 节点备注
|
||||
|
||||
@@ -44,6 +44,7 @@ class Drag extends Base {
|
||||
this.mouseMoveY = 0
|
||||
// 鼠标移动的距离距鼠标按下的位置距离多少以上才认为是拖动事件
|
||||
this.checkDragOffset = 10
|
||||
this.minOffset = 10
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
@@ -105,6 +106,9 @@ class Drag extends Base {
|
||||
this.node.isDrag = false
|
||||
this.node.show()
|
||||
this.removeCloneNode()
|
||||
let overlapNodeUid = this.overlapNode ? this.overlapNode.nodeData.data.uid : ''
|
||||
let prevNodeUid = this.prevNode ? this.prevNode.nodeData.data.uid : ''
|
||||
let nextNodeUid = this.nextNode ? this.nextNode.nodeData.data.uid : ''
|
||||
// 存在重叠子节点,则移动作为其子节点
|
||||
if (this.overlapNode) {
|
||||
this.mindMap.renderer.setNodeActive(this.overlapNode, false)
|
||||
@@ -134,7 +138,11 @@ class Drag extends Base {
|
||||
this.mindMap.render()
|
||||
}
|
||||
this.reset()
|
||||
this.mindMap.emit('node_dragend')
|
||||
this.mindMap.emit('node_dragend', {
|
||||
overlapNodeUid,
|
||||
prevNodeUid,
|
||||
nextNodeUid
|
||||
})
|
||||
}
|
||||
|
||||
// 创建克隆节点
|
||||
@@ -199,6 +207,7 @@ class Drag extends Base {
|
||||
if (!this.drawTransform) {
|
||||
return
|
||||
}
|
||||
const { nodeDragPlaceholderMaxSize } = this.mindMap.opt
|
||||
let x = this.mouseMoveX
|
||||
let y = this.mouseMoveY
|
||||
this.overlapNode = null
|
||||
@@ -240,19 +249,19 @@ class Drag extends Base {
|
||||
let prevNodeRect = this.getNodeRect(prevBrother)
|
||||
prevBrotherOffset = nodeRect.top - prevNodeRect.bottom
|
||||
// 间距小于10就当它不存在
|
||||
prevBrotherOffset = prevBrotherOffset >= 10 ? prevBrotherOffset / 2 : 0
|
||||
prevBrotherOffset = prevBrotherOffset >= this.minOffset ? prevBrotherOffset / 2 : 0
|
||||
} else {
|
||||
// 没有前一个兄弟节点,那么假设和前一个节点的距离为20
|
||||
prevBrotherOffset = 10
|
||||
prevBrotherOffset = this.minOffset
|
||||
}
|
||||
// 和后一个兄弟节点的距离
|
||||
let nextBrotherOffset = 0
|
||||
if (nextBrother) {
|
||||
let nextNodeRect = this.getNodeRect(nextBrother)
|
||||
nextBrotherOffset = nextNodeRect.top - nodeRect.bottom
|
||||
nextBrotherOffset = nextBrotherOffset >= 10 ? nextBrotherOffset / 2 : 0
|
||||
nextBrotherOffset = nextBrotherOffset >= this.minOffset ? nextBrotherOffset / 2 : 0
|
||||
} else {
|
||||
nextBrotherOffset = 10
|
||||
nextBrotherOffset = this.minOffset
|
||||
}
|
||||
if (nodeRect.left <= x && nodeRect.right >= x) {
|
||||
// 检测兄弟节点位置
|
||||
@@ -265,11 +274,11 @@ class Drag extends Base {
|
||||
y >= nodeRect.top && y <= nodeRect.top + oneFourthHeight
|
||||
if (checkIsPrevNode) {
|
||||
this.prevNode = node
|
||||
let size = nextBrotherOffset > 0 ? nextBrotherOffset : 5
|
||||
let size = nextBrotherOffset > 0 ? Math.min(nextBrotherOffset, nodeDragPlaceholderMaxSize) : 5
|
||||
this.placeholder.size(node.width, size).move(nodeRect.originLeft, nodeRect.originBottom)
|
||||
} else if (checkIsNextNode) {
|
||||
this.nextNode = node
|
||||
let size = prevBrotherOffset > 0 ? prevBrotherOffset : 5
|
||||
let size = prevBrotherOffset > 0 ? Math.min(prevBrotherOffset, nodeDragPlaceholderMaxSize) : 5
|
||||
this.placeholder.size(node.width, size).move(nodeRect.originLeft, nodeRect.originTop - size)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,11 @@ class ExportXMind {
|
||||
const zipData = await xmind.transformToXmind(data, name)
|
||||
return zipData
|
||||
}
|
||||
|
||||
// 获取解析器
|
||||
getXmind() {
|
||||
return xmind
|
||||
}
|
||||
}
|
||||
|
||||
ExportXMind.instanceName = 'doExportXMind'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isWhite, isTransparent } from '../utils/index'
|
||||
import { isWhite, isTransparent, getVisibleColorFromTheme } from '../utils/index'
|
||||
|
||||
// 小地图插件
|
||||
class MiniMap {
|
||||
@@ -25,6 +25,11 @@ class MiniMap {
|
||||
let { svg, rect, origWidth, origHeight, scaleX, scaleY } =
|
||||
this.mindMap.getSvgData()
|
||||
// 计算数据
|
||||
const elRect = this.mindMap.elRect
|
||||
rect.x -= elRect.left
|
||||
rect.x2 -= elRect.left
|
||||
rect.y -= elRect.top
|
||||
rect.y2 -= elRect.top
|
||||
let boxRatio = boxWidth / boxHeight
|
||||
let actWidth = 0
|
||||
let actHeight = 0
|
||||
@@ -55,19 +60,28 @@ class MiniMap {
|
||||
bottom: 0
|
||||
}
|
||||
viewBoxStyle.left =
|
||||
Math.max(0, (-_rectX / _rectWidth) * actWidth) + miniMapBoxLeft + 'px'
|
||||
Math.max(0, (-_rectX / _rectWidth) * actWidth) + miniMapBoxLeft
|
||||
viewBoxStyle.right =
|
||||
Math.max(0, ((_rectX2 - origWidth) / _rectWidth) * actWidth) +
|
||||
miniMapBoxLeft +
|
||||
'px'
|
||||
miniMapBoxLeft
|
||||
|
||||
viewBoxStyle.top =
|
||||
Math.max(0, (-_rectY / _rectHeight) * actHeight) + miniMapBoxTop + 'px'
|
||||
Math.max(0, (-_rectY / _rectHeight) * actHeight) + miniMapBoxTop
|
||||
viewBoxStyle.bottom =
|
||||
Math.max(0, ((_rectY2 - origHeight) / _rectHeight) * actHeight) +
|
||||
miniMapBoxTop +
|
||||
'px'
|
||||
|
||||
miniMapBoxTop
|
||||
|
||||
if (viewBoxStyle.top > miniMapBoxTop + actHeight) {
|
||||
viewBoxStyle.top = miniMapBoxTop + actHeight
|
||||
}
|
||||
if (viewBoxStyle.left > miniMapBoxLeft + actWidth) {
|
||||
viewBoxStyle.left = miniMapBoxLeft + actWidth
|
||||
}
|
||||
|
||||
Object.keys(viewBoxStyle).forEach((key) => {
|
||||
viewBoxStyle[key] = viewBoxStyle[key] + 'px'
|
||||
})
|
||||
|
||||
this.removeNodeContent(svg)
|
||||
return {
|
||||
svgHTML: svg.svg(), // 小地图html
|
||||
@@ -84,7 +98,7 @@ class MiniMap {
|
||||
let shape = svg.findOne('.smm-node-shape')
|
||||
let fill = shape.attr('fill')
|
||||
if (isWhite(fill) || isTransparent(fill)) {
|
||||
shape.attr('fill', this.getDefaultFill())
|
||||
shape.attr('fill', getVisibleColorFromTheme(this.mindMap.themeConfig))
|
||||
}
|
||||
svg.clear()
|
||||
svg.add(shape)
|
||||
@@ -98,18 +112,6 @@ class MiniMap {
|
||||
}
|
||||
}
|
||||
|
||||
// 计算默认的填充颜色
|
||||
getDefaultFill() {
|
||||
let { lineColor, root, second, node } = this.mindMap.themeConfig
|
||||
let list = [lineColor, root.fillColor, root.color, second.fillColor, second.color, node.fillColor, node.color, root.borderColor, second.borderColor, node.borderColor]
|
||||
for(let i = 0; i < list.length; i++) {
|
||||
let color = list[i]
|
||||
if (!isTransparent(color) && !isWhite(color)) {
|
||||
return color
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 小地图鼠标按下事件
|
||||
onMousedown(e) {
|
||||
this.isMousedown = true
|
||||
|
||||
@@ -82,7 +82,7 @@ class NodeImgAdjust {
|
||||
this.createResizeBtnEl()
|
||||
}
|
||||
this.setHandleElRect()
|
||||
document.body.appendChild(this.handleEl)
|
||||
this.handleEl.style.display = 'block'
|
||||
this.isShowHandleEl = true
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ class NodeImgAdjust {
|
||||
hideHandleEl() {
|
||||
if (!this.isShowHandleEl) return
|
||||
this.isShowHandleEl = false
|
||||
document.body.removeChild(this.handleEl)
|
||||
this.handleEl.style.display = 'none'
|
||||
this.handleEl.style.backgroundImage = ``
|
||||
this.handleEl.style.width = 0
|
||||
this.handleEl.style.height = 0
|
||||
@@ -121,8 +121,10 @@ class NodeImgAdjust {
|
||||
this.handleEl.style.cssText = `
|
||||
pointer-events: none;
|
||||
position: fixed;
|
||||
display:none;
|
||||
background-size: cover;
|
||||
`
|
||||
this.handleEl.className = 'node-img-handle'
|
||||
// 调整按钮元素
|
||||
const btnEl = document.createElement('div')
|
||||
btnEl.innerHTML = btnsSvg.imgAdjust
|
||||
@@ -139,7 +141,7 @@ class NodeImgAdjust {
|
||||
align-items: center;
|
||||
cursor: nwse-resize;
|
||||
`
|
||||
this.handleEl.appendChild(btnEl)
|
||||
btnEl.className = 'node-image-resize'
|
||||
// 给按钮元素绑定事件
|
||||
btnEl.addEventListener('mouseenter', () => {
|
||||
// 移入按钮,会触发节点图片的移出事件,所以需要再次显示按钮
|
||||
@@ -151,8 +153,50 @@ class NodeImgAdjust {
|
||||
this.hideHandleEl()
|
||||
})
|
||||
btnEl.addEventListener('mousedown', e => {
|
||||
e.stopPropagation()
|
||||
this.onMousedown(e)
|
||||
})
|
||||
btnEl.addEventListener('mouseup', e => {
|
||||
setTimeout(() => {
|
||||
//点击后直接松开异常处理; 其他事件响应之后处理
|
||||
this.hideHandleEl()
|
||||
this.isAdjusted = false
|
||||
}, 0)
|
||||
})
|
||||
btnEl.addEventListener('click', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
this.handleEl.appendChild(btnEl)
|
||||
// 删除按钮
|
||||
const btnRemove = document.createElement('div')
|
||||
this.handleEl.prepend(btnRemove)
|
||||
btnRemove.className = 'node-image-remove'
|
||||
btnRemove.innerHTML = btnsSvg.remove
|
||||
btnRemove.style.cssText = `
|
||||
position: absolute;
|
||||
right: 0;top:0;color:#fff;
|
||||
pointer-events: auto;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
width: ${this.resizeBtnSize}px;
|
||||
height: ${this.resizeBtnSize}px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
`
|
||||
btnRemove.addEventListener('mouseenter', e => {
|
||||
this.showHandleEl()
|
||||
})
|
||||
btnRemove.addEventListener('mouseleave', e => {
|
||||
if (this.isMousedown) return
|
||||
this.hideHandleEl()
|
||||
})
|
||||
btnRemove.addEventListener('click', e => {
|
||||
this.mindMap.execCommand('SET_NODE_IMAGE', this.node, { url: null })
|
||||
})
|
||||
// 添加元素到页面
|
||||
const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body
|
||||
targetNode.appendChild(this.handleEl)
|
||||
}
|
||||
|
||||
// 鼠标按钮按下事件
|
||||
|
||||
76
simple-mind-map/src/plugins/Painter.js
Normal file
76
simple-mind-map/src/plugins/Painter.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import { nodeDataNoStylePropList } from '../constants/constant'
|
||||
|
||||
// 格式刷插件
|
||||
class Painter {
|
||||
constructor({ mindMap }) {
|
||||
this.mindMap = mindMap
|
||||
this.isInPainter = false
|
||||
this.painterNode = null
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
bindEvent() {
|
||||
this.painterOneNode = this.painterOneNode.bind(this)
|
||||
this.onEndPainter = this.onEndPainter.bind(this)
|
||||
this.mindMap.on('node_click', this.painterOneNode)
|
||||
this.mindMap.on('draw_click', this.onEndPainter)
|
||||
}
|
||||
|
||||
unBindEvent() {
|
||||
this.mindMap.off('node_click', this.painterOneNode)
|
||||
this.mindMap.off('draw_click', this.onEndPainter)
|
||||
}
|
||||
|
||||
// 开始格式刷
|
||||
startPainter() {
|
||||
if (this.mindMap.opt.readonly) return
|
||||
let activeNodeList = this.mindMap.renderer.activeNodeList
|
||||
if (activeNodeList.length <= 0) return
|
||||
this.painterNode = activeNodeList[0]
|
||||
this.isInPainter = true
|
||||
this.mindMap.emit('painter_start')
|
||||
}
|
||||
|
||||
// 结束格式刷
|
||||
endPainter() {
|
||||
this.painterNode = null
|
||||
this.isInPainter = false
|
||||
}
|
||||
|
||||
onEndPainter() {
|
||||
this.endPainter()
|
||||
this.mindMap.emit('painter_end')
|
||||
}
|
||||
|
||||
// 格式刷某个节点
|
||||
painterOneNode(node) {
|
||||
if (
|
||||
!node ||
|
||||
!this.isInPainter ||
|
||||
!this.painterNode ||
|
||||
!node ||
|
||||
node === this.painterNode
|
||||
)
|
||||
return
|
||||
const style = {}
|
||||
const painterNodeData = this.painterNode.nodeData.data
|
||||
Object.keys(painterNodeData).forEach(key => {
|
||||
if (!nodeDataNoStylePropList.includes(key)) {
|
||||
style[key] = painterNodeData[key]
|
||||
}
|
||||
})
|
||||
node.setStyles(style)
|
||||
if (painterNodeData.activeStyle) {
|
||||
node.setStyles(painterNodeData.activeStyle, true)
|
||||
}
|
||||
}
|
||||
|
||||
// 插件被移除前做的事情
|
||||
beforePluginRemove() {
|
||||
this.unBindEvent()
|
||||
}
|
||||
}
|
||||
|
||||
Painter.instanceName = 'painter'
|
||||
|
||||
export default Painter
|
||||
@@ -1,7 +1,7 @@
|
||||
import Quill from 'quill'
|
||||
import 'quill/dist/quill.snow.css'
|
||||
import html2canvas from 'html2canvas'
|
||||
import { walk, getTextFromHtml } from '../utils'
|
||||
import { walk, getTextFromHtml, isWhite, getVisibleColorFromTheme } from '../utils'
|
||||
import { CONSTANTS } from '../constants/constant'
|
||||
|
||||
let extended = false
|
||||
@@ -172,15 +172,20 @@ class RichText {
|
||||
this.textEditNode.addEventListener('click', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
document.body.appendChild(this.textEditNode)
|
||||
this.textEditNode.addEventListener('mousedown', (e) => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body
|
||||
targetNode.appendChild(this.textEditNode)
|
||||
}
|
||||
// 使用节点的填充色,否则如果节点颜色是白色的话编辑时看不见
|
||||
let bgColor = node.style.merge('fillColor')
|
||||
let color = node.style.merge('color')
|
||||
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
|
||||
bgColor === 'transparent' ? isWhite(color) ? getVisibleColorFromTheme(this.mindMap.themeConfig) : '#fff' : bgColor
|
||||
this.textEditNode.style.minWidth = originWidth + paddingX * 2 + 'px'
|
||||
this.textEditNode.style.minHeight = originHeight + 'px'
|
||||
this.textEditNode.style.left = rect.left + 'px'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { bfsWalk, getTextFromHtml, isUndef } from '../utils/index'
|
||||
import { bfsWalk, getTextFromHtml, isUndef, replaceHtmlText } from '../utils/index'
|
||||
|
||||
// 搜索插件
|
||||
class Search {
|
||||
@@ -15,12 +15,19 @@ class Search {
|
||||
this.currentIndex = -1
|
||||
// 不要复位搜索文本
|
||||
this.notResetSearchText = false
|
||||
// 是否自动跳转下一个匹配节点
|
||||
this.isJumpNext = false
|
||||
this.onDataChange = this.onDataChange.bind(this)
|
||||
this.mindMap.on('data_change', this.onDataChange)
|
||||
}
|
||||
|
||||
// 节点数据改变了,需要重新搜索
|
||||
onDataChange() {
|
||||
if (this.isJumpNext) {
|
||||
this.isJumpNext = false
|
||||
this.search(this.searchText)
|
||||
return
|
||||
}
|
||||
if (this.notResetSearchText) {
|
||||
this.notResetSearchText = false
|
||||
return
|
||||
@@ -29,7 +36,7 @@ class Search {
|
||||
}
|
||||
|
||||
// 搜索
|
||||
search(text, callback) {
|
||||
search(text, callback = () => {}) {
|
||||
if (isUndef(text)) return this.endSearch()
|
||||
text = String(text)
|
||||
this.isSearching = true
|
||||
@@ -88,13 +95,16 @@ class Search {
|
||||
}
|
||||
|
||||
// 替换当前节点
|
||||
replace(replaceText) {
|
||||
replace(replaceText, jumpNext = false) {
|
||||
if (
|
||||
isUndef(replaceText) ||
|
||||
replaceText === null ||
|
||||
replaceText === undefined ||
|
||||
!this.isSearching ||
|
||||
this.matchNodeList.length <= 0
|
||||
)
|
||||
return
|
||||
// 自动跳转下一个匹配节点
|
||||
this.isJumpNext = jumpNext
|
||||
replaceText = String(replaceText)
|
||||
let currentNode = this.matchNodeList[this.currentIndex]
|
||||
if (!currentNode) return
|
||||
@@ -115,7 +125,8 @@ class Search {
|
||||
// 替换所有
|
||||
replaceAll(replaceText) {
|
||||
if (
|
||||
isUndef(replaceText) ||
|
||||
replaceText === null ||
|
||||
replaceText === undefined ||
|
||||
!this.isSearching ||
|
||||
this.matchNodeList.length <= 0
|
||||
)
|
||||
@@ -141,9 +152,10 @@ class Search {
|
||||
getReplacedText(node, searchText, replaceText) {
|
||||
let { richText, text } = node.nodeData.data
|
||||
if (richText) {
|
||||
text = getTextFromHtml(text)
|
||||
return replaceHtmlText(text, searchText, replaceText)
|
||||
} else {
|
||||
return text.replaceAll(searchText, replaceText)
|
||||
}
|
||||
return text.replaceAll(searchText, replaceText)
|
||||
}
|
||||
|
||||
// 发送事件
|
||||
|
||||
@@ -59,10 +59,10 @@ class TouchEvent {
|
||||
let cy = (touch1ClientY + touch2ClientY) / 2
|
||||
if (distance > this.doubleTouchmoveDistance) {
|
||||
// 放大
|
||||
this.mindMap.view.enlarge(cx, cy)
|
||||
this.mindMap.view.enlarge(cx, cy, true)
|
||||
} else {
|
||||
// 缩小
|
||||
this.mindMap.view.narrow(cx, cy)
|
||||
this.mindMap.view.narrow(cx, cy, true)
|
||||
}
|
||||
this.doubleTouchmoveDistance = distance
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@ function showEditTextBox(g) {
|
||||
this.textEditNode.addEventListener('click', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
document.body.appendChild(this.textEditNode)
|
||||
const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body
|
||||
targetNode.appendChild(this.textEditNode)
|
||||
}
|
||||
let {
|
||||
associativeLineTextFontSize,
|
||||
|
||||
@@ -4,11 +4,15 @@ const open = `<svg t="1618141562310" class="icon" viewBox="0 0 1024 1024" versio
|
||||
// 收缩按钮
|
||||
const close = `<svg t="1618141589243" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13611" width="200" height="200"><path d="M512 105.472c225.28 0 407.04 181.76 407.04 407.04s-181.76 407.04-407.04 407.04-407.04-181.76-407.04-407.04 181.76-407.04 407.04-407.04z m0-74.24c-265.216 0-480.768 215.552-480.768 480.768s215.552 480.768 480.768 480.768 480.768-215.552 480.768-480.768-215.552-480.768-480.768-480.768z" p-id="13612"></path><path d="M252.928 474.624h518.144v74.24h-518.144z" p-id="13613"></path></svg>`
|
||||
|
||||
// 删除按钮
|
||||
const remove = `<svg width="14px" height="14px" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13611" width="200" height="200"><path fill="#ffffff" d="M512 105.472c225.28 0 407.04 181.76 407.04 407.04s-181.76 407.04-407.04 407.04-407.04-181.76-407.04-407.04 181.76-407.04 407.04-407.04z m0-74.24c-265.216 0-480.768 215.552-480.768 480.768s215.552 480.768 480.768 480.768 480.768-215.552 480.768-480.768-215.552-480.768-480.768-480.768z" p-id="13612"></path><path fill="#ffffff" d="M252.928 474.624h518.144v74.24h-518.144z" p-id="13613"></path></svg>`
|
||||
|
||||
// 图片调整按钮
|
||||
const imgAdjust = `<svg width="12px" height="12px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M1008.128 614.4a25.6 25.6 0 0 0-27.648 5.632l-142.848 142.848L259.072 186.88 401.92 43.52A25.6 25.6 0 0 0 384 0h-358.4a25.6 25.6 0 0 0-25.6 25.6v358.4a25.6 25.6 0 0 0 43.52 17.92l143.36-142.848 578.048 578.048-142.848 142.848a25.6 25.6 0 0 0 17.92 43.52h358.4a25.6 25.6 0 0 0 25.6-25.6v-358.4a25.6 25.6 0 0 0-15.872-25.088z" /></svg>`
|
||||
|
||||
export default {
|
||||
open,
|
||||
close,
|
||||
remove,
|
||||
imgAdjust
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ export default {
|
||||
lineDasharray: 'none',
|
||||
// 连线风格
|
||||
lineStyle: 'straight', // 针对logicalStructure、mindMap两种结构。曲线(curve)、直线(straight)、直连(direct)
|
||||
// 曲线连接时,根节点和其他节点的连接线样式保持统一,默认根节点为 ( 型,其他节点为 { 型,设为true后,都为 { 型
|
||||
rootLineKeepSameInCurve: true,
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 1,
|
||||
// 概要连线的颜色
|
||||
|
||||
@@ -527,4 +527,71 @@ export const isWhite = (color) => {
|
||||
export const isTransparent = (color) => {
|
||||
color = String(color).replaceAll(/\s+/g, '')
|
||||
return ['', 'transparent'].includes(color) || /rgba\(\d+,\d+,\d+,0\)/.test(color)
|
||||
}
|
||||
|
||||
// 从当前主题里获取一个非透明非白色的颜色
|
||||
export const getVisibleColorFromTheme = (themeConfig) => {
|
||||
let { lineColor, root, second, node } = themeConfig
|
||||
let list = [lineColor, root.fillColor, root.color, second.fillColor, second.color, node.fillColor, node.color, root.borderColor, second.borderColor, node.borderColor]
|
||||
for(let i = 0; i < list.length; i++) {
|
||||
let color = list[i]
|
||||
if (!isTransparent(color) && !isWhite(color)) {
|
||||
return color
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 将<p><span></span><p>形式的节点富文本内容转换成<br>换行的文本
|
||||
let nodeRichTextToTextWithWrapEl = null
|
||||
export const nodeRichTextToTextWithWrap = (html) => {
|
||||
if (!nodeRichTextToTextWithWrapEl) {
|
||||
nodeRichTextToTextWithWrapEl = document.createElement('div')
|
||||
}
|
||||
nodeRichTextToTextWithWrapEl.innerHTML = html
|
||||
const childNodes = nodeRichTextToTextWithWrapEl.childNodes
|
||||
let res = ''
|
||||
for(let i = 0; i < childNodes.length; i++) {
|
||||
const node = childNodes[i]
|
||||
if (node.nodeType === 1) {// 元素节点
|
||||
if (node.tagName.toLowerCase() === 'p') {
|
||||
res += node.textContent + '\n'
|
||||
} else {
|
||||
res += node.textContent
|
||||
}
|
||||
} else if (node.nodeType === 3) {// 文本节点
|
||||
res += node.nodeValue
|
||||
}
|
||||
}
|
||||
return res.replace(/\n$/, '')
|
||||
}
|
||||
|
||||
// 将<br>换行的文本转换成<p><span></span><p>形式的节点富文本内容
|
||||
let textToNodeRichTextWithWrapEl = null
|
||||
export const textToNodeRichTextWithWrap = (html) => {
|
||||
if (!textToNodeRichTextWithWrapEl) {
|
||||
textToNodeRichTextWithWrapEl = document.createElement('div')
|
||||
}
|
||||
textToNodeRichTextWithWrapEl.innerHTML = html
|
||||
const childNodes = textToNodeRichTextWithWrapEl.childNodes
|
||||
let list = []
|
||||
let str = ''
|
||||
for(let i = 0; i < childNodes.length; i++) {
|
||||
const node = childNodes[i]
|
||||
if (node.nodeType === 1) {// 元素节点
|
||||
if (node.tagName.toLowerCase() === 'br') {
|
||||
list.push(str)
|
||||
str = ''
|
||||
} else {
|
||||
str += node.textContent
|
||||
}
|
||||
} else if (node.nodeType === 3) {// 文本节点
|
||||
str += node.nodeValue
|
||||
}
|
||||
}
|
||||
if (str) {
|
||||
list.push(str)
|
||||
}
|
||||
return list.map((item) => {
|
||||
return `<p><span>${item}</span></p>`
|
||||
}).join('')
|
||||
}
|
||||
1
web/.env.library
Normal file
1
web/.env.library
Normal file
@@ -0,0 +1 @@
|
||||
NODE_ENV=library
|
||||
15
web/package-lock.json
generated
15
web/package-lock.json
generated
@@ -35,7 +35,8 @@
|
||||
"prettier": "^1.19.1",
|
||||
"vconsole": "^3.15.1",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"webpack": "^4.44.2"
|
||||
"webpack": "^4.44.2",
|
||||
"webpack-dynamic-public-path": "^1.0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@achrinza/node-ipc": {
|
||||
@@ -16224,6 +16225,12 @@
|
||||
"decamelize": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dynamic-public-path": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/webpack-dynamic-public-path/-/webpack-dynamic-public-path-1.0.8.tgz",
|
||||
"integrity": "sha512-AF6onorpvmiC+I/dQ19SOi+oN66oEy9h4deam7gPs1Qa1mOQ9i7IRsOahaukohKAciys7NfX+YFboRn4rmpuKw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/webpack-log": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz",
|
||||
@@ -29476,6 +29483,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"webpack-dynamic-public-path": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/webpack-dynamic-public-path/-/webpack-dynamic-public-path-1.0.8.tgz",
|
||||
"integrity": "sha512-AF6onorpvmiC+I/dQ19SOi+oN66oEy9h4deam7gPs1Qa1mOQ9i7IRsOahaukohKAciys7NfX+YFboRn4rmpuKw==",
|
||||
"dev": true
|
||||
},
|
||||
"webpack-log": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build && node ../copy.js",
|
||||
"lint": "vue-cli-service lint",
|
||||
"buildLibrary": "vue-cli-service build --target lib --name simpleMindMap ../simple-mind-map/full.js --dest ../simple-mind-map/dist && esbuild ../simple-mind-map/full.js --bundle --external:buffer --format=esm --outfile=../simple-mind-map/dist/simpleMindMap.esm.js && esbuild ../simple-mind-map/full.js --bundle --minify --external:buffer --format=esm --outfile=../simple-mind-map/dist/simpleMindMap.esm.min.js",
|
||||
"buildLibrary": "vue-cli-service build --mode library --target lib --name simpleMindMap ../simple-mind-map/full.js --dest ../simple-mind-map/dist && esbuild ../simple-mind-map/full.js --bundle --external:buffer --format=esm --outfile=../simple-mind-map/dist/simpleMindMap.esm.js && esbuild ../simple-mind-map/full.js --bundle --minify --external:buffer --format=esm --outfile=../simple-mind-map/dist/simpleMindMap.esm.min.js",
|
||||
"format": "prettier --write src/* src/*/* src/*/*/* src/*/*/*/*",
|
||||
"buildDoc": "node ./scripts/buildDoc.js",
|
||||
"autoBuildDoc": "node ./scripts/autoBuildDoc.js",
|
||||
@@ -40,7 +40,8 @@
|
||||
"prettier": "^1.19.1",
|
||||
"vconsole": "^3.15.1",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"webpack": "^4.44.2"
|
||||
"webpack": "^4.44.2",
|
||||
"webpack-dynamic-public-path": "^1.0.8"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 695 KiB |
@@ -4,8 +4,14 @@
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0">
|
||||
<link rel="icon" href="./dist/logo.ico">
|
||||
<link rel="icon" href="dist/logo.ico">
|
||||
<title>思绪思维导图</title>
|
||||
<script>
|
||||
// 自定义静态资源的路径
|
||||
window.externalPublicPath = './dist/'
|
||||
// 接管应用
|
||||
window.takeOverApp = false
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
@@ -13,5 +19,73 @@
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
<script>
|
||||
const getDataFromBackend = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
mindMapData: {
|
||||
root:{
|
||||
"data": {
|
||||
"text": "根节点"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
theme:{
|
||||
"template":"avocado",
|
||||
"config":{}
|
||||
},
|
||||
layout:"logicalStructure",
|
||||
config: {},
|
||||
view: null,
|
||||
},
|
||||
lang: 'zh',
|
||||
localConfig: null
|
||||
})
|
||||
}, 200)
|
||||
})
|
||||
}
|
||||
const setTakeOverAppMethods = (data) => {
|
||||
window.takeOverAppMethods = {}
|
||||
// 获取思维导图数据的函数
|
||||
window.takeOverAppMethods.getMindMapData = () => {
|
||||
return data.mindMapData
|
||||
}
|
||||
// 保存思维导图数据的函数
|
||||
window.takeOverAppMethods.saveMindMapData = (data) => {
|
||||
console.log(data)
|
||||
}
|
||||
// 获取语言的函数
|
||||
window.takeOverAppMethods.getLanguage = () => {
|
||||
return data.lang
|
||||
}
|
||||
// 保存语言的函数
|
||||
window.takeOverAppMethods.saveLanguage = (lang) => {
|
||||
console.log(lang)
|
||||
}
|
||||
// 获取本地配置的函数
|
||||
window.takeOverAppMethods.getLocalConfig = () => {
|
||||
return data.localConfig
|
||||
}
|
||||
// 保存本地配置的函数
|
||||
window.takeOverAppMethods.saveLocalConfig = (config) => {
|
||||
console.log(config)
|
||||
}
|
||||
}
|
||||
window.onload = async () => {
|
||||
if (!window.takeOverApp) return
|
||||
// 请求数据
|
||||
const data = await getDataFromBackend()
|
||||
// 设置全局的方法
|
||||
setTakeOverAppMethods(data)
|
||||
// 思维导图实例创建完成事件
|
||||
window.$bus.$on('app_inited', (mindMap) => {
|
||||
console.log(mindMap)
|
||||
})
|
||||
// 可以通过window.$bus.$on()来监听应用的一些事件
|
||||
// 实例化页面
|
||||
window.initApp()
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 8.5 KiB |
@@ -6,6 +6,8 @@ const SIMPLE_MIND_MAP_DATA = 'SIMPLE_MIND_MAP_DATA'
|
||||
const SIMPLE_MIND_MAP_LANG = 'SIMPLE_MIND_MAP_LANG'
|
||||
const SIMPLE_MIND_MAP_LOCAL_CONFIG = 'SIMPLE_MIND_MAP_LOCAL_CONFIG'
|
||||
|
||||
let mindMapData = null
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-02 22:36:48
|
||||
@@ -29,6 +31,10 @@ const copyMindMapTreeData = (tree, root) => {
|
||||
* @Desc: 获取缓存的思维导图数据
|
||||
*/
|
||||
export const getData = () => {
|
||||
if (window.takeOverApp) {
|
||||
mindMapData = window.takeOverAppMethods.getMindMapData()
|
||||
return mindMapData
|
||||
}
|
||||
let store = localStorage.getItem(SIMPLE_MIND_MAP_DATA)
|
||||
if (store === null) {
|
||||
return simpleDeepClone(exampleData)
|
||||
@@ -48,8 +54,18 @@ export const getData = () => {
|
||||
*/
|
||||
export const storeData = data => {
|
||||
try {
|
||||
let originData = getData()
|
||||
let originData = null
|
||||
if (window.takeOverApp) {
|
||||
originData = mindMapData
|
||||
} else {
|
||||
originData = getData()
|
||||
}
|
||||
originData.root = copyMindMapTreeData({}, data)
|
||||
if (window.takeOverApp) {
|
||||
mindMapData = originData
|
||||
window.takeOverAppMethods.saveMindMapData(originData)
|
||||
return
|
||||
}
|
||||
Vue.prototype.$bus.$emit('write_local_file', originData)
|
||||
let dataStr = JSON.stringify(originData)
|
||||
localStorage.setItem(SIMPLE_MIND_MAP_DATA, dataStr)
|
||||
@@ -65,11 +81,21 @@ export const storeData = data => {
|
||||
*/
|
||||
export const storeConfig = config => {
|
||||
try {
|
||||
let originData = getData()
|
||||
let originData = null
|
||||
if (window.takeOverApp) {
|
||||
originData = mindMapData
|
||||
} else {
|
||||
originData = getData()
|
||||
}
|
||||
originData = {
|
||||
...originData,
|
||||
...config
|
||||
}
|
||||
if (window.takeOverApp) {
|
||||
mindMapData = originData
|
||||
window.takeOverAppMethods.saveMindMapData(originData)
|
||||
return
|
||||
}
|
||||
Vue.prototype.$bus.$emit('write_local_file', originData)
|
||||
let dataStr = JSON.stringify(originData)
|
||||
localStorage.setItem(SIMPLE_MIND_MAP_DATA, dataStr)
|
||||
@@ -85,6 +111,10 @@ export const storeConfig = config => {
|
||||
* @Desc: 存储语言
|
||||
*/
|
||||
export const storeLang = lang => {
|
||||
if (window.takeOverApp) {
|
||||
window.takeOverAppMethods.saveLanguage(lang)
|
||||
return
|
||||
}
|
||||
localStorage.setItem(SIMPLE_MIND_MAP_LANG, lang)
|
||||
}
|
||||
|
||||
@@ -95,6 +125,9 @@ export const storeLang = lang => {
|
||||
* @Desc: 获取存储的语言
|
||||
*/
|
||||
export const getLang = () => {
|
||||
if (window.takeOverApp) {
|
||||
return window.takeOverAppMethods.getLanguage() || 'zh'
|
||||
}
|
||||
let lang = localStorage.getItem(SIMPLE_MIND_MAP_LANG)
|
||||
if (lang) {
|
||||
return lang
|
||||
@@ -110,6 +143,9 @@ export const getLang = () => {
|
||||
* @Desc: 存储本地配置
|
||||
*/
|
||||
export const storeLocalConfig = config => {
|
||||
if (window.takeOverApp) {
|
||||
return window.takeOverAppMethods.saveLocalConfig(config)
|
||||
}
|
||||
localStorage.setItem(SIMPLE_MIND_MAP_LOCAL_CONFIG, JSON.stringify(config))
|
||||
}
|
||||
|
||||
@@ -120,6 +156,9 @@ export const storeLocalConfig = config => {
|
||||
* @Desc: 获取本地配置
|
||||
*/
|
||||
export const getLocalConfig = () => {
|
||||
if (window.takeOverApp) {
|
||||
return window.takeOverAppMethods.getLocalConfig()
|
||||
}
|
||||
let config = localStorage.getItem(SIMPLE_MIND_MAP_LOCAL_CONFIG)
|
||||
if (config) {
|
||||
return JSON.parse(config)
|
||||
|
||||
BIN
web/src/assets/avatar/才镇.jpg
Normal file
BIN
web/src/assets/avatar/才镇.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 176 KiB |
@@ -102,6 +102,18 @@ export const lineStyleList = [
|
||||
}
|
||||
]
|
||||
|
||||
// 曲线风格中,根节点样式是否和其他节点保持一致
|
||||
export const rootLineKeepSameInCurveList = [
|
||||
{
|
||||
name: 'Bracket',
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: 'Brace',
|
||||
value: true
|
||||
}
|
||||
]
|
||||
|
||||
// 图片重复方式
|
||||
export const backgroundRepeatList = [
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
fontFamilyList as fontFamilyListZh,
|
||||
borderDasharrayList as borderDasharrayListZh,
|
||||
lineStyleList as lineStyleListZh,
|
||||
rootLineKeepSameInCurveList as rootLineKeepSameInCurveListZh,
|
||||
backgroundRepeatList as backgroundRepeatListZh,
|
||||
backgroundPositionList as backgroundPositionListZh,
|
||||
shortcutKeyList as shortcutKeyListZh,
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
fontFamilyList as fontFamilyListEn,
|
||||
borderDasharrayList as borderDasharrayListEn,
|
||||
lineStyleList as lineStyleListEn,
|
||||
rootLineKeepSameInCurveList as rootLineKeepSameInCurveListEn,
|
||||
backgroundRepeatList as backgroundRepeatListEn,
|
||||
backgroundPositionList as backgroundPositionListEn,
|
||||
shortcutKeyList as shortcutKeyListEn,
|
||||
@@ -46,6 +48,11 @@ const lineStyleList = {
|
||||
en: lineStyleListEn
|
||||
}
|
||||
|
||||
const rootLineKeepSameInCurveList = {
|
||||
zh: rootLineKeepSameInCurveListZh,
|
||||
en: rootLineKeepSameInCurveListEn
|
||||
}
|
||||
|
||||
const backgroundRepeatList = {
|
||||
zh: backgroundRepeatListZh,
|
||||
en: backgroundRepeatListEn
|
||||
@@ -93,6 +100,7 @@ export {
|
||||
fontFamilyList,
|
||||
borderDasharrayList,
|
||||
lineStyleList,
|
||||
rootLineKeepSameInCurveList,
|
||||
backgroundRepeatList,
|
||||
backgroundPositionList,
|
||||
backgroundSizeList,
|
||||
|
||||
@@ -157,6 +157,18 @@ export const lineStyleList = [
|
||||
}
|
||||
]
|
||||
|
||||
// 曲线风格中,根节点样式是否和其他节点保持一致
|
||||
export const rootLineKeepSameInCurveList = [
|
||||
{
|
||||
name: '括号',
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: '大括号',
|
||||
value: true
|
||||
}
|
||||
]
|
||||
|
||||
// 图片重复方式
|
||||
export const backgroundRepeatList = [
|
||||
{
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import Vue from 'vue'
|
||||
import VueI18n from 'vue-i18n'
|
||||
import messages from './lang'
|
||||
import { getLang } from '@/api'
|
||||
|
||||
Vue.use(VueI18n)
|
||||
|
||||
const i18n = new VueI18n({
|
||||
locale: getLang(),
|
||||
messages
|
||||
})
|
||||
|
||||
|
||||
@@ -46,7 +46,11 @@ export default {
|
||||
associativeLineActiveColor: 'Active color',
|
||||
mousewheelZoomActionReverse: 'Mouse Wheel Zoom',
|
||||
mousewheelZoomActionReverse1: 'Zoom out forward and zoom in back',
|
||||
mousewheelZoomActionReverse2: 'Zoom in forward and zoom out back'
|
||||
mousewheelZoomActionReverse2: 'Zoom in forward and zoom out back',
|
||||
rootStyle: 'Root Node',
|
||||
associativeLineText: 'Associative line text',
|
||||
fontFamily: 'Font family',
|
||||
fontSize: 'Font size'
|
||||
},
|
||||
color: {
|
||||
moreColor: 'More color'
|
||||
@@ -202,6 +206,7 @@ export default {
|
||||
export: 'Export',
|
||||
shortcutKey: 'Shortcut key',
|
||||
associativeLine: 'Associative line',
|
||||
painter: 'Painter'
|
||||
},
|
||||
edit: {
|
||||
newFeatureNoticeTitle: 'New feature reminder',
|
||||
|
||||
@@ -46,7 +46,11 @@ export default {
|
||||
associativeLineActiveColor: '激活颜色',
|
||||
mousewheelZoomActionReverse: '鼠标滚轮缩放',
|
||||
mousewheelZoomActionReverse1: '向前缩小向后放大',
|
||||
mousewheelZoomActionReverse2: '向前放大向后缩小'
|
||||
mousewheelZoomActionReverse2: '向前放大向后缩小',
|
||||
rootStyle: '根节点',
|
||||
associativeLineText: '关联线文字',
|
||||
fontFamily: '字体',
|
||||
fontSize: '字号'
|
||||
},
|
||||
color: {
|
||||
moreColor: '更多颜色'
|
||||
@@ -202,6 +206,7 @@ export default {
|
||||
export: '导出',
|
||||
shortcutKey: '快捷键',
|
||||
associativeLine: '关联线',
|
||||
painter: '格式刷'
|
||||
},
|
||||
edit: {
|
||||
newFeatureNoticeTitle: '新特性提醒',
|
||||
|
||||
@@ -8,17 +8,31 @@ import '@/assets/icon-font/iconfont.css'
|
||||
import 'viewerjs/dist/viewer.css'
|
||||
import VueViewer from 'v-viewer'
|
||||
import i18n from './i18n'
|
||||
import { getLang } from '@/api'
|
||||
// import VConsole from 'vconsole'
|
||||
// const vConsole = new VConsole()
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.prototype.$bus = new Vue()
|
||||
const bus = new Vue()
|
||||
Vue.prototype.$bus = bus
|
||||
Vue.use(ElementUI)
|
||||
Vue.use(VueViewer)
|
||||
|
||||
new Vue({
|
||||
render: h => h(App),
|
||||
router,
|
||||
store,
|
||||
i18n
|
||||
}).$mount('#app')
|
||||
const initApp = () => {
|
||||
i18n.locale = getLang()
|
||||
new Vue({
|
||||
render: h => h(App),
|
||||
router,
|
||||
store,
|
||||
i18n
|
||||
}).$mount('#app')
|
||||
}
|
||||
|
||||
// 是否处于接管应用模式
|
||||
if (window.takeOverApp) {
|
||||
window.initApp = initApp
|
||||
window.$bus = bus
|
||||
} else {
|
||||
initApp()
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ let APIList = [
|
||||
'touchEvent',
|
||||
'nodeImgAdjust',
|
||||
'search',
|
||||
'painter',
|
||||
'xmind',
|
||||
'markdown',
|
||||
'utils'
|
||||
|
||||
@@ -1,5 +1,53 @@
|
||||
# Changelog
|
||||
|
||||
## 0.6.12
|
||||
|
||||
Fix:
|
||||
|
||||
> 1.Fix the issue where the indicator in the mini map will also move out of the mini map area when the mind map is completely moved out of the visible area.
|
||||
>
|
||||
> 2.Fix the issue of overly sensitive dual finger scaling on the mobile end.
|
||||
>
|
||||
> 3.Fix the issue of holding down nodes while dragging the canvas in read-only mode.
|
||||
>
|
||||
> 4.Fix the issue of incorrect rendering of the mini map when the distance between the mind map and the top left corner of the browser window is not 0.
|
||||
>
|
||||
> 5.Fix the issue of the prompt block being too large for the new location when moving nodes.
|
||||
>
|
||||
> 6.Fix the issue where search cannot be replaced with empty characters.
|
||||
>
|
||||
> 7.Fixed the issue of missing line breaks after searching and replacing in rich text mode.
|
||||
>
|
||||
> 8.Fixed the issue of missing focus in the input box when clicking on text editing in the outline.
|
||||
|
||||
New:
|
||||
|
||||
> 1.Adding a callback parameter to the node move end event (node_drag) can obtain the uid of the move to the node.
|
||||
>
|
||||
> 2.Support specifying the location to which internal elements are added through configuration.
|
||||
>
|
||||
> 3.Support the format brush function.
|
||||
>
|
||||
> 4.Under the curve style, the connection line style of the root node supports consistency with other nodes.
|
||||
>
|
||||
> 5.Search supports continuous replacement.
|
||||
>
|
||||
> 6.Add and delete button for node image.
|
||||
>
|
||||
> 7.Support dragging the canvas while holding down the middle mouse button.
|
||||
|
||||
Demo:
|
||||
|
||||
> 1.Provide an application takeover mode to facilitate docking with one's own storage services; Supports setting static resource paths at runtime.
|
||||
>
|
||||
> 2.Refactoring outline: 1. No longer use the text style that comes with the node; 2. Support full screen editing of the outline; 3. The outline supports dragging and moving nodes; 4. The outline supports deleting nodes.
|
||||
>
|
||||
> 3.Fix the issue of interface dark mode not updating in the scenario of importing data.
|
||||
|
||||
## 0.6.11-fix.1
|
||||
|
||||
Fix: 1.Fixed the issue of invisible editing when node text is white.
|
||||
|
||||
## 0.6.11
|
||||
|
||||
New: 1.Optimize the mini map, remove node content within the mini map, and optimize performance.
|
||||
|
||||
@@ -1,6 +1,36 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Changelog</h1>
|
||||
<h2>0.6.12</h2>
|
||||
<p>Fix:</p>
|
||||
<blockquote>
|
||||
<p>1.Fix the issue where the indicator in the mini map will also move out of the mini map area when the mind map is completely moved out of the visible area.</p>
|
||||
<p>2.Fix the issue of overly sensitive dual finger scaling on the mobile end.</p>
|
||||
<p>3.Fix the issue of holding down nodes while dragging the canvas in read-only mode.</p>
|
||||
<p>4.Fix the issue of incorrect rendering of the mini map when the distance between the mind map and the top left corner of the browser window is not 0.</p>
|
||||
<p>5.Fix the issue of the prompt block being too large for the new location when moving nodes.</p>
|
||||
<p>6.Fix the issue where search cannot be replaced with empty characters.</p>
|
||||
<p>7.Fixed the issue of missing line breaks after searching and replacing in rich text mode.</p>
|
||||
<p>8.Fixed the issue of missing focus in the input box when clicking on text editing in the outline.</p>
|
||||
</blockquote>
|
||||
<p>New:</p>
|
||||
<blockquote>
|
||||
<p>1.Adding a callback parameter to the node move end event (node_drag) can obtain the uid of the move to the node.</p>
|
||||
<p>2.Support specifying the location to which internal elements are added through configuration.</p>
|
||||
<p>3.Support the format brush function.</p>
|
||||
<p>4.Under the curve style, the connection line style of the root node supports consistency with other nodes.</p>
|
||||
<p>5.Search supports continuous replacement.</p>
|
||||
<p>6.Add and delete button for node image.</p>
|
||||
<p>7.Support dragging the canvas while holding down the middle mouse button.</p>
|
||||
</blockquote>
|
||||
<p>Demo:</p>
|
||||
<blockquote>
|
||||
<p>1.Provide an application takeover mode to facilitate docking with one's own storage services; Supports setting static resource paths at runtime.</p>
|
||||
<p>2.Refactoring outline: 1. No longer use the text style that comes with the node; 2. Support full screen editing of the outline; 3. The outline supports dragging and moving nodes; 4. The outline supports deleting nodes.</p>
|
||||
<p>3.Fix the issue of interface dark mode not updating in the scenario of importing data.</p>
|
||||
</blockquote>
|
||||
<h2>0.6.11-fix.1</h2>
|
||||
<p>Fix: 1.Fixed the issue of invisible editing when node text is white.</p>
|
||||
<h2>0.6.11</h2>
|
||||
<p>New: 1.Optimize the mini map, remove node content within the mini map, and optimize performance.</p>
|
||||
<p>Demo: 1.Add a new topic and add tab differentiation to the topic list. 2.Node image upload supports inputting network image addresses. 3.Node image upload supports inputting network images.</p>
|
||||
|
||||
@@ -71,6 +71,8 @@ const mindMap = new MindMap({
|
||||
| isUseCustomNodeContent(v0.6.3+) | Boolean | false | Whether to customize node content | |
|
||||
| customCreateNodeContent(v0.6.3+) | Function/null | null | If `isUseCustomNodeContent` is set to `true`, then this option needs to be used to pass in a method that receives the node instance `node` as a parameter (if you want to obtain data for that node, you can use `node.nodeData.data`). You need to return the custom node content element, which is the DOM node. If a node does not require customization, you can return `null` | |
|
||||
| mouseScaleCenterUseMousePosition(v0.6.4-fix.1+) | Boolean | true | Is the mouse zoom centered around the current position of the mouse, otherwise centered around the canvas | |
|
||||
| customInnerElsAppendTo(v0.6.12+) | null/HTMLElement | null | Specify the location where some internal elements (node text editing element, node note display element, associated line text editing element, node image adjustment button element) are added, and default to document.body | |
|
||||
| nodeDragPlaceholderMaxSize(v0.6.12+) | Number | 20 | When dragging an element, the maximum height of the block indicating the new position of the element | |
|
||||
|
||||
### Watermark config
|
||||
|
||||
@@ -242,11 +244,12 @@ Listen to an event. Event list:
|
||||
| rich_text_selection_change(v0.4.0+) | Available when the `RichText` plugin is registered. Triggered when the text selection area changes when the node is edited | hasRange(Whether there is a selection)、rectInfo(Size and location information of the selected area)、formatInfo(Text formatting information of the selected area) |
|
||||
| transforming-dom-to-images(v0.4.0+) | Available when the `RichText` plugin is registered. When there is a `DOM` node in `svg`, the `DOM` node will be converted to an image when exporting to an image. This event will be triggered during the conversion process. You can use this event to prompt the user about the node to which you are currently converting | index(Index of the node currently converted to)、len(Total number of nodes to be converted) |
|
||||
| node_dragging(v0.4.5+) | Triggered when a node is dragged | node(The currently dragged node) |
|
||||
| node_dragend(v0.4.5+) | Triggered when the node is dragged and ends | |
|
||||
| node_dragend(v0.4.5+) | Triggered when the node is dragged and ends | { overlapNodeUid, prevNodeUid, nextNodeUid }(v0.6.12+,The node uid to which the node is moved this time, for example, if it is moved to node A, then the overlayNodeUid is the uid of node A. If it is moved to the front of node B, then the nextNodeUid is the uid of node B. You can obtain the node instance through the mindMap. extender.findNodeByUid(uid) method) |
|
||||
| associative_line_click(v0.4.5+) | Triggered when an associated line is clicked | path(Connector node)、clickPath(Invisible click line node)、node(Start node)、toNode(Target node) |
|
||||
| svg_mouseenter(v0.5.1+) | Triggered when the mouse moves into the SVG canvas | e(event object) |
|
||||
| svg_mouseleave(v0.5.1+) | Triggered when the mouse moves out of the SVG canvas | e(event object) |
|
||||
| node_icon_click(v0.6.10+) | Triggered when clicking on an icon within a node | this(node instance)、item(Click on the icon name)、e(event object) |
|
||||
| view_theme_change(v0.6.12+) | Triggered after calling the setTheme method to set the theme | theme(theme name) |
|
||||
|
||||
### emit(event, ...args)
|
||||
|
||||
@@ -326,7 +329,8 @@ redo. All commands are as follows:
|
||||
| DOWN_NODE | Move node down, the active node will be the operation node. If there are multiple active nodes, only the first one will be effective. Using this command on the root node or the last node in the list will be invalid | |
|
||||
| REMOVE_NODE | Remove node, the active node or appoint node will be the operation node | appointNodes(v0.4.7+, Optional, appoint node, Specifying multiple nodes can pass an array) |
|
||||
| PASTE_NODE | Paste node to a node, the active node will be the operation node | data (the node data to paste, usually obtained through the renderer.copyNode() and renderer.cutNode() methods) |
|
||||
| SET_NODE_STYLE | Modify node style | node (the node to set the style of), prop (style property), value (style property value), isActive (boolean, whether the style being set is for the active state) |
|
||||
| SET_NODE_STYLE | Modify node single style | node (the node to set the style of), prop (style property), value (style property value), isActive (boolean, whether the style being set is for the active state) |
|
||||
| SET_NODE_STYLEs(v0.6.12+) | Modify multiple styles of nodes | node(the node to set the style of)、style(Style object,key is style prop,value is style value)、isActive(boolean, whether the style being set is for the active state) |
|
||||
| SET_NODE_ACTIVE | Set whether the node is active | node (the node to set), active (boolean, whether to activate) |
|
||||
| CLEAR_ACTIVE_NODE | Clear the active state of the currently active node(s), the active node will be the operation node | |
|
||||
| SET_NODE_EXPAND | Set whether the node is expanded | node (the node to set), expand (boolean, whether to expand) |
|
||||
|
||||
@@ -357,6 +357,20 @@
|
||||
<td>Is the mouse zoom centered around the current position of the mouse, otherwise centered around the canvas</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>customInnerElsAppendTo(v0.6.12+)</td>
|
||||
<td>null/HTMLElement</td>
|
||||
<td>null</td>
|
||||
<td>Specify the location where some internal elements (node text editing element, node note display element, associated line text editing element, node image adjustment button element) are added, and default to document.body</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>nodeDragPlaceholderMaxSize(v0.6.12+)</td>
|
||||
<td>Number</td>
|
||||
<td>20</td>
|
||||
<td>When dragging an element, the maximum height of the block indicating the new position of the element</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Watermark config</h3>
|
||||
@@ -697,7 +711,7 @@ poor performance and should be used sparingly.</p>
|
||||
<tr>
|
||||
<td>node_dragend(v0.4.5+)</td>
|
||||
<td>Triggered when the node is dragged and ends</td>
|
||||
<td></td>
|
||||
<td>{ overlapNodeUid, prevNodeUid, nextNodeUid }(v0.6.12+,The node uid to which the node is moved this time, for example, if it is moved to node A, then the overlayNodeUid is the uid of node A. If it is moved to the front of node B, then the nextNodeUid is the uid of node B. You can obtain the node instance through the mindMap. extender.findNodeByUid(uid) method)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>associative_line_click(v0.4.5+)</td>
|
||||
@@ -719,6 +733,11 @@ poor performance and should be used sparingly.</p>
|
||||
<td>Triggered when clicking on an icon within a node</td>
|
||||
<td>this(node instance)、item(Click on the icon name)、e(event object)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>view_theme_change(v0.6.12+)</td>
|
||||
<td>Triggered after calling the setTheme method to set the theme</td>
|
||||
<td>theme(theme name)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>emit(event, ...args)</h3>
|
||||
@@ -817,10 +836,15 @@ redo. All commands are as follows:</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SET_NODE_STYLE</td>
|
||||
<td>Modify node style</td>
|
||||
<td>Modify node single style</td>
|
||||
<td>node (the node to set the style of), prop (style property), value (style property value), isActive (boolean, whether the style being set is for the active state)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SET_NODE_STYLEs(v0.6.12+)</td>
|
||||
<td>Modify multiple styles of nodes</td>
|
||||
<td>node(the node to set the style of)、style(Style object,key is style prop,value is style value)、isActive(boolean, whether the style being set is for the active state)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SET_NODE_ACTIVE</td>
|
||||
<td>Set whether the node is active</td>
|
||||
<td>node (the node to set), active (boolean, whether to activate)</td>
|
||||
|
||||
@@ -40,7 +40,7 @@ If you want to package 'index.html' into the 'dist' directory as well, you can m
|
||||
|
||||
If you want to modify the directory for packaging output, you can modify the 'outputDir' configuration of the 'web/vue.config.js' file to the path you want to output.
|
||||
|
||||
If you want to modify the path of the 'index. html' file referencing static resources, you can modify the 'publicPath' configuration of the 'web/vue.config.js' file.
|
||||
If you want to modify the path of the 'index. html' file referencing static resources, you can modify the 'publicPath' configuration of the 'web/vue.config.js' file. And the `window.externalPublicPath` config in `web/public/index.html` file.
|
||||
|
||||
In addition, the default route used is 'hash ', which means that there will be '#'in the path. If you want to use the 'history' route, you can modify the 'web/src/router.js' file to:
|
||||
|
||||
@@ -63,4 +63,167 @@ However, this requires backend support, as our application is a single page clie
|
||||
|
||||
## Docker
|
||||
|
||||
In writing...
|
||||
## Docker
|
||||
|
||||
> Thank you very much [水车](https://github.com/shuiche-it), This section is written by him, and the corresponding Docker package is also maintained by him.
|
||||
|
||||
Install directly from Docker Hub:
|
||||
|
||||
```
|
||||
docker run -d -p 8081:8080 shuiche/mind-map:latest
|
||||
```
|
||||
|
||||
Mindmap has activated port 8080 as the web service entry point in the container. When running the container through Docker, it is necessary to specify a local mapping port. In the above case, we mapped the local port 8081 to the container port 8080.
|
||||
|
||||
After the installation is completed, check the container's running status through 'Docker PS'.
|
||||
|
||||
Open 127.0.0.1:8081 in the browser to use the Web mind map function.
|
||||
|
||||
## Docking with one's own storage services
|
||||
|
||||
The application data is stored locally in the browser by default, and the local storage capacity of the browser is relatively small, so it is easy to trigger restrictions when inserting more images in the mind map. Therefore, a better choice is to dock with your own storage service, which usually has two ways:
|
||||
|
||||
### The first
|
||||
|
||||
Simply clone the warehouse code and modify the relevant methods in 'web/src/API/index.js' to obtain data from your database and store it in your data.
|
||||
|
||||
### The second
|
||||
|
||||
Many times, you may want to always use the latest code from this repository, so the first method is not very convenient because you need to manually merge the code, so the second method is provided.
|
||||
|
||||
Specific operating steps:
|
||||
|
||||
1. Copy the packaged resources of the web application
|
||||
|
||||
This includes the 'dist' directory and the 'index.html' file.
|
||||
|
||||
2. Modify the copied 'index.html' file
|
||||
|
||||
Firstly, insert the following code into the 'head' tag:
|
||||
|
||||
```js
|
||||
<script>
|
||||
window.takeOverApp = true
|
||||
</script>
|
||||
```
|
||||
|
||||
This line of code will prompt the application not to initialize the application 'i.e.: new Vue()', but to give control to you. Next, insert your own 'js' code at the end of the 'body', either inline or out of chain. The inline example is as follows:
|
||||
|
||||
```js
|
||||
<script>
|
||||
// Your own method of requesting data
|
||||
const getDataFromBackend = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
// MindMap data
|
||||
mindMapData: {
|
||||
root: {
|
||||
"data": {
|
||||
"text": "根节点"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
theme: { "template":"avocado","config":{} },
|
||||
layout: "logicalStructure",
|
||||
config: {},
|
||||
view: {}
|
||||
},
|
||||
// Page language, supporting Chinese (zh) and English (en)
|
||||
lang: 'zh',
|
||||
// Page Section Configuration
|
||||
localConfig: null
|
||||
})
|
||||
}, 200)
|
||||
})
|
||||
}
|
||||
// Register Global Method
|
||||
const setTakeOverAppMethods = (data) => {
|
||||
window.takeOverAppMethods = {}
|
||||
// Function for obtaining mind map data
|
||||
window.takeOverAppMethods.getMindMapData = () => {
|
||||
return data.mindMapData
|
||||
}
|
||||
// Functions for Saving Mind Map Data
|
||||
window.takeOverAppMethods.saveMindMapData = (data) => {
|
||||
console.log(data)
|
||||
// The trigger frequency of this function may be high, so you should do throttling or anti shaking measures
|
||||
}
|
||||
// Function to obtain language
|
||||
window.takeOverAppMethods.getLanguage = () => {
|
||||
return data.lang
|
||||
}
|
||||
// Functions for Saving Languages
|
||||
window.takeOverAppMethods.saveLanguage = (lang) => {
|
||||
console.log(lang)
|
||||
}
|
||||
// Get locally configured functions
|
||||
window.takeOverAppMethods.getLocalConfig = () => {
|
||||
return data.localConfig
|
||||
}
|
||||
// Save locally configured functions
|
||||
window.takeOverAppMethods.saveLocalConfig = (config) => {
|
||||
console.log(config)
|
||||
}
|
||||
}
|
||||
window.onload = async () => {
|
||||
if (!window.takeOverApp) return
|
||||
// request data
|
||||
const data = await getDataFromBackend()
|
||||
// Method for setting global
|
||||
setTakeOverAppMethods(data)
|
||||
// Mind Map Instance Creation Completion Event
|
||||
window.$bus.$on('app_inited', (mindMap) => {
|
||||
console.log(mindMap)
|
||||
})
|
||||
// You can use window$ Bus$ On() to listen for some events in the application
|
||||
// Instantiate Page
|
||||
window.initApp()
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
As shown above, when you set the 'window.takeOverApp=true' flag, the application will no longer actively instantiate, but will expose the instantiated methods for you to call. You can first request the data of the mind map from the backend, and then register the relevant methods. The application will call internally at the appropriate time to achieve the purpose of echo and save.
|
||||
|
||||
The advantage of doing this is that whenever the code in this repository is updated, you can simply copy the packaged files to your own server. With a slight modification of the 'index. html' page, you can achieve synchronous updates and use your own storage service.
|
||||
|
||||
## Modifying Static Resource Paths
|
||||
|
||||
If you want to maintain synchronous updates with the code in this repository as in the previous section, but also want to modify the storage location of static resources, for example, the default hierarchical relationship is:
|
||||
|
||||
```
|
||||
-dist
|
||||
--css
|
||||
--fonts
|
||||
--img
|
||||
--js
|
||||
-logo.ico
|
||||
|
||||
-index.html
|
||||
```
|
||||
|
||||
And you want to adjust it to this:
|
||||
|
||||
```
|
||||
-assets
|
||||
--dist
|
||||
---css
|
||||
---fonts
|
||||
---img
|
||||
---js
|
||||
-logo.ico
|
||||
|
||||
-index.html
|
||||
```
|
||||
|
||||
So you can configure the 'window.externalPublicPath' in 'index.html' as the default `./dist/` is modified to:
|
||||
|
||||
```js
|
||||
window.externalPublicPath = './assets/dist/'
|
||||
```
|
||||
|
||||
At the same time, the paths of the inline '.ico', '.js', and '.css' resources in 'index.html' need to be manually modified by you.
|
||||
|
||||
It should be noted that it is best not to adjust the directory hierarchy within the 'dist' directory, otherwise exceptions may occur.
|
||||
|
||||
If you want to replace some of the static resources, such as the theme image and structure image, with your own designed image, you can directly overwrite it with the same name.
|
||||
@@ -25,7 +25,7 @@ npm link simple-mind-map
|
||||
<p>If you do not have any code modification requirements, it is also possible to directly copy these files from this repository.</p>
|
||||
<p>If you want to package 'index.html' into the 'dist' directory as well, you can modify the 'scripts.build' command in the 'web/package.json' file to delete '&& node ../copy.js' from 'vue-cli-service build && node ../copy.js'.</p>
|
||||
<p>If you want to modify the directory for packaging output, you can modify the 'outputDir' configuration of the 'web/vue.config.js' file to the path you want to output.</p>
|
||||
<p>If you want to modify the path of the 'index. html' file referencing static resources, you can modify the 'publicPath' configuration of the 'web/vue.config.js' file.</p>
|
||||
<p>If you want to modify the path of the 'index. html' file referencing static resources, you can modify the 'publicPath' configuration of the 'web/vue.config.js' file. And the <code>window.externalPublicPath</code> config in <code>web/public/index.html</code> file.</p>
|
||||
<p>In addition, the default route used is 'hash ', which means that there will be '#'in the path. If you want to use the 'history' route, you can modify the 'web/src/router.js' file to:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">const</span> router = <span class="hljs-keyword">new</span> VueRouter({
|
||||
routes
|
||||
@@ -39,7 +39,138 @@ npm link simple-mind-map
|
||||
</code></pre>
|
||||
<p>However, this requires backend support, as our application is a single page client application. If the backend is not properly configured, users will return 404 when accessing sub routes directly in the browser. Therefore, you need to add a candidate resource on the server that covers all situations: if the 'URL' cannot match any static resources, the same 'index. html' page should be returned.</p>
|
||||
<h2>Docker</h2>
|
||||
<p>In writing...</p>
|
||||
<h2>Docker</h2>
|
||||
<blockquote>
|
||||
<p>Thank you very much <a href="https://github.com/shuiche-it">水车</a>, This section is written by him, and the corresponding Docker package is also maintained by him.</p>
|
||||
</blockquote>
|
||||
<p>Install directly from Docker Hub:</p>
|
||||
<pre class="hljs"><code>docker run -d -p 8081:8080 shuiche/mind-map:latest
|
||||
</code></pre>
|
||||
<p>Mindmap has activated port 8080 as the web service entry point in the container. When running the container through Docker, it is necessary to specify a local mapping port. In the above case, we mapped the local port 8081 to the container port 8080.</p>
|
||||
<p>After the installation is completed, check the container's running status through 'Docker PS'.</p>
|
||||
<p>Open 127.0.0.1:8081 in the browser to use the Web mind map function.</p>
|
||||
<h2>Docking with one's own storage services</h2>
|
||||
<p>The application data is stored locally in the browser by default, and the local storage capacity of the browser is relatively small, so it is easy to trigger restrictions when inserting more images in the mind map. Therefore, a better choice is to dock with your own storage service, which usually has two ways:</p>
|
||||
<h3>The first</h3>
|
||||
<p>Simply clone the warehouse code and modify the relevant methods in 'web/src/API/index.js' to obtain data from your database and store it in your data.</p>
|
||||
<h3>The second</h3>
|
||||
<p>Many times, you may want to always use the latest code from this repository, so the first method is not very convenient because you need to manually merge the code, so the second method is provided.</p>
|
||||
<p>Specific operating steps:</p>
|
||||
<ol>
|
||||
<li>Copy the packaged resources of the web application</li>
|
||||
</ol>
|
||||
<p>This includes the 'dist' directory and the 'index.html' file.</p>
|
||||
<ol start="2">
|
||||
<li>Modify the copied 'index.html' file</li>
|
||||
</ol>
|
||||
<p>Firstly, insert the following code into the 'head' tag:</p>
|
||||
<pre class="hljs"><code><script>
|
||||
<span class="hljs-built_in">window</span>.takeOverApp = <span class="hljs-literal">true</span>
|
||||
</script>
|
||||
</code></pre>
|
||||
<p>This line of code will prompt the application not to initialize the application 'i.e.: new Vue()', but to give control to you. Next, insert your own 'js' code at the end of the 'body', either inline or out of chain. The inline example is as follows:</p>
|
||||
<pre class="hljs"><code><script>
|
||||
<span class="hljs-comment">// Your own method of requesting data</span>
|
||||
<span class="hljs-keyword">const</span> getDataFromBackend = <span class="hljs-function">() =></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">resolve, reject</span>) =></span> {
|
||||
<span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =></span> {
|
||||
resolve({
|
||||
<span class="hljs-comment">// MindMap data</span>
|
||||
<span class="hljs-attr">mindMapData</span>: {
|
||||
<span class="hljs-attr">root</span>: {
|
||||
<span class="hljs-string">"data"</span>: {
|
||||
<span class="hljs-string">"text"</span>: <span class="hljs-string">"根节点"</span>
|
||||
},
|
||||
<span class="hljs-string">"children"</span>: []
|
||||
},
|
||||
<span class="hljs-attr">theme</span>: { <span class="hljs-string">"template"</span>:<span class="hljs-string">"avocado"</span>,<span class="hljs-string">"config"</span>:{} },
|
||||
<span class="hljs-attr">layout</span>: <span class="hljs-string">"logicalStructure"</span>,
|
||||
<span class="hljs-attr">config</span>: {},
|
||||
<span class="hljs-attr">view</span>: {}
|
||||
},
|
||||
<span class="hljs-comment">// Page language, supporting Chinese (zh) and English (en)</span>
|
||||
<span class="hljs-attr">lang</span>: <span class="hljs-string">'zh'</span>,
|
||||
<span class="hljs-comment">// Page Section Configuration</span>
|
||||
<span class="hljs-attr">localConfig</span>: <span class="hljs-literal">null</span>
|
||||
})
|
||||
}, <span class="hljs-number">200</span>)
|
||||
})
|
||||
}
|
||||
<span class="hljs-comment">// Register Global Method</span>
|
||||
<span class="hljs-keyword">const</span> setTakeOverAppMethods = <span class="hljs-function">(<span class="hljs-params">data</span>) =></span> {
|
||||
<span class="hljs-built_in">window</span>.takeOverAppMethods = {}
|
||||
<span class="hljs-comment">// Function for obtaining mind map data</span>
|
||||
<span class="hljs-built_in">window</span>.takeOverAppMethods.getMindMapData = <span class="hljs-function">() =></span> {
|
||||
<span class="hljs-keyword">return</span> data.mindMapData
|
||||
}
|
||||
<span class="hljs-comment">// Functions for Saving Mind Map Data</span>
|
||||
<span class="hljs-built_in">window</span>.takeOverAppMethods.saveMindMapData = <span class="hljs-function">(<span class="hljs-params">data</span>) =></span> {
|
||||
<span class="hljs-built_in">console</span>.log(data)
|
||||
<span class="hljs-comment">// The trigger frequency of this function may be high, so you should do throttling or anti shaking measures</span>
|
||||
}
|
||||
<span class="hljs-comment">// Function to obtain language</span>
|
||||
<span class="hljs-built_in">window</span>.takeOverAppMethods.getLanguage = <span class="hljs-function">() =></span> {
|
||||
<span class="hljs-keyword">return</span> data.lang
|
||||
}
|
||||
<span class="hljs-comment">// Functions for Saving Languages</span>
|
||||
<span class="hljs-built_in">window</span>.takeOverAppMethods.saveLanguage = <span class="hljs-function">(<span class="hljs-params">lang</span>) =></span> {
|
||||
<span class="hljs-built_in">console</span>.log(lang)
|
||||
}
|
||||
<span class="hljs-comment">// Get locally configured functions</span>
|
||||
<span class="hljs-built_in">window</span>.takeOverAppMethods.getLocalConfig = <span class="hljs-function">() =></span> {
|
||||
<span class="hljs-keyword">return</span> data.localConfig
|
||||
}
|
||||
<span class="hljs-comment">// Save locally configured functions</span>
|
||||
<span class="hljs-built_in">window</span>.takeOverAppMethods.saveLocalConfig = <span class="hljs-function">(<span class="hljs-params">config</span>) =></span> {
|
||||
<span class="hljs-built_in">console</span>.log(config)
|
||||
}
|
||||
}
|
||||
<span class="hljs-built_in">window</span>.onload = <span class="hljs-keyword">async</span> () => {
|
||||
<span class="hljs-keyword">if</span> (!<span class="hljs-built_in">window</span>.takeOverApp) <span class="hljs-keyword">return</span>
|
||||
<span class="hljs-comment">// request data</span>
|
||||
<span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> getDataFromBackend()
|
||||
<span class="hljs-comment">// Method for setting global</span>
|
||||
setTakeOverAppMethods(data)
|
||||
<span class="hljs-comment">// Mind Map Instance Creation Completion Event</span>
|
||||
<span class="hljs-built_in">window</span>.$bus.$on(<span class="hljs-string">'app_inited'</span>, <span class="hljs-function">(<span class="hljs-params">mindMap</span>) =></span> {
|
||||
<span class="hljs-built_in">console</span>.log(mindMap)
|
||||
})
|
||||
<span class="hljs-comment">// You can use window$ Bus$ On() to listen for some events in the application</span>
|
||||
<span class="hljs-comment">// Instantiate Page</span>
|
||||
<span class="hljs-built_in">window</span>.initApp()
|
||||
}
|
||||
</script>
|
||||
</code></pre>
|
||||
<p>As shown above, when you set the 'window.takeOverApp=true' flag, the application will no longer actively instantiate, but will expose the instantiated methods for you to call. You can first request the data of the mind map from the backend, and then register the relevant methods. The application will call internally at the appropriate time to achieve the purpose of echo and save.</p>
|
||||
<p>The advantage of doing this is that whenever the code in this repository is updated, you can simply copy the packaged files to your own server. With a slight modification of the 'index. html' page, you can achieve synchronous updates and use your own storage service.</p>
|
||||
<h2>Modifying Static Resource Paths</h2>
|
||||
<p>If you want to maintain synchronous updates with the code in this repository as in the previous section, but also want to modify the storage location of static resources, for example, the default hierarchical relationship is:</p>
|
||||
<pre class="hljs"><code>-dist
|
||||
--css
|
||||
--fonts
|
||||
--img
|
||||
--js
|
||||
-logo.ico
|
||||
|
||||
-index.html
|
||||
</code></pre>
|
||||
<p>And you want to adjust it to this:</p>
|
||||
<pre class="hljs"><code>-assets
|
||||
--dist
|
||||
---css
|
||||
---fonts
|
||||
---img
|
||||
---js
|
||||
-logo.ico
|
||||
|
||||
-index.html
|
||||
</code></pre>
|
||||
<p>So you can configure the 'window.externalPublicPath' in 'index.html' as the default <code>./dist/</code> is modified to:</p>
|
||||
<pre class="hljs"><code><span class="hljs-built_in">window</span>.externalPublicPath = <span class="hljs-string">'./assets/dist/'</span>
|
||||
</code></pre>
|
||||
<p>At the same time, the paths of the inline '.ico', '.js', and '.css' resources in 'index.html' need to be manually modified by you.</p>
|
||||
<p>It should be noted that it is best not to adjust the directory hierarchy within the 'dist' directory, otherwise exceptions may occur.</p>
|
||||
<p>If you want to replace some of the static resources, such as the theme image and structure image, with your own designed image, you can directly overwrite it with the same name.</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -8,21 +8,16 @@
|
||||
|
||||
## Features
|
||||
|
||||
- [x] Plugin architecture. In addition to core functions, other functions are provided as plugins, which can be used as needed to reduce the overall volume
|
||||
- [x] Supports six types of structures: logical structure diagrams, mind maps,
|
||||
organizational structure diagrams, directory organization diagrams, timeline, and fishbone diagrams
|
||||
- [x] Built-in multiple themes and allows for highly customized styles, and support register new themes
|
||||
- [x] Supports shortcuts
|
||||
- [x] Node content supports images, icons, hyperlinks, notes, tags, and
|
||||
summaries
|
||||
- [x] Supports forward and backward navigation
|
||||
- [x] Supports dragging and scaling
|
||||
- [x] Supports right-click and Ctrl + left-click to select multiple items
|
||||
- [x] Supports free dragging and dragging to adjust nodes
|
||||
- [x] Supports various node shapes
|
||||
- [x] Supports export to json, png, svg, pdf markdown, and import from json, xmind, markdown
|
||||
- [x] Supports mini map、support watermark
|
||||
- [x] Supports associative lines
|
||||
- [x] Pluggable architecture, in addition to core functions, other functions are provided as plugins, which can be used as needed to reduce packaging volume
|
||||
- [x] Support logical structure chart, mind map, Organizational chart, directory organization chart, timeline (horizontal and vertical), fishbone chart and other structures
|
||||
- [x] Built-in multiple themes, allowing for highly customizable styles, and supporting registration of new themes
|
||||
- [x] Node content supports text (regular text, rich text), images, icons, hyperlinks, notes, labels, and summaries
|
||||
- [x] Nodes support drag and drop (drag and move, freely adjust), multiple node shapes, and fully customize node content using DDM
|
||||
- [x] Support canvas dragging and scaling
|
||||
- [x] Supports two multi node selection methods: mouse button drag selection and Ctrl+left button selection
|
||||
- [x] Supoorts to export as `json`、`png`、`svg`、`pdf`、`markdown`、`xmind`, support import from `json`、`xmind`、`markdown`
|
||||
- [x] Support shortcut keys, forward and backward, correlation lines, search and replacement, small maps, and watermarks
|
||||
- [x] Provide rich configurations to meet various scenarios and usage habits
|
||||
|
||||
## Repository Catalog Introduction
|
||||
|
||||
@@ -172,4 +167,8 @@ Open source is not easy. If this project is helpful to you, you can invite the a
|
||||
<img src="../../../../assets/avatar/千帆.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>千帆</p>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/才镇.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>才镇</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -8,21 +8,16 @@
|
||||
</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>
|
||||
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>
|
||||
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="checkbox30" checked="true" /><label for="checkbox30">Pluggable architecture, in addition to core functions, other functions are provided as plugins, which can be used as needed to reduce packaging volume</label></li>
|
||||
<li><input type="checkbox" id="checkbox31" checked="true" /><label for="checkbox31">Support logical structure chart, mind map, Organizational chart, directory organization chart, timeline (horizontal and vertical), fishbone chart and other structures</label></li>
|
||||
<li><input type="checkbox" id="checkbox32" checked="true" /><label for="checkbox32">Built-in multiple themes, allowing for highly customizable styles, and supporting registration of new themes</label></li>
|
||||
<li><input type="checkbox" id="checkbox33" checked="true" /><label for="checkbox33">Node content supports text (regular text, rich text), images, icons, hyperlinks, notes, labels, and summaries</label></li>
|
||||
<li><input type="checkbox" id="checkbox34" checked="true" /><label for="checkbox34">Nodes support drag and drop (drag and move, freely adjust), multiple node shapes, and fully customize node content using DDM</label></li>
|
||||
<li><input type="checkbox" id="checkbox35" checked="true" /><label for="checkbox35">Support canvas dragging and scaling</label></li>
|
||||
<li><input type="checkbox" id="checkbox36" checked="true" /><label for="checkbox36">Supports two multi node selection methods: mouse button drag selection and Ctrl+left button selection</label></li>
|
||||
<li><input type="checkbox" id="checkbox37" checked="true" /><label for="checkbox37">Supoorts to export as </label><code>json</code>、<code>png</code>、<code>svg</code>、<code>pdf</code>、<code>markdown</code>、<code>xmind</code>, support import from <code>json</code>、<code>xmind</code>、<code>markdown</code></li>
|
||||
<li><input type="checkbox" id="checkbox38" checked="true" /><label for="checkbox38">Support shortcut keys, forward and backward, correlation lines, search and replacement, small maps, and watermarks</label></li>
|
||||
<li><input type="checkbox" id="checkbox39" checked="true" /><label for="checkbox39">Provide rich configurations to meet various scenarios and usage habits</label></li>
|
||||
</ul>
|
||||
<h2>Repository Catalog Introduction</h2>
|
||||
<p>1.<code>simple-mind-map</code></p>
|
||||
@@ -32,16 +27,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="checkbox40" checked="true" /><label for="checkbox40">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="checkbox41" checked="true" /><label for="checkbox41">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="checkbox42" checked="true" /><label for="checkbox42">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="checkbox43" checked="true" /><label for="checkbox43">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="checkbox44" checked="true" /><label for="checkbox44">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>
|
||||
@@ -131,6 +126,10 @@ full screen, support mini map</li>
|
||||
<img src="../../../../assets/avatar/千帆.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>千帆</p>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/才镇.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>才镇</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -47,6 +47,15 @@ Small map idea:
|
||||
|
||||
1.Prepare a container element `container`, position is not `static`
|
||||
|
||||
If using rich text editing mode, it is best to remove the default style from the elements inside the 'container', otherwise there may be text offset issues within nodes:
|
||||
|
||||
```css
|
||||
.container * {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
```
|
||||
|
||||
2.In `container`, create a small map container element `miniMapContainer`,
|
||||
absolute positioning
|
||||
|
||||
|
||||
@@ -34,6 +34,12 @@ MindMap.usePlugin(MiniMap)
|
||||
</code></pre>
|
||||
<p>Small map idea:</p>
|
||||
<p>1.Prepare a container element <code>container</code>, position is not <code>static</code></p>
|
||||
<p>If using rich text editing mode, it is best to remove the default style from the elements inside the 'container', otherwise there may be text offset issues within nodes:</p>
|
||||
<pre class="hljs"><code><span class="hljs-selector-class">.container</span> * {
|
||||
<span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
|
||||
<span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;
|
||||
}
|
||||
</code></pre>
|
||||
<p>2.In <code>container</code>, create a small map container element <code>miniMapContainer</code>,
|
||||
absolute positioning</p>
|
||||
<p>3.In <code>container</code>, create a view box element <code>viewBoxContainer</code>, absolute
|
||||
|
||||
@@ -117,6 +117,12 @@ default `false`
|
||||
|
||||
Modify a style of the node, a shortcut method for the `SET_NODE_STYLE` command
|
||||
|
||||
### setStyles(style, isActive)
|
||||
|
||||
> v0.6.12+
|
||||
|
||||
Modify multiple styles of nodes, a shortcut method for the `SET_NODE_STYLES` command
|
||||
|
||||
### getData(key)
|
||||
|
||||
Get the specified value in the `data` object of the node's real data `nodeData`,
|
||||
|
||||
@@ -68,6 +68,11 @@
|
||||
default <code>false</code></p>
|
||||
<h3>setStyle(prop, value, isActive)</h3>
|
||||
<p>Modify a style of the node, a shortcut method for the <code>SET_NODE_STYLE</code> command</p>
|
||||
<h3>setStyles(style, isActive)</h3>
|
||||
<blockquote>
|
||||
<p>v0.6.12+</p>
|
||||
</blockquote>
|
||||
<p>Modify multiple styles of nodes, a shortcut method for the <code>SET_NODE_STYLES</code> command</p>
|
||||
<h3>getData(key)</h3>
|
||||
<p>Get the specified value in the <code>data</code> object of the node's real data <code>nodeData</code>,
|
||||
if <code>key</code> is not passed, return the <code>data</code> object</p>
|
||||
|
||||
35
web/src/pages/Doc/en/painter/index.md
Normal file
35
web/src/pages/Doc/en/painter/index.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Painter plugin
|
||||
|
||||
> v0.6.12+
|
||||
|
||||
Node format brush plugin.
|
||||
|
||||
## Register
|
||||
|
||||
```js
|
||||
import MindMap from 'simple-mind-map'
|
||||
import Painter from 'simple-mind-map/src/plugins/Painter.js'
|
||||
MindMap.usePlugin(Painter)
|
||||
```
|
||||
|
||||
After registration and instantiation of `MindMap`, the instance can be obtained through `mindMap.painter`.
|
||||
|
||||
## Event
|
||||
|
||||
> You can use mindMap.on('event name', () => {}) method to listen events.
|
||||
|
||||
### painter_start
|
||||
|
||||
The event of painter start.
|
||||
|
||||
### painter_end
|
||||
|
||||
The event of painter end.
|
||||
|
||||
## Method
|
||||
|
||||
### startPainter()
|
||||
|
||||
Start painter.
|
||||
|
||||
After calling this method, if there is currently an active node, the first active node will be taken as the specified node by default. After clicking on other nodes, the style of that node will be applied to the other nodes being clicked. When clicking on the canvas, the format brushing operation ends.
|
||||
38
web/src/pages/Doc/en/painter/index.vue
Normal file
38
web/src/pages/Doc/en/painter/index.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Painter plugin</h1>
|
||||
<blockquote>
|
||||
<p>v0.6.12+</p>
|
||||
</blockquote>
|
||||
<p>Node format brush plugin.</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> Painter <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/plugins/Painter.js'</span>
|
||||
MindMap.usePlugin(Painter)
|
||||
</code></pre>
|
||||
<p>After registration and instantiation of <code>MindMap</code>, the instance can be obtained through <code>mindMap.painter</code>.</p>
|
||||
<h2>Event</h2>
|
||||
<blockquote>
|
||||
<p>You can use mindMap.on('event name', () => {}) method to listen events.</p>
|
||||
</blockquote>
|
||||
<h3>painter_start</h3>
|
||||
<p>The event of painter start.</p>
|
||||
<h3>painter_end</h3>
|
||||
<p>The event of painter end.</p>
|
||||
<h2>Method</h2>
|
||||
<h3>startPainter()</h3>
|
||||
<p>Start painter.</p>
|
||||
<p>After calling this method, if there is currently an active node, the first active node will be taken as the specified node by default. After clicking on other nodes, the style of that node will be applied to the other nodes being clicked. When clicking on the canvas, the format brushing operation ends.</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -45,10 +45,12 @@ Search for node content, which can be called repeatedly. Each call will search a
|
||||
|
||||
End search.
|
||||
|
||||
### replace(replaceText)
|
||||
### replace(replaceText, jumpNext = false)
|
||||
|
||||
- `replaceText`: Text to be replaced
|
||||
|
||||
- `jumpNext`: v0.6.12+, Whether to automatically jump to the next matching node
|
||||
|
||||
To replace the content of the current node, call the 'search' method after calling it to replace the content of the currently located matching node.
|
||||
|
||||
### replaceAll(replaceText)
|
||||
|
||||
@@ -36,9 +36,14 @@ MindMap.usePlugin(Search)
|
||||
<p>Search for node content, which can be called repeatedly. Each call will search and locate to the next matching node. If the search text changes, it will be searched again.</p>
|
||||
<h3>endSearch()</h3>
|
||||
<p>End search.</p>
|
||||
<h3>replace(replaceText)</h3>
|
||||
<h3>replace(replaceText, jumpNext = false)</h3>
|
||||
<ul>
|
||||
<li><code>replaceText</code>: Text to be replaced</li>
|
||||
<li>
|
||||
<p><code>replaceText</code>: Text to be replaced</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>jumpNext</code>: v0.6.12+, Whether to automatically jump to the next matching node</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>To replace the content of the current node, call the 'search' method after calling it to replace the content of the currently located matching node.</p>
|
||||
<h3>replaceAll(replaceText)</h3>
|
||||
|
||||
@@ -228,8 +228,22 @@ Determine whether a color is white.
|
||||
|
||||
#### isTransparent(color)
|
||||
|
||||
> v0.6.11+
|
||||
|
||||
Determine whether a color is transparent.
|
||||
|
||||
#### nodeRichTextToTextWithWrap(html)
|
||||
|
||||
> v0.6.12+
|
||||
|
||||
Convert the rich text content of nodes in the form of `<p><span></span><p>` into text wrapped in `<br>`.
|
||||
|
||||
#### textToNodeRichTextWithWrap(html)
|
||||
|
||||
> v0.6.12+
|
||||
|
||||
Convert the wrapped text of `<br>` into node rich text content in the form of `<p><span></span><p>`.
|
||||
|
||||
## Simulate CSS background in Canvas
|
||||
|
||||
Import:
|
||||
|
||||
@@ -160,7 +160,20 @@ and copying the <code>data</code> of the data object, example:</p>
|
||||
</blockquote>
|
||||
<p>Determine whether a color is white.</p>
|
||||
<h4>isTransparent(color)</h4>
|
||||
<blockquote>
|
||||
<p>v0.6.11+</p>
|
||||
</blockquote>
|
||||
<p>Determine whether a color is transparent.</p>
|
||||
<h4>nodeRichTextToTextWithWrap(html)</h4>
|
||||
<blockquote>
|
||||
<p>v0.6.12+</p>
|
||||
</blockquote>
|
||||
<p>Convert the rich text content of nodes in the form of <code><p><span></span><p></code> into text wrapped in <code><br></code>.</p>
|
||||
<h4>textToNodeRichTextWithWrap(html)</h4>
|
||||
<blockquote>
|
||||
<p>v0.6.12+</p>
|
||||
</blockquote>
|
||||
<p>Convert the wrapped text of <code><br></code> into node rich text content in the form of <code><p><span></span><p></code>.</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>
|
||||
|
||||
@@ -50,6 +50,7 @@ export default [
|
||||
{ path: 'touchEvent', title: 'TouchEvent插件' },
|
||||
{ path: 'nodeImgAdjust', title: 'NodeImgAdjust插件' },
|
||||
{ path: 'search', title: 'Search插件' },
|
||||
{ path: 'painter', title: 'Painter插件' },
|
||||
{ path: 'help1', title: '概要/关联线' },
|
||||
{ path: 'help2', title: '客户端' }
|
||||
]
|
||||
@@ -82,7 +83,8 @@ export default [
|
||||
{ path: 'deploy', title: 'Deploy' },
|
||||
{ path: 'touchEvent', title: 'TouchEvent plugin' },
|
||||
{ path: 'nodeImgAdjust', title: 'NodeImgAdjust plugin' },
|
||||
{ path: 'search', title: 'Search plugin' }
|
||||
{ path: 'search', title: 'Search plugin' },
|
||||
{ path: 'painter', title: 'Painter plugin' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,5 +1,53 @@
|
||||
# Changelog
|
||||
|
||||
## 0.6.12
|
||||
|
||||
修复:
|
||||
|
||||
> 1.修复当思维导图全部移出可视区域后小地图中的指示器也会移出小地图区域的问题。
|
||||
>
|
||||
> 2.修复移动端双指缩放过于灵敏的问题。
|
||||
>
|
||||
> 3.修复只读模式下按住节点无法拖动画布的问题。
|
||||
>
|
||||
> 4.修复当思维导图距浏览器窗口左上角不为0时,小地图渲染不正确的问题。
|
||||
>
|
||||
> 5.修复移动节点时新位置的提示块过大的问题。
|
||||
>
|
||||
> 6.修复搜索不能替换为空字符的问题。
|
||||
>
|
||||
> 7.修复富文本模式下,搜索替换后换行会丢失的问题。
|
||||
>
|
||||
> 8.修复大纲里点击文字编辑时输入框焦点丢失的问题。
|
||||
|
||||
新增:
|
||||
|
||||
> 1.节点移动结束事件(node_dragend)增加回调参数,可以获取到移动到节点的uid。
|
||||
>
|
||||
> 2.支持通过配置指定内部一些元素添加到的位置。
|
||||
>
|
||||
> 3.支持格式刷功能。
|
||||
>
|
||||
> 4.曲线风格下,根节点的连接线样式支持和其他节点保持一致。
|
||||
>
|
||||
> 5.搜索支持连续替换。
|
||||
>
|
||||
> 6.节点图片新增删除按钮。
|
||||
>
|
||||
> 7.支持按住鼠标中键拖动画布。
|
||||
|
||||
Demo:
|
||||
|
||||
> 1.提供应用接管模式,方便对接自己的存储服务;支持运行时设置静态资源路径。
|
||||
>
|
||||
> 2.重构大纲:1.不再使用节点自带的文本样式;2.支持全屏编辑大纲;3.大纲支持拖拽移动节点;4.大纲支持删除节点。
|
||||
>
|
||||
> 3.修复导入数据场景下界面暗黑模式没有更新的问题。
|
||||
|
||||
## 0.6.11-fix.1
|
||||
|
||||
修复:1.修复节点文字为白色时编辑的时候看不见的问题。
|
||||
|
||||
## 0.6.11
|
||||
|
||||
新增:1.优化小地图,去除小地图内的节点内容,优化性能。
|
||||
|
||||
@@ -1,6 +1,36 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Changelog</h1>
|
||||
<h2>0.6.12</h2>
|
||||
<p>修复:</p>
|
||||
<blockquote>
|
||||
<p>1.修复当思维导图全部移出可视区域后小地图中的指示器也会移出小地图区域的问题。</p>
|
||||
<p>2.修复移动端双指缩放过于灵敏的问题。</p>
|
||||
<p>3.修复只读模式下按住节点无法拖动画布的问题。</p>
|
||||
<p>4.修复当思维导图距浏览器窗口左上角不为0时,小地图渲染不正确的问题。</p>
|
||||
<p>5.修复移动节点时新位置的提示块过大的问题。</p>
|
||||
<p>6.修复搜索不能替换为空字符的问题。</p>
|
||||
<p>7.修复富文本模式下,搜索替换后换行会丢失的问题。</p>
|
||||
<p>8.修复大纲里点击文字编辑时输入框焦点丢失的问题。</p>
|
||||
</blockquote>
|
||||
<p>新增:</p>
|
||||
<blockquote>
|
||||
<p>1.节点移动结束事件(node_dragend)增加回调参数,可以获取到移动到节点的uid。</p>
|
||||
<p>2.支持通过配置指定内部一些元素添加到的位置。</p>
|
||||
<p>3.支持格式刷功能。</p>
|
||||
<p>4.曲线风格下,根节点的连接线样式支持和其他节点保持一致。</p>
|
||||
<p>5.搜索支持连续替换。</p>
|
||||
<p>6.节点图片新增删除按钮。</p>
|
||||
<p>7.支持按住鼠标中键拖动画布。</p>
|
||||
</blockquote>
|
||||
<p>Demo:</p>
|
||||
<blockquote>
|
||||
<p>1.提供应用接管模式,方便对接自己的存储服务;支持运行时设置静态资源路径。</p>
|
||||
<p>2.重构大纲:1.不再使用节点自带的文本样式;2.支持全屏编辑大纲;3.大纲支持拖拽移动节点;4.大纲支持删除节点。</p>
|
||||
<p>3.修复导入数据场景下界面暗黑模式没有更新的问题。</p>
|
||||
</blockquote>
|
||||
<h2>0.6.11-fix.1</h2>
|
||||
<p>修复:1.修复节点文字为白色时编辑的时候看不见的问题。</p>
|
||||
<h2>0.6.11</h2>
|
||||
<p>新增:1.优化小地图,去除小地图内的节点内容,优化性能。</p>
|
||||
<p>Demo:1.新增主题、主题列表新增tab区分。 2.节点图片上传支持输入网络图片地址。 3.节点图片上传支持输入网络图片。</p>
|
||||
|
||||
@@ -71,6 +71,8 @@ const mindMap = new MindMap({
|
||||
| isUseCustomNodeContent(v0.6.3+) | Boolean | false | 是否自定义节点内容 | |
|
||||
| customCreateNodeContent(v0.6.3+) | Function/null | null | 如果`isUseCustomNodeContent`设为`true`,那么需要使用该选项传入一个方法,接收节点实例`node`为参数(如果要获取该节点的数据,可以通过`node.nodeData.data`),需要返回自定义节点内容元素,也就是DOM节点,如果某个节点不需要自定义,那么返回`null`即可 | |
|
||||
| mouseScaleCenterUseMousePosition(v0.6.4-fix.1+) | Boolean | true | 鼠标缩放是否以鼠标当前位置为中心点,否则以画布中心点 | |
|
||||
| customInnerElsAppendTo(v0.6.12+) | null/HTMLElement | null | 指定内部一些元素(节点文本编辑元素、节点备注显示元素、关联线文本编辑元素、节点图片调整按钮元素)添加到的位置,默认添加到document.body下 | |
|
||||
| nodeDragPlaceholderMaxSize(v0.6.12+) | Number | 20 | 拖拽元素时,指示元素新位置的块的最大高度 | |
|
||||
|
||||
### 水印配置
|
||||
|
||||
@@ -237,11 +239,12 @@ mindMap.setTheme('主题名称')
|
||||
| rich_text_selection_change(v0.4.0+) | 当注册了`RichText`插件时可用。当节点编辑时,文本选区发生改变时触发 | hasRange(是否存在选区)、rectInfo(选区的尺寸和位置信息)、formatInfo(选区的文本格式化信息) |
|
||||
| transforming-dom-to-images(v0.4.0+) | 当注册了`RichText`插件时可用。当`svg`中存在`DOM`节点时,导出为图片时会将`DOM`节点转换为图片,转换过程中会触发该事件,可用通过该事件给用户提示,告知目前转换到的节点 | index(当前转换到的节点索引)、len(一共需要转换的节点数量) |
|
||||
| node_dragging(v0.4.5+) | 当某个节点被拖拽时触发 | node(当前被拖拽的节点) |
|
||||
| node_dragend(v0.4.5+) | 节点被拖拽结束时触发 | |
|
||||
| node_dragend(v0.4.5+) | 节点被拖拽结束时触发 | { overlapNodeUid, prevNodeUid, nextNodeUid }(v0.6.12+,本次节点移动到的节点uid,比如本次移动到了节点A上,那么overlapNodeUid就是节点A的uid,如果移动到了B节点的前面,那么nextNodeUid就是节点B的uid,你可以通过mindMap.renderer.findNodeByUid(uid)方法来获取节点实例) |
|
||||
| associative_line_click(v0.4.5+) | 点击某条关联线时触发 | path(连接线节点)、clickPath(不可见的点击线节点)、node(起始节点)、toNode(目标节点) |
|
||||
| svg_mouseenter(v0.5.1+) | 鼠标移入svg画布时触发 | e(事件对象) |
|
||||
| svg_mouseleave(v0.5.1+) | 鼠标移出svg画布时触发 | e(事件对象) |
|
||||
| node_icon_click(v0.6.10+) | 点击节点内的图标时触发 | this(节点实例)、item(点击的图标名称)、e(事件对象) |
|
||||
| view_theme_change(v0.6.12+) | 调用了setTheme方法设置主题后触发 | theme(设置的新主题名称) |
|
||||
|
||||
### emit(event, ...args)
|
||||
|
||||
@@ -319,7 +322,8 @@ mindMap.updateConfig({
|
||||
| REMOVE_NODE | 删除节点,操作节点为当前激活的节点或指定节点 | appointNodes(v0.4.7+,可选,指定节点,指定多个节点可以传一个数组) |
|
||||
| PASTE_NODE | 粘贴节点到节点,操作节点为当前激活的节点 | data(要粘贴的节点数据,一般通过`renderer.copyNode()`方法和`renderer.cutNode()`方法获取) |
|
||||
| CUT_NODE | 剪切节点,操作节点为当前激活的节点,如果有多个激活节点,只会对第一个有效,对根节点使用无效 | callback(回调函数,剪切的节点数据会通过调用该函数并通过参数返回) |
|
||||
| SET_NODE_STYLE | 修改节点样式 | node(要设置样式的节点)、prop(样式属性)、value(样式属性值)、isActive(布尔值,是否设置的是激活状态的样式) |
|
||||
| SET_NODE_STYLE | 修改节点单个样式 | node(要设置样式的节点)、style(样式属性)、value(样式属性值)、isActive(布尔值,是否设置的是激活状态的样式) |
|
||||
| SET_NODE_STYLEs(v0.6.12+) | 修改节点多个样式 | node(要设置样式的节点)、style(样式对象,key为样式属性,value为样式值)、isActive(布尔值,是否设置的是激活状态的样式) |
|
||||
| SET_NODE_ACTIVE | 设置节点是否激活 | node(要设置的节点)、active(布尔值,是否激活) |
|
||||
| CLEAR_ACTIVE_NODE | 清除当前已激活节点的激活状态,操作节点为当前激活的节点 | |
|
||||
| SET_NODE_EXPAND | 设置节点是否展开 | node(要设置的节点)、expand(布尔值,是否展开) |
|
||||
|
||||
@@ -357,6 +357,20 @@
|
||||
<td>鼠标缩放是否以鼠标当前位置为中心点,否则以画布中心点</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>customInnerElsAppendTo(v0.6.12+)</td>
|
||||
<td>null/HTMLElement</td>
|
||||
<td>null</td>
|
||||
<td>指定内部一些元素(节点文本编辑元素、节点备注显示元素、关联线文本编辑元素、节点图片调整按钮元素)添加到的位置,默认添加到document.body下</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>nodeDragPlaceholderMaxSize(v0.6.12+)</td>
|
||||
<td>Number</td>
|
||||
<td>20</td>
|
||||
<td>拖拽元素时,指示元素新位置的块的最大高度</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>水印配置</h3>
|
||||
@@ -690,7 +704,7 @@ mindMap.setTheme(<span class="hljs-string">'主题名称'</span>)
|
||||
<tr>
|
||||
<td>node_dragend(v0.4.5+)</td>
|
||||
<td>节点被拖拽结束时触发</td>
|
||||
<td></td>
|
||||
<td>{ overlapNodeUid, prevNodeUid, nextNodeUid }(v0.6.12+,本次节点移动到的节点uid,比如本次移动到了节点A上,那么overlapNodeUid就是节点A的uid,如果移动到了B节点的前面,那么nextNodeUid就是节点B的uid,你可以通过mindMap.renderer.findNodeByUid(uid)方法来获取节点实例)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>associative_line_click(v0.4.5+)</td>
|
||||
@@ -712,6 +726,11 @@ mindMap.setTheme(<span class="hljs-string">'主题名称'</span>)
|
||||
<td>点击节点内的图标时触发</td>
|
||||
<td>this(节点实例)、item(点击的图标名称)、e(事件对象)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>view_theme_change(v0.6.12+)</td>
|
||||
<td>调用了setTheme方法设置主题后触发</td>
|
||||
<td>theme(设置的新主题名称)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>emit(event, ...args)</h3>
|
||||
@@ -812,8 +831,13 @@ mindMap.setTheme(<span class="hljs-string">'主题名称'</span>)
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SET_NODE_STYLE</td>
|
||||
<td>修改节点样式</td>
|
||||
<td>node(要设置样式的节点)、prop(样式属性)、value(样式属性值)、isActive(布尔值,是否设置的是激活状态的样式)</td>
|
||||
<td>修改节点单个样式</td>
|
||||
<td>node(要设置样式的节点)、style(样式属性)、value(样式属性值)、isActive(布尔值,是否设置的是激活状态的样式)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SET_NODE_STYLEs(v0.6.12+)</td>
|
||||
<td>修改节点多个样式</td>
|
||||
<td>node(要设置样式的节点)、style(样式对象,key为样式属性,value为样式值)、isActive(布尔值,是否设置的是激活状态的样式)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SET_NODE_ACTIVE</td>
|
||||
|
||||
@@ -40,7 +40,7 @@ npm run build
|
||||
|
||||
如果你想修改打包输出的目录,可以修改`web/vue.config.js`文件的`outputDir`配置,改成你想要输出的路径即可。
|
||||
|
||||
如果你想修改`index.html`文件引用静态资源的路径的话可以修改`web/vue.config.js`文件的`publicPath`配置。
|
||||
如果你想修改`index.html`文件引用静态资源的路径的话可以修改`web/vue.config.js`文件的`publicPath`配置。以及`web/public/index.html`文件的`window.externalPublicPath`配置。
|
||||
|
||||
另外默认使用的是`hash`路由,也就是路径中会在`#`,如果你想使用`history`路由,可以修改`web/src/router.js`文件,将:
|
||||
|
||||
@@ -63,4 +63,165 @@ const router = new VueRouter({
|
||||
|
||||
## Docker
|
||||
|
||||
编写中。。。
|
||||
> 非常感谢[水车](https://github.com/shuiche-it),本小节由他编写,对应的 Docker 包也由他维护。
|
||||
|
||||
直接从 Docker hup 中安装:
|
||||
|
||||
```
|
||||
docker run -d -p 8081:8080 shuiche/mind-map:latest
|
||||
```
|
||||
|
||||
mind-map在容器中启动了8080端口作为web服务入口,通过docker运行容器时,需要指定本地映射端口,上面案例中,我们通过本地的8081端口映射到容器端口8080。
|
||||
|
||||
安装完成后,通过 `docker ps` 查看容器运行状态。
|
||||
|
||||
浏览器打开 127.0.0.1:8081 即可使用Web 思维导图功能。
|
||||
|
||||
## 对接自己的存储服务
|
||||
|
||||
应用数据默认存储在浏览器本地,浏览器本地存储容量是比较小的,所以当在思维导图中插入更多图片后很容易触发限制,所以更好的选择是对接你自己的存储服务,这通常有两种方式:
|
||||
|
||||
### 第一种
|
||||
|
||||
直接clone本仓库代码,然后修改`web/src/api/index.js`内的相关方法即可实现从你的数据库里获取数据,以及存储到你的数据中。
|
||||
|
||||
### 第二种
|
||||
|
||||
很多时候,你可能想始终使用本仓库的最新代码,那么第一种方式就不太方便,因为你要手动去合并代码,所以提供了第二种方式。
|
||||
|
||||
具体操作步骤:
|
||||
|
||||
1.复制web应用打包后的资源
|
||||
|
||||
包括:`dist`目录和`index.html`文件。
|
||||
|
||||
2.修改复制后的`index.html`文件
|
||||
|
||||
首先在`head`标签里插入如下代码:
|
||||
|
||||
```js
|
||||
<script>
|
||||
window.takeOverApp = true
|
||||
</script>
|
||||
```
|
||||
|
||||
这行代码会提示应用不要初始化应用`即:new Vue()`,而是把控制权交给你,接下来再在`body`的最后插入你自己的`js`代码,内联或则外链都可以,内联示例如下:
|
||||
|
||||
```js
|
||||
<script>
|
||||
// 你自己的请求数据的方法
|
||||
const getDataFromBackend = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
// 思维导图数据
|
||||
mindMapData: {
|
||||
root: {
|
||||
"data": {
|
||||
"text": "根节点"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
theme: { "template":"avocado","config":{} },
|
||||
layout: "logicalStructure",
|
||||
config: {},
|
||||
view: {}
|
||||
},
|
||||
// 页面语言,支持中文(zh)、英文(en)
|
||||
lang: 'zh',
|
||||
// 页面部分配置
|
||||
localConfig: null
|
||||
})
|
||||
}, 200)
|
||||
})
|
||||
}
|
||||
// 注册全局方法
|
||||
const setTakeOverAppMethods = (data) => {
|
||||
window.takeOverAppMethods = {}
|
||||
// 获取思维导图数据的函数
|
||||
window.takeOverAppMethods.getMindMapData = () => {
|
||||
return data.mindMapData
|
||||
}
|
||||
// 保存思维导图数据的函数
|
||||
window.takeOverAppMethods.saveMindMapData = (data) => {
|
||||
console.log(data)
|
||||
// 该函数触发频率可能会很高,所以你应该做一下节流或防抖
|
||||
}
|
||||
// 获取语言的函数
|
||||
window.takeOverAppMethods.getLanguage = () => {
|
||||
return data.lang
|
||||
}
|
||||
// 保存语言的函数
|
||||
window.takeOverAppMethods.saveLanguage = (lang) => {
|
||||
console.log(lang)
|
||||
}
|
||||
// 获取本地配置的函数
|
||||
window.takeOverAppMethods.getLocalConfig = () => {
|
||||
return data.localConfig
|
||||
}
|
||||
// 保存本地配置的函数
|
||||
window.takeOverAppMethods.saveLocalConfig = (config) => {
|
||||
console.log(config)
|
||||
}
|
||||
}
|
||||
window.onload = async () => {
|
||||
if (!window.takeOverApp) return
|
||||
// 请求数据
|
||||
const data = await getDataFromBackend()
|
||||
// 设置全局的方法
|
||||
setTakeOverAppMethods(data)
|
||||
// 思维导图实例创建完成事件
|
||||
window.$bus.$on('app_inited', (mindMap) => {
|
||||
console.log(mindMap)
|
||||
})
|
||||
// 可以通过window.$bus.$on()来监听应用的一些事件
|
||||
// 实例化页面
|
||||
window.initApp()
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
如上所示,当你设置了`window.takeOverApp = true`标志,应用不再主动进行实例化,而是会将实例化的方法暴露出来由你调用,那么你可以先从后端请求思维导图的数据,然后再注册相关的方法,应用内部会在合适的时机进行调用,从而达到回显和保存的目的。
|
||||
|
||||
这样做的好处是,每当本仓库代码更新了,你可以简单的复制打包后的文件到你自己的服务器,只要稍微修改一下`index.html`页面即可达到同步更新且使用自己的存储服务的目的。
|
||||
|
||||
## 修改静态资源路径
|
||||
|
||||
如果你想和上一节一样保持和本仓库代码的同步更新,但是又想修改静态资源的存放位置,比如默认的层级关系为:
|
||||
|
||||
```
|
||||
-dist
|
||||
--css
|
||||
--fonts
|
||||
--img
|
||||
--js
|
||||
-logo.ico
|
||||
|
||||
-index.html
|
||||
```
|
||||
|
||||
而你想调整成这样:
|
||||
|
||||
```
|
||||
-assets
|
||||
--dist
|
||||
---css
|
||||
---fonts
|
||||
---img
|
||||
---js
|
||||
-logo.ico
|
||||
|
||||
-index.html
|
||||
```
|
||||
|
||||
那么你可以将`index.html`中的`window.externalPublicPath`配置由默认的`./dist/`修改为:
|
||||
|
||||
```js
|
||||
window.externalPublicPath = './assets/dist/'
|
||||
```
|
||||
|
||||
同时`index.html`中内联的`.ico`、`.js`、`.css`资源的路径需要你手动修改。
|
||||
|
||||
需要注意的是,`dist`目录内的目录层级关系最好不要调整,否则可能会出现异常。
|
||||
|
||||
如果你想替换其中的一些静态资源,比如你想将主题图片和结构的图片替换成你自己设计的图片,那么可以直接同名覆盖。
|
||||
@@ -25,7 +25,7 @@ npm link simple-mind-map
|
||||
<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>index.html</code>文件引用静态资源的路径的话可以修改<code>web/vue.config.js</code>文件的<code>publicPath</code>配置。以及<code>web/public/index.html</code>文件的<code>window.externalPublicPath</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
|
||||
@@ -39,7 +39,133 @@ npm link simple-mind-map
|
||||
</code></pre>
|
||||
<p>不过这需要后台支持,因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问子路由时会返回404,所以呢你要在服务端增加一个覆盖所有情况的候选资源:如果<code>URL</code>匹配不到任何静态资源,则应该返回同一个<code>index.html</code>页面。</p>
|
||||
<h2>Docker</h2>
|
||||
<p>编写中。。。</p>
|
||||
<blockquote>
|
||||
<p>非常感谢<a href="https://github.com/shuiche-it">水车</a>,本小节由他编写,对应的 Docker 包也由他维护。</p>
|
||||
</blockquote>
|
||||
<p>直接从 Docker hup 中安装:</p>
|
||||
<pre class="hljs"><code>docker run -d -p 8081:8080 shuiche/mind-map:latest
|
||||
</code></pre>
|
||||
<p>mind-map在容器中启动了8080端口作为web服务入口,通过docker运行容器时,需要指定本地映射端口,上面案例中,我们通过本地的8081端口映射到容器端口8080。</p>
|
||||
<p>安装完成后,通过 <code>docker ps</code> 查看容器运行状态。</p>
|
||||
<p>浏览器打开 127.0.0.1:8081 即可使用Web 思维导图功能。</p>
|
||||
<h2>对接自己的存储服务</h2>
|
||||
<p>应用数据默认存储在浏览器本地,浏览器本地存储容量是比较小的,所以当在思维导图中插入更多图片后很容易触发限制,所以更好的选择是对接你自己的存储服务,这通常有两种方式:</p>
|
||||
<h3>第一种</h3>
|
||||
<p>直接clone本仓库代码,然后修改<code>web/src/api/index.js</code>内的相关方法即可实现从你的数据库里获取数据,以及存储到你的数据中。</p>
|
||||
<h3>第二种</h3>
|
||||
<p>很多时候,你可能想始终使用本仓库的最新代码,那么第一种方式就不太方便,因为你要手动去合并代码,所以提供了第二种方式。</p>
|
||||
<p>具体操作步骤:</p>
|
||||
<p>1.复制web应用打包后的资源</p>
|
||||
<p>包括:<code>dist</code>目录和<code>index.html</code>文件。</p>
|
||||
<p>2.修改复制后的<code>index.html</code>文件</p>
|
||||
<p>首先在<code>head</code>标签里插入如下代码:</p>
|
||||
<pre class="hljs"><code><script>
|
||||
<span class="hljs-built_in">window</span>.takeOverApp = <span class="hljs-literal">true</span>
|
||||
</script>
|
||||
</code></pre>
|
||||
<p>这行代码会提示应用不要初始化应用<code>即:new Vue()</code>,而是把控制权交给你,接下来再在<code>body</code>的最后插入你自己的<code>js</code>代码,内联或则外链都可以,内联示例如下:</p>
|
||||
<pre class="hljs"><code><script>
|
||||
<span class="hljs-comment">// 你自己的请求数据的方法</span>
|
||||
<span class="hljs-keyword">const</span> getDataFromBackend = <span class="hljs-function">() =></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">resolve, reject</span>) =></span> {
|
||||
<span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =></span> {
|
||||
resolve({
|
||||
<span class="hljs-comment">// 思维导图数据</span>
|
||||
<span class="hljs-attr">mindMapData</span>: {
|
||||
<span class="hljs-attr">root</span>: {
|
||||
<span class="hljs-string">"data"</span>: {
|
||||
<span class="hljs-string">"text"</span>: <span class="hljs-string">"根节点"</span>
|
||||
},
|
||||
<span class="hljs-string">"children"</span>: []
|
||||
},
|
||||
<span class="hljs-attr">theme</span>: { <span class="hljs-string">"template"</span>:<span class="hljs-string">"avocado"</span>,<span class="hljs-string">"config"</span>:{} },
|
||||
<span class="hljs-attr">layout</span>: <span class="hljs-string">"logicalStructure"</span>,
|
||||
<span class="hljs-attr">config</span>: {},
|
||||
<span class="hljs-attr">view</span>: {}
|
||||
},
|
||||
<span class="hljs-comment">// 页面语言,支持中文(zh)、英文(en)</span>
|
||||
<span class="hljs-attr">lang</span>: <span class="hljs-string">'zh'</span>,
|
||||
<span class="hljs-comment">// 页面部分配置</span>
|
||||
<span class="hljs-attr">localConfig</span>: <span class="hljs-literal">null</span>
|
||||
})
|
||||
}, <span class="hljs-number">200</span>)
|
||||
})
|
||||
}
|
||||
<span class="hljs-comment">// 注册全局方法</span>
|
||||
<span class="hljs-keyword">const</span> setTakeOverAppMethods = <span class="hljs-function">(<span class="hljs-params">data</span>) =></span> {
|
||||
<span class="hljs-built_in">window</span>.takeOverAppMethods = {}
|
||||
<span class="hljs-comment">// 获取思维导图数据的函数</span>
|
||||
<span class="hljs-built_in">window</span>.takeOverAppMethods.getMindMapData = <span class="hljs-function">() =></span> {
|
||||
<span class="hljs-keyword">return</span> data.mindMapData
|
||||
}
|
||||
<span class="hljs-comment">// 保存思维导图数据的函数</span>
|
||||
<span class="hljs-built_in">window</span>.takeOverAppMethods.saveMindMapData = <span class="hljs-function">(<span class="hljs-params">data</span>) =></span> {
|
||||
<span class="hljs-built_in">console</span>.log(data)
|
||||
<span class="hljs-comment">// 该函数触发频率可能会很高,所以你应该做一下节流或防抖</span>
|
||||
}
|
||||
<span class="hljs-comment">// 获取语言的函数</span>
|
||||
<span class="hljs-built_in">window</span>.takeOverAppMethods.getLanguage = <span class="hljs-function">() =></span> {
|
||||
<span class="hljs-keyword">return</span> data.lang
|
||||
}
|
||||
<span class="hljs-comment">// 保存语言的函数</span>
|
||||
<span class="hljs-built_in">window</span>.takeOverAppMethods.saveLanguage = <span class="hljs-function">(<span class="hljs-params">lang</span>) =></span> {
|
||||
<span class="hljs-built_in">console</span>.log(lang)
|
||||
}
|
||||
<span class="hljs-comment">// 获取本地配置的函数</span>
|
||||
<span class="hljs-built_in">window</span>.takeOverAppMethods.getLocalConfig = <span class="hljs-function">() =></span> {
|
||||
<span class="hljs-keyword">return</span> data.localConfig
|
||||
}
|
||||
<span class="hljs-comment">// 保存本地配置的函数</span>
|
||||
<span class="hljs-built_in">window</span>.takeOverAppMethods.saveLocalConfig = <span class="hljs-function">(<span class="hljs-params">config</span>) =></span> {
|
||||
<span class="hljs-built_in">console</span>.log(config)
|
||||
}
|
||||
}
|
||||
<span class="hljs-built_in">window</span>.onload = <span class="hljs-keyword">async</span> () => {
|
||||
<span class="hljs-keyword">if</span> (!<span class="hljs-built_in">window</span>.takeOverApp) <span class="hljs-keyword">return</span>
|
||||
<span class="hljs-comment">// 请求数据</span>
|
||||
<span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> getDataFromBackend()
|
||||
<span class="hljs-comment">// 设置全局的方法</span>
|
||||
setTakeOverAppMethods(data)
|
||||
<span class="hljs-comment">// 思维导图实例创建完成事件</span>
|
||||
<span class="hljs-built_in">window</span>.$bus.$on(<span class="hljs-string">'app_inited'</span>, <span class="hljs-function">(<span class="hljs-params">mindMap</span>) =></span> {
|
||||
<span class="hljs-built_in">console</span>.log(mindMap)
|
||||
})
|
||||
<span class="hljs-comment">// 可以通过window.$bus.$on()来监听应用的一些事件</span>
|
||||
<span class="hljs-comment">// 实例化页面</span>
|
||||
<span class="hljs-built_in">window</span>.initApp()
|
||||
}
|
||||
</script>
|
||||
</code></pre>
|
||||
<p>如上所示,当你设置了<code>window.takeOverApp = true</code>标志,应用不再主动进行实例化,而是会将实例化的方法暴露出来由你调用,那么你可以先从后端请求思维导图的数据,然后再注册相关的方法,应用内部会在合适的时机进行调用,从而达到回显和保存的目的。</p>
|
||||
<p>这样做的好处是,每当本仓库代码更新了,你可以简单的复制打包后的文件到你自己的服务器,只要稍微修改一下<code>index.html</code>页面即可达到同步更新且使用自己的存储服务的目的。</p>
|
||||
<h2>修改静态资源路径</h2>
|
||||
<p>如果你想和上一节一样保持和本仓库代码的同步更新,但是又想修改静态资源的存放位置,比如默认的层级关系为:</p>
|
||||
<pre class="hljs"><code>-dist
|
||||
--css
|
||||
--fonts
|
||||
--img
|
||||
--js
|
||||
-logo.ico
|
||||
|
||||
-index.html
|
||||
</code></pre>
|
||||
<p>而你想调整成这样:</p>
|
||||
<pre class="hljs"><code>-assets
|
||||
--dist
|
||||
---css
|
||||
---fonts
|
||||
---img
|
||||
---js
|
||||
-logo.ico
|
||||
|
||||
-index.html
|
||||
</code></pre>
|
||||
<p>那么你可以将<code>index.html</code>中的<code>window.externalPublicPath</code>配置由默认的<code>./dist/</code>修改为:</p>
|
||||
<pre class="hljs"><code><span class="hljs-built_in">window</span>.externalPublicPath = <span class="hljs-string">'./assets/dist/'</span>
|
||||
</code></pre>
|
||||
<p>同时<code>index.html</code>中内联的<code>.ico</code>、<code>.js</code>、<code>.css</code>资源的路径需要你手动修改。</p>
|
||||
<p>需要注意的是,<code>dist</code>目录内的目录层级关系最好不要调整,否则可能会出现异常。</p>
|
||||
<p>如果你想替换其中的一些静态资源,比如你想将主题图片和结构的图片替换成你自己设计的图片,那么可以直接同名覆盖。</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -8,19 +8,16 @@
|
||||
|
||||
## 特性
|
||||
|
||||
- [x] 插件化架构,除核心功能外,其他功能作为插件提供,按需使用,减小整体体积
|
||||
- [x] 支持逻辑结构图、思维导图、组织结构图、目录组织图、时间轴、鱼骨图六种结构
|
||||
- [x] 插件化架构,除核心功能外,其他功能作为插件提供,按需使用,减小打包体积
|
||||
- [x] 支持逻辑结构图、思维导图、组织结构图、目录组织图、时间轴(横向、竖向)、鱼骨图等结构
|
||||
- [x] 内置多种主题,允许高度自定义样式,支持注册新主题
|
||||
- [x] 支持快捷键
|
||||
- [x] 节点内容支持图片、图标、超链接、备注、标签、概要
|
||||
- [x] 支持前进后退
|
||||
- [x] 支持拖动、缩放
|
||||
- [x] 支持右键和Ctrl+左键两种多选方式
|
||||
- [x] 支持节点自由拖拽、拖拽调整
|
||||
- [x] 支持多种节点形状
|
||||
- [x] 支持导出为`json`、`png`、`svg`、`pdf`、`markdown`,支持从`json`、`xmind`、`markdown`导入
|
||||
- [x] 支持小地图、支持水印
|
||||
- [x] 支持关联线
|
||||
- [x] 节点内容支持文本(普通文本、富文本)、图片、图标、超链接、备注、标签、概要
|
||||
- [x] 节点支持拖拽(拖拽移动、自由调整)、多种节点形状,支持使用 DDM 完全自定义节点内容
|
||||
- [x] 支持画布拖动、缩放
|
||||
- [x] 支持鼠标按键拖动选择和Ctrl+左键两种多选节点方式
|
||||
- [x] 支持导出为`json`、`png`、`svg`、`pdf`、`markdown`、`xmind`,支持从`json`、`xmind`、`markdown`导入
|
||||
- [x] 支持快捷键、前进后退、关联线、搜索替换、小地图、水印
|
||||
- [x] 提供丰富的配置,满足各种场景各种使用习惯
|
||||
|
||||
## 仓库目录介绍
|
||||
|
||||
@@ -163,4 +160,8 @@
|
||||
<img src="../../../../assets/avatar/千帆.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>千帆</p>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/才镇.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>才镇</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -8,19 +8,16 @@
|
||||
</blockquote>
|
||||
<h2>特性</h2>
|
||||
<ul>
|
||||
<li><input type="checkbox" id="checkbox0" checked="true" /><label for="checkbox0">插件化架构,除核心功能外,其他功能作为插件提供,按需使用,减小整体体积</label></li>
|
||||
<li><input type="checkbox" id="checkbox1" checked="true" /><label for="checkbox1">支持逻辑结构图、思维导图、组织结构图、目录组织图、时间轴、鱼骨图六种结构</label></li>
|
||||
<li><input type="checkbox" id="checkbox2" checked="true" /><label for="checkbox2">内置多种主题,允许高度自定义样式,支持注册新主题</label></li>
|
||||
<li><input type="checkbox" id="checkbox3" checked="true" /><label for="checkbox3">支持快捷键</label></li>
|
||||
<li><input type="checkbox" id="checkbox4" checked="true" /><label for="checkbox4">节点内容支持图片、图标、超链接、备注、标签、概要</label></li>
|
||||
<li><input type="checkbox" id="checkbox5" checked="true" /><label for="checkbox5">支持前进后退</label></li>
|
||||
<li><input type="checkbox" id="checkbox6" checked="true" /><label for="checkbox6">支持拖动、缩放</label></li>
|
||||
<li><input type="checkbox" id="checkbox7" checked="true" /><label for="checkbox7">支持右键和Ctrl+左键两种多选方式</label></li>
|
||||
<li><input type="checkbox" id="checkbox8" checked="true" /><label for="checkbox8">支持节点自由拖拽、拖拽调整</label></li>
|
||||
<li><input type="checkbox" id="checkbox9" checked="true" /><label for="checkbox9">支持多种节点形状</label></li>
|
||||
<li><input type="checkbox" id="checkbox10" checked="true" /><label for="checkbox10">支持导出为</label><code>json</code>、<code>png</code>、<code>svg</code>、<code>pdf</code>、<code>markdown</code>,支持从<code>json</code>、<code>xmind</code>、<code>markdown</code>导入</li>
|
||||
<li><input type="checkbox" id="checkbox11" checked="true" /><label for="checkbox11">支持小地图、支持水印</label></li>
|
||||
<li><input type="checkbox" id="checkbox12" checked="true" /><label for="checkbox12">支持关联线</label></li>
|
||||
<li><input type="checkbox" id="checkbox15" checked="true" /><label for="checkbox15">插件化架构,除核心功能外,其他功能作为插件提供,按需使用,减小打包体积</label></li>
|
||||
<li><input type="checkbox" id="checkbox16" checked="true" /><label for="checkbox16">支持逻辑结构图、思维导图、组织结构图、目录组织图、时间轴(横向、竖向)、鱼骨图等结构</label></li>
|
||||
<li><input type="checkbox" id="checkbox17" checked="true" /><label for="checkbox17">内置多种主题,允许高度自定义样式,支持注册新主题</label></li>
|
||||
<li><input type="checkbox" id="checkbox18" checked="true" /><label for="checkbox18">节点内容支持文本(普通文本、富文本)、图片、图标、超链接、备注、标签、概要</label></li>
|
||||
<li><input type="checkbox" id="checkbox19" checked="true" /><label for="checkbox19">节点支持拖拽(拖拽移动、自由调整)、多种节点形状,支持使用 DDM 完全自定义节点内容</label></li>
|
||||
<li><input type="checkbox" id="checkbox20" checked="true" /><label for="checkbox20">支持画布拖动、缩放</label></li>
|
||||
<li><input type="checkbox" id="checkbox21" checked="true" /><label for="checkbox21">支持鼠标按键拖动选择和Ctrl+左键两种多选节点方式</label></li>
|
||||
<li><input type="checkbox" id="checkbox22" checked="true" /><label for="checkbox22">支持导出为</label><code>json</code>、<code>png</code>、<code>svg</code>、<code>pdf</code>、<code>markdown</code>、<code>xmind</code>,支持从<code>json</code>、<code>xmind</code>、<code>markdown</code>导入</li>
|
||||
<li><input type="checkbox" id="checkbox23" checked="true" /><label for="checkbox23">支持快捷键、前进后退、关联线、搜索替换、小地图、水印</label></li>
|
||||
<li><input type="checkbox" id="checkbox24" checked="true" /><label for="checkbox24">提供丰富的配置,满足各种场景各种使用习惯</label></li>
|
||||
</ul>
|
||||
<h2>仓库目录介绍</h2>
|
||||
<p>1.<code>simple-mind-map</code></p>
|
||||
@@ -28,11 +25,11 @@
|
||||
<p>2.<code>web</code></p>
|
||||
<p>使用<code>simple-mind-map</code>库,基于<code>vue2.x</code>、<code>ElementUI</code>搭建的在线思维导图。特性:</p>
|
||||
<ul>
|
||||
<li><input type="checkbox" id="checkbox13" checked="true" /><label for="checkbox13">工具栏,支持插入节点、删除节点;编辑节点图片、图标、超链接、备注、标签、概要</label></li>
|
||||
<li><input type="checkbox" id="checkbox14" checked="true" /><label for="checkbox14">侧边栏,基础样式设置面板、节点样式设置面板、大纲面板、主题选择面板、结构选择面板</label></li>
|
||||
<li><input type="checkbox" id="checkbox15" checked="true" /><label for="checkbox15">导入导出功能;数据默认保存在浏览器本地存储,也支持直接创建、打开、编辑电脑本地文件</label></li>
|
||||
<li><input type="checkbox" id="checkbox16" checked="true" /><label for="checkbox16">右键菜单,支持展开、收起、整理布局等操作</label></li>
|
||||
<li><input type="checkbox" id="checkbox17" checked="true" /><label for="checkbox17">底部栏,支持节点数量、字数统计;支持切换编辑和只读模式;支持放大缩小;支持全屏切换;支持小地图</label></li>
|
||||
<li><input type="checkbox" id="checkbox25" checked="true" /><label for="checkbox25">工具栏,支持插入节点、删除节点;编辑节点图片、图标、超链接、备注、标签、概要</label></li>
|
||||
<li><input type="checkbox" id="checkbox26" checked="true" /><label for="checkbox26">侧边栏,基础样式设置面板、节点样式设置面板、大纲面板、主题选择面板、结构选择面板</label></li>
|
||||
<li><input type="checkbox" id="checkbox27" checked="true" /><label for="checkbox27">导入导出功能;数据默认保存在浏览器本地存储,也支持直接创建、打开、编辑电脑本地文件</label></li>
|
||||
<li><input type="checkbox" id="checkbox28" checked="true" /><label for="checkbox28">右键菜单,支持展开、收起、整理布局等操作</label></li>
|
||||
<li><input type="checkbox" id="checkbox29" checked="true" /><label for="checkbox29">底部栏,支持节点数量、字数统计;支持切换编辑和只读模式;支持放大缩小;支持全屏切换;支持小地图</label></li>
|
||||
</ul>
|
||||
<p>提供文档页面服务。</p>
|
||||
<p>3.<code>dist</code></p>
|
||||
@@ -123,6 +120,10 @@
|
||||
<img src="../../../../assets/avatar/千帆.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>千帆</p>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/才镇.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>才镇</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -42,6 +42,15 @@ MindMap.usePlugin(MiniMap)
|
||||
|
||||
1.准备一个容器元素`container`,定位不为`static`
|
||||
|
||||
如果使用的是富文本编辑模式,那么最好给`container`内部的元素去除一下默认样式,否则可能会出现节点内文本偏移的问题:
|
||||
|
||||
```css
|
||||
.container * {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
```
|
||||
|
||||
2.在`container`内创建一个小地图容器元素`miniMapContainer`,绝对定位
|
||||
|
||||
3.在`container`内创建一个视口框元素`viewBoxContainer`,绝对定位,设置边框样式,过渡属性(可选)
|
||||
|
||||
@@ -29,6 +29,12 @@ MindMap.usePlugin(MiniMap)
|
||||
</code></pre>
|
||||
<p>小地图思路:</p>
|
||||
<p>1.准备一个容器元素<code>container</code>,定位不为<code>static</code></p>
|
||||
<p>如果使用的是富文本编辑模式,那么最好给<code>container</code>内部的元素去除一下默认样式,否则可能会出现节点内文本偏移的问题:</p>
|
||||
<pre class="hljs"><code><span class="hljs-selector-class">.container</span> * {
|
||||
<span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
|
||||
<span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;
|
||||
}
|
||||
</code></pre>
|
||||
<p>2.在<code>container</code>内创建一个小地图容器元素<code>miniMapContainer</code>,绝对定位</p>
|
||||
<p>3.在<code>container</code>内创建一个视口框元素<code>viewBoxContainer</code>,绝对定位,设置边框样式,过渡属性(可选)</p>
|
||||
<p>4.监听<code>data_change</code>和<code>view_data_change</code>事件,在该事件内调用<code>calculationMiniMap</code>方法获取计算数据,然后将<code>svgHTML</code>渲染到<code>miniMapContainer</code>元素内,并且设置<code>miniMapContainer</code>元素的样式:</p>
|
||||
|
||||
@@ -116,6 +116,12 @@
|
||||
|
||||
修改节点的某个样式,`SET_NODE_STYLE`命令的快捷方法
|
||||
|
||||
### setStyles(style, isActive)
|
||||
|
||||
> v0.6.12+
|
||||
|
||||
修改节点多个样式,`SET_NODE_STYLES`命令的快捷方法
|
||||
|
||||
### getData(key)
|
||||
|
||||
获取该节点真实数据`nodeData`的`data`对象里的指定值,`key`不传返回这个`data`对象
|
||||
|
||||
@@ -67,6 +67,11 @@
|
||||
<p><code>isActive</code>:获取的是否是激活状态的样式值,默认<code>false</code></p>
|
||||
<h3>setStyle(prop, value, isActive)</h3>
|
||||
<p>修改节点的某个样式,<code>SET_NODE_STYLE</code>命令的快捷方法</p>
|
||||
<h3>setStyles(style, isActive)</h3>
|
||||
<blockquote>
|
||||
<p>v0.6.12+</p>
|
||||
</blockquote>
|
||||
<p>修改节点多个样式,<code>SET_NODE_STYLES</code>命令的快捷方法</p>
|
||||
<h3>getData(key)</h3>
|
||||
<p>获取该节点真实数据<code>nodeData</code>的<code>data</code>对象里的指定值,<code>key</code>不传返回这个<code>data</code>对象</p>
|
||||
<h3>setData(data)</h3>
|
||||
|
||||
35
web/src/pages/Doc/zh/painter/index.md
Normal file
35
web/src/pages/Doc/zh/painter/index.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Painter 插件
|
||||
|
||||
> v0.6.12+
|
||||
|
||||
节点格式刷插件。
|
||||
|
||||
## 注册
|
||||
|
||||
```js
|
||||
import MindMap from 'simple-mind-map'
|
||||
import Painter from 'simple-mind-map/src/plugins/Painter.js'
|
||||
MindMap.usePlugin(Painter)
|
||||
```
|
||||
|
||||
注册完且实例化`MindMap`后可通过`mindMap.painter`获取到该实例。
|
||||
|
||||
## 事件
|
||||
|
||||
> 可以通过mindMap.on('事件名称', () => {})来监听事件。
|
||||
|
||||
### painter_start
|
||||
|
||||
开始格式刷事件。
|
||||
|
||||
### painter_end
|
||||
|
||||
结束格式刷事件。
|
||||
|
||||
## 方法
|
||||
|
||||
### startPainter()
|
||||
|
||||
开始格式刷。
|
||||
|
||||
当调用了该方法后,如果当前存在激活节点,那么会默认取第一个激活的节点为指定节点,点击其他节点后,会把该节点的样式应用到被点击的其他节点,当点击画布后本次格式刷操作结束。
|
||||
38
web/src/pages/Doc/zh/painter/index.vue
Normal file
38
web/src/pages/Doc/zh/painter/index.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Painter 插件</h1>
|
||||
<blockquote>
|
||||
<p>v0.6.12+</p>
|
||||
</blockquote>
|
||||
<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> Painter <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/plugins/Painter.js'</span>
|
||||
MindMap.usePlugin(Painter)
|
||||
</code></pre>
|
||||
<p>注册完且实例化<code>MindMap</code>后可通过<code>mindMap.painter</code>获取到该实例。</p>
|
||||
<h2>事件</h2>
|
||||
<blockquote>
|
||||
<p>可以通过mindMap.on('事件名称', () => {})来监听事件。</p>
|
||||
</blockquote>
|
||||
<h3>painter_start</h3>
|
||||
<p>开始格式刷事件。</p>
|
||||
<h3>painter_end</h3>
|
||||
<p>结束格式刷事件。</p>
|
||||
<h2>方法</h2>
|
||||
<h3>startPainter()</h3>
|
||||
<p>开始格式刷。</p>
|
||||
<p>当调用了该方法后,如果当前存在激活节点,那么会默认取第一个激活的节点为指定节点,点击其他节点后,会把该节点的样式应用到被点击的其他节点,当点击画布后本次格式刷操作结束。</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -45,10 +45,12 @@ mindMap.on('search_info_change', (data) => {
|
||||
|
||||
结束搜索。
|
||||
|
||||
### replace(replaceText)
|
||||
### replace(replaceText, jumpNext = false)
|
||||
|
||||
- `replaceText`:要进行替换的文本
|
||||
|
||||
- `jumpNext`:v0.6.12+,是否自动跳转到下一个匹配节点
|
||||
|
||||
替换当前节点内容,要在调用了`search`方法之后调用,会替换当前定位到的匹配节点内容。
|
||||
|
||||
### replaceAll(replaceText)
|
||||
|
||||
@@ -36,9 +36,14 @@ MindMap.usePlugin(Search)
|
||||
<p>搜索节点内容,可以重复调用,每调一次,会搜索和定位到下一个匹配的节点。如果搜索文本改变了,那么会重新搜索。</p>
|
||||
<h3>endSearch()</h3>
|
||||
<p>结束搜索。</p>
|
||||
<h3>replace(replaceText)</h3>
|
||||
<h3>replace(replaceText, jumpNext = false)</h3>
|
||||
<ul>
|
||||
<li><code>replaceText</code>:要进行替换的文本</li>
|
||||
<li>
|
||||
<p><code>replaceText</code>:要进行替换的文本</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>jumpNext</code>:v0.6.12+,是否自动跳转到下一个匹配节点</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>替换当前节点内容,要在调用了<code>search</code>方法之后调用,会替换当前定位到的匹配节点内容。</p>
|
||||
<h3>replaceAll(replaceText)</h3>
|
||||
|
||||
@@ -223,9 +223,21 @@ copyNodeTree({}, node)
|
||||
|
||||
#### isTransparent(color)
|
||||
|
||||
> v0.6.11+
|
||||
|
||||
判断一个颜色是否是透明。
|
||||
|
||||
> v0.6.11+
|
||||
#### nodeRichTextToTextWithWrap(html)
|
||||
|
||||
> v0.6.12+
|
||||
|
||||
将`<p><span></span><p>`形式的节点富文本内容转换成`<br>`换行的文本。
|
||||
|
||||
#### textToNodeRichTextWithWrap(html)
|
||||
|
||||
> v0.6.12+
|
||||
|
||||
将`<br>`换行的文本转换成`<p><span></span><p>`形式的节点富文本内容。
|
||||
|
||||
## 在canvas中模拟css的背景属性
|
||||
|
||||
|
||||
@@ -155,10 +155,20 @@
|
||||
</blockquote>
|
||||
<p>判断一个颜色是否是白色。</p>
|
||||
<h4>isTransparent(color)</h4>
|
||||
<p>判断一个颜色是否是透明。</p>
|
||||
<blockquote>
|
||||
<p>v0.6.11+</p>
|
||||
</blockquote>
|
||||
<p>判断一个颜色是否是透明。</p>
|
||||
<h4>nodeRichTextToTextWithWrap(html)</h4>
|
||||
<blockquote>
|
||||
<p>v0.6.12+</p>
|
||||
</blockquote>
|
||||
<p>将<code><p><span></span><p></code>形式的节点富文本内容转换成<code><br></code>换行的文本。</p>
|
||||
<h4>textToNodeRichTextWithWrap(html)</h4>
|
||||
<blockquote>
|
||||
<p>v0.6.12+</p>
|
||||
</blockquote>
|
||||
<p>将<code><br></code>换行的文本转换成<code><p><span></span><p></code>形式的节点富文本内容。</p>
|
||||
<h2>在canvas中模拟css的背景属性</h2>
|
||||
<p>引入:</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>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<div class="container" :class="{ isDark: isDark }">
|
||||
<div
|
||||
class="container"
|
||||
:class="{ isDark: isDark, activeSidebar: activeSidebar }"
|
||||
>
|
||||
<template v-if="show">
|
||||
<Toolbar v-if="!isZenMode"></Toolbar>
|
||||
<Edit></Edit>
|
||||
@@ -27,7 +30,8 @@ export default {
|
||||
computed: {
|
||||
...mapState({
|
||||
isZenMode: state => state.localConfig.isZenMode,
|
||||
isDark: state => state.isDark
|
||||
isDark: state => state.isDark,
|
||||
activeSidebar: state => state.activeSidebar
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
@@ -75,14 +79,15 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.container {}
|
||||
.container {
|
||||
}
|
||||
|
||||
body {
|
||||
&.isDark {
|
||||
/* el-button */
|
||||
.el-button {
|
||||
background-color: #363b3f;
|
||||
color: hsla(0,0%,100%,.9);
|
||||
color: hsla(0, 0%, 100%, 0.9);
|
||||
border-color: hsla(0, 0%, 100%, 0.1);
|
||||
}
|
||||
|
||||
@@ -96,10 +101,11 @@ body {
|
||||
.el-input.is-disabled .el-input__inner {
|
||||
background-color: #363b3f;
|
||||
border-color: hsla(0, 0%, 100%, 0.1);
|
||||
color: hsla(0,0%,100%,.3);
|
||||
color: hsla(0, 0%, 100%, 0.3);
|
||||
}
|
||||
|
||||
.el-input-group__append, .el-input-group__prepend {
|
||||
.el-input-group__append,
|
||||
.el-input-group__prepend {
|
||||
background-color: #363b3f;
|
||||
border-color: hsla(0, 0%, 100%, 0.1);
|
||||
}
|
||||
@@ -145,11 +151,11 @@ body {
|
||||
border-bottom-color: #36393d;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^=top] .popper__arrow {
|
||||
.el-popper[x-placement^='top'] .popper__arrow {
|
||||
background-color: #36393d;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^=top] .popper__arrow::after {
|
||||
.el-popper[x-placement^='top'] .popper__arrow::after {
|
||||
border-top-color: #36393d;
|
||||
}
|
||||
|
||||
@@ -159,7 +165,7 @@ body {
|
||||
|
||||
&:hover,
|
||||
&.is-active {
|
||||
color: #409EFF;
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,9 +185,9 @@ body {
|
||||
color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
|
||||
.el-radio-button__orig-radio:checked+.el-radio-button__inner {
|
||||
color: #FFF;
|
||||
background-color: #409EFF;
|
||||
.el-radio-button__orig-radio:checked + .el-radio-button__inner {
|
||||
color: #fff;
|
||||
background-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,11 +196,11 @@ body {
|
||||
background-color: #262a2e;
|
||||
|
||||
.el-dialog__header {
|
||||
border-bottom: 1px solid hsla(0,0%,100%,.1);
|
||||
border-bottom: 1px solid hsla(0, 0%, 100%, 0.1);
|
||||
}
|
||||
|
||||
.el-dialog__title {
|
||||
color: hsla(0,0%,100%,.9);
|
||||
color: hsla(0, 0%, 100%, 0.9);
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
@@ -202,7 +208,7 @@ body {
|
||||
}
|
||||
|
||||
.el-dialog__footer {
|
||||
border-top: 1px solid hsla(0,0%,100%,.1);
|
||||
border-top: 1px solid hsla(0, 0%, 100%, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -164,6 +164,28 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="rowItem" v-if="style.lineStyle === 'curve'">
|
||||
<span class="name">{{ $t('baseStyle.rootStyle') }}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 80px"
|
||||
v-model="style.rootLineKeepSameInCurve"
|
||||
placeholder=""
|
||||
@change="
|
||||
value => {
|
||||
update('rootLineKeepSameInCurve', value)
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in rootLineKeepSameInCurveList"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 概要连线 -->
|
||||
<div class="title noTop">{{ $t('baseStyle.lineOfOutline') }}</div>
|
||||
@@ -296,10 +318,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- 关联线文字 -->
|
||||
<div class="title noTop">关联线文字</div>
|
||||
<div class="title noTop">{{ $t('baseStyle.associativeLineText') }}</div>
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">字体</span>
|
||||
<span class="name">{{ $t('baseStyle.fontFamily') }}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
v-model="style.associativeLineTextFontFamily"
|
||||
@@ -319,7 +341,7 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">颜色</span>
|
||||
<span class="name">{{ $t('baseStyle.color') }}</span>
|
||||
<span
|
||||
class="block"
|
||||
v-popover:popover6
|
||||
@@ -337,7 +359,7 @@
|
||||
</el-popover>
|
||||
</div>
|
||||
<div class="rowItem">
|
||||
<span class="name">字号</span>
|
||||
<span class="name">{{ $t('baseStyle.fontSize') }}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 80px"
|
||||
@@ -630,7 +652,7 @@
|
||||
<script>
|
||||
import Sidebar from './Sidebar'
|
||||
import Color from './Color'
|
||||
import { lineWidthList, lineStyleList, backgroundRepeatList, backgroundPositionList, backgroundSizeList, fontFamilyList, fontSizeList } from '@/config'
|
||||
import { lineWidthList, lineStyleList, backgroundRepeatList, backgroundPositionList, backgroundSizeList, fontFamilyList, fontSizeList, rootLineKeepSameInCurveList } from '@/config'
|
||||
import ImgUpload from '@/components/ImgUpload'
|
||||
import { storeConfig } from '@/api'
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
@@ -667,6 +689,7 @@ export default {
|
||||
lineColor: '',
|
||||
lineWidth: '',
|
||||
lineStyle: '',
|
||||
rootLineKeepSameInCurve: '',
|
||||
generalizationLineWidth: '',
|
||||
generalizationLineColor: '',
|
||||
associativeLineColor: '',
|
||||
@@ -716,6 +739,9 @@ export default {
|
||||
lineStyleList() {
|
||||
return lineStyleList[this.$i18n.locale] || lineStyleList.zh
|
||||
},
|
||||
rootLineKeepSameInCurveList() {
|
||||
return rootLineKeepSameInCurveList[this.$i18n.locale] || rootLineKeepSameInCurveList.zh
|
||||
},
|
||||
backgroundRepeatList() {
|
||||
return backgroundRepeatList[this.$i18n.locale] || backgroundRepeatList.zh
|
||||
},
|
||||
@@ -759,6 +785,7 @@ export default {
|
||||
'backgroundColor',
|
||||
'lineWidth',
|
||||
'lineStyle',
|
||||
'rootLineKeepSameInCurve',
|
||||
'lineColor',
|
||||
'generalizationLineWidth',
|
||||
'generalizationLineColor',
|
||||
|
||||
@@ -184,8 +184,8 @@ export default {
|
||||
this.$bus.$off('node_click', this.hide)
|
||||
this.$bus.$off('draw_click', this.hide)
|
||||
this.$bus.$off('expand_btn_click', this.hide)
|
||||
this.$bus.$on('svg_mousedown', this.onMousedown)
|
||||
this.$bus.$on('mouseup', this.onMouseup)
|
||||
this.$bus.$off('svg_mousedown', this.onMousedown)
|
||||
this.$bus.$off('mouseup', this.onMouseup)
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['setLocalConfig']),
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Count v-if="!isZenMode"></Count>
|
||||
<Navigator :mindMap="mindMap"></Navigator>
|
||||
<NavigatorToolbar :mindMap="mindMap" v-if="!isZenMode"></NavigatorToolbar>
|
||||
<Outline :mindMap="mindMap"></Outline>
|
||||
<OutlineSidebar :mindMap="mindMap"></OutlineSidebar>
|
||||
<Style v-if="!isZenMode"></Style>
|
||||
<BaseStyle :data="mindMapData" :mindMap="mindMap"></BaseStyle>
|
||||
<Theme v-if="mindMap" :mindMap="mindMap"></Theme>
|
||||
@@ -21,6 +21,7 @@
|
||||
<Search v-if="mindMap" :mindMap="mindMap"></Search>
|
||||
<NodeIconSidebar v-if="mindMap" :mindMap="mindMap"></NodeIconSidebar>
|
||||
<NodeIconToolbar v-if="mindMap" :mindMap="mindMap"></NodeIconToolbar>
|
||||
<OutlineEdit v-if="mindMap" :mindMap="mindMap"></OutlineEdit>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -39,7 +40,8 @@ import AssociativeLine from 'simple-mind-map/src/plugins/AssociativeLine.js'
|
||||
import TouchEvent from 'simple-mind-map/src/plugins/TouchEvent.js'
|
||||
import NodeImgAdjust from 'simple-mind-map/src/plugins/NodeImgAdjust.js'
|
||||
import SearchPlugin from 'simple-mind-map/src/plugins/Search.js'
|
||||
import Outline from './Outline'
|
||||
import Painter from 'simple-mind-map/src/plugins/Painter.js'
|
||||
import OutlineSidebar from './OutlineSidebar'
|
||||
import Style from './Style'
|
||||
import BaseStyle from './BaseStyle'
|
||||
import Theme from './Theme'
|
||||
@@ -66,6 +68,7 @@ import i18n from '../../../i18n'
|
||||
import Search from './Search.vue'
|
||||
import NodeIconSidebar from './NodeIconSidebar.vue'
|
||||
import NodeIconToolbar from './NodeIconToolbar.vue'
|
||||
import OutlineEdit from './OutlineEdit.vue'
|
||||
|
||||
// 注册插件
|
||||
MindMap
|
||||
@@ -81,6 +84,7 @@ MindMap
|
||||
.usePlugin(NodeImgAdjust)
|
||||
.usePlugin(TouchEvent)
|
||||
.usePlugin(SearchPlugin)
|
||||
.usePlugin(Painter)
|
||||
|
||||
// 注册自定义主题
|
||||
customThemeList.forEach((item) => {
|
||||
@@ -95,7 +99,7 @@ customThemeList.forEach((item) => {
|
||||
export default {
|
||||
name: 'Edit',
|
||||
components: {
|
||||
Outline,
|
||||
OutlineSidebar,
|
||||
Style,
|
||||
BaseStyle,
|
||||
Theme,
|
||||
@@ -111,14 +115,14 @@ export default {
|
||||
SidebarTrigger,
|
||||
Search,
|
||||
NodeIconSidebar,
|
||||
NodeIconToolbar
|
||||
NodeIconToolbar,
|
||||
OutlineEdit
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mindMap: null,
|
||||
mindMapData: null,
|
||||
prevImg: '',
|
||||
openTest: false
|
||||
prevImg: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -154,100 +158,14 @@ export default {
|
||||
this.$bus.$on('createAssociativeLine', () => {
|
||||
this.mindMap.associativeLine.createLineFromActiveNode()
|
||||
})
|
||||
this.$bus.$on('startPainter', () => {
|
||||
this.mindMap.painter.startPainter()
|
||||
})
|
||||
window.addEventListener('resize', () => {
|
||||
this.mindMap.resize()
|
||||
})
|
||||
if (this.openTest) {
|
||||
setTimeout(() => {
|
||||
this.test()
|
||||
}, 5000)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-11-22 19:39:28
|
||||
* @Desc: 数据更改测试
|
||||
*/
|
||||
test() {
|
||||
let nodeData = {
|
||||
data: { text: '根节点', expand: true, isActive: false },
|
||||
children: []
|
||||
}
|
||||
setTimeout(() => {
|
||||
nodeData.data.text = '理想青年实验室'
|
||||
this.mindMap.setData(JSON.parse(JSON.stringify(nodeData)))
|
||||
|
||||
setTimeout(() => {
|
||||
nodeData.children.push({
|
||||
data: { text: '网站', expand: true, isActive: false },
|
||||
children: []
|
||||
})
|
||||
this.mindMap.setData(JSON.parse(JSON.stringify(nodeData)))
|
||||
|
||||
setTimeout(() => {
|
||||
nodeData.children.push({
|
||||
data: { text: '博客', expand: true, isActive: false },
|
||||
children: []
|
||||
})
|
||||
this.mindMap.setData(JSON.parse(JSON.stringify(nodeData)))
|
||||
|
||||
setTimeout(() => {
|
||||
let viewData = {
|
||||
transform: {
|
||||
scaleX: 1,
|
||||
scaleY: 1,
|
||||
shear: 0,
|
||||
rotate: 0,
|
||||
translateX: 179,
|
||||
translateY: 0,
|
||||
originX: 0,
|
||||
originY: 0,
|
||||
a: 1,
|
||||
b: 0,
|
||||
c: 0,
|
||||
d: 1,
|
||||
e: 179,
|
||||
f: 0
|
||||
},
|
||||
state: { scale: 1, x: 179, y: 0, sx: 0, sy: 0 }
|
||||
}
|
||||
this.mindMap.view.setTransformData(viewData)
|
||||
|
||||
setTimeout(() => {
|
||||
let viewData = {
|
||||
transform: {
|
||||
scaleX: 1.6000000000000005,
|
||||
scaleY: 1.6000000000000005,
|
||||
shear: 0,
|
||||
rotate: 0,
|
||||
translateX: -373.3000000000004,
|
||||
translateY: -281.10000000000025,
|
||||
originX: 0,
|
||||
originY: 0,
|
||||
a: 1.6000000000000005,
|
||||
b: 0,
|
||||
c: 0,
|
||||
d: 1.6000000000000005,
|
||||
e: -373.3000000000004,
|
||||
f: -281.10000000000025
|
||||
},
|
||||
state: {
|
||||
scale: 1.6000000000000005,
|
||||
x: 179,
|
||||
y: 0,
|
||||
sx: 0,
|
||||
sy: 0
|
||||
}
|
||||
}
|
||||
this.mindMap.view.setTransformData(viewData)
|
||||
}, 1000)
|
||||
}, 1000)
|
||||
}, 1000)
|
||||
}, 1000)
|
||||
}, 1000)
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-03 22:11:37
|
||||
@@ -264,9 +182,6 @@ export default {
|
||||
* @Desc: 存储数据当数据有变时
|
||||
*/
|
||||
bindSaveEvent() {
|
||||
if (this.openTest) {
|
||||
return
|
||||
}
|
||||
this.$bus.$on('data_change', data => {
|
||||
storeData(data)
|
||||
})
|
||||
@@ -283,9 +198,6 @@ export default {
|
||||
* @Desc: 手动保存
|
||||
*/
|
||||
manualSave() {
|
||||
if (this.openTest) {
|
||||
return
|
||||
}
|
||||
let data = this.mindMap.getData(true)
|
||||
storeConfig(data)
|
||||
},
|
||||
@@ -317,6 +229,7 @@ export default {
|
||||
...(config || {}),
|
||||
iconList: icon,
|
||||
useLeftKeySelectionRightKeyDrag: this.useLeftKeySelectionRightKeyDrag,
|
||||
customInnerElsAppendTo: null,
|
||||
// isUseCustomNodeContent: true,
|
||||
// 示例1:组件里用到了router、store、i18n等实例化vue组件时需要用到的东西
|
||||
// customCreateNodeContent: (node) => {
|
||||
@@ -363,7 +276,9 @@ export default {
|
||||
'node_tree_render_end',
|
||||
'rich_text_selection_change',
|
||||
'transforming-dom-to-images',
|
||||
'generalization_node_contextmenu'
|
||||
'generalization_node_contextmenu',
|
||||
'painter_start',
|
||||
'painter_end'
|
||||
].forEach(event => {
|
||||
this.mindMap.on(event, (...args) => {
|
||||
this.$bus.$emit(event, ...args)
|
||||
@@ -384,6 +299,10 @@ export default {
|
||||
// 动态删除指定节点
|
||||
// this.mindMap.execCommand('REMOVE_NODE', this.mindMap.renderer.root.children[0])
|
||||
// }, 5000);
|
||||
// 如果应用被接管,那么抛出事件传递思维导图实例
|
||||
if (window.takeOverApp) {
|
||||
this.$bus.$emit('app_inited', this.mindMap)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="nodeDialog"
|
||||
class="nodeExportDialog"
|
||||
:title="$t('export.title')"
|
||||
:visible.sync="dialogVisible"
|
||||
width="700px"
|
||||
@@ -197,7 +197,7 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.nodeDialog {
|
||||
.nodeExportDialog {
|
||||
/deep/ .el-dialog__body {
|
||||
background-color: #f2f4f7;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="nodeDialog"
|
||||
class="nodeImportDialog"
|
||||
:title="$t('import.title')"
|
||||
:visible.sync="dialogVisible"
|
||||
width="300px"
|
||||
@@ -245,6 +245,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.nodeDialog {
|
||||
.nodeImportDialog {
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -108,7 +108,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
langList,
|
||||
lang: getLang(),
|
||||
lang: '',
|
||||
isReadonly: false,
|
||||
openMiniMap: false
|
||||
}
|
||||
@@ -116,6 +116,9 @@ export default {
|
||||
computed: {
|
||||
...mapState(['isDark'])
|
||||
},
|
||||
created () {
|
||||
this.lang = getLang()
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['setIsDark']),
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="nodeDialog"
|
||||
class="nodeHyperlinkDialog"
|
||||
:title="$t('nodeHyperlink.title')"
|
||||
:visible.sync="dialogVisible"
|
||||
width="500"
|
||||
@@ -89,7 +89,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.nodeDialog {
|
||||
.nodeHyperlinkDialog {
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="nodeDialog"
|
||||
class="nodeIconDialog"
|
||||
:title="$t('nodeIcon.title')"
|
||||
:visible.sync="dialogVisible"
|
||||
width="500"
|
||||
@@ -95,7 +95,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.nodeDialog {
|
||||
.nodeIconDialog {
|
||||
/deep/ .el-dialog__body {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="nodeDialog"
|
||||
class="nodeImageDialog"
|
||||
:title="$t('nodeImage.title')"
|
||||
:visible.sync="dialogVisible"
|
||||
width="500"
|
||||
@@ -112,7 +112,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.nodeDialog {
|
||||
.nodeImageDialog {
|
||||
.title {
|
||||
font-size: 18px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="nodeDialog"
|
||||
class="nodeNoteDialog"
|
||||
:title="$t('nodeNote.title')"
|
||||
:visible.sync="dialogVisible"
|
||||
width="500"
|
||||
@@ -105,7 +105,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.nodeDialog {
|
||||
.nodeNoteDialog {
|
||||
.tip {
|
||||
margin-top: 5px;
|
||||
color: #dcdfe6;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="nodeDialog"
|
||||
class="nodeTagDialog"
|
||||
:title="$t('nodeTag.title')"
|
||||
:visible.sync="dialogVisible"
|
||||
width="500"
|
||||
@@ -120,7 +120,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.nodeDialog {
|
||||
.nodeTagDialog {
|
||||
.tagList {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -1,42 +1,53 @@
|
||||
<template>
|
||||
<Sidebar ref="sidebar" :title="$t('outline.title')">
|
||||
<el-tree
|
||||
class="outlineTree"
|
||||
:class="{ isDark: isDark }"
|
||||
:data="data"
|
||||
:props="defaultProps"
|
||||
:expand-on-click-node="false"
|
||||
default-expand-all
|
||||
<el-tree
|
||||
ref="tree"
|
||||
class="outlineTree"
|
||||
node-key="uid"
|
||||
draggable
|
||||
default-expand-all
|
||||
:class="{ isDark: isDark }"
|
||||
:data="data"
|
||||
:props="defaultProps"
|
||||
:highlight-current="true"
|
||||
:expand-on-click-node="false"
|
||||
:allow-drag="checkAllowDrag"
|
||||
@node-drop="onNodeDrop"
|
||||
@current-change="onCurrentChange"
|
||||
@mouseenter.native="isInTreArea = true"
|
||||
@mouseleave.native="isInTreArea = false"
|
||||
>
|
||||
<span
|
||||
class="customNode"
|
||||
slot-scope="{ node, data }"
|
||||
:data-id="data.uid"
|
||||
@click="onClick(data)"
|
||||
>
|
||||
<span class="customNode" slot-scope="{ node, data }" @click="onClick($event, node)">
|
||||
<span
|
||||
class="nodeEdit"
|
||||
:key="getKey()"
|
||||
contenteditable="true"
|
||||
@keydown.stop="onKeydown($event, node)"
|
||||
@keyup.stop
|
||||
@blur="onBlur($event, node)"
|
||||
v-html="node.label"
|
||||
></span>
|
||||
</span>
|
||||
</el-tree>
|
||||
</Sidebar>
|
||||
<span
|
||||
class="nodeEdit"
|
||||
contenteditable="true"
|
||||
:key="getKey()"
|
||||
@keydown.stop="onNodeInputKeydown($event, node)"
|
||||
@keyup.stop
|
||||
@blur="onBlur($event, node)"
|
||||
@paste="onPaste($event, node)"
|
||||
v-html="node.label"
|
||||
></span>
|
||||
</span>
|
||||
</el-tree>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Sidebar from './Sidebar'
|
||||
import { mapState } from 'vuex'
|
||||
import {
|
||||
nodeRichTextToTextWithWrap,
|
||||
textToNodeRichTextWithWrap,
|
||||
getTextFromHtml,
|
||||
createUid
|
||||
} from 'simple-mind-map/src/utils'
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-24 22:54:14
|
||||
* @Desc: 大纲内容
|
||||
*/
|
||||
// 大纲树
|
||||
export default {
|
||||
name: 'Outline',
|
||||
components: {
|
||||
Sidebar
|
||||
},
|
||||
props: {
|
||||
mindMap: {
|
||||
type: Object
|
||||
@@ -46,79 +57,250 @@ export default {
|
||||
return {
|
||||
data: [],
|
||||
defaultProps: {
|
||||
label(data) {
|
||||
return data.data.richText ? data.data.text : data.data.text.replaceAll(/\n/g, '</br>')
|
||||
}
|
||||
label: 'label'
|
||||
},
|
||||
notHandleDataChange: false
|
||||
currentData: null,
|
||||
notHandleDataChange: false,
|
||||
handleNodeTreeRenderEnd: false,
|
||||
beInsertNodeUid: '',
|
||||
insertType: '',
|
||||
isInTreArea: false,
|
||||
isAfterCreateNewNode: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['activeSidebar', 'isDark'])
|
||||
},
|
||||
watch: {
|
||||
activeSidebar(val) {
|
||||
if (val === 'outline') {
|
||||
this.$refs.sidebar.show = true
|
||||
} else {
|
||||
this.$refs.sidebar.show = false
|
||||
}
|
||||
}
|
||||
...mapState(['isDark', 'isOutlineEdit'])
|
||||
},
|
||||
created() {
|
||||
this.$bus.$on('data_change', data => {
|
||||
// 激活节点会让当前大纲失去焦点
|
||||
window.addEventListener('keydown', this.onKeyDown)
|
||||
this.$bus.$on('data_change', () => {
|
||||
// 在大纲里操作节点时不要响应该事件,否则会重新刷新树
|
||||
if (this.notHandleDataChange) {
|
||||
this.notHandleDataChange = false
|
||||
return
|
||||
}
|
||||
this.data = [this.mindMap.renderer.renderTree]
|
||||
if (this.isAfterCreateNewNode) {
|
||||
this.isAfterCreateNewNode = false
|
||||
return
|
||||
}
|
||||
this.refresh()
|
||||
})
|
||||
this.$bus.$on('node_tree_render_end', () => {
|
||||
// 当前存在未完成的节点插入操作
|
||||
if (this.insertType) {
|
||||
this[this.insertType]()
|
||||
this.insertType = ''
|
||||
return
|
||||
}
|
||||
// 插入了新节点后需要做一些操作
|
||||
if (this.handleNodeTreeRenderEnd) {
|
||||
this.handleNodeTreeRenderEnd = false
|
||||
this.refresh()
|
||||
this.$nextTick(() => {
|
||||
this.afterCreateNewNode()
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
mounted() {
|
||||
this.refresh()
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('keydown', this.onKeyDown)
|
||||
},
|
||||
methods: {
|
||||
// 刷新树数据
|
||||
refresh() {
|
||||
let data = this.mindMap.getData()
|
||||
data.root = true // 标记根节点
|
||||
let walk = root => {
|
||||
const text = (root.data.richText
|
||||
? nodeRichTextToTextWithWrap(root.data.text)
|
||||
: root.data.text
|
||||
).replaceAll(/\n/g, '<br>')
|
||||
root.textCache = text // 保存一份修改前的数据,用于对比是否修改了
|
||||
root.label = text
|
||||
root.uid = root.data.uid
|
||||
if (root.children && root.children.length > 0) {
|
||||
root.children.forEach(item => {
|
||||
walk(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
walk(data)
|
||||
this.data = [data]
|
||||
},
|
||||
|
||||
// 插入了新节点之后
|
||||
afterCreateNewNode() {
|
||||
// 如果是新插入节点,那么需要手动高亮该节点、定位该节点及聚焦
|
||||
let id = this.beInsertNodeUid
|
||||
if (id && this.$refs.tree) {
|
||||
try {
|
||||
this.isAfterCreateNewNode = true
|
||||
// 高亮树节点
|
||||
this.$refs.tree.setCurrentKey(id)
|
||||
let node = this.$refs.tree.getNode(id)
|
||||
this.onCurrentChange(node.data)
|
||||
// 定位该节点
|
||||
this.onClick(node.data)
|
||||
// 聚焦该树节点的编辑框
|
||||
const el = document.querySelector(
|
||||
`.customNode[data-id="${id}"] .nodeEdit`
|
||||
)
|
||||
if (el) {
|
||||
let selection = window.getSelection()
|
||||
let range = document.createRange()
|
||||
range.selectNodeContents(el)
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
let offsetTop = el.offsetTop
|
||||
this.$emit('scrollTo', offsetTop)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
this.beInsertNodeUid = ''
|
||||
},
|
||||
|
||||
// 根节点不允许拖拽
|
||||
checkAllowDrag(node) {
|
||||
return !node.data.root
|
||||
},
|
||||
|
||||
// 失去焦点更新节点文本
|
||||
onBlur(e, node) {
|
||||
// 节点数据没有修改
|
||||
if (node.data.textCache === e.target.innerHTML) {
|
||||
// 如果存在未执行的插入新节点操作,那么直接执行
|
||||
if (this.insertType) {
|
||||
this[this.insertType]()
|
||||
this.insertType = ''
|
||||
}
|
||||
return
|
||||
}
|
||||
// 否则插入新节点操作需要等待当前修改事件渲染完成后再执行
|
||||
const richText = node.data.data.richText
|
||||
const text = richText ? e.target.innerHTML : e.target.innerText
|
||||
const targetNode = this.mindMap.renderer.findNodeByUid(node.data.uid)
|
||||
if (!targetNode) return
|
||||
if (richText) {
|
||||
node.data._node.setText(e.target.innerHTML, true)
|
||||
targetNode.setText(textToNodeRichTextWithWrap(text), true, true)
|
||||
} else {
|
||||
node.data._node.setText(e.target.innerText)
|
||||
targetNode.setText(text)
|
||||
}
|
||||
},
|
||||
|
||||
// 拦截粘贴事件
|
||||
onPaste(e) {
|
||||
e.preventDefault()
|
||||
const selection = window.getSelection()
|
||||
if (!selection.rangeCount) return
|
||||
selection.deleteFromDocument()
|
||||
let text = (e.clipboardData || window.clipboardData).getData('text')
|
||||
// 去除格式
|
||||
text = getTextFromHtml(text)
|
||||
// 去除换行
|
||||
text = text.replaceAll(/\n/g, '')
|
||||
const node = document.createTextNode(text)
|
||||
selection.getRangeAt(0).insertNode(node)
|
||||
selection.collapseToEnd()
|
||||
},
|
||||
|
||||
// 生成唯一的key
|
||||
getKey() {
|
||||
return Math.random()
|
||||
},
|
||||
|
||||
onKeydown(e) {
|
||||
// 节点输入区域按键事件
|
||||
onNodeInputKeydown(e) {
|
||||
if (e.keyCode === 13 && !e.shiftKey) {
|
||||
e.preventDefault()
|
||||
this.insertNode()
|
||||
this.insertType = 'insertNode'
|
||||
e.target.blur()
|
||||
}
|
||||
if (e.keyCode === 9) {
|
||||
e.preventDefault()
|
||||
this.insertChildNode()
|
||||
this.insertType = 'insertChildNode'
|
||||
e.target.blur()
|
||||
}
|
||||
},
|
||||
|
||||
// 插入兄弟节点
|
||||
insertNode() {
|
||||
this.notHandleDataChange = false
|
||||
this.mindMap.execCommand('INSERT_NODE', false)
|
||||
this.notHandleDataChange = true
|
||||
this.handleNodeTreeRenderEnd = true
|
||||
this.beInsertNodeUid = createUid()
|
||||
this.mindMap.execCommand('INSERT_NODE', false, [], {
|
||||
uid: this.beInsertNodeUid
|
||||
})
|
||||
},
|
||||
|
||||
// 插入下级节点
|
||||
insertChildNode() {
|
||||
this.notHandleDataChange = false
|
||||
this.mindMap.execCommand('INSERT_CHILD_NODE', false)
|
||||
this.notHandleDataChange = true
|
||||
this.handleNodeTreeRenderEnd = true
|
||||
this.beInsertNodeUid = createUid()
|
||||
this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], {
|
||||
uid: this.beInsertNodeUid
|
||||
})
|
||||
},
|
||||
|
||||
// 激活当前节点且移动当前节点到画布中间
|
||||
onClick(e, node) {
|
||||
onClick(data) {
|
||||
this.notHandleDataChange = true
|
||||
let targetNode = node.data._node
|
||||
const targetNode = this.mindMap.renderer.findNodeByUid(data.uid)
|
||||
if (targetNode && targetNode.nodeData.data.isActive) return
|
||||
this.mindMap.execCommand('GO_TARGET_NODE', node.data.data.uid)
|
||||
this.mindMap.renderer.textEdit.stopFocusOnNodeActive()
|
||||
this.mindMap.execCommand('GO_TARGET_NODE', data.uid, () => {
|
||||
this.mindMap.renderer.textEdit.openFocusOnNodeActive()
|
||||
this.notHandleDataChange = false
|
||||
})
|
||||
},
|
||||
|
||||
// 拖拽结束事件
|
||||
onNodeDrop(data, target, postion) {
|
||||
this.notHandleDataChange = true
|
||||
const node = this.mindMap.renderer.findNodeByUid(data.data.uid)
|
||||
const targetNode = this.mindMap.renderer.findNodeByUid(target.data.uid)
|
||||
if (!node || !targetNode) {
|
||||
return
|
||||
}
|
||||
switch (postion) {
|
||||
case 'before':
|
||||
this.mindMap.execCommand('INSERT_BEFORE', node, targetNode)
|
||||
break
|
||||
case 'after':
|
||||
this.mindMap.execCommand('INSERT_AFTER', node, targetNode)
|
||||
break
|
||||
case 'inner':
|
||||
this.mindMap.execCommand('MOVE_NODE_TO', node, targetNode)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
},
|
||||
|
||||
// 当前选中的树节点变化事件
|
||||
onCurrentChange(data) {
|
||||
this.currentData = data
|
||||
},
|
||||
|
||||
// 删除节点
|
||||
onKeyDown(e) {
|
||||
if (!this.isInTreArea) return
|
||||
if ([46, 8].includes(e.keyCode) && this.currentData) {
|
||||
e.stopPropagation()
|
||||
this.mindMap.renderer.textEdit.hideEditTextBox()
|
||||
const node = this.mindMap.renderer.findNodeByUid(this.currentData.uid)
|
||||
if (node && !node.isRoot) {
|
||||
this.notHandleDataChange = true
|
||||
this.$refs.tree.remove(this.currentData)
|
||||
this.mindMap.execCommand('REMOVE_NODE', [node])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -126,52 +308,73 @@ export default {
|
||||
<style lang="less" scoped>
|
||||
.customNode {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
border-radius: 7px;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
display: none;
|
||||
}
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-weight: bold;
|
||||
|
||||
.nodeEdit {
|
||||
outline: none;
|
||||
white-space: normal;
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.outlineTree {
|
||||
|
||||
&.isDark {
|
||||
background-color: #262a2e;
|
||||
|
||||
.customNode {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.el-tree--highlight-current {
|
||||
/deep/ .el-tree-node.is-current > .el-tree-node__content {
|
||||
background-color: hsla(0, 0%, 100%, 0.05) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .el-tree-node__content:hover, .el-upload-list__item:hover {
|
||||
background-color: hsla(0, 0%, 100%, 0.02) !important;
|
||||
}
|
||||
|
||||
/deep/ .el-tree-node__content {
|
||||
.el-tree-node__expand-icon {
|
||||
color: #fff;
|
||||
|
||||
&.is-leaf {
|
||||
&::after {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .el-tree-node > .el-tree-node__children {
|
||||
overflow: inherit;
|
||||
}
|
||||
|
||||
/deep/ .el-tree-node__content {
|
||||
height: auto;
|
||||
margin: 5px 0;
|
||||
|
||||
.el-tree-node__expand-icon.is-leaf {
|
||||
position: relative;
|
||||
.el-tree-node__expand-icon {
|
||||
color: #262a2e;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
background-color: #c0c4cc;
|
||||
left: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
&.is-leaf {
|
||||
color: transparent;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
background-color: #262a2e;
|
||||
position: absolute;
|
||||
content: '';
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
left: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
118
web/src/pages/Edit/components/OutlineEdit.vue
Normal file
118
web/src/pages/Edit/components/OutlineEdit.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div
|
||||
class="outlineEditContainer"
|
||||
:class="{ isDark: isDark }"
|
||||
ref="outlineEditContainer"
|
||||
v-if="isOutlineEdit"
|
||||
>
|
||||
<div class="closeBtn" @click="onClose">
|
||||
<span class="icon iconfont iconguanbi"></span>
|
||||
</div>
|
||||
<div class="outlineEditBox" ref="outlineEditBox">
|
||||
<div class="outlineEdit">
|
||||
<Outline :mindMap="mindMap" @scrollTo="onScrollTo"></Outline>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
import Outline from './Outline.vue'
|
||||
|
||||
// 大纲侧边栏
|
||||
export default {
|
||||
name: 'OutlineEdit',
|
||||
components: {
|
||||
Outline
|
||||
},
|
||||
props: {
|
||||
mindMap: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['isOutlineEdit', 'isDark'])
|
||||
},
|
||||
watch: {
|
||||
isOutlineEdit(val) {
|
||||
if (val) {
|
||||
this.$nextTick(() => {
|
||||
document.body.appendChild(this.$refs.outlineEditContainer)
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['setIsOutlineEdit']),
|
||||
|
||||
onClose() {
|
||||
this.setIsOutlineEdit(false)
|
||||
},
|
||||
|
||||
onScrollTo(y) {
|
||||
let container = this.$refs.outlineEditBox
|
||||
let height = container.offsetHeight
|
||||
let top = container.scrollTop
|
||||
y += 50
|
||||
if (y > top + height) {
|
||||
container.scrollTo(0, y - height / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.outlineEditContainer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 9999;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
|
||||
&.isDark {
|
||||
background-color: #262a2e;
|
||||
|
||||
.closeBtn {
|
||||
.icon {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.closeBtn {
|
||||
position: absolute;
|
||||
right: 40px;
|
||||
top: 20px;
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.outlineEditBox {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding: 50px 0;
|
||||
|
||||
.outlineEdit {
|
||||
width: 1000px;
|
||||
height: 100%;
|
||||
height: max-content;
|
||||
margin: 0 auto;
|
||||
|
||||
/deep/ .customNode {
|
||||
.nodeEdit {
|
||||
max-width: 800px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
78
web/src/pages/Edit/components/OutlineSidebar.vue
Normal file
78
web/src/pages/Edit/components/OutlineSidebar.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<Sidebar ref="sidebar" :title="$t('outline.title')">
|
||||
<div
|
||||
class="changeBtn"
|
||||
:class="{ isDark: isDark }"
|
||||
@click="onChangeToOutlineEdit"
|
||||
>
|
||||
<span class="icon iconfont iconquanping1"></span>
|
||||
</div>
|
||||
<Outline
|
||||
:mindMap="mindMap"
|
||||
v-if="activeSidebar === 'outline'"
|
||||
@scrollTo="onScrollTo"
|
||||
></Outline>
|
||||
</Sidebar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Sidebar from './Sidebar'
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
import Outline from './Outline.vue'
|
||||
|
||||
// 大纲侧边栏
|
||||
export default {
|
||||
name: 'OutlineSidebar',
|
||||
components: {
|
||||
Sidebar,
|
||||
Outline
|
||||
},
|
||||
props: {
|
||||
mindMap: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['activeSidebar', 'isOutlineEdit', 'isDark'])
|
||||
},
|
||||
watch: {
|
||||
activeSidebar(val) {
|
||||
if (val === 'outline') {
|
||||
this.$refs.sidebar.show = true
|
||||
} else {
|
||||
this.$refs.sidebar.show = false
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['setIsOutlineEdit', 'setActiveSidebar']),
|
||||
|
||||
onChangeToOutlineEdit() {
|
||||
this.setActiveSidebar('')
|
||||
this.setIsOutlineEdit(true)
|
||||
},
|
||||
|
||||
onScrollTo(y) {
|
||||
let container = this.$refs.sidebar.getEl()
|
||||
let height = container.offsetHeight
|
||||
let top = container.scrollTop
|
||||
if (y > top + height) {
|
||||
container.scrollTo(0, y - height / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.changeBtn {
|
||||
position: absolute;
|
||||
right: 50px;
|
||||
top: 12px;
|
||||
cursor: pointer;
|
||||
|
||||
&.isDark {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -117,7 +117,7 @@ export default {
|
||||
},
|
||||
|
||||
replace() {
|
||||
this.mindMap.search.replace(this.replaceText)
|
||||
this.mindMap.search.replace(this.replaceText, true)
|
||||
},
|
||||
|
||||
replaceAll() {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="sidebarHeader" v-if="title">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div class="sidebarContent">
|
||||
<div class="sidebarContent" ref="sidebarContent">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,6 +59,10 @@ export default {
|
||||
close() {
|
||||
this.show = false
|
||||
this.setActiveSidebar('')
|
||||
},
|
||||
|
||||
getEl() {
|
||||
return this.$refs.sidebarContent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user