Compare commits

...

39 Commits

Author SHA1 Message Date
wanglin2
2b6263acb4 打包0.6.12 2023-08-07 18:07:58 +08:00
wanglin2
187c940e56 Doc: update 2023-08-07 17:54:56 +08:00
wanglin2
4b59bec01c Demo:优化界面暗黑效果 2023-08-07 17:05:47 +08:00
街角小林
9427ee550c Merge pull request #240 from kalcaddle/main
部分问题修复及细节优化
2023-08-07 16:25:18 +08:00
街角小林
14975e117c Merge branch 'main' into main 2023-08-07 16:25:08 +08:00
wanglin2
d6254c0cc2 Merge branch 'feature' into main 2023-08-07 16:07:12 +08:00
wanglin2
4a81ce9cc2 修复本地开发热更新失效的问题 2023-08-07 10:32:42 +08:00
wanglin2
d17191c890 Demo:优化大纲编辑 2023-08-07 10:09:05 +08:00
wanglin2
ff56fe3e68 Demo:扩展大纲功能,支持拖拽,删除 2023-08-06 22:37:13 +08:00
wanglin2
6bdcec0fca Demo:支持全屏编辑大纲 2023-08-05 18:38:22 +08:00
wanglin2
27885aabe7 Demo:大纲不再使用节点默认样式 2023-08-05 12:04:43 +08:00
wanglin2
e345037f9b Doc:update 2023-08-04 22:38:34 +08:00
warlee
da5290e649 ### 问题修复
- mindMap.setFullData设置文件内容,主题为深色时主界面没有自动切换到深色,要点击主题按钮才切换;
- xmind 部分老版本文件解析报错;(xmind解析导出给外部可用)
- 点击节点图片上的缩放按钮,点击后松开(没有拖拽缩放),后续图片缩放按钮都不显示问题;

### 细节优化
- 右侧边栏展开关闭添加css activeSidebar到container上,方便界面样式自适应调整;
- 节点插入图片后无法删除图片(添加贴纸也如此)
- 双击节点输入时,输入框样式边框覆盖到完整节点的话会更好;
- 双击节点选中文字;
- 鼠标中键按住拖拽画布支持(左键拖拽可能误操作在节点上,右键框选, 中键拖拽画布就很完善了);
2023-08-04 21:19:39 +08:00
wanglin2
516676b484 更新群二维码 2023-08-04 20:14:26 +08:00
wanglin2
20a8934da9 Fix:修复大纲里点击文字编辑时输入框焦点丢失的问题 2023-08-04 16:19:08 +08:00
wanglin2
0075c44b29 Fix:修复富文本模式下,搜索替换后换行会丢失的问题 2023-08-04 15:54:16 +08:00
wanglin2
ec7a8cdd43 Feat:搜索支持连续替换 2023-08-04 15:38:03 +08:00
wanglin2
1629bb7ccf Fix:修复搜索不能替换为空字符串的问题 2023-08-04 14:21:36 +08:00
wanglin2
35bff6ab57 Feat:曲线风格下,根节点的连接线样式支持和其他节点保持一致 2023-08-04 09:39:21 +08:00
wanglin2
ef9d8b0ea4 Fix:修复移动节点时新位置的提示块过大的问题 2023-08-04 09:11:02 +08:00
wanglin2
5d4c5703bb Fix:修复当思维导图距浏览器窗口左上角不为0时,小地图渲染不正确的问题 2023-08-03 17:04:41 +08:00
wanglin2
11bb519db8 Feat:支持格式刷功能 2023-08-03 16:50:07 +08:00
wanglin2
1952280003 Demo:给对话框添加唯一的class,方便开发者调整样式 2023-08-03 08:49:29 +08:00
wanglin2
c845a0b7fa Fix:修复只读模式下按住节点无法拖动画布的问题 2023-08-02 20:00:08 +08:00
wanglin2
f1748e7e42 Fix:修复移动端双指缩放过于灵敏的问题 2023-08-02 19:49:12 +08:00
wanglin2
49063d257b Feat:支持通过配置指定内部一些元素添加到的位置 2023-08-02 19:36:03 +08:00
wanglin2
803c83ac4f Feat:节点移动结束事件node_dragend增加回调参数,可以获取到移动到节点的uid 2023-08-02 19:05:15 +08:00
wanglin2
0bdf9f3add Fix:修复当思维导图全部移除可视区域后小地图中的指示器也会移除小地图区域的问题 2023-08-02 18:48:08 +08:00
wanglin2
10ed3d4f7c Merge branch 'feature' into main 2023-08-01 18:55:55 +08:00
wanglin2
8fc7f7d32c 打包 2023-08-01 18:55:32 +08:00
wanglin2
27a0efa4e0 Demo:1.打包不修改静态资源的文件名;2.支持运行时设置静态资源路径 2023-08-01 18:54:40 +08:00
wanglin2
7d227e901a Merge branch 'feature' into main 2023-08-01 09:51:30 +08:00
wanglin2
080d7489e7 打包 2023-08-01 09:51:12 +08:00
wanglin2
b11bd5a7ef Demo:提供应用接管模式,方便对接自己的存储服务 2023-08-01 09:49:44 +08:00
wanglin2
80ae38d295 Merge branch 'feature' into main 2023-07-31 09:48:07 +08:00
wanglin2
a4b7915196 Doc:update 2023-07-31 09:45:20 +08:00
wanglin2
4d4f1b993e Merge branch 'feature' into main 2023-07-30 21:55:37 +08:00
wanglin2
b814bd35ca 打包0.6.11-fix.1 2023-07-30 21:55:20 +08:00
wanglin2
c57361a360 Fix:修复节点文字为白色时编辑的时候看不见的问题 2023-07-30 21:53:32 +08:00
105 changed files with 2498 additions and 438 deletions

View File

@@ -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>

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 182 KiB

View File

@@ -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

View File

@@ -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)
}
// 获取当前主题

View File

@@ -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",

View File

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

View File

@@ -323,7 +323,9 @@ export const nodeDataNoStylePropList = [
'richText',
'resetRichText',
'uid',
'activeStyle'
'activeStyle',
'associativeLineTargets',
'associativeLineTargetControlOffsets'
]
// 数据缓存

View File

@@ -122,5 +122,9 @@ export const defaultOpt = {
// 是否开启自定义节点内容
isUseCustomNodeContent: false,
// 自定义返回节点内容的方法
customCreateNodeContent: null
customCreateNodeContent: null,
// 指定内部一些元素节点文本编辑元素、节点备注显示元素、关联线文本编辑元素、节点图片调整按钮元素添加到的位置默认添加到document.body下
customInnerElsAppendTo: null,
// 拖拽元素时,指示元素新位置的块的最大高度
nodeDragPlaceholderMaxSize: 20
}

View File

@@ -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)
}

View File

@@ -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')
}
}

View File

@@ -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')

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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

View File

@@ -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 {
// 节点备注

View File

@@ -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)
}
}

View File

@@ -12,6 +12,11 @@ class ExportXMind {
const zipData = await xmind.transformToXmind(data, name)
return zipData
}
// 获取解析器
getXmind() {
return xmind
}
}
ExportXMind.instanceName = 'doExportXMind'

View File

@@ -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

View File

@@ -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)
}
// 鼠标按钮按下事件

View 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

View File

@@ -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'

View File

@@ -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)
}
// 发送事件

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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
}

View File

@@ -18,6 +18,8 @@ export default {
lineDasharray: 'none',
// 连线风格
lineStyle: 'straight', // 针对logicalStructure、mindMap两种结构。曲线curve、直线straight、直连direct
// 曲线连接时,根节点和其他节点的连接线样式保持统一,默认根节点为 ( 型,其他节点为 { 型设为true后都为 { 型
rootLineKeepSameInCurve: true,
// 概要连线的粗细
generalizationLineWidth: 1,
// 概要连线的颜色

View File

@@ -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
View File

@@ -0,0 +1 @@
NODE_ENV=library

15
web/package-lock.json generated
View File

@@ -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",

View File

@@ -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

View File

@@ -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

View File

@@ -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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

View File

@@ -102,6 +102,18 @@ export const lineStyleList = [
}
]
// 曲线风格中,根节点样式是否和其他节点保持一致
export const rootLineKeepSameInCurveList = [
{
name: 'Bracket',
value: false
},
{
name: 'Brace',
value: true
}
]
// 图片重复方式
export const backgroundRepeatList = [
{

View File

@@ -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,

View File

@@ -157,6 +157,18 @@ export const lineStyleList = [
}
]
// 曲线风格中,根节点样式是否和其他节点保持一致
export const rootLineKeepSameInCurveList = [
{
name: '括号',
value: false
},
{
name: '大括号',
value: true
}
]
// 图片重复方式
export const backgroundRepeatList = [
{

View File

@@ -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
})

View File

@@ -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',

View File

@@ -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: '新特性提醒',

View File

@@ -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()
}

View File

@@ -33,6 +33,7 @@ let APIList = [
'touchEvent',
'nodeImgAdjust',
'search',
'painter',
'xmind',
'markdown',
'utils'

View File

@@ -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.

View File

@@ -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>

View File

@@ -71,6 +71,8 @@ const mindMap = new MindMap({
| isUseCustomNodeContentv0.6.3+ | Boolean | false | Whether to customize node content | |
| customCreateNodeContentv0.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` | |
| mouseScaleCenterUseMousePositionv0.6.4-fix.1+ | Boolean | true | Is the mouse zoom centered around the current position of the mouse, otherwise centered around the canvas | |
| customInnerElsAppendTov0.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 | |
| nodeDragPlaceholderMaxSizev0.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_changev0.4.0+ | Available when the `RichText` plugin is registered. Triggered when the text selection area changes when the node is edited | hasRangeWhether there is a selection、rectInfoSize and location information of the selected area、formatInfoText formatting information of the selected area |
| transforming-dom-to-imagesv0.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 | indexIndex of the node currently converted to、lenTotal number of nodes to be converted |
| node_draggingv0.4.5+ | Triggered when a node is dragged | node(The currently dragged node) |
| node_dragendv0.4.5+ | Triggered when the node is dragged and ends | |
| node_dragendv0.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_clickv0.4.5+ | Triggered when an associated line is clicked | path(Connector node)、clickPath(Invisible click line node)、node(Start node)、toNode(Target node) |
| svg_mouseenterv0.5.1+ | Triggered when the mouse moves into the SVG canvas | eevent object |
| svg_mouseleavev0.5.1+ | Triggered when the mouse moves out of the SVG canvas | eevent object |
| node_icon_clickv0.6.10+ | Triggered when clicking on an icon within a node | thisnode instance、itemClick on the icon name、eevent object |
| view_theme_changev0.6.12+ | Triggered after calling the setTheme method to set the theme | themetheme 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 | appointNodesv0.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_STYLEsv0.6.12+ | Modify multiple styles of nodes | nodethe node to set the style of、styleStyle objectkey is style propvalue is style value、isActiveboolean, 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) |

View File

@@ -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>customInnerElsAppendTov0.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>nodeDragPlaceholderMaxSizev0.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_dragendv0.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_clickv0.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>thisnode instanceitemClick on the icon nameeevent object</td>
</tr>
<tr>
<td>view_theme_changev0.6.12+</td>
<td>Triggered after calling the setTheme method to set the theme</td>
<td>themetheme 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_STYLEsv0.6.12+</td>
<td>Modify multiple styles of nodes</td>
<td>nodethe node to set the style ofstyleStyle objectkey is style propvalue is style valueisActiveboolean, 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>

View File

@@ -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.

View File

@@ -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 '&amp;&amp; node ../copy.js' from 'vue-cli-service build &amp;&amp; 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>&lt;script&gt;
<span class="hljs-built_in">window</span>.takeOverApp = <span class="hljs-literal">true</span>
&lt;/script&gt;
</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>&lt;script&gt;
<span class="hljs-comment">// Your own method of requesting data</span>
<span class="hljs-keyword">const</span> getDataFromBackend = <span class="hljs-function">() =&gt;</span> {
<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
<span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</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">&quot;data&quot;</span>: {
<span class="hljs-string">&quot;text&quot;</span>: <span class="hljs-string">&quot;根节点&quot;</span>
},
<span class="hljs-string">&quot;children&quot;</span>: []
},
<span class="hljs-attr">theme</span>: { <span class="hljs-string">&quot;template&quot;</span>:<span class="hljs-string">&quot;avocado&quot;</span>,<span class="hljs-string">&quot;config&quot;</span>:{} },
<span class="hljs-attr">layout</span>: <span class="hljs-string">&quot;logicalStructure&quot;</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">&#x27;zh&#x27;</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>) =&gt;</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">() =&gt;</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>) =&gt;</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">() =&gt;</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>) =&gt;</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">() =&gt;</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>) =&gt;</span> {
<span class="hljs-built_in">console</span>.log(config)
}
}
<span class="hljs-built_in">window</span>.onload = <span class="hljs-keyword">async</span> () =&gt; {
<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">&#x27;app_inited&#x27;</span>, <span class="hljs-function">(<span class="hljs-params">mindMap</span>) =&gt;</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()
}
&lt;/script&gt;
</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">&#x27;./assets/dist/&#x27;</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>

View File

@@ -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>

View File

@@ -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 mapsupport 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>

View File

@@ -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

View File

@@ -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

View File

@@ -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`,

View File

@@ -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>

View 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.

View 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">&#x27;simple-mind-map&#x27;</span>
<span class="hljs-keyword">import</span> Painter <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/plugins/Painter.js&#x27;</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', () =&gt; {}) 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>

View File

@@ -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)

View File

@@ -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>

View File

@@ -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:

View File

@@ -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>&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;p&gt;</code> into text wrapped in <code>&lt;br&gt;</code>.</p>
<h4>textToNodeRichTextWithWrap(html)</h4>
<blockquote>
<p>v0.6.12+</p>
</blockquote>
<p>Convert the wrapped text of <code>&lt;br&gt;</code> into node rich text content in the form of <code>&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;p&gt;</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">&#x27;simple-mind-map/src/utils/simulateCSSBackgroundInCanvas&#x27;</span>

View File

@@ -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' }
]
}
]

View File

@@ -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.优化小地图,去除小地图内的节点内容,优化性能。

View File

@@ -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>Demo1.新增主题主题列表新增tab区分 2.节点图片上传支持输入网络图片地址 3.节点图片上传支持输入网络图片</p>

View File

@@ -71,6 +71,8 @@ const mindMap = new MindMap({
| isUseCustomNodeContentv0.6.3+ | Boolean | false | 是否自定义节点内容 | |
| customCreateNodeContentv0.6.3+ | Function/null | null | 如果`isUseCustomNodeContent`设为`true`,那么需要使用该选项传入一个方法,接收节点实例`node`为参数(如果要获取该节点的数据,可以通过`node.nodeData.data`需要返回自定义节点内容元素也就是DOM节点如果某个节点不需要自定义那么返回`null`即可 | |
| mouseScaleCenterUseMousePositionv0.6.4-fix.1+ | Boolean | true | 鼠标缩放是否以鼠标当前位置为中心点,否则以画布中心点 | |
| customInnerElsAppendTov0.6.12+ | null/HTMLElement | null | 指定内部一些元素节点文本编辑元素、节点备注显示元素、关联线文本编辑元素、节点图片调整按钮元素添加到的位置默认添加到document.body下 | |
| nodeDragPlaceholderMaxSizev0.6.12+ | Number | 20 | 拖拽元素时,指示元素新位置的块的最大高度 | |
### 水印配置
@@ -237,11 +239,12 @@ mindMap.setTheme('主题名称')
| rich_text_selection_changev0.4.0+ | 当注册了`RichText`插件时可用。当节点编辑时,文本选区发生改变时触发 | hasRange是否存在选区、rectInfo选区的尺寸和位置信息、formatInfo选区的文本格式化信息 |
| transforming-dom-to-imagesv0.4.0+ | 当注册了`RichText`插件时可用。当`svg`中存在`DOM`节点时,导出为图片时会将`DOM`节点转换为图片,转换过程中会触发该事件,可用通过该事件给用户提示,告知目前转换到的节点 | index当前转换到的节点索引、len一共需要转换的节点数量 |
| node_draggingv0.4.5+ | 当某个节点被拖拽时触发 | node当前被拖拽的节点 |
| node_dragendv0.4.5+ | 节点被拖拽结束时触发 | |
| node_dragendv0.4.5+ | 节点被拖拽结束时触发 | { overlapNodeUid, prevNodeUid, nextNodeUid }v0.6.12+本次节点移动到的节点uid比如本次移动到了节点A上那么overlapNodeUid就是节点A的uid如果移动到了B节点的前面那么nextNodeUid就是节点B的uid你可以通过mindMap.renderer.findNodeByUid(uid)方法来获取节点实例) |
| associative_line_clickv0.4.5+ | 点击某条关联线时触发 | path连接线节点、clickPath不可见的点击线节点、node起始节点、toNode目标节点 |
| svg_mouseenterv0.5.1+ | 鼠标移入svg画布时触发 | e事件对象 |
| svg_mouseleavev0.5.1+ | 鼠标移出svg画布时触发 | e事件对象 |
| node_icon_clickv0.6.10+ | 点击节点内的图标时触发 | this节点实例、item点击的图标名称、e事件对象 |
| view_theme_changev0.6.12+ | 调用了setTheme方法设置主题后触发 | theme设置的新主题名称 |
### emit(event, ...args)
@@ -319,7 +322,8 @@ mindMap.updateConfig({
| REMOVE_NODE | 删除节点,操作节点为当前激活的节点或指定节点 | appointNodesv0.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_STYLEsv0.6.12+ | 修改节点多个样式 | node要设置样式的节点、style样式对象key为样式属性value为样式值、isActive布尔值是否设置的是激活状态的样式 |
| SET_NODE_ACTIVE | 设置节点是否激活 | node要设置的节点、active布尔值是否激活 |
| CLEAR_ACTIVE_NODE | 清除当前已激活节点的激活状态,操作节点为当前激活的节点 | |
| SET_NODE_EXPAND | 设置节点是否展开 | node要设置的节点、expand布尔值是否展开 |

View File

@@ -357,6 +357,20 @@
<td>鼠标缩放是否以鼠标当前位置为中心点否则以画布中心点</td>
<td></td>
</tr>
<tr>
<td>customInnerElsAppendTov0.6.12+</td>
<td>null/HTMLElement</td>
<td>null</td>
<td>指定内部一些元素节点文本编辑元素节点备注显示元素关联线文本编辑元素节点图片调整按钮元素添加到的位置默认添加到document.body下</td>
<td></td>
</tr>
<tr>
<td>nodeDragPlaceholderMaxSizev0.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">&#x27;主题名称&#x27;</span>)
<tr>
<td>node_dragendv0.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_clickv0.4.5+</td>
@@ -712,6 +726,11 @@ mindMap.setTheme(<span class="hljs-string">&#x27;主题名称&#x27;</span>)
<td>点击节点内的图标时触发</td>
<td>this节点实例item点击的图标名称e事件对象</td>
</tr>
<tr>
<td>view_theme_changev0.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">&#x27;主题名称&#x27;</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_STYLEsv0.6.12+</td>
<td>修改节点多个样式</td>
<td>node要设置样式的节点style样式对象key为样式属性value为样式值isActive布尔值是否设置的是激活状态的样式</td>
</tr>
<tr>
<td>SET_NODE_ACTIVE</td>

View File

@@ -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`目录内的目录层级关系最好不要调整,否则可能会出现异常。
如果你想替换其中的一些静态资源,比如你想将主题图片和结构的图片替换成你自己设计的图片,那么可以直接同名覆盖。

View File

@@ -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 &amp;&amp; node ../copy.js</code>中的<code> &amp;&amp; node ../copy.js</code>删除即可</p>
<p>如果你想修改打包输出的目录可以修改<code>web/vue.config.js</code>文件的<code>outputDir</code>配置改成你想要输出的路径即可</p>
<p>如果你想修改<code>index.html</code>文件引用静态资源的路径的话可以修改<code>web/vue.config.js</code>文件的<code>publicPath</code>配置</p>
<p>如果你想修改<code>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>&lt;script&gt;
<span class="hljs-built_in">window</span>.takeOverApp = <span class="hljs-literal">true</span>
&lt;/script&gt;
</code></pre>
<p>这行代码会提示应用不要初始化应用<code>new Vue()</code>而是把控制权交给你接下来再在<code>body</code>的最后插入你自己的<code>js</code>代码内联或则外链都可以内联示例如下</p>
<pre class="hljs"><code>&lt;script&gt;
<span class="hljs-comment">// 你自己的请求数据的方法</span>
<span class="hljs-keyword">const</span> getDataFromBackend = <span class="hljs-function">() =&gt;</span> {
<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
<span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
resolve({
<span class="hljs-comment">// 思维导图数据</span>
<span class="hljs-attr">mindMapData</span>: {
<span class="hljs-attr">root</span>: {
<span class="hljs-string">&quot;data&quot;</span>: {
<span class="hljs-string">&quot;text&quot;</span>: <span class="hljs-string">&quot;根节点&quot;</span>
},
<span class="hljs-string">&quot;children&quot;</span>: []
},
<span class="hljs-attr">theme</span>: { <span class="hljs-string">&quot;template&quot;</span>:<span class="hljs-string">&quot;avocado&quot;</span>,<span class="hljs-string">&quot;config&quot;</span>:{} },
<span class="hljs-attr">layout</span>: <span class="hljs-string">&quot;logicalStructure&quot;</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">&#x27;zh&#x27;</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>) =&gt;</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">() =&gt;</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>) =&gt;</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">() =&gt;</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>) =&gt;</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">() =&gt;</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>) =&gt;</span> {
<span class="hljs-built_in">console</span>.log(config)
}
}
<span class="hljs-built_in">window</span>.onload = <span class="hljs-keyword">async</span> () =&gt; {
<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">&#x27;app_inited&#x27;</span>, <span class="hljs-function">(<span class="hljs-params">mindMap</span>) =&gt;</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()
}
&lt;/script&gt;
</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">&#x27;./assets/dist/&#x27;</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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -42,6 +42,15 @@ MindMap.usePlugin(MiniMap)
1.准备一个容器元素`container`,定位不为`static`
如果使用的是富文本编辑模式,那么最好给`container`内部的元素去除一下默认样式,否则可能会出现节点内文本偏移的问题:
```css
.container * {
margin: 0;
padding: 0;
}
```
2.在`container`内创建一个小地图容器元素`miniMapContainer`,绝对定位
3.在`container`内创建一个视口框元素`viewBoxContainer`,绝对定位,设置边框样式,过渡属性(可选)

View File

@@ -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>

View File

@@ -116,6 +116,12 @@
修改节点的某个样式,`SET_NODE_STYLE`命令的快捷方法
### setStyles(style, isActive)
> v0.6.12+
修改节点多个样式,`SET_NODE_STYLES`命令的快捷方法
### getData(key)
获取该节点真实数据`nodeData``data`对象里的指定值,`key`不传返回这个`data`对象

View File

@@ -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>

View 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()
开始格式刷。
当调用了该方法后,如果当前存在激活节点,那么会默认取第一个激活的节点为指定节点,点击其他节点后,会把该节点的样式应用到被点击的其他节点,当点击画布后本次格式刷操作结束。

View 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">&#x27;simple-mind-map&#x27;</span>
<span class="hljs-keyword">import</span> Painter <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/plugins/Painter.js&#x27;</span>
MindMap.usePlugin(Painter)
</code></pre>
<p>注册完且实例化<code>MindMap</code>后可通过<code>mindMap.painter</code>获取到该实例</p>
<h2>事件</h2>
<blockquote>
<p>可以通过mindMap.on('事件名称', () =&gt; {})来监听事件</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>

View File

@@ -45,10 +45,12 @@ mindMap.on('search_info_change', (data) => {
结束搜索。
### replace(replaceText)
### replace(replaceText, jumpNext = false)
- `replaceText`:要进行替换的文本
- `jumpNext`v0.6.12+,是否自动跳转到下一个匹配节点
替换当前节点内容,要在调用了`search`方法之后调用,会替换当前定位到的匹配节点内容。
### replaceAll(replaceText)

View File

@@ -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>

View File

@@ -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的背景属性

View File

@@ -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>&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;p&gt;</code><code>&lt;br&gt;</code></p>
<h4>textToNodeRichTextWithWrap(html)</h4>
<blockquote>
<p>v0.6.12+</p>
</blockquote>
<p><code>&lt;br&gt;</code>换行的文本转换成<code>&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;p&gt;</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">&#x27;simple-mind-map/src/utils/simulateCSSBackgroundInCanvas&#x27;</span>

View File

@@ -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);
}
}

View File

@@ -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',

View File

@@ -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']),

View File

@@ -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)
}
},
/**

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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']),

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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%);
}
}
}
}

View 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>

View 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>

View File

@@ -117,7 +117,7 @@ export default {
},
replace() {
this.mindMap.search.replace(this.replaceText)
this.mindMap.search.replace(this.replaceText, true)
},
replaceAll() {

View File

@@ -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