Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89983f9f47 | ||
|
|
b71a80e383 | ||
|
|
2bf146816b | ||
|
|
daf9888da4 | ||
|
|
b42cee7a2f | ||
|
|
9ecb199608 | ||
|
|
adccef5699 | ||
|
|
94230f8ec6 | ||
|
|
a161661c6b | ||
|
|
08b971cd9a | ||
|
|
b64c8f132b | ||
|
|
d6181591c5 | ||
|
|
e093cb1741 | ||
|
|
a63a92c423 | ||
|
|
2c4f065626 | ||
|
|
ae5d4dd2a6 | ||
|
|
e574883e5f | ||
|
|
1bf60c49c7 | ||
|
|
ddc173cf84 | ||
|
|
2c71c7d102 | ||
|
|
f6cb08bdaa | ||
|
|
05728de21b | ||
|
|
b51027f641 | ||
|
|
ea4fdf8290 | ||
|
|
55796b4e39 | ||
|
|
e534c3138d | ||
|
|
e185abd223 | ||
|
|
3b73f72866 | ||
|
|
0db2f47133 | ||
|
|
80c7ec0fac | ||
|
|
780ce363de | ||
|
|
eaa8929457 | ||
|
|
ad0f62a5ac | ||
|
|
31f21ce013 | ||
|
|
7a20ce2f79 | ||
|
|
b98e7a97ec | ||
|
|
0bb50b3371 | ||
|
|
a03091b28c | ||
|
|
6c33984b8c | ||
|
|
28a4be0631 | ||
|
|
b8a3be7a62 | ||
|
|
259d4028f3 | ||
|
|
fb1251afc1 | ||
|
|
06e3fd428a |
37
README.md
@@ -86,7 +86,7 @@ const mindMap = new MindMap({
|
||||
|
||||
# License
|
||||
|
||||
MIT
|
||||
[MIT](./LICENSE)
|
||||
|
||||
# 微信交流群
|
||||
|
||||
@@ -96,11 +96,44 @@ MIT
|
||||
|
||||
# 请作者喝杯咖啡
|
||||
|
||||
开源不易,如果本项目有帮助到你的话,可以考虑请作者喝杯咖啡哟~
|
||||
|
||||
> 厚椰乳一盒 + 纯牛奶半盒 + 冰块 + 咖啡液 = 生椰拿铁 yyds
|
||||
|
||||
> 转账请备注哦~你的头像和名称会出现在[文档页面](https://wanglin2.github.io/mind-map/#/doc/zh/introduction/%E8%AF%B7%E4%BD%9C%E8%80%85%E5%96%9D%E6%9D%AF%E5%92%96%E5%95%A1)
|
||||
> 转账请备注【思维导图】。你的头像和名字将会出现在下面和[文档页面](https://wanglin2.github.io/mind-map/#/doc/zh/introduction/%E8%AF%B7%E4%BD%9C%E8%80%85%E5%96%9D%E6%9D%AF%E5%92%96%E5%95%A1)
|
||||
|
||||
<p>
|
||||
<img src="./web/src/assets/img/alipay.jpg" style="width: 300px" />
|
||||
<img src="./web/src/assets/img/wechat.jpg" style="width: 300px" />
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/Think.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>Think</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/志斌.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>志斌</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/小土渣的宇宙.jpeg" style="width: 50px;height: 50px;" />
|
||||
<span>小土渣的宇宙</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/qp.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>qp</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/ZXR.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>ZXR</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/花儿朵朵.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>花儿朵朵</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/suka.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>suka</span>
|
||||
</span>
|
||||
</p>
|
||||
BIN
qrcode.jpg
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 184 KiB |
@@ -32,9 +32,6 @@ class MindMap {
|
||||
this.svg = SVG().addTo(this.el).size(this.width, this.height)
|
||||
this.draw = this.svg.group()
|
||||
|
||||
// 节点id
|
||||
this.uid = 1
|
||||
|
||||
// 初始化主题
|
||||
this.initTheme()
|
||||
|
||||
@@ -194,7 +191,7 @@ class MindMap {
|
||||
this.opt.layout = layout
|
||||
this.view.reset()
|
||||
this.renderer.setLayout()
|
||||
this.render()
|
||||
this.render(null, CONSTANTS.CHANGE_LAYOUT)
|
||||
}
|
||||
|
||||
// 执行命令
|
||||
@@ -238,7 +235,7 @@ class MindMap {
|
||||
|
||||
// 获取思维导图数据,节点树、主题、布局等
|
||||
getData(withConfig) {
|
||||
let nodeData = this.command.removeDataUid(this.command.getCopyData())
|
||||
let nodeData = this.command.getCopyData()
|
||||
let data = {}
|
||||
if (withConfig) {
|
||||
data = {
|
||||
|
||||
4
simple-mind-map/package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.7",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.7",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@svgdotjs/svg.js": "^3.0.16",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.6.5",
|
||||
"version": "0.6.7",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
|
||||
@@ -157,6 +157,7 @@ export const themeList = [
|
||||
// 常量
|
||||
export const CONSTANTS = {
|
||||
CHANGE_THEME: 'changeTheme',
|
||||
CHANGE_LAYOUT: 'changeLayout',
|
||||
SET_DATA: 'setData',
|
||||
TRANSFORM_TO_NORMAL_NODE: 'transformAllNodesToNormalNode',
|
||||
MODE: {
|
||||
@@ -170,7 +171,8 @@ export const CONSTANTS = {
|
||||
CATALOG_ORGANIZATION: 'catalogOrganization',
|
||||
TIMELINE: 'timeline',
|
||||
TIMELINE2: 'timeline2',
|
||||
FISHBONE: 'fishbone'
|
||||
FISHBONE: 'fishbone',
|
||||
VERTICAL_TIMELINE: 'verticalTimeline'
|
||||
},
|
||||
DIR: {
|
||||
UP: 'up',
|
||||
@@ -206,8 +208,10 @@ export const CONSTANTS = {
|
||||
BOTTOM: 'bottom',
|
||||
CENTER: 'center'
|
||||
},
|
||||
TIMELINE_DIR: {
|
||||
LAYOUT_GROW_DIR: {
|
||||
LEFT: 'left',
|
||||
TOP: 'top',
|
||||
RIGHT: 'right',
|
||||
BOTTOM: 'bottom'
|
||||
}
|
||||
}
|
||||
@@ -246,6 +250,10 @@ export const layoutList = [
|
||||
name: '时间轴2',
|
||||
value: CONSTANTS.LAYOUT.TIMELINE2,
|
||||
},
|
||||
{
|
||||
name: '竖向时间轴',
|
||||
value: CONSTANTS.LAYOUT.VERTICAL_TIMELINE,
|
||||
},
|
||||
{
|
||||
name: '鱼骨图',
|
||||
value: CONSTANTS.LAYOUT.FISHBONE,
|
||||
@@ -258,6 +266,7 @@ export const layoutValueList = [
|
||||
CONSTANTS.LAYOUT.ORGANIZATION_STRUCTURE,
|
||||
CONSTANTS.LAYOUT.TIMELINE,
|
||||
CONSTANTS.LAYOUT.TIMELINE2,
|
||||
CONSTANTS.LAYOUT.VERTICAL_TIMELINE,
|
||||
CONSTANTS.LAYOUT.FISHBONE
|
||||
]
|
||||
|
||||
|
||||
@@ -79,10 +79,6 @@ export const defaultOpt = {
|
||||
},
|
||||
// 是否只有当鼠标在画布内才响应快捷键事件
|
||||
enableShortcutOnlyWhenMouseInSvg: true,
|
||||
// 是否开启节点动画过渡
|
||||
enableNodeTransitionMove: true,
|
||||
// 如果开启节点动画过渡,可以通过该属性设置过渡的时间,单位ms
|
||||
nodeTransitionMoveDuration: 300,
|
||||
// 初始根节点的位置
|
||||
initRootNodePosition: null,
|
||||
// 导出png、svg、pdf时的图形内边距
|
||||
|
||||
@@ -89,7 +89,7 @@ class Command {
|
||||
this.history.shift()
|
||||
}
|
||||
this.activeHistoryIndex = this.history.length - 1
|
||||
this.mindMap.emit('data_change', this.removeDataUid(data))
|
||||
this.mindMap.emit('data_change', data)
|
||||
this.mindMap.emit(
|
||||
'back_forward',
|
||||
this.activeHistoryIndex,
|
||||
@@ -110,7 +110,7 @@ class Command {
|
||||
this.history.length
|
||||
)
|
||||
let data = simpleDeepClone(this.history[this.activeHistoryIndex])
|
||||
this.mindMap.emit('data_change', this.removeDataUid(data))
|
||||
this.mindMap.emit('data_change', data)
|
||||
return data
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ class Command {
|
||||
this.activeHistoryIndex += step
|
||||
this.mindMap.emit('back_forward', this.activeHistoryIndex, this.history.length)
|
||||
let data = simpleDeepClone(this.history[this.activeHistoryIndex])
|
||||
this.mindMap.emit('data_change', this.removeDataUid(data))
|
||||
this.mindMap.emit('data_change', data)
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ import MindMap from '../../layouts/MindMap'
|
||||
import CatalogOrganization from '../../layouts/CatalogOrganization'
|
||||
import OrganizationStructure from '../../layouts/OrganizationStructure'
|
||||
import Timeline from '../../layouts/Timeline'
|
||||
import VerticalTimeline from '../../layouts/VerticalTimeline'
|
||||
import Fishbone from '../../layouts/Fishbone'
|
||||
import TextEdit from './TextEdit'
|
||||
import { copyNodeTree, simpleDeepClone, walk } from '../../utils'
|
||||
import { copyNodeTree, simpleDeepClone, walk, bfsWalk } from '../../utils'
|
||||
import { shapeList } from './node/Shape'
|
||||
import { lineStyleProps } from '../../themes/default'
|
||||
import { CONSTANTS } from '../../constants/constant'
|
||||
@@ -25,6 +26,8 @@ const layouts = {
|
||||
[CONSTANTS.LAYOUT.TIMELINE]: Timeline,
|
||||
// 时间轴2
|
||||
[CONSTANTS.LAYOUT.TIMELINE2]: Timeline,
|
||||
// 竖向时间轴
|
||||
[CONSTANTS.LAYOUT.VERTICAL_TIMELINE]: VerticalTimeline,
|
||||
// 鱼骨图
|
||||
[CONSTANTS.LAYOUT.FISHBONE]: Fishbone,
|
||||
}
|
||||
@@ -192,6 +195,9 @@ class Render {
|
||||
// 设置节点形状
|
||||
this.setNodeShape = this.setNodeShape.bind(this)
|
||||
this.mindMap.command.add('SET_NODE_SHAPE', this.setNodeShape)
|
||||
// 定位节点
|
||||
this.goTargetNode = this.goTargetNode.bind(this)
|
||||
this.mindMap.command.add('GO_TARGET_NODE', this.goTargetNode)
|
||||
}
|
||||
|
||||
// 注册快捷键
|
||||
@@ -256,7 +262,6 @@ class Render {
|
||||
|
||||
// 渲染
|
||||
render(callback = () => {}, source) {
|
||||
let t = Date.now()
|
||||
// 如果当前还没有渲染完毕,不再触发渲染
|
||||
if (this.isRendering) {
|
||||
// 等待当前渲染完毕后再进行一次渲染
|
||||
@@ -287,7 +292,7 @@ class Render {
|
||||
// 更新根节点
|
||||
this.root = root
|
||||
// 渲染节点
|
||||
const onEnd = () => {
|
||||
this.root.render(() => {
|
||||
this.isRendering = false
|
||||
this.mindMap.emit('node_tree_render_end')
|
||||
callback && callback()
|
||||
@@ -300,18 +305,6 @@ class Render {
|
||||
this.mindMap.command.addHistory()
|
||||
}
|
||||
}
|
||||
}
|
||||
let { enableNodeTransitionMove, nodeTransitionMoveDuration } =
|
||||
this.mindMap.opt
|
||||
this.root.render(() => {
|
||||
let dur = Date.now() - t
|
||||
if (enableNodeTransitionMove && dur <= nodeTransitionMoveDuration) {
|
||||
setTimeout(() => {
|
||||
onEnd()
|
||||
}, nodeTransitionMoveDuration - dur);
|
||||
} else {
|
||||
onEnd()
|
||||
}
|
||||
})
|
||||
})
|
||||
this.mindMap.emit('node_active', null, this.activeNodeList)
|
||||
@@ -436,11 +429,14 @@ class Render {
|
||||
first.parent.destroy()
|
||||
}
|
||||
let index = this.getNodeIndex(first)
|
||||
let isRichText = !!this.mindMap.richText
|
||||
first.parent.nodeData.children.splice(index + 1, 0, {
|
||||
inserting: openEdit,
|
||||
data: {
|
||||
text: text,
|
||||
expand: true,
|
||||
richText: isRichText,
|
||||
resetRichText: isRichText,
|
||||
...(appointData || {})
|
||||
},
|
||||
children: []
|
||||
@@ -465,11 +461,14 @@ class Render {
|
||||
node.nodeData.children = []
|
||||
}
|
||||
let text = node.isRoot ? defaultInsertSecondLevelNodeText : defaultInsertBelowSecondLevelNodeText
|
||||
let isRichText = !!this.mindMap.richText
|
||||
node.nodeData.children.push({
|
||||
inserting: openEdit,
|
||||
data: {
|
||||
text: text,
|
||||
expand: true,
|
||||
richText: isRichText,
|
||||
resetRichText: isRichText,
|
||||
...(appointData || {})
|
||||
},
|
||||
children: []
|
||||
@@ -693,11 +692,11 @@ class Render {
|
||||
if (node.isRoot) {
|
||||
return
|
||||
}
|
||||
let copyData = copyNodeTree({}, node, false, true)
|
||||
// let copyData = copyNodeTree({}, node, false, true)
|
||||
this.removeActiveNode(node)
|
||||
this.removeOneNode(node)
|
||||
this.mindMap.emit('node_active', null, this.activeNodeList)
|
||||
toNode.nodeData.children.push(copyData)
|
||||
toNode.nodeData.children.push(node.nodeData)
|
||||
this.mindMap.render()
|
||||
if (toNode.isRoot) {
|
||||
toNode.destroy()
|
||||
@@ -989,6 +988,19 @@ class Render {
|
||||
})
|
||||
}
|
||||
|
||||
// 定位到指定节点
|
||||
goTargetNode(node) {
|
||||
let uid = typeof node === 'string' ? node : node.nodeData.data.uid
|
||||
if (!uid) return
|
||||
this.expandToNodeUid(uid, () => {
|
||||
let targetNode = this.findNodeByUid(uid)
|
||||
if (targetNode) {
|
||||
targetNode.active()
|
||||
this.moveNodeToCenter(targetNode)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 更新节点数据
|
||||
setNodeData(node, data) {
|
||||
Object.keys(data).forEach(key => {
|
||||
@@ -1025,6 +1037,44 @@ class Render {
|
||||
this.mindMap.view.translateY(offsetY)
|
||||
this.mindMap.view.setScale(1)
|
||||
}
|
||||
|
||||
// 展开到指定uid的节点
|
||||
expandToNodeUid(uid, callback = () => {}) {
|
||||
let parentsList = []
|
||||
const cache = {}
|
||||
bfsWalk(this.renderTree, (node, parent) => {
|
||||
if (node.data.uid === uid) {
|
||||
parentsList = parent ? [...cache[parent.data.uid], parent] : []
|
||||
return 'stop'
|
||||
} else {
|
||||
cache[node.data.uid] = parent ? [...cache[parent.data.uid], parent]: []
|
||||
}
|
||||
})
|
||||
let needRender = false
|
||||
parentsList.forEach((node) => {
|
||||
if (!node.data.expand) {
|
||||
needRender = true
|
||||
node.data.expand = true
|
||||
}
|
||||
})
|
||||
if (needRender) {
|
||||
this.mindMap.render(callback)
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
// 根据uid找到对应的节点实例
|
||||
findNodeByUid(uid) {
|
||||
let res = null
|
||||
walk(this.root, null, (node) => {
|
||||
if (node.nodeData.data.uid === uid) {
|
||||
res = node
|
||||
return true
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
export default Render
|
||||
|
||||
@@ -65,7 +65,8 @@ export default class TextEdit {
|
||||
}
|
||||
|
||||
// 显示文本编辑框
|
||||
async show(node) {
|
||||
// isInserting:是否是刚创建的节点
|
||||
async show(node, e, isInserting = false) {
|
||||
// 使用了自定义节点内容那么不响应编辑事件
|
||||
if (node.isUseCustomNodeContent()) {
|
||||
return
|
||||
@@ -74,7 +75,7 @@ export default class TextEdit {
|
||||
if (typeof beforeTextEdit === 'function') {
|
||||
let isShow = false
|
||||
try {
|
||||
isShow = await beforeTextEdit(node)
|
||||
isShow = await beforeTextEdit(node, isInserting)
|
||||
} catch (error) {
|
||||
isShow = false
|
||||
}
|
||||
@@ -85,7 +86,7 @@ export default class TextEdit {
|
||||
this.mindMap.view.translateXY(offsetLeft, offsetTop)
|
||||
let rect = node._textData.node.node.getBoundingClientRect()
|
||||
if (this.mindMap.richText) {
|
||||
this.mindMap.richText.showEditText(node, rect)
|
||||
this.mindMap.richText.showEditText(node, rect, isInserting)
|
||||
return
|
||||
}
|
||||
this.showEditTextBox(node, rect)
|
||||
|
||||
@@ -269,15 +269,7 @@ class Node {
|
||||
this.group.add(this.shapeNode)
|
||||
this.updateNodeShape()
|
||||
// 渲染一个隐藏的矩形区域,用来触发展开收起按钮的显示
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
if (!this._unVisibleRectRegionNode) {
|
||||
this._unVisibleRectRegionNode = new Rect()
|
||||
}
|
||||
this._unVisibleRectRegionNode.fill({
|
||||
color: 'transparent'
|
||||
}).size(this.expandBtnSize, height).x(width).y(0)
|
||||
this.group.add(this._unVisibleRectRegionNode)
|
||||
}
|
||||
this.renderExpandBtnPlaceholderRect()
|
||||
// 概要节点添加一个带所属节点id的类名
|
||||
if (this.isGeneralization && this.generalizationBelongNode) {
|
||||
this.group.addClass('generalization_' + this.generalizationBelongNode.uid)
|
||||
@@ -289,7 +281,7 @@ class Node {
|
||||
foreignObject.height(height)
|
||||
foreignObject.add(SVG(this._customNodeContent))
|
||||
this.group.add(foreignObject)
|
||||
return
|
||||
return
|
||||
}
|
||||
// 图片节点
|
||||
let imgHeight = 0
|
||||
@@ -364,6 +356,21 @@ class Node {
|
||||
this.group.add(textContentNested)
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
renderExpandBtnPlaceholderRect() {
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
let { width, height } = this
|
||||
if (!this._unVisibleRectRegionNode) {
|
||||
this._unVisibleRectRegionNode = new Rect()
|
||||
this._unVisibleRectRegionNode.fill({
|
||||
color: 'transparent'
|
||||
})
|
||||
}
|
||||
this.group.add(this._unVisibleRectRegionNode)
|
||||
this.renderer.layout.renderExpandBtnRect(this._unVisibleRectRegionNode, this.expandBtnSize, width, height, this)
|
||||
}
|
||||
}
|
||||
|
||||
// 给节点绑定事件
|
||||
bindGroupEvent() {
|
||||
// 单击事件,选中节点
|
||||
@@ -433,7 +440,8 @@ class Node {
|
||||
// 右键菜单事件
|
||||
this.group.on('contextmenu', e => {
|
||||
// 按住ctrl键点击鼠标左键不知为何触发的是contextmenu事件
|
||||
if (this.mindMap.opt.readonly || e.ctrlKey) {// || this.isGeneralization
|
||||
if (this.mindMap.opt.readonly || e.ctrlKey) {
|
||||
// || this.isGeneralization
|
||||
return
|
||||
}
|
||||
e.stopPropagation()
|
||||
@@ -467,8 +475,9 @@ class Node {
|
||||
if (!this.group) {
|
||||
return
|
||||
}
|
||||
let { enableNodeTransitionMove, nodeTransitionMoveDuration, alwaysShowExpandBtn } =
|
||||
this.mindMap.opt
|
||||
let {
|
||||
alwaysShowExpandBtn
|
||||
} = this.mindMap.opt
|
||||
if (alwaysShowExpandBtn) {
|
||||
// 需要移除展开收缩按钮
|
||||
if (this._expandBtn && this.nodeData.children.length <= 0) {
|
||||
@@ -492,13 +501,7 @@ class Node {
|
||||
let t = this.group.transform()
|
||||
// 如果节点位置没有变化,则返回
|
||||
if (this.left === t.translateX && this.top === t.translateY) return
|
||||
if (!isLayout && enableNodeTransitionMove) {
|
||||
this.group
|
||||
.animate(nodeTransitionMoveDuration)
|
||||
.translate(this.left - t.translateX, this.top - t.translateY)
|
||||
} else {
|
||||
this.group.translate(this.left - t.translateX, this.top - t.translateY)
|
||||
}
|
||||
this.group.translate(this.left - t.translateX, this.top - t.translateY)
|
||||
}
|
||||
|
||||
// 重新渲染节点,即重新创建节点内容、计算节点大小、计算节点内容布局、更新展开收起按钮,概要及位置
|
||||
@@ -520,8 +523,6 @@ class Node {
|
||||
|
||||
// 递归渲染
|
||||
render(callback = () => {}) {
|
||||
let { enableNodeTransitionMove, nodeTransitionMoveDuration } =
|
||||
this.mindMap.opt
|
||||
// 节点
|
||||
// 重新渲染连线
|
||||
this.renderLine()
|
||||
@@ -543,6 +544,10 @@ class Node {
|
||||
this.needLayout = false
|
||||
this.layout()
|
||||
}
|
||||
if (this.needRerenderExpandBtnPlaceholderRect) {
|
||||
this.needRerenderExpandBtnPlaceholderRect = false
|
||||
this.renderExpandBtnPlaceholderRect()
|
||||
}
|
||||
this.update()
|
||||
}
|
||||
// 子节点
|
||||
@@ -565,20 +570,14 @@ class Node {
|
||||
})
|
||||
)
|
||||
} else {
|
||||
if (enableNodeTransitionMove && !isLayout) {
|
||||
setTimeout(() => {
|
||||
callback()
|
||||
}, nodeTransitionMoveDuration)
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
callback()
|
||||
}
|
||||
// 手动插入的节点立即获得焦点并且开启编辑模式
|
||||
if (this.nodeData.inserting) {
|
||||
delete this.nodeData.inserting
|
||||
this.active()
|
||||
setTimeout(() => {
|
||||
this.mindMap.emit('node_dblclick', this)
|
||||
this.mindMap.emit('node_dblclick', this, null, true)
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
@@ -783,7 +782,7 @@ class Node {
|
||||
|
||||
// 获取padding值
|
||||
getPaddingVale() {
|
||||
let { isActive }= this.nodeData.data
|
||||
let { isActive } = this.nodeData.data
|
||||
return {
|
||||
paddingX: this.getStyle('paddingX', true, isActive),
|
||||
paddingY: this.getStyle('paddingY', true, isActive)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Node from './Node'
|
||||
import { createUid } from '../../../utils/index'
|
||||
|
||||
// 检查是否存在概要
|
||||
function checkHasGeneralization () {
|
||||
@@ -18,7 +19,7 @@ function createGeneralizationNode () {
|
||||
data: {
|
||||
data: this.nodeData.data.generalization
|
||||
},
|
||||
uid: this.mindMap.uid++,
|
||||
uid: createUid(),
|
||||
renderer: this.renderer,
|
||||
mindMap: this.mindMap,
|
||||
draw: this.draw,
|
||||
|
||||
@@ -82,12 +82,12 @@ class View {
|
||||
// 鼠标滚轮,向上和向左,都是缩小
|
||||
case CONSTANTS.DIR.UP:
|
||||
case CONSTANTS.DIR.LEFT:
|
||||
mousewheelZoomActionReverse ? this.enlarge(cx, cy) : this.narrow(cx, cy)
|
||||
mousewheelZoomActionReverse ? this.enlarge(cx, cy, isTouchPad) : this.narrow(cx, cy, isTouchPad)
|
||||
break
|
||||
// 鼠标滚轮,向下和向右,都是放大
|
||||
case CONSTANTS.DIR.DOWN:
|
||||
case CONSTANTS.DIR.RIGHT:
|
||||
mousewheelZoomActionReverse ? this.narrow(cx, cy) : this.enlarge(cx, cy)
|
||||
mousewheelZoomActionReverse ? this.narrow(cx, cy, isTouchPad) : this.enlarge(cx, cy, isTouchPad)
|
||||
break
|
||||
}
|
||||
} else {// 鼠标滚轮事件控制画布移动
|
||||
@@ -199,16 +199,18 @@ class View {
|
||||
}
|
||||
|
||||
// 缩小
|
||||
narrow(cx, cy) {
|
||||
const scale = Math.max(this.scale - this.mindMap.opt.scaleRatio, 0.1)
|
||||
narrow(cx, cy, isTouchPad) {
|
||||
const scaleRatio = this.mindMap.opt.scaleRatio / (isTouchPad ? 5 : 1)
|
||||
const scale = Math.max(this.scale - scaleRatio, 0.1)
|
||||
this.scaleInCenter(scale, cx, cy)
|
||||
this.transform()
|
||||
this.mindMap.emit('scale', this.scale)
|
||||
}
|
||||
|
||||
// 放大
|
||||
enlarge(cx, cy) {
|
||||
const scale = this.scale + this.mindMap.opt.scaleRatio
|
||||
enlarge(cx, cy, isTouchPad) {
|
||||
const scaleRatio = this.mindMap.opt.scaleRatio / (isTouchPad ? 5 : 1)
|
||||
const scale = this.scale + scaleRatio
|
||||
this.scaleInCenter(scale, cx, cy)
|
||||
this.transform()
|
||||
this.mindMap.emit('scale', this.scale)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Node from '../core/render/node/Node'
|
||||
import { CONSTANTS, initRootNodePositionMap } from '../constants/constant'
|
||||
import Lru from '../utils/Lru'
|
||||
import { createUid } from '../utils/index'
|
||||
|
||||
// 布局基类
|
||||
class Base {
|
||||
@@ -48,6 +49,20 @@ class Base {
|
||||
return [CONSTANTS.CHANGE_THEME, CONSTANTS.TRANSFORM_TO_NORMAL_NODE].includes(this.renderer.renderSource)
|
||||
}
|
||||
|
||||
// 层级类型改变
|
||||
checkIsLayerTypeChange(oldIndex, newIndex) {
|
||||
if (oldIndex >= 2 && newIndex >= 2) return false
|
||||
if (oldIndex >= 2 && newIndex < 2) return true
|
||||
if (oldIndex < 2 && newIndex >= 2) return true
|
||||
}
|
||||
|
||||
// 检查是否是结构布局改变重新渲染展开收起按钮占位元素
|
||||
checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(node) {
|
||||
if (this.renderer.renderSource === CONSTANTS.CHANGE_LAYOUT) {
|
||||
node.needRerenderExpandBtnPlaceholderRect = true
|
||||
}
|
||||
}
|
||||
|
||||
// 创建节点实例
|
||||
createNode(data, parent, isRoot, layerIndex) {
|
||||
// 创建节点
|
||||
@@ -55,11 +70,13 @@ class Base {
|
||||
// 数据上保存了节点引用,那么直接复用节点
|
||||
if (data && data._node && !this.renderer.reRender) {
|
||||
newNode = data._node
|
||||
let isLayerTypeChange = this.checkIsLayerTypeChange(newNode.layerIndex, layerIndex)
|
||||
newNode.reset()
|
||||
newNode.layerIndex = layerIndex
|
||||
this.cacheNode(data._node.uid, newNode)
|
||||
this.checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(newNode)
|
||||
// 主题或主题配置改变了需要重新计算节点大小和布局
|
||||
if (this.checkIsNeedResizeSources()) {
|
||||
if (this.checkIsNeedResizeSources() || isLayerTypeChange) {
|
||||
newNode.getSize()
|
||||
newNode.needLayout = true
|
||||
}
|
||||
@@ -68,22 +85,24 @@ class Base {
|
||||
newNode = this.lru.get(data.data.uid)
|
||||
// 保存该节点上一次的数据
|
||||
let lastData = JSON.stringify(newNode.nodeData.data)
|
||||
let isLayerTypeChange = this.checkIsLayerTypeChange(newNode.layerIndex, layerIndex)
|
||||
newNode.reset()
|
||||
newNode.nodeData = newNode.handleData(data || {})
|
||||
newNode.layerIndex = layerIndex
|
||||
this.cacheNode(data.data.uid, newNode)
|
||||
this.checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(newNode)
|
||||
data._node = newNode
|
||||
// 主题或主题配置改变了需要重新计算节点大小和布局
|
||||
let isResizeSource = this.checkIsNeedResizeSources()
|
||||
// 节点数据改变了需要重新计算节点大小和布局
|
||||
let isNodeDataChange = lastData !== JSON.stringify(data.data)
|
||||
if (isResizeSource || isNodeDataChange) {
|
||||
if (isResizeSource || isNodeDataChange || isLayerTypeChange) {
|
||||
newNode.getSize()
|
||||
newNode.needLayout = true
|
||||
}
|
||||
} else {
|
||||
// 创建新节点
|
||||
let uid = this.mindMap.uid++
|
||||
let uid = data.data.uid || createUid()
|
||||
newNode = new Node({
|
||||
data,
|
||||
uid,
|
||||
|
||||
@@ -349,6 +349,11 @@ class CatalogOrganization extends Base {
|
||||
gNode.left = right + generalizationNodeMargin
|
||||
gNode.top = top + (bottom - top - gNode.height) / 2
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
renderExpandBtnRect(rect, expandBtnSize, width, height, node) {
|
||||
rect.size(width, expandBtnSize).x(0).y(height)
|
||||
}
|
||||
}
|
||||
|
||||
export default CatalogOrganization
|
||||
|
||||
@@ -51,8 +51,8 @@ class Fishbone extends Base {
|
||||
// 节点生长方向
|
||||
newNode.dir =
|
||||
index % 2 === 0
|
||||
? CONSTANTS.TIMELINE_DIR.TOP
|
||||
: CONSTANTS.TIMELINE_DIR.BOTTOM
|
||||
? CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
: CONSTANTS.LAYOUT_GROW_DIR.BOTTOM
|
||||
}
|
||||
// 计算二级节点的top值
|
||||
if (parent._node.isRoot) {
|
||||
@@ -222,7 +222,7 @@ class Fishbone extends Base {
|
||||
|
||||
// 检查节点是否是上方节点
|
||||
checkIsTop(node) {
|
||||
return node.dir === CONSTANTS.TIMELINE_DIR.TOP
|
||||
return node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
}
|
||||
|
||||
// 绘制连线,连接该节点到其子节点
|
||||
@@ -239,7 +239,7 @@ class Fishbone extends Base {
|
||||
// 当前节点是根节点
|
||||
// 根节点的子节点是和根节点同一水平线排列
|
||||
let maxx = -Infinity
|
||||
node.children.forEach((item) => {
|
||||
node.children.forEach(item => {
|
||||
if (item.left > maxx) {
|
||||
maxx = item.left
|
||||
}
|
||||
@@ -250,15 +250,15 @@ class Fishbone extends Base {
|
||||
let line = this.draw.path()
|
||||
if (this.checkIsTop(item)) {
|
||||
line.plot(
|
||||
`M ${nodeLineX - offsetX},${item.top + item.height + offset} L ${item.left},${
|
||||
item.top + item.height
|
||||
}`
|
||||
`M ${nodeLineX - offsetX},${item.top + item.height + offset} L ${
|
||||
item.left
|
||||
},${item.top + item.height}`
|
||||
)
|
||||
} else {
|
||||
line.plot(
|
||||
`M ${nodeLineX - offsetX},${
|
||||
item.top - offset
|
||||
} L ${nodeLineX},${item.top}`
|
||||
`M ${nodeLineX - offsetX},${item.top - offset} L ${nodeLineX},${
|
||||
item.top
|
||||
}`
|
||||
)
|
||||
}
|
||||
node.style.line(line)
|
||||
@@ -373,6 +373,27 @@ class Fishbone extends Base {
|
||||
gNode.left = right + generalizationNodeMargin
|
||||
gNode.top = top + (bottom - top - gNode.height) / 2
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
renderExpandBtnRect(rect, expandBtnSize, width, height, node) {
|
||||
let dir = ''
|
||||
if (node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP) {
|
||||
dir =
|
||||
node.layerIndex === 1
|
||||
? CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
: CONSTANTS.LAYOUT_GROW_DIR.BOTTOM
|
||||
} else {
|
||||
dir =
|
||||
node.layerIndex === 1
|
||||
? CONSTANTS.LAYOUT_GROW_DIR.BOTTOM
|
||||
: CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
}
|
||||
if (dir === CONSTANTS.LAYOUT_GROW_DIR.TOP) {
|
||||
rect.size(width, expandBtnSize).x(0).y(-expandBtnSize)
|
||||
} else {
|
||||
rect.size(width, expandBtnSize).x(0).y(height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Fishbone
|
||||
|
||||
@@ -52,8 +52,8 @@ class Fishbone extends Base {
|
||||
// 节点生长方向
|
||||
newNode.dir =
|
||||
index % 2 === 0
|
||||
? CONSTANTS.TIMELINE_DIR.TOP
|
||||
: CONSTANTS.TIMELINE_DIR.BOTTOM
|
||||
? CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
: CONSTANTS.LAYOUT_GROW_DIR.BOTTOM
|
||||
}
|
||||
// 计算二级节点的top值
|
||||
if (parent._node.isRoot) {
|
||||
|
||||
@@ -52,8 +52,8 @@ class Fishbone extends Base {
|
||||
// 节点生长方向
|
||||
newNode.dir =
|
||||
index % 2 === 0
|
||||
? CONSTANTS.TIMELINE_DIR.TOP
|
||||
: CONSTANTS.TIMELINE_DIR.BOTTOM
|
||||
? CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
: CONSTANTS.LAYOUT_GROW_DIR.BOTTOM
|
||||
}
|
||||
// 计算二级节点的top值
|
||||
if (parent._node.isRoot) {
|
||||
@@ -281,7 +281,7 @@ class Fishbone extends Base {
|
||||
if (
|
||||
node.parent &&
|
||||
node.parent.isRoot &&
|
||||
node.dir === CONSTANTS.TIMELINE_DIR.TOP
|
||||
node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
) {
|
||||
line.plot(
|
||||
`M ${x},${top} L ${x + lineLength},${
|
||||
|
||||
@@ -172,9 +172,7 @@ class LogicalStructure extends Base {
|
||||
let x2 = item.left
|
||||
let y2 = item.top + item.height / 2
|
||||
// 节点使用横线风格,需要额外渲染横线
|
||||
let nodeUseLineStyleOffset = nodeUseLineStyle
|
||||
? item.width
|
||||
: 0
|
||||
let nodeUseLineStyleOffset = nodeUseLineStyle ? item.width : 0
|
||||
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
|
||||
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
|
||||
let path = `M ${x1},${y1} L ${x1 + s1},${y1} L ${x1 + s1},${y2} L ${
|
||||
@@ -260,10 +258,7 @@ class LogicalStructure extends Base {
|
||||
if (_x === translateX && _y === translateY) {
|
||||
return
|
||||
}
|
||||
btn.translate(
|
||||
_x - translateX,
|
||||
_y - translateY
|
||||
)
|
||||
btn.translate(_x - translateX, _y - translateY)
|
||||
}
|
||||
|
||||
// 创建概要节点
|
||||
@@ -286,6 +281,11 @@ class LogicalStructure extends Base {
|
||||
gNode.left = right + generalizationNodeMargin
|
||||
gNode.top = top + (bottom - top - gNode.height) / 2
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
renderExpandBtnRect(rect, expandBtnSize, width, height, node) {
|
||||
rect.size(expandBtnSize, height).x(width).y(0)
|
||||
}
|
||||
}
|
||||
|
||||
export default LogicalStructure
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun } from '../utils'
|
||||
import { CONSTANTS } from '../constants/constant'
|
||||
|
||||
// 思维导图
|
||||
class MindMap extends Base {
|
||||
@@ -45,11 +46,14 @@ class MindMap extends Base {
|
||||
newNode.dir = parent._node.dir
|
||||
} else {
|
||||
// 节点生长方向
|
||||
newNode.dir = index % 2 === 0 ? 'right' : 'left'
|
||||
newNode.dir =
|
||||
index % 2 === 0
|
||||
? CONSTANTS.LAYOUT_GROW_DIR.RIGHT
|
||||
: CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
}
|
||||
// 根据生长方向定位到父节点的左侧或右侧
|
||||
newNode.left =
|
||||
newNode.dir === 'right'
|
||||
newNode.dir === CONSTANTS.LAYOUT_GROW_DIR.RIGHT
|
||||
? parent._node.left +
|
||||
parent._node.width +
|
||||
this.getMarginX(layerIndex)
|
||||
@@ -72,7 +76,7 @@ class MindMap extends Base {
|
||||
let leftChildrenAreaHeight = 0
|
||||
let rightChildrenAreaHeight = 0
|
||||
cur._node.children.forEach(item => {
|
||||
if (item.dir === 'left') {
|
||||
if (item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT) {
|
||||
leftLen++
|
||||
leftChildrenAreaHeight += item.height
|
||||
} else {
|
||||
@@ -109,7 +113,7 @@ class MindMap extends Base {
|
||||
let leftTotalTop = baseTop - node.leftChildrenAreaHeight / 2
|
||||
let rightTotalTop = baseTop - node.rightChildrenAreaHeight / 2
|
||||
node.children.forEach(cur => {
|
||||
if (cur.dir === 'left') {
|
||||
if (cur.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT) {
|
||||
cur.top = leftTotalTop
|
||||
leftTotalTop += cur.height + marginY
|
||||
} else {
|
||||
@@ -162,7 +166,10 @@ class MindMap extends Base {
|
||||
return
|
||||
}
|
||||
let _offset = 0
|
||||
let addHeight = item.dir === 'left' ? leftAddHeight : rightAddHeight
|
||||
let addHeight =
|
||||
item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
? leftAddHeight
|
||||
: rightAddHeight
|
||||
// 上面的节点往上移
|
||||
if (_index < index) {
|
||||
_offset = -addHeight
|
||||
@@ -208,10 +215,8 @@ class MindMap extends Base {
|
||||
let x1 = 0
|
||||
let _s = 0
|
||||
// 节点使用横线风格,需要额外渲染横线
|
||||
let nodeUseLineStyleOffset = nodeUseLineStyle
|
||||
? item.width
|
||||
: 0
|
||||
if (item.dir === 'left') {
|
||||
let nodeUseLineStyleOffset = nodeUseLineStyle ? item.width : 0
|
||||
if (item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT) {
|
||||
_s = -s1
|
||||
x1 = node.layerIndex === 0 ? left : left - expandBtnSize
|
||||
nodeUseLineStyleOffset = -nodeUseLineStyleOffset
|
||||
@@ -220,7 +225,10 @@ class MindMap extends Base {
|
||||
x1 = node.layerIndex === 0 ? left + width : left + width + expandBtnSize
|
||||
}
|
||||
let y1 = top + height / 2
|
||||
let x2 = item.dir === 'left' ? item.left + item.width : item.left
|
||||
let x2 =
|
||||
item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
? item.left + item.width
|
||||
: item.left
|
||||
let y2 = item.top + item.height / 2
|
||||
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
|
||||
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
|
||||
@@ -246,18 +254,21 @@ class MindMap extends Base {
|
||||
let x1 =
|
||||
node.layerIndex === 0
|
||||
? left + width / 2
|
||||
: item.dir === 'left'
|
||||
: item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
? left - expandBtnSize
|
||||
: left + width + expandBtnSize
|
||||
let y1 = top + height / 2
|
||||
let x2 = item.dir === 'left' ? item.left + item.width : item.left
|
||||
let x2 =
|
||||
item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
? item.left + item.width
|
||||
: item.left
|
||||
let y2 = item.top + item.height / 2
|
||||
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
|
||||
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
|
||||
// 节点使用横线风格,需要额外渲染横线
|
||||
let nodeUseLineStylePath = ''
|
||||
if (nodeUseLineStyle) {
|
||||
if (item.dir === 'left') {
|
||||
if (item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT) {
|
||||
nodeUseLineStylePath = ` L ${item.left},${y2}`
|
||||
} else {
|
||||
nodeUseLineStylePath = ` L ${item.left + item.width},${y2}`
|
||||
@@ -283,11 +294,14 @@ class MindMap extends Base {
|
||||
let x1 =
|
||||
node.layerIndex === 0
|
||||
? left + width / 2
|
||||
: item.dir === 'left'
|
||||
: item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
? left - expandBtnSize
|
||||
: left + width + expandBtnSize
|
||||
let y1 = top + height / 2
|
||||
let x2 = item.dir === 'left' ? item.left + item.width : item.left
|
||||
let x2 =
|
||||
item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
? item.left + item.width
|
||||
: item.left
|
||||
let y2 = item.top + item.height / 2
|
||||
let path = ''
|
||||
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
|
||||
@@ -295,7 +309,7 @@ class MindMap extends Base {
|
||||
// 节点使用横线风格,需要额外渲染横线
|
||||
let nodeUseLineStylePath = ''
|
||||
if (this.mindMap.themeConfig.nodeUseLineStyle) {
|
||||
if (item.dir === 'left') {
|
||||
if (item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT) {
|
||||
nodeUseLineStylePath = ` L ${item.left},${y2}`
|
||||
} else {
|
||||
nodeUseLineStylePath = ` L ${item.left + item.width},${y2}`
|
||||
@@ -320,7 +334,8 @@ class MindMap extends Base {
|
||||
? height / 2
|
||||
: 0
|
||||
// 位置没有变化则返回
|
||||
let _x = (node.dir === 'left' ? 0 - expandBtnSize : width)
|
||||
let _x =
|
||||
node.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT ? 0 - expandBtnSize : width
|
||||
let _y = height / 2 + nodeUseLineStyleOffset
|
||||
if (_x === translateX && _y === translateY) {
|
||||
return
|
||||
@@ -332,7 +347,7 @@ class MindMap extends Base {
|
||||
|
||||
// 创建概要节点
|
||||
renderGeneralization(node, gLine, gNode) {
|
||||
let isLeft = node.dir === 'left'
|
||||
let isLeft = node.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
let {
|
||||
top,
|
||||
bottom,
|
||||
@@ -358,6 +373,15 @@ class MindMap extends Base {
|
||||
(isLeft ? gNode.width : 0)
|
||||
gNode.top = top + (bottom - top - gNode.height) / 2
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
renderExpandBtnRect(rect, expandBtnSize, width, height, node) {
|
||||
if (node.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT) {
|
||||
rect.size(expandBtnSize, height).x(-expandBtnSize).y(0)
|
||||
} else {
|
||||
rect.size(expandBtnSize, height).x(width).y(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default MindMap
|
||||
|
||||
@@ -255,6 +255,11 @@ class OrganizationStructure extends Base {
|
||||
gNode.top = bottom + generalizationNodeMargin
|
||||
gNode.left = left + (right - left - gNode.width) / 2
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
renderExpandBtnRect(rect, expandBtnSize, width, height, node) {
|
||||
rect.size(width, expandBtnSize).x(0).y(height)
|
||||
}
|
||||
}
|
||||
|
||||
export default OrganizationStructure
|
||||
|
||||
@@ -50,8 +50,8 @@ class Timeline extends Base {
|
||||
// 节点生长方向
|
||||
newNode.dir =
|
||||
index % 2 === 0
|
||||
? CONSTANTS.TIMELINE_DIR.BOTTOM
|
||||
: CONSTANTS.TIMELINE_DIR.TOP
|
||||
? CONSTANTS.LAYOUT_GROW_DIR.BOTTOM
|
||||
: CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
}
|
||||
} else {
|
||||
newNode.dir = ''
|
||||
@@ -151,7 +151,7 @@ class Timeline extends Base {
|
||||
if (
|
||||
parent &&
|
||||
parent.isRoot &&
|
||||
node.dir === CONSTANTS.TIMELINE_DIR.TOP
|
||||
node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
) {
|
||||
// 遍历二级节点的子节点
|
||||
node.children.forEach(item => {
|
||||
@@ -280,7 +280,7 @@ class Timeline extends Base {
|
||||
if (
|
||||
node.parent &&
|
||||
node.parent.isRoot &&
|
||||
node.dir === CONSTANTS.TIMELINE_DIR.TOP
|
||||
node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
) {
|
||||
line.plot(`M ${x},${top} L ${x},${miny}`)
|
||||
} else {
|
||||
@@ -301,7 +301,7 @@ class Timeline extends Base {
|
||||
if (
|
||||
node.parent &&
|
||||
node.parent.isRoot &&
|
||||
node.dir === CONSTANTS.TIMELINE_DIR.TOP
|
||||
node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
) {
|
||||
btn.translate(
|
||||
width * 0.3 - expandBtnSize / 2 - translateX,
|
||||
@@ -336,6 +336,28 @@ class Timeline extends Base {
|
||||
gNode.left = right + generalizationNodeMargin
|
||||
gNode.top = top + (bottom - top - gNode.height) / 2
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
renderExpandBtnRect(rect, expandBtnSize, width, height, node) {
|
||||
if (this.layout === CONSTANTS.LAYOUT.TIMELINE) {
|
||||
rect.size(width, expandBtnSize).x(0).y(height)
|
||||
} else {
|
||||
let dir = ''
|
||||
if (node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP) {
|
||||
dir =
|
||||
node.layerIndex === 1
|
||||
? CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
: CONSTANTS.LAYOUT_GROW_DIR.BOTTOM
|
||||
} else {
|
||||
dir = CONSTANTS.LAYOUT_GROW_DIR.BOTTOM
|
||||
}
|
||||
if (dir === CONSTANTS.LAYOUT_GROW_DIR.TOP) {
|
||||
rect.size(width, expandBtnSize).x(0).y(-expandBtnSize)
|
||||
} else {
|
||||
rect.size(width, expandBtnSize).x(0).y(height)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Timeline
|
||||
|
||||
431
simple-mind-map/src/layouts/VerticalTimeline.js
Normal file
@@ -0,0 +1,431 @@
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun } from '../utils'
|
||||
import { CONSTANTS } from '../constants/constant'
|
||||
|
||||
// 竖向时间轴
|
||||
class VerticalTimeline extends Base {
|
||||
// 构造函数
|
||||
constructor(opt = {}, layout) {
|
||||
super(opt)
|
||||
this.layout = layout
|
||||
}
|
||||
|
||||
// 布局
|
||||
doLayout(callback) {
|
||||
let task = [
|
||||
() => {
|
||||
this.computedBaseValue()
|
||||
},
|
||||
() => {
|
||||
this.computedTopValue()
|
||||
},
|
||||
() => {
|
||||
this.adjustLeftTopValue()
|
||||
},
|
||||
() => {
|
||||
callback(this.root)
|
||||
}
|
||||
]
|
||||
asyncRun(task)
|
||||
}
|
||||
|
||||
// 遍历数据创建节点、计算根节点的位置,计算根节点的子节点的top值
|
||||
computedBaseValue() {
|
||||
walk(
|
||||
this.renderer.renderTree,
|
||||
null,
|
||||
(cur, parent, isRoot, layerIndex, index) => {
|
||||
let newNode = this.createNode(cur, parent, isRoot, layerIndex)
|
||||
// 根节点定位在画布中心位置
|
||||
if (isRoot) {
|
||||
this.setNodeCenter(newNode)
|
||||
} else {
|
||||
// 非根节点
|
||||
// 节点生长方向
|
||||
// 三级及以下节点以上级为准
|
||||
if (parent._node.dir) {
|
||||
newNode.dir = parent._node.dir
|
||||
} else {
|
||||
newNode.dir =
|
||||
index % 2 === 0
|
||||
? CONSTANTS.LAYOUT_GROW_DIR.RIGHT
|
||||
: CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
}
|
||||
// 定位二级节点的left
|
||||
if (parent._node.isRoot) {
|
||||
newNode.left =
|
||||
parent._node.left +
|
||||
(cur._node.width > parent._node.width
|
||||
? -(cur._node.width - parent._node.width) / 2
|
||||
: (parent._node.width - cur._node.width) / 2)
|
||||
} else {
|
||||
newNode.left =
|
||||
newNode.dir === CONSTANTS.LAYOUT_GROW_DIR.RIGHT
|
||||
? parent._node.left +
|
||||
parent._node.width +
|
||||
this.getMarginX(layerIndex)
|
||||
: parent._node.left -
|
||||
this.getMarginX(layerIndex) -
|
||||
newNode.width
|
||||
}
|
||||
}
|
||||
if (!cur.data.expand) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
(cur, parent, isRoot, layerIndex) => {
|
||||
// 返回时计算节点的areaHeight,也就是子节点所占的高度之和,包括外边距
|
||||
if (isRoot) {
|
||||
return
|
||||
}
|
||||
let len = cur.data.expand === false ? 0 : cur._node.children.length
|
||||
cur._node.childrenAreaHeight = len
|
||||
? cur._node.children.reduce((h, item) => {
|
||||
return h + item.height
|
||||
}, 0) +
|
||||
(len + 1) * this.getMarginY(layerIndex + 1)
|
||||
: 0
|
||||
},
|
||||
true,
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
// 遍历节点树计算节点的top
|
||||
computedTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex, index) => {
|
||||
if (
|
||||
node.nodeData.data.expand &&
|
||||
node.children &&
|
||||
node.children.length
|
||||
) {
|
||||
let marginY = this.getMarginY(layerIndex + 1)
|
||||
// 定位二级节点的top
|
||||
if (isRoot) {
|
||||
let top = node.top + node.height
|
||||
let totalTop = top + marginY
|
||||
node.children.forEach(cur => {
|
||||
cur.top = totalTop
|
||||
totalTop += cur.height + marginY
|
||||
})
|
||||
} else {
|
||||
// 定位三级及以下节点的top
|
||||
let marginY = this.getMarginY(layerIndex + 1)
|
||||
let baseTop = node.top + node.height / 2 + marginY
|
||||
// 第一个子节点的top值 = 该节点中心的top值 - 子节点的高度之和的一半
|
||||
let totalTop = baseTop - node.childrenAreaHeight / 2
|
||||
node.children.forEach(cur => {
|
||||
cur.top = totalTop
|
||||
totalTop += cur.height + marginY
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 调整节点left、top
|
||||
adjustLeftTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (!node.nodeData.data.expand) {
|
||||
return
|
||||
}
|
||||
if (isRoot) return
|
||||
// 判断子节点所占的高度之和是否大于该节点自身,大于则需要调整位置
|
||||
let base = this.getMarginY(layerIndex + 1) * 2 + node.height
|
||||
let difference = node.childrenAreaHeight - base
|
||||
if (difference > 0) {
|
||||
this.updateBrothers(node, difference / 2)
|
||||
}
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 更新兄弟节点的top
|
||||
updateBrothers(node, addHeight) {
|
||||
if (node.parent) {
|
||||
let childrenList = node.parent.children
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item === node
|
||||
})
|
||||
childrenList.forEach((item, _index) => {
|
||||
// 自定义节点位置
|
||||
if (item.hasCustomPosition()) return
|
||||
// 三级或三级以下节点自身位置不需要动
|
||||
if (!node.parent.isRoot && item === node) return
|
||||
let _offset = 0
|
||||
// 二级节点上面的兄弟节点不需要移动,自身需要往下移动
|
||||
if (node.parent.isRoot) {
|
||||
// 上面的节点不用移
|
||||
if (_index < index) {
|
||||
_offset = 0
|
||||
} else if (_index > index) {
|
||||
// 下面的节点往下移
|
||||
_offset = addHeight * 2
|
||||
} else {
|
||||
// 自身也要移动
|
||||
_offset = addHeight
|
||||
}
|
||||
} else {
|
||||
// 三级或三级以下节点两侧的兄弟节点向两侧移动
|
||||
// 上面的节点往上移
|
||||
if (_index < index) {
|
||||
_offset = -addHeight
|
||||
} else if (_index > index) {
|
||||
// 下面的节点往下移
|
||||
_offset = addHeight
|
||||
}
|
||||
}
|
||||
item.top += _offset
|
||||
// 同步更新子节点的位置
|
||||
if (item.children && item.children.length) {
|
||||
this.updateChildren(item.children, 'top', _offset)
|
||||
}
|
||||
})
|
||||
// 更新父节点的位置
|
||||
this.updateBrothers(node.parent, addHeight)
|
||||
}
|
||||
}
|
||||
|
||||
// 调整兄弟节点的top
|
||||
updateBrothersTop(node, addHeight) {
|
||||
if (node.parent && !node.parent.isRoot) {
|
||||
let childrenList = node.parent.children
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item === node
|
||||
})
|
||||
childrenList.forEach((item, _index) => {
|
||||
if (item.hasCustomPosition()) {
|
||||
// 适配自定义位置
|
||||
return
|
||||
}
|
||||
let _offset = 0
|
||||
// 下面的节点往下移
|
||||
if (_index > index) {
|
||||
_offset = addHeight
|
||||
}
|
||||
item.top += _offset
|
||||
// 同步更新子节点的位置
|
||||
if (item.children && item.children.length) {
|
||||
this.updateChildren(item.children, 'top', _offset)
|
||||
}
|
||||
})
|
||||
// 更新父节点的位置
|
||||
this.updateBrothersTop(node.parent, addHeight)
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制连线,连接该节点到其子节点
|
||||
renderLine(node, lines, style, lineStyle) {
|
||||
if (lineStyle === 'curve') {
|
||||
this.renderLineCurve(node, lines, style)
|
||||
} else if (lineStyle === 'direct') {
|
||||
this.renderLineDirect(node, lines, style)
|
||||
} else {
|
||||
this.renderLineStraight(node, lines, style)
|
||||
}
|
||||
}
|
||||
|
||||
// 直线连接
|
||||
renderLineStraight(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return []
|
||||
}
|
||||
let { expandBtnSize } = node
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
if (node.isRoot) {
|
||||
// 当前节点是根节点
|
||||
let prevBother = node
|
||||
// 根节点的子节点是和根节点同一水平线排列
|
||||
node.children.forEach((item, index) => {
|
||||
let y1 = prevBother.top + prevBother.height
|
||||
let y2 = item.top
|
||||
let x = node.left + node.width / 2
|
||||
let path = `M ${x},${y1} L ${x},${y2}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
prevBother = item
|
||||
})
|
||||
} else {
|
||||
// 当前节点为非根节点
|
||||
if (node.dir === CONSTANTS.LAYOUT_GROW_DIR.RIGHT) {
|
||||
let nodeRight = node.left + node.width
|
||||
let nodeYCenter = node.top + node.height / 2
|
||||
let marginX = this.getMarginX(node.layerIndex + 1)
|
||||
let offset = (marginX - expandBtnSize) * 0.6
|
||||
node.children.forEach((item, index) => {
|
||||
let itemLeft = item.left
|
||||
let itemYCenter = item.top + item.height / 2
|
||||
let path = `
|
||||
M ${nodeRight},${nodeYCenter}
|
||||
L ${nodeRight + offset},${nodeYCenter}
|
||||
L ${nodeRight + offset},${itemYCenter}
|
||||
L ${itemLeft},${itemYCenter}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
})
|
||||
} else {
|
||||
let nodeLeft = node.left
|
||||
let nodeYCenter = node.top + node.height / 2
|
||||
let marginX = this.getMarginX(node.layerIndex + 1)
|
||||
let offset = (marginX - expandBtnSize) * 0.6
|
||||
node.children.forEach((item, index) => {
|
||||
let itemRight = item.left + item.width
|
||||
let itemYCenter = item.top + item.height / 2
|
||||
let path = `
|
||||
M ${nodeLeft},${nodeYCenter}
|
||||
L ${nodeLeft - offset},${nodeYCenter}
|
||||
L ${nodeLeft - offset},${itemYCenter}
|
||||
L ${itemRight},${itemYCenter}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 直连
|
||||
renderLineDirect(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return []
|
||||
}
|
||||
let { left, top, width, height, expandBtnSize } = node
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
node.children.forEach((item, index) => {
|
||||
if (node.isRoot) {
|
||||
let prevBother = node
|
||||
// 根节点的子节点是和根节点同一水平线排列
|
||||
node.children.forEach((item, index) => {
|
||||
let y1 = prevBother.top + prevBother.height
|
||||
let y2 = item.top
|
||||
let x = node.left + node.width / 2
|
||||
let path = `M ${x},${y1} L ${x},${y2}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
prevBother = item
|
||||
})
|
||||
} else {
|
||||
let x1 =
|
||||
item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
? left - expandBtnSize
|
||||
: left + width + expandBtnSize
|
||||
let y1 = top + height / 2
|
||||
let x2 =
|
||||
item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
? item.left + item.width
|
||||
: item.left
|
||||
let y2 = item.top + item.height / 2
|
||||
let path = `M ${x1},${y1} L ${x2},${y2}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 曲线风格连线
|
||||
renderLineCurve(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return []
|
||||
}
|
||||
let { left, top, width, height, expandBtnSize } = node
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
node.children.forEach((item, index) => {
|
||||
if (node.isRoot) {
|
||||
let prevBother = node
|
||||
// 根节点的子节点是和根节点同一水平线排列
|
||||
node.children.forEach((item, index) => {
|
||||
let y1 = prevBother.top + prevBother.height
|
||||
let y2 = item.top
|
||||
let x = node.left + node.width / 2
|
||||
let path = `M ${x},${y1} L ${x},${y2}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
prevBother = item
|
||||
})
|
||||
} else {
|
||||
let x1 =
|
||||
item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
? left - expandBtnSize
|
||||
: left + width + expandBtnSize
|
||||
let y1 = top + height / 2
|
||||
let x2 =
|
||||
item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
? item.left + item.width
|
||||
: item.left
|
||||
let y2 = item.top + item.height / 2
|
||||
let path = this.cubicBezierPath(x1, y1, x2, y2)
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 渲染按钮
|
||||
renderExpandBtn(node, btn) {
|
||||
let { width, height, expandBtnSize, isRoot } = node
|
||||
if (!isRoot) {
|
||||
let { translateX, translateY } = btn.transform()
|
||||
if (node.dir === CONSTANTS.LAYOUT_GROW_DIR.RIGHT) {
|
||||
btn.translate(width - translateX, height / 2 - translateY)
|
||||
} else {
|
||||
btn.translate(-expandBtnSize - translateX, height / 2 - translateY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建概要节点
|
||||
renderGeneralization(node, gLine, gNode) {
|
||||
let isLeft = node.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
let {
|
||||
top,
|
||||
bottom,
|
||||
left,
|
||||
right,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
} = this.getNodeBoundaries(node, 'h', isLeft)
|
||||
let x = isLeft
|
||||
? left - generalizationLineMargin
|
||||
: right + generalizationLineMargin
|
||||
let x1 = x
|
||||
let y1 = top
|
||||
let x2 = x
|
||||
let y2 = bottom
|
||||
let cx = x1 + (isLeft ? -20 : 20)
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
gLine.plot(path)
|
||||
gNode.left =
|
||||
x +
|
||||
(isLeft ? -generalizationNodeMargin : generalizationNodeMargin) -
|
||||
(isLeft ? gNode.width : 0)
|
||||
gNode.top = top + (bottom - top - gNode.height) / 2
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
renderExpandBtnRect(rect, expandBtnSize, width, height, node) {
|
||||
if (node.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT) {
|
||||
rect.size(expandBtnSize, height).x(-expandBtnSize).y(0)
|
||||
} else {
|
||||
rect.size(expandBtnSize, height).x(width).y(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default VerticalTimeline
|
||||
@@ -1,5 +1,11 @@
|
||||
import JSZip from 'jszip'
|
||||
import xmlConvert from 'xml-js'
|
||||
import {
|
||||
getTextFromHtml,
|
||||
imgToDataUrl,
|
||||
parseDataUrl,
|
||||
getImageSize
|
||||
} from '../utils/index'
|
||||
|
||||
// 解析.xmind文件
|
||||
const parseXmindFile = file => {
|
||||
@@ -10,7 +16,7 @@ const parseXmindFile = file => {
|
||||
let content = ''
|
||||
if (zip.files['content.json']) {
|
||||
let json = await zip.files['content.json'].async('string')
|
||||
content = transformXmind(json)
|
||||
content = await transformXmind(json, zip.files)
|
||||
} else if (zip.files['content.xml']) {
|
||||
let xml = await zip.files['content.xml'].async('string')
|
||||
let json = xmlConvert.xml2json(xml)
|
||||
@@ -33,11 +39,12 @@ const parseXmindFile = file => {
|
||||
}
|
||||
|
||||
// 转换xmind数据
|
||||
const transformXmind = content => {
|
||||
const transformXmind = async (content, files) => {
|
||||
let data = JSON.parse(content)[0]
|
||||
let nodeTree = data.rootTopic
|
||||
let newTree = {}
|
||||
let walk = (node, newNode) => {
|
||||
let waitLoadImageList = []
|
||||
let walk = async (node, newNode) => {
|
||||
newNode.data = {
|
||||
// 节点内容
|
||||
text: node.title
|
||||
@@ -55,6 +62,42 @@ const transformXmind = content => {
|
||||
if (node.labels && node.labels.length > 0) {
|
||||
newNode.data.tag = node.labels
|
||||
}
|
||||
// 图片
|
||||
if (node.image && /\.(jpg|jpeg|png|gif|webp)$/.test(node.image.src)) {
|
||||
try {
|
||||
// 处理异步逻辑
|
||||
let resolve = null
|
||||
let promise = new Promise(_resolve => {
|
||||
resolve = _resolve
|
||||
})
|
||||
waitLoadImageList.push(promise)
|
||||
// 读取图片
|
||||
let imageType = /\.([^.]+)$/.exec(node.image.src)[1]
|
||||
let imageBase64 =
|
||||
`data:image/${imageType};base64,` +
|
||||
(await files['resources/' + node.image.src.split('/')[1]].async(
|
||||
'base64'
|
||||
))
|
||||
newNode.data.image = imageBase64
|
||||
// 如果图片尺寸不存在
|
||||
if (!node.image.width && !node.image.height) {
|
||||
let imageSize = await getImageSize(imageBase64)
|
||||
newNode.data.imageSize = {
|
||||
width: imageSize.width,
|
||||
height: imageSize.height
|
||||
}
|
||||
} else {
|
||||
newNode.data.imageSize = {
|
||||
width: node.image.width,
|
||||
height: node.image.height
|
||||
}
|
||||
}
|
||||
resolve()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
// 子节点
|
||||
newNode.children = []
|
||||
if (
|
||||
@@ -70,6 +113,7 @@ const transformXmind = content => {
|
||||
}
|
||||
}
|
||||
walk(nodeTree, newTree)
|
||||
await Promise.all(waitLoadImageList)
|
||||
return newTree
|
||||
}
|
||||
|
||||
@@ -158,8 +202,127 @@ const transformOldXmind = content => {
|
||||
return newTree
|
||||
}
|
||||
|
||||
// 数据转换为xmind文件
|
||||
const transformToXmind = async (data, name) => {
|
||||
const id = 'simpleMindMap_' + Date.now()
|
||||
const imageList = []
|
||||
// 转换核心数据
|
||||
let newTree = {}
|
||||
let waitLoadImageList = []
|
||||
let walk = async (node, newNode, isRoot) => {
|
||||
let newData = {
|
||||
structureClass: 'org.xmind.ui.logic.right',
|
||||
title: getTextFromHtml(node.data.text), // 节点文本
|
||||
children: {
|
||||
attached: []
|
||||
}
|
||||
}
|
||||
// 备注
|
||||
if (node.data.note !== undefined) {
|
||||
newData.notes = {
|
||||
realHTML: {
|
||||
content: node.data.note
|
||||
},
|
||||
plain: {
|
||||
content: node.data.note
|
||||
}
|
||||
}
|
||||
}
|
||||
// 超链接
|
||||
if (node.data.hyperlink !== undefined) {
|
||||
newData.href = node.data.hyperlink
|
||||
}
|
||||
// 标签
|
||||
if (node.data.tag !== undefined) {
|
||||
newData.labels = node.data.tag || []
|
||||
}
|
||||
// 图片
|
||||
if (node.data.image) {
|
||||
try {
|
||||
// 处理异步逻辑
|
||||
let resolve = null
|
||||
let promise = new Promise(_resolve => {
|
||||
resolve = _resolve
|
||||
})
|
||||
waitLoadImageList.push(promise)
|
||||
let imgName = ''
|
||||
let imgData = node.data.image
|
||||
// 网络图片要先转换成data:url
|
||||
if (/^https?:\/\//.test(node.data.image)) {
|
||||
imgData = await imgToDataUrl(node.data.image)
|
||||
}
|
||||
// 从data:url中解析出图片类型和base64
|
||||
let dataUrlRes = parseDataUrl(imgData)
|
||||
imgName = 'image_' + imageList.length + '.' + dataUrlRes.type
|
||||
imageList.push({
|
||||
name: imgName,
|
||||
data: dataUrlRes.base64
|
||||
})
|
||||
newData.image = {
|
||||
src: 'xap:resources/' + imgName,
|
||||
width: node.data.imageSize.width,
|
||||
height: node.data.imageSize.height
|
||||
}
|
||||
resolve()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
// 样式
|
||||
// 暂时不考虑样式
|
||||
if (isRoot) {
|
||||
newData.class = 'topic'
|
||||
newNode.id = id
|
||||
newNode.class = 'sheet'
|
||||
newNode.title = name
|
||||
newNode.extensions = []
|
||||
newNode.topicPositioning = 'fixed'
|
||||
newNode.topicOverlapping = 'overlap'
|
||||
newNode.coreVersion = '2.100.0'
|
||||
newNode.rootTopic = newData
|
||||
} else {
|
||||
Object.keys(newData).forEach(key => {
|
||||
newNode[key] = newData[key]
|
||||
})
|
||||
}
|
||||
if (node.children && node.children.length > 0) {
|
||||
node.children.forEach(child => {
|
||||
let newChild = {}
|
||||
walk(child, newChild)
|
||||
newData.children.attached.push(newChild)
|
||||
})
|
||||
}
|
||||
}
|
||||
walk(data, newTree, true)
|
||||
await Promise.all(waitLoadImageList)
|
||||
const contentData = [newTree]
|
||||
// 创建压缩包
|
||||
const zip = new JSZip()
|
||||
zip.file('content.json', JSON.stringify(contentData))
|
||||
zip.file(
|
||||
'metadata.json',
|
||||
`{"modifier":"","dataStructureVersion":"1","layoutEngineVersion":"2","activeSheetId":"${id}"}`
|
||||
)
|
||||
const manifestData = {
|
||||
'file-entries': { 'content.json': {}, 'metadata.json': {} }
|
||||
}
|
||||
// 图片
|
||||
if (imageList.length > 0) {
|
||||
imageList.forEach(item => {
|
||||
manifestData['file-entries']['resources/' + item.name] = {}
|
||||
const img = zip.folder('resources')
|
||||
img.file(item.name, item.data, { base64: true })
|
||||
})
|
||||
}
|
||||
zip.file('manifest.json', JSON.stringify(manifestData))
|
||||
const zipData = await zip.generateAsync({ type: 'blob' })
|
||||
return zipData
|
||||
}
|
||||
|
||||
export default {
|
||||
parseXmindFile,
|
||||
transformXmind,
|
||||
transformOldXmind
|
||||
transformOldXmind,
|
||||
transformToXmind
|
||||
}
|
||||
|
||||
@@ -180,6 +180,17 @@ class Export {
|
||||
this.mindMap.doExportPDF.pdf(name, img)
|
||||
}
|
||||
|
||||
// 导出为xmind
|
||||
async xmind(name) {
|
||||
if (!this.mindMap.doExportXMind) {
|
||||
throw new Error('请注册ExportXMind插件')
|
||||
}
|
||||
const data = this.mindMap.getData()
|
||||
const blob = await this.mindMap.doExportXMind.xmind(data, name)
|
||||
const res = await readBlob(blob)
|
||||
return res
|
||||
}
|
||||
|
||||
// 导出为svg
|
||||
// plusCssText:附加的css样式,如果svg中存在dom节点,想要设置一些针对节点的样式可以通过这个参数传入
|
||||
async svg(name, plusCssText) {
|
||||
|
||||
19
simple-mind-map/src/plugins/ExportXMind.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import xmind from '../parse/xmind'
|
||||
|
||||
// 导出XMind类,需要通过Export插件使用
|
||||
class ExportXMind {
|
||||
// 构造函数
|
||||
constructor(opt) {
|
||||
this.mindMap = opt.mindMap
|
||||
}
|
||||
|
||||
// 导出xmind
|
||||
async xmind(data, name) {
|
||||
const zipData = await xmind.transformToXmind(data, name)
|
||||
return zipData
|
||||
}
|
||||
}
|
||||
|
||||
ExportXMind.instanceName = 'doExportXMind'
|
||||
|
||||
export default ExportXMind
|
||||
@@ -28,8 +28,7 @@ class KeyboardNavigation {
|
||||
this.focus(dir)
|
||||
} else {
|
||||
let root = this.mindMap.renderer.root
|
||||
this.mindMap.renderer.moveNodeToCenter(root)
|
||||
root.active()
|
||||
this.mindMap.execCommand('GO_TARGET_NODE', root)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,8 +80,7 @@ class KeyboardNavigation {
|
||||
|
||||
// 找到了则让目标节点聚焦
|
||||
if (targetNode) {
|
||||
this.mindMap.renderer.moveNodeToCenter(targetNode)
|
||||
targetNode.active()
|
||||
this.mindMap.execCommand('GO_TARGET_NODE', targetNode)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class NodeImgAdjust {
|
||||
// 节点图片鼠标移动事件
|
||||
onNodeImgMousemove(node, img) {
|
||||
// 如果当前正在拖动调整中那么直接返回
|
||||
if (this.isMousedown || this.isAdjusted) return
|
||||
if (this.isMousedown || this.isAdjusted || this.mindMap.opt.readonly) return
|
||||
// 如果在当前节点内移动,以及自定义元素已经是显示状态,那么直接返回
|
||||
if (this.node === node && this.isShowHandleEl) return
|
||||
// 更新当前节点信息
|
||||
@@ -194,11 +194,12 @@ class NodeImgAdjust {
|
||||
this.hideHandleEl()
|
||||
// 更新节点图片为新的大小
|
||||
let { image, imageTitle } = this.node.nodeData.data
|
||||
let { scaleX, scaleY } = this.mindMap.draw.transform()
|
||||
this.mindMap.execCommand('SET_NODE_IMAGE', this.node, {
|
||||
url: image,
|
||||
title: imageTitle,
|
||||
width: this.currentImgWidth,
|
||||
height: this.currentImgHeight,
|
||||
width: this.currentImgWidth / scaleX,
|
||||
height: this.currentImgHeight / scaleY,
|
||||
custom: true // 代表自定义了图片大小
|
||||
})
|
||||
this.isAdjusted = true
|
||||
|
||||
@@ -39,6 +39,7 @@ class RichText {
|
||||
this.range = null
|
||||
this.lastRange = null
|
||||
this.node = null
|
||||
this.isInserting = false
|
||||
this.styleEl = null
|
||||
this.cacheEditingText = ''
|
||||
this.lostStyle = false
|
||||
@@ -145,11 +146,12 @@ class RichText {
|
||||
}
|
||||
|
||||
// 显示文本编辑控件
|
||||
showEditText(node, rect) {
|
||||
showEditText(node, rect, isInserting) {
|
||||
if (this.showTextEdit) {
|
||||
return
|
||||
}
|
||||
this.node = node
|
||||
this.isInserting = isInserting
|
||||
if (!rect) rect = node._textData.node.node.getBoundingClientRect()
|
||||
this.mindMap.emit('before_show_text_edit')
|
||||
this.mindMap.renderer.textEdit.registerTmpShortcut()
|
||||
@@ -200,7 +202,8 @@ class RichText {
|
||||
this.initQuillEditor()
|
||||
document.querySelector('.ql-editor').style.minHeight = originHeight + 'px'
|
||||
this.showTextEdit = true
|
||||
this.focus()
|
||||
// 如果是刚创建的节点,那么默认全选,否则普通激活不全选
|
||||
this.focus(isInserting ? 0 : null)
|
||||
if (!node.nodeData.data.richText) {
|
||||
// 如果是非富文本的情况,需要手动应用文本样式
|
||||
this.setTextStyleIfNotRichText(node)
|
||||
@@ -250,6 +253,7 @@ class RichText {
|
||||
this.showTextEdit = false
|
||||
this.mindMap.emit('rich_text_selection_change', false)
|
||||
this.node = null
|
||||
this.isInserting = false
|
||||
}
|
||||
|
||||
// 初始化Quill富文本编辑器
|
||||
@@ -271,6 +275,8 @@ class RichText {
|
||||
theme: 'snow'
|
||||
})
|
||||
this.quill.on('selection-change', range => {
|
||||
// 刚创建的节点全选不需要显示操作条
|
||||
if (this.isInserting) return
|
||||
this.lastRange = this.range
|
||||
this.range = null
|
||||
if (range) {
|
||||
@@ -338,9 +344,9 @@ class RichText {
|
||||
}
|
||||
|
||||
// 聚焦
|
||||
focus() {
|
||||
focus(start) {
|
||||
let len = this.quill.getLength()
|
||||
this.quill.setSelection(len, len)
|
||||
this.quill.setSelection(typeof start === 'number' ? start : len, len)
|
||||
}
|
||||
|
||||
// 格式化当前选中的文本
|
||||
|
||||
@@ -86,7 +86,8 @@ class TouchEvent {
|
||||
this.clickNum = 0
|
||||
this.dispatchMouseEvent('dblclick', ev.target, ev)
|
||||
} else {
|
||||
this.dispatchMouseEvent('click', ev.target, ev)
|
||||
// 点击事件应该不用模拟
|
||||
// this.dispatchMouseEvent('click', ev.target, ev)
|
||||
}
|
||||
}
|
||||
this.touchesNum = 0
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
// 深度优先遍历树
|
||||
export const walk = (
|
||||
root,
|
||||
@@ -31,9 +33,11 @@ export const walk = (
|
||||
|
||||
// 广度优先遍历树
|
||||
export const bfsWalk = (root, callback) => {
|
||||
callback(root)
|
||||
let stack = [root]
|
||||
let isStop = false
|
||||
if (callback(root, null) === 'stop') {
|
||||
isStop = true
|
||||
}
|
||||
while (stack.length) {
|
||||
if (isStop) {
|
||||
break
|
||||
@@ -41,8 +45,9 @@ export const bfsWalk = (root, callback) => {
|
||||
let cur = stack.shift()
|
||||
if (cur.children && cur.children.length) {
|
||||
cur.children.forEach(item => {
|
||||
if (isStop) return
|
||||
stack.push(item)
|
||||
if (callback(item) === 'stop') {
|
||||
if (callback(item, cur) === 'stop') {
|
||||
isStop = true
|
||||
}
|
||||
})
|
||||
@@ -213,6 +218,18 @@ export const imgToDataUrl = src => {
|
||||
})
|
||||
}
|
||||
|
||||
// 解析dataUrl
|
||||
export const parseDataUrl = data => {
|
||||
if (!/^data:/.test(data)) return data
|
||||
let [typeStr, base64] = data.split(',')
|
||||
let res = /^data:[^/]+\/([^;]+);/.exec(typeStr)
|
||||
let type = res[1]
|
||||
return {
|
||||
type,
|
||||
base64
|
||||
}
|
||||
}
|
||||
|
||||
// 下载文件
|
||||
export const downloadFile = (file, fileName) => {
|
||||
let a = document.createElement('a')
|
||||
@@ -392,3 +409,28 @@ export const nodeToHTML = node => {
|
||||
nodeToHTMLWrapEl.appendChild(node)
|
||||
return nodeToHTMLWrapEl.innerHTML
|
||||
}
|
||||
|
||||
// 获取图片大小
|
||||
export const getImageSize = src => {
|
||||
return new Promise(resolve => {
|
||||
let img = new Image()
|
||||
img.src = src
|
||||
img.onload = () => {
|
||||
resolve({
|
||||
width: img.width,
|
||||
height: img.height
|
||||
})
|
||||
}
|
||||
img.onerror = () => {
|
||||
resolve({
|
||||
width: 0,
|
||||
height: 0
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 创建节点唯一的id
|
||||
export const createUid = () => {
|
||||
return uuidv4()
|
||||
}
|
||||
@@ -4,8 +4,8 @@
|
||||
<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.png">
|
||||
<title>一个简单的web思维导图实现</title>
|
||||
<link rel="icon" href="./dist/logo.ico">
|
||||
<title>思绪思维导图</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
|
||||
BIN
web/public/logo.ico
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
web/src/assets/avatar/ZXR.jpg
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
web/src/assets/avatar/default.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
web/src/assets/avatar/qp.jpg
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
web/src/assets/avatar/suka.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
web/src/assets/avatar/花儿朵朵.jpg
Normal file
|
After Width: | Height: | Size: 54 KiB |
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 2479351 */
|
||||
src: url('iconfont.woff2?t=1686298427624') format('woff2'),
|
||||
url('iconfont.woff?t=1686298427624') format('woff'),
|
||||
url('iconfont.ttf?t=1686298427624') format('truetype');
|
||||
src: url('iconfont.woff2?t=1689407546912') format('woff2'),
|
||||
url('iconfont.woff?t=1689407546912') format('woff'),
|
||||
url('iconfont.ttf?t=1689407546912') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,6 +13,58 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.iconjiantouyou:before {
|
||||
content: "\e62d";
|
||||
}
|
||||
|
||||
.iconbianji1:before {
|
||||
content: "\e60a";
|
||||
}
|
||||
|
||||
.icondaohang1:before {
|
||||
content: "\e632";
|
||||
}
|
||||
|
||||
.iconyanjing:before {
|
||||
content: "\e8bf";
|
||||
}
|
||||
|
||||
.iconwangzhan:before {
|
||||
content: "\e628";
|
||||
}
|
||||
|
||||
.iconcsdn:before {
|
||||
content: "\e608";
|
||||
}
|
||||
|
||||
.iconshejiaotubiao-10:before {
|
||||
content: "\e644";
|
||||
}
|
||||
|
||||
.iconstar:before {
|
||||
content: "\e7df";
|
||||
}
|
||||
|
||||
.iconfork:before {
|
||||
content: "\e641";
|
||||
}
|
||||
|
||||
.iconxiazai:before {
|
||||
content: "\e613";
|
||||
}
|
||||
|
||||
.iconteamwork:before {
|
||||
content: "\e870";
|
||||
}
|
||||
|
||||
.iconshuiyin:before {
|
||||
content: "\e67a";
|
||||
}
|
||||
|
||||
.iconxmind:before {
|
||||
content: "\ea57";
|
||||
}
|
||||
|
||||
.iconmouseR:before {
|
||||
content: "\e6bd";
|
||||
}
|
||||
|
||||
BIN
web/src/assets/img/block1.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
web/src/assets/img/block3.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
web/src/assets/img/block4.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
web/src/assets/img/catalogOrganization.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
web/src/assets/img/fishbone.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
web/src/assets/img/logicalStructure.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
web/src/assets/img/logo2.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
web/src/assets/img/mindMap.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
web/src/assets/img/organizationStructure.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
web/src/assets/img/split.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
web/src/assets/img/timeline.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
web/src/assets/img/timeline2.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
web/src/assets/img/verticalTimeline.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
web/src/assets/img/verticalTimeline.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
@@ -1,12 +1,13 @@
|
||||
// 布局结构图片映射
|
||||
export const layoutImgMap = {
|
||||
logicalStructure: require('../assets/img/logicalStructure.jpg'),
|
||||
mindMap: require('../assets/img/mindMap.jpg'),
|
||||
organizationStructure: require('../assets/img/organizationStructure.jpg'),
|
||||
catalogOrganization: require('../assets/img/catalogOrganization.jpg'),
|
||||
timeline: require('../assets/img/timeline.jpg'),
|
||||
timeline2: require('../assets/img/timeline2.jpg'),
|
||||
fishbone: require('../assets/img/fishbone.jpg'),
|
||||
logicalStructure: require('../assets/img/logicalStructure.png'),
|
||||
mindMap: require('../assets/img/mindMap.png'),
|
||||
organizationStructure: require('../assets/img/organizationStructure.png'),
|
||||
catalogOrganization: require('../assets/img/catalogOrganization.png'),
|
||||
timeline: require('../assets/img/timeline.png'),
|
||||
timeline2: require('../assets/img/timeline2.png'),
|
||||
fishbone: require('../assets/img/fishbone.png'),
|
||||
verticalTimeline: require('../assets/img/verticalTimeline.png'),
|
||||
}
|
||||
|
||||
// 主题图片映射
|
||||
|
||||
@@ -412,5 +412,11 @@ export const downTypeList = [
|
||||
type: 'md',
|
||||
icon: 'iconmarkdown',
|
||||
desc: 'Easy for other software to open'
|
||||
},
|
||||
{
|
||||
name: 'XMind',
|
||||
type: 'xmind',
|
||||
icon: 'iconxmind',
|
||||
desc: 'XMind file'
|
||||
}
|
||||
]
|
||||
@@ -484,5 +484,11 @@ export const downTypeList = [
|
||||
type: 'md',
|
||||
icon: 'iconmarkdown',
|
||||
desc: '便于其他软件打开'
|
||||
},
|
||||
{
|
||||
name: 'XMind',
|
||||
type: 'xmind',
|
||||
icon: 'iconxmind',
|
||||
desc: 'XMind格式'
|
||||
}
|
||||
]
|
||||
@@ -114,8 +114,9 @@ export default {
|
||||
},
|
||||
navigatorToolbar: {
|
||||
openMiniMap: 'Open mini map',
|
||||
readonly: 'Readonly',
|
||||
edit: 'Edit'
|
||||
closeMiniMap: 'Close mini map',
|
||||
readonly: 'Change to eadonly',
|
||||
edit: 'Change to edit'
|
||||
},
|
||||
nodeHyperlink: {
|
||||
title: 'Link',
|
||||
|
||||
@@ -114,8 +114,9 @@ export default {
|
||||
},
|
||||
navigatorToolbar: {
|
||||
openMiniMap: '开启小地图',
|
||||
readonly: '只读模式',
|
||||
edit: '编辑模式'
|
||||
closeMiniMap: '关闭小地图',
|
||||
readonly: '切换为只读模式',
|
||||
edit: '切换为编辑模式'
|
||||
},
|
||||
nodeHyperlink: {
|
||||
title: '超链接',
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
import Header from './components/Header.vue'
|
||||
import Sidebar from './components/Sidebar.vue'
|
||||
import CatalogBar from './components/CatalogBar.vue'
|
||||
import 'highlight.js/styles/atom-one-dark.css'
|
||||
// import 'highlight.js/styles/atom-one-dark.css'
|
||||
import 'highlight.js/styles/github.css'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -103,7 +104,7 @@ export default {
|
||||
a {
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
color: #42b883;
|
||||
color: #1ea59a;
|
||||
transition: color 0.25s;
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -225,7 +225,7 @@ export default {
|
||||
left: -10px;
|
||||
width: 4px;
|
||||
height: 20px;
|
||||
background-color: #42b883;
|
||||
background-color: #1ea59a;
|
||||
border-radius: 4px;
|
||||
transition: top 0.25s cubic-bezier(0, 1, 0.5, 1), opacity 0.25s,
|
||||
background-color 0.5s;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="headerContainer">
|
||||
<div class="left">
|
||||
<div class="title">
|
||||
<img src="../../../assets/img/logo.png" alt="">
|
||||
<div class="title" @click="toIndex">
|
||||
<img src="../../../assets/img/logo2.png" alt="">
|
||||
SimpleMindMap
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,6 +74,10 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
toIndex() {
|
||||
this.$router.push('/index')
|
||||
},
|
||||
|
||||
toDemo() {
|
||||
this.$router.push('/')
|
||||
},
|
||||
@@ -108,6 +112,7 @@ export default {
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
width: 30px;
|
||||
@@ -130,7 +135,7 @@ export default {
|
||||
font-size: 14px;
|
||||
|
||||
&:hover {
|
||||
color: #42b883;
|
||||
color: #1ea59a;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ export default {
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: #42b883;
|
||||
color: #1ea59a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
# Changelog
|
||||
|
||||
## 0.6.7
|
||||
|
||||
Fix: 1.Fixed the issue of missing placeholder elements for the expand and collapse button after node collapse and expansion. 2.Fixed the issue of being able to scale images in read-only mode.
|
||||
|
||||
New: 1.Support locating to a node based on node instance or node uid. 2.Modify the creation method of node uids and export data to add node uids.
|
||||
|
||||
Remove: 1.Remove the node transition effect.
|
||||
|
||||
Demo: 1.Add website homepage. 2.Fixed the issue of missing node styles when creating new nodes in the outline. 3.Fixed the issue of missing edited text after pressing Enter or Tab after editing nodes in the outline. 4.Optimize the node positioning of the outline, and the collapsed nodes will automatically expand. 5.The sidebar button supports folding. 6.Optimize small screen adaptation.
|
||||
|
||||
## 0.6.6
|
||||
|
||||
New: 1.Support exporting to Xmind new version files. 2.Importing the new version of Xmind file supports importing images from nodes. 3.Add a vertical timeline structure.
|
||||
|
||||
Fix: 1.The TouchEvent plugin no longer sends click events, solving the problem of two windows opening when clicking on a hyperlink on the mobile end. 2.Fix the issue of dragging and moving a node to become a child node of another node, where the parent node of that node points to not being updated. 3.Fixed an issue where the node border style was not updated when dragging a second level node into a third level node. 4.Fix the issue where the mouse will not trigger the button display when moving into the unfolded or retracted button position, except for the structure growing to the right.
|
||||
|
||||
optimization: 1.The issue of excessive amplitude when optimizing the touchpad to scale the canvas. 2.The newly created node defaults to selecting all for easy deletion of default text.
|
||||
|
||||
## 0.6.5-fix.1
|
||||
|
||||
Fix: 1.Fix the issue of adjusting the image size incorrectly while zooming.
|
||||
|
||||
## 0.6.5
|
||||
|
||||
Fix: 1.Fix the issue of xmind file import errors. 2.Fixed a rare issue where line breaks occur when the width of the node text is decimal.
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Changelog</h1>
|
||||
<h2>0.6.7</h2>
|
||||
<p>Fix: 1.Fixed the issue of missing placeholder elements for the expand and collapse button after node collapse and expansion. 2.Fixed the issue of being able to scale images in read-only mode.</p>
|
||||
<p>New: 1.Support locating to a node based on node instance or node uid. 2.Modify the creation method of node uids and export data to add node uids.</p>
|
||||
<p>Remove: 1.Remove the node transition effect.</p>
|
||||
<p>Demo: 1.Add website homepage. 2.Fixed the issue of missing node styles when creating new nodes in the outline. 3.Fixed the issue of missing edited text after pressing Enter or Tab after editing nodes in the outline. 4.Optimize the node positioning of the outline, and the collapsed nodes will automatically expand. 5.The sidebar button supports folding. 6.Optimize small screen adaptation.</p>
|
||||
<h2>0.6.6</h2>
|
||||
<p>New: 1.Support exporting to Xmind new version files. 2.Importing the new version of Xmind file supports importing images from nodes. 3.Add a vertical timeline structure.</p>
|
||||
<p>Fix: 1.The TouchEvent plugin no longer sends click events, solving the problem of two windows opening when clicking on a hyperlink on the mobile end. 2.Fix the issue of dragging and moving a node to become a child node of another node, where the parent node of that node points to not being updated. 3.Fixed an issue where the node border style was not updated when dragging a second level node into a third level node. 4.Fix the issue where the mouse will not trigger the button display when moving into the unfolded or retracted button position, except for the structure growing to the right.</p>
|
||||
<p>optimization: 1.The issue of excessive amplitude when optimizing the touchpad to scale the canvas. 2.The newly created node defaults to selecting all for easy deletion of default text.</p>
|
||||
<h2>0.6.5-fix.1</h2>
|
||||
<p>Fix: 1.Fix the issue of adjusting the image size incorrectly while zooming.</p>
|
||||
<h2>0.6.5</h2>
|
||||
<p>Fix: 1.Fix the issue of xmind file import errors.</p>
|
||||
<p>Fix: 1.Fix the issue of xmind file import errors. 2.Fixed a rare issue where line breaks occur when the width of the node text is decimal.</p>
|
||||
<p>New: 1.The packaged library supports obtaining built-in constants, themes, and other data. 2.Supports configuring the zoom behavior corresponding to the direction of the mouse wheel. 3.Node images support dragging and resizing.</p>
|
||||
<h2>0.6.4-fix.1</h2>
|
||||
<p>New: 1.When zooming with the mouse wheel, the default zoom is centered around the current position of the mouse, which can be turned off by configuring.</p>
|
||||
|
||||
@@ -51,8 +51,8 @@ const mindMap = new MindMap({
|
||||
| expandBtnStyle(v0.5.0+) | Object | { color: '#808080', fill: '#fff' } | Expand the color of the stow button | |
|
||||
| expandBtnIcon(v0.5.0+) | Object | { open: '', close: '' } | Customize the icon of the expand/collapse button, and you can transfer the svg string of the icon | |
|
||||
| enableShortcutOnlyWhenMouseInSvg(v0.5.1+) | Boolean | true | Only respond to shortcut key events when the mouse is inside the canvas | |
|
||||
| enableNodeTransitionMove(v0.5.1+) | Boolean | true | Whether to enable node animation transition | |
|
||||
| nodeTransitionMoveDuration(v0.5.1+) | Number | 300 | If node animation transition is enabled, the transition time can be set using this attribute, in milliseconds | |
|
||||
| enableNodeTransitionMove(v0.5.1+)(v0.6.7+ is remove this feature) | Boolean | true | Whether to enable node animation transition | |
|
||||
| nodeTransitionMoveDuration(v0.5.1+)(v0.6.7+ is remove this feature) | Number | 300 | If node animation transition is enabled, the transition time can be set using this attribute, in milliseconds | |
|
||||
| initRootNodePosition(v0.5.3+) | Array | null | The position of the initial root node can be passed as an array, default is `['center', 'center']`, Represents the root node at the center of the canvas, In addition to `center`, keywords can also be set to `left`, `top`, `right`, and `bottom`, In addition to passing keywords, each item in the array can also pass a number representing a specific pixel, Can pass a percentage string, such as `['40%', '60%']`, Represents a horizontal position at `40%` of the canvas width, and a vertical position at `60%` of the canvas height | |
|
||||
| exportPaddingX(v0.5.5+) | Number | 10 | Horizontal padding of graphics when exporting PNG, SVG, and PDF | |
|
||||
| exportPaddingY(v0.5.5+) | Number | 10 | Vertical padding of graphics when exporting PNG, SVG, and PDF | |
|
||||
@@ -347,6 +347,7 @@ redo. All commands are as follows:
|
||||
| SET_NODE_CUSTOM_POSITION (v0.2.0+) | Set a custom position for a node | node (the node to set), left (custom x coordinate, default is undefined), top (custom y coordinate, default is undefined) |
|
||||
| RESET_LAYOUT (v0.2.0+) | Arrange layout with one click | |
|
||||
| SET_NODE_SHAPE (v0.2.4+) | Set the shape of a node | node (the node to set), shape (the shape, all shapes: [Shape.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/core/render/node/Shape.js)) |
|
||||
| GO_TARGET_NODE(v0.6.7+) | Navigate to a node, and if the node is collapsed, it will automatically expand to that node | node(Node instance or node uid to locate) |
|
||||
|
||||
### setData(data)
|
||||
|
||||
|
||||
@@ -218,14 +218,14 @@
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>enableNodeTransitionMove(v0.5.1+)</td>
|
||||
<td>enableNodeTransitionMove(v0.5.1+)(v0.6.7+ is remove this feature)</td>
|
||||
<td>Boolean</td>
|
||||
<td>true</td>
|
||||
<td>Whether to enable node animation transition</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>nodeTransitionMoveDuration(v0.5.1+)</td>
|
||||
<td>nodeTransitionMoveDuration(v0.5.1+)(v0.6.7+ is remove this feature)</td>
|
||||
<td>Number</td>
|
||||
<td>300</td>
|
||||
<td>If node animation transition is enabled, the transition time can be set using this attribute, in milliseconds</td>
|
||||
@@ -920,6 +920,11 @@ redo. All commands are as follows:</p>
|
||||
<td>Set the shape of a node</td>
|
||||
<td>node (the node to set), shape (the shape, all shapes: <a href="https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/core/render/node/Shape.js">Shape.js</a>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>GO_TARGET_NODE(v0.6.7+)</td>
|
||||
<td>Navigate to a node, and if the node is collapsed, it will automatically expand to that node</td>
|
||||
<td>node(Node instance or node uid to locate)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>setData(data)</h3>
|
||||
|
||||
@@ -104,4 +104,15 @@ Gets `svg` data, an async method that returns an object:
|
||||
node // svg node
|
||||
str // svg string
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
### xmind(name)
|
||||
|
||||
> v0.6.6+, an additional ExportXMind plugin needs to be registered
|
||||
|
||||
```js
|
||||
import ExportXMind from 'simple-mind-map/src/plugins/ExportXMind.js'
|
||||
MindMap.usePlugin(ExportXMind)
|
||||
```
|
||||
|
||||
Export as an `xmind` file type, asynchronous method, returns a `Promise` instance, and the returned data is the `data:url` data of a `zip` compressed package, which can be directly downloaded.
|
||||
@@ -85,6 +85,14 @@ MindMap.usePlugin(ExportPDF)
|
||||
str <span class="hljs-comment">// svg string</span>
|
||||
}
|
||||
</code></pre>
|
||||
<h3>xmind(name)</h3>
|
||||
<blockquote>
|
||||
<p>v0.6.6+, an additional ExportXMind plugin needs to be registered</p>
|
||||
</blockquote>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> ExportXMind <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/plugins/ExportXMind.js'</span>
|
||||
MindMap.usePlugin(ExportXMind)
|
||||
</code></pre>
|
||||
<p>Export as an <code>xmind</code> file type, asynchronous method, returns a <code>Promise</code> instance, and the returned data is the <code>data:url</code> data of a <code>zip</code> compressed package, which can be directly downloaded.</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -136,4 +136,24 @@ 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/小土渣的宇宙.jpeg" 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/qp.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>qp</p>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/ZXR.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>ZXR</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 style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/suka.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>suka</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -95,6 +95,26 @@ 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/小土渣的宇宙.jpeg" 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/qp.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>qp</p>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/ZXR.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>ZXR</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 style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/suka.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>suka</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -91,4 +91,22 @@ Move a node behind another node
|
||||
|
||||
Move a node to the center of the canvas.
|
||||
|
||||
Currently, if there is zoom, returning to the center will reset the zoom.
|
||||
Currently, if there is zoom, returning to the center will reset the zoom.
|
||||
|
||||
### expandToNodeUid(uid, callback)
|
||||
|
||||
> v0.6.7+
|
||||
|
||||
- `uid`: uid of node
|
||||
|
||||
- `callback`: Expand completed callback function
|
||||
|
||||
Expand to the node of the specified uid.
|
||||
|
||||
### findNodeByUid(uid)
|
||||
|
||||
> v0.6.7+
|
||||
|
||||
- `uid`: uid of node
|
||||
|
||||
Find the corresponding node instance based on the uid.
|
||||
@@ -62,6 +62,27 @@ is an object, e.g. <code>{text: 'I am new text'}</code></p>
|
||||
</blockquote>
|
||||
<p>Move a node to the center of the canvas.</p>
|
||||
<p>Currently, if there is zoom, returning to the center will reset the zoom.</p>
|
||||
<h3>expandToNodeUid(uid, callback)</h3>
|
||||
<blockquote>
|
||||
<p>v0.6.7+</p>
|
||||
</blockquote>
|
||||
<ul>
|
||||
<li>
|
||||
<p><code>uid</code>: uid of node</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>callback</code>: Expand completed callback function</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>Expand to the node of the specified uid.</p>
|
||||
<h3>findNodeByUid(uid)</h3>
|
||||
<blockquote>
|
||||
<p>v0.6.7+</p>
|
||||
</blockquote>
|
||||
<ul>
|
||||
<li><code>uid</code>: uid of node</li>
|
||||
</ul>
|
||||
<p>Find the corresponding node instance based on the uid.</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -153,6 +153,34 @@ Extract plain text content from an HTML string.
|
||||
|
||||
Convert `blob` data to `data:url` data.
|
||||
|
||||
#### parseDataUrl(data)
|
||||
|
||||
> v0.6.6+
|
||||
|
||||
Parse `data:url` data, return:
|
||||
|
||||
```js
|
||||
{
|
||||
type,// file type of data
|
||||
base64// base64 data
|
||||
}
|
||||
```
|
||||
|
||||
#### getImageSize(src)
|
||||
|
||||
> v0.6.6+
|
||||
|
||||
- `src`: The url of img
|
||||
|
||||
Get the size of image, return:
|
||||
|
||||
```js
|
||||
{
|
||||
width,
|
||||
height
|
||||
}
|
||||
```
|
||||
|
||||
## Simulate CSS background in Canvas
|
||||
|
||||
Import:
|
||||
|
||||
@@ -98,6 +98,29 @@ and copying the <code>data</code> of the data object, example:</p>
|
||||
<p>v0.5.9+</p>
|
||||
</blockquote>
|
||||
<p>Convert <code>blob</code> data to <code>data:url</code> data.</p>
|
||||
<h4>parseDataUrl(data)</h4>
|
||||
<blockquote>
|
||||
<p>v0.6.6+</p>
|
||||
</blockquote>
|
||||
<p>Parse <code>data:url</code> data, return:</p>
|
||||
<pre class="hljs"><code>{
|
||||
type,<span class="hljs-comment">// file type of data</span>
|
||||
base64<span class="hljs-comment">// base64 data</span>
|
||||
}
|
||||
</code></pre>
|
||||
<h4>getImageSize(src)</h4>
|
||||
<blockquote>
|
||||
<p>v0.6.6+</p>
|
||||
</blockquote>
|
||||
<ul>
|
||||
<li><code>src</code>: The url of img</li>
|
||||
</ul>
|
||||
<p>Get the size of image, return:</p>
|
||||
<pre class="hljs"><code>{
|
||||
width,
|
||||
height
|
||||
}
|
||||
</code></pre>
|
||||
<h2>Simulate CSS background in Canvas</h2>
|
||||
<p>Import:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> drawBackgroundImageToCanvas <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/utils/simulateCSSBackgroundInCanvas'</span>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> v0.2.7+
|
||||
|
||||
Provides methods for importing `XMind` files.
|
||||
Provides methods for importing and export `XMind` files.
|
||||
|
||||
## Import
|
||||
|
||||
@@ -31,6 +31,8 @@ Parsing the `.xmind` file and returning the parsed data. You can use
|
||||
|
||||
### xmind.transformXmind(content)
|
||||
|
||||
> V0.6.6+version changes the method to asynchronous and returns a Promise instance
|
||||
|
||||
Convert `xmind` data. The `.xmind` file is essentially a `zip` file that can be
|
||||
decompressed by changing the suffix to zip. Inside, there is a `content.json`
|
||||
file. If you have parsed this file yourself, you can pass the contents of this
|
||||
@@ -48,4 +50,14 @@ For data parsing of the `xmind8` version, because the `.xmind` file in this
|
||||
version does not have a `content.json`, it corresponds to `content.xml`.
|
||||
|
||||
`content`: the contents of the `content.xml` file within the `.xmind` zip
|
||||
package
|
||||
package
|
||||
|
||||
### transformToXmind(data, name)
|
||||
|
||||
> v0.6.6+
|
||||
|
||||
- `data`: `simple-mind-map` data, you can get it by `mindMap.getData()` method.
|
||||
|
||||
- `name`: The file name to export.
|
||||
|
||||
Convert the `simple mind map` data to an `xmind` file. This method is asynchronous and returns an instance of `Promise`. The returned data is a `blob` type `zip` compressed package data, which you can download as a file yourself.
|
||||
@@ -4,7 +4,7 @@
|
||||
<blockquote>
|
||||
<p>v0.2.7+</p>
|
||||
</blockquote>
|
||||
<p>Provides methods for importing <code>XMind</code> files.</p>
|
||||
<p>Provides methods for importing and export <code>XMind</code> files.</p>
|
||||
<h2>Import</h2>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> xmind <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/parse/xmind.js'</span>
|
||||
</code></pre>
|
||||
@@ -19,6 +19,9 @@
|
||||
<code>mindMap.setData(data)</code> to render the returned data to the canvas.</p>
|
||||
<p><code>file</code>: <code>File</code> object</p>
|
||||
<h3>xmind.transformXmind(content)</h3>
|
||||
<blockquote>
|
||||
<p>V0.6.6+version changes the method to asynchronous and returns a Promise instance</p>
|
||||
</blockquote>
|
||||
<p>Convert <code>xmind</code> data. The <code>.xmind</code> file is essentially a <code>zip</code> file that can be
|
||||
decompressed by changing the suffix to zip. Inside, there is a <code>content.json</code>
|
||||
file. If you have parsed this file yourself, you can pass the contents of this
|
||||
@@ -34,6 +37,19 @@ package</p>
|
||||
version does not have a <code>content.json</code>, it corresponds to <code>content.xml</code>.</p>
|
||||
<p><code>content</code>: the contents of the <code>content.xml</code> file within the <code>.xmind</code> zip
|
||||
package</p>
|
||||
<h3>transformToXmind(data, name)</h3>
|
||||
<blockquote>
|
||||
<p>v0.6.6+</p>
|
||||
</blockquote>
|
||||
<ul>
|
||||
<li>
|
||||
<p><code>data</code>: <code>simple-mind-map</code> data, you can get it by <code>mindMap.getData()</code> method.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>name</code>: The file name to export.</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>Convert the <code>simple mind map</code> data to an <code>xmind</code> file. This method is asynchronous and returns an instance of <code>Promise</code>. The returned data is a <code>blob</code> type <code>zip</code> compressed package data, which you can download as a file yourself.</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
# Changelog
|
||||
|
||||
## 0.6.7
|
||||
|
||||
修复:1.修复节点收起再展开后展开收起按钮占位元素丢失的问题。 2.修复只读模式下可以缩放图片的问题。
|
||||
|
||||
新增:1.支持根据节点实例或节点uid定位到某个节点。 2.修改节点uid的创建方式,导出数据添加节点的uid。
|
||||
|
||||
移除:1.移除节点过渡效果。
|
||||
|
||||
Demo:1.添加网站首页。 2.修复大纲里创建新节点时节点样式丢失的问题。 3.修复大纲里编辑节点后按回车或Tab键后编辑文本丢失的问题。 4.优化大纲的节点定位,被收起的节点会自动展开。 5.侧边栏按钮支持收起。 6.优化小屏适配。
|
||||
|
||||
## 0.6.6
|
||||
|
||||
新增:1.支持导出为Xmind新版文件。2.导入Xmind新版文件支持导入节点中的图片。 3.新增竖向时间轴结构。
|
||||
|
||||
修复:1.TouchEvent插件不再派发click事件,解决移动端点击超链接会打开两个窗口的问题。 2.修复拖拽移动一个节点成为另一个节点的子节点时该节点的父节点指向未更新的问题。 3.修复二级节点拖拽成三级节点时节点边框样式未更新的问题。 4.修复向右生长的结构外其他结构鼠标移入展开收起按钮位置时不会触发按钮显示的问题。
|
||||
|
||||
优化:1.优化触控板缩放画布时幅度过大的问题。2.刚创建的节点默认全选方便删除默认文本。
|
||||
|
||||
## 0.6.5-fix.1
|
||||
|
||||
修复:1.修复在缩放情况下调整图片大小不正确的问题。
|
||||
|
||||
## 0.6.5
|
||||
|
||||
修复:1.修复xmind文件导入报错的问题。 2.修复极少数情况下当节点文本的宽度为小数时显示发生换行的问题。
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Changelog</h1>
|
||||
<h2>0.6.7</h2>
|
||||
<p>修复:1.修复节点收起再展开后展开收起按钮占位元素丢失的问题。 2.修复只读模式下可以缩放图片的问题。</p>
|
||||
<p>新增:1.支持根据节点实例或节点uid定位到某个节点。 2.修改节点uid的创建方式,导出数据添加节点的uid。</p>
|
||||
<p>移除:1.移除节点过渡效果。</p>
|
||||
<p>Demo:1.添加网站首页。 2.修复大纲里创建新节点时节点样式丢失的问题。 3.修复大纲里编辑节点后按回车或Tab键后编辑文本丢失的问题。 4.优化大纲的节点定位,被收起的节点会自动展开。 5.侧边栏按钮支持收起。 6.优化小屏适配。</p>
|
||||
<h2>0.6.6</h2>
|
||||
<p>新增:1.支持导出为Xmind新版文件。2.导入Xmind新版文件支持导入节点中的图片。 3.新增竖向时间轴结构。</p>
|
||||
<p>修复:1.TouchEvent插件不再派发click事件,解决移动端点击超链接会打开两个窗口的问题。 2.修复拖拽移动一个节点成为另一个节点的子节点时该节点的父节点指向未更新的问题。 3.修复二级节点拖拽成三级节点时节点边框样式未更新的问题。 4.修复向右生长的结构外其他结构鼠标移入展开收起按钮位置时不会触发按钮显示的问题。</p>
|
||||
<p>优化:1.优化触控板缩放画布时幅度过大的问题。2.刚创建的节点默认全选方便删除默认文本。</p>
|
||||
<h2>0.6.5-fix.1</h2>
|
||||
<p>修复:1.修复在缩放情况下调整图片大小不正确的问题。</p>
|
||||
<h2>0.6.5</h2>
|
||||
<p>修复:1.修复xmind文件导入报错的问题。</p>
|
||||
<p>修复:1.修复xmind文件导入报错的问题。 2.修复极少数情况下当节点文本的宽度为小数时显示发生换行的问题。</p>
|
||||
<p>新增:1.打包后的库支持获取内置常量、主题等数据。 2.支持配置鼠标滚轮方向对应的缩放行为。 3.节点图片支持拖拽调整大小。</p>
|
||||
<h2>0.6.4-fix.1</h2>
|
||||
<p>新增:1.鼠标滚轮缩放时默认以鼠标当前位置为中心进行缩放,可以通过配置关闭该特性。</p>
|
||||
|
||||
@@ -51,8 +51,8 @@ const mindMap = new MindMap({
|
||||
| expandBtnStyle(v0.5.0+) | Object | { color: '#808080', fill: '#fff' } | 展开收起按钮的颜色 | |
|
||||
| expandBtnIcon(v0.5.0+) | Object | { open: '', close: '' } | 自定义展开收起按钮的图标,可以传图标的svg字符串 | |
|
||||
| enableShortcutOnlyWhenMouseInSvg(v0.5.1+) | Boolean | true | 是否只有当鼠标在画布内才响应快捷键事件 | |
|
||||
| enableNodeTransitionMove(v0.5.1+) | Boolean | true | 是否开启节点动画过渡 | |
|
||||
| nodeTransitionMoveDuration(v0.5.1+) | Number | 300 | 如果开启节点动画过渡,可以通过该属性设置过渡的时间,单位ms | |
|
||||
| enableNodeTransitionMove(v0.5.1+)(v0.6.7+已去除该特性) | Boolean | true | 是否开启节点动画过渡 | |
|
||||
| nodeTransitionMoveDuration(v0.5.1+)(v0.6.7+已去除该特性) | Number | 300 | 如果开启节点动画过渡,可以通过该属性设置过渡的时间,单位ms | |
|
||||
| initRootNodePosition(v0.5.3+) | Array | null | 初始根节点的位置,可传一个数组,默认为`['center', 'center']`,代表根节点处于画布中心位置,除了`center`,关键词还可以设置`left`、`top`、`right`、`bottom`,除了可以传关键词,数组的每项还可以传递一个数字,代表具体的像素,可以传递一个百分比字符串,比如`['40%', '60%']`,代表水平位置在画布宽度的`40%`的位置,垂直位置在画布高度的`60%`的位置 | |
|
||||
| exportPaddingX(v0.5.5+) | Number | 10 | 导出png、svg、pdf时的图形水平内边距 | |
|
||||
| exportPaddingY(v0.5.5+) | Number | 10 | 导出png、svg、pdf时的图形垂直内边距 | |
|
||||
@@ -340,6 +340,7 @@ mindMap.updateConfig({
|
||||
| SET_NODE_CUSTOM_POSITION(v0.2.0+) | 设置节点自定义位置 | node(要设置的节点)、 left(自定义的x坐标,默认为undefined)、 top(自定义的y坐标,默认为undefined) |
|
||||
| RESET_LAYOUT(v0.2.0+) | 一键整理布局 | |
|
||||
| SET_NODE_SHAPE(v0.2.4+) | 设置节点形状 | node(要设置的节点)、shape(形状,全部形状:[Shape.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/core/render/node/Shape.js)) |
|
||||
| GO_TARGET_NODE(v0.6.7+) | 定位到某个节点,如果该节点被收起,那么会自动展开到该节点 | node(要定位到的节点实例或节点uid) |
|
||||
|
||||
### setData(data)
|
||||
|
||||
|
||||
@@ -218,14 +218,14 @@
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>enableNodeTransitionMove(v0.5.1+)</td>
|
||||
<td>enableNodeTransitionMove(v0.5.1+)(v0.6.7+已去除该特性)</td>
|
||||
<td>Boolean</td>
|
||||
<td>true</td>
|
||||
<td>是否开启节点动画过渡</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>nodeTransitionMoveDuration(v0.5.1+)</td>
|
||||
<td>nodeTransitionMoveDuration(v0.5.1+)(v0.6.7+已去除该特性)</td>
|
||||
<td>Number</td>
|
||||
<td>300</td>
|
||||
<td>如果开启节点动画过渡,可以通过该属性设置过渡的时间,单位ms</td>
|
||||
@@ -915,6 +915,11 @@ mindMap.setTheme(<span class="hljs-string">'主题名称'</span>)
|
||||
<td>设置节点形状</td>
|
||||
<td>node(要设置的节点)、shape(形状,全部形状:<a href="https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/core/render/node/Shape.js">Shape.js</a>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>GO_TARGET_NODE(v0.6.7+)</td>
|
||||
<td>定位到某个节点,如果该节点被收起,那么会自动展开到该节点</td>
|
||||
<td>node(要定位到的节点实例或节点uid)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>setData(data)</h3>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 结构
|
||||
|
||||
`simple-mind-map`目前支持四种结构:logicalStructure(逻辑结构图)、mindMap(思维导图)、organizationStructure(组织结构图)、catalogOrganization(目录组织图)、timeline(时间轴)、timeline2(时间轴2)、fishbone(鱼骨图)。
|
||||
`simple-mind-map`目前支持四种结构:logicalStructure(逻辑结构图)、mindMap(思维导图)、organizationStructure(组织结构图)、catalogOrganization(目录组织图)、timeline(时间轴)、timeline2(时间轴2)、fishbone(鱼骨图)、verticalTimeline(v0.6.6+竖向时间轴)。
|
||||
|
||||
可以在实例化`simple-mind-map`时通过选项指定使用的结构:
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>结构</h1>
|
||||
<p><code>simple-mind-map</code>目前支持四种结构:logicalStructure(逻辑结构图)、mindMap(思维导图)、organizationStructure(组织结构图)、catalogOrganization(目录组织图)、timeline(时间轴)、timeline2(时间轴2)、fishbone(鱼骨图)。</p>
|
||||
<p><code>simple-mind-map</code>目前支持四种结构:logicalStructure(逻辑结构图)、mindMap(思维导图)、organizationStructure(组织结构图)、catalogOrganization(目录组织图)、timeline(时间轴)、timeline2(时间轴2)、fishbone(鱼骨图)、verticalTimeline(v0.6.6+竖向时间轴)。</p>
|
||||
<p>可以在实例化<code>simple-mind-map</code>时通过选项指定使用的结构:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">new</span> MindMap({
|
||||
<span class="hljs-comment">// ...</span>
|
||||
|
||||
@@ -18,6 +18,9 @@ mindMap.on('data_change', (data) => {
|
||||
const node = data._node
|
||||
mindMap.renderer.moveNodeToCenter(node)
|
||||
node.active()
|
||||
|
||||
// 在v0.6.7+版本可以这么做:
|
||||
mindMap.execCommand('GO_TARGET_NODE', node)// 或者传节点的uid
|
||||
```
|
||||
|
||||
当在大纲树上编辑了某个节点的内容,需要同步到思维导图树上:
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
<pre class="hljs"><code><span class="hljs-keyword">const</span> node = data._node
|
||||
mindMap.renderer.moveNodeToCenter(node)
|
||||
node.active()
|
||||
|
||||
<span class="hljs-comment">// 在v0.6.7+版本可以这么做:</span>
|
||||
mindMap.execCommand(<span class="hljs-string">'GO_TARGET_NODE'</span>, node)<span class="hljs-comment">// 或者传节点的uid</span>
|
||||
</code></pre>
|
||||
<p>当在大纲树上编辑了某个节点的内容,需要同步到思维导图树上:</p>
|
||||
<pre class="hljs"><code>data._node.setText(<span class="hljs-string">'xxx'</span>)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
> 要使用导出功能需要使用导出插件。
|
||||
|
||||
目前支持导出为`.smm`、`.json`、`.svg`、`.png`、`.pdf`、`.md`文件。
|
||||
目前支持导出为`.smm`、`.json`、`.svg`、`.png`、`.pdf`、`.md`、`.xmind`文件。
|
||||
|
||||
`.smm`是`simple-mind-map`自己定义的一种文件,其实就是`json`文件,换了一个扩展名而已。
|
||||
|
||||
@@ -94,6 +94,18 @@ mindMap.export(
|
||||
mindMap.export('md', true, '文件名')
|
||||
```
|
||||
|
||||
### 导出为xmind
|
||||
|
||||
> v0.6.6+
|
||||
|
||||
> 需要注册`ExportXMind`插件
|
||||
|
||||
导出为`Xmind`新版文件。
|
||||
|
||||
```js
|
||||
mindMap.export('xmind', '文件名')
|
||||
```
|
||||
|
||||
## 导入
|
||||
|
||||
目前支持从`.smm`、`.json`、`.xmind`、`.xlsx`、`.md`格式的文件导入。
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<blockquote>
|
||||
<p>要使用导出功能需要使用导出插件。</p>
|
||||
</blockquote>
|
||||
<p>目前支持导出为<code>.smm</code>、<code>.json</code>、<code>.svg</code>、<code>.png</code>、<code>.pdf</code>、<code>.md</code>文件。</p>
|
||||
<p>目前支持导出为<code>.smm</code>、<code>.json</code>、<code>.svg</code>、<code>.png</code>、<code>.pdf</code>、<code>.md</code>、<code>.xmind</code>文件。</p>
|
||||
<p><code>.smm</code>是<code>simple-mind-map</code>自己定义的一种文件,其实就是<code>json</code>文件,换了一个扩展名而已。</p>
|
||||
<p>导出直接调用<code>export</code>方法即可:</p>
|
||||
<pre class="hljs"><code>mindMap.export(type, isDownload, fileName, ...)
|
||||
@@ -62,6 +62,16 @@ mindMap.export(<span class="hljs-string">'pdf'</span>, <span class="hl
|
||||
<p>导出为<code>markdown</code>文件只要传递默认的三个参数即可:</p>
|
||||
<pre class="hljs"><code>mindMap.export(<span class="hljs-string">'md'</span>, <span class="hljs-literal">true</span>, <span class="hljs-string">'文件名'</span>)
|
||||
</code></pre>
|
||||
<h3>导出为xmind</h3>
|
||||
<blockquote>
|
||||
<p>v0.6.6+</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>需要注册<code>ExportXMind</code>插件</p>
|
||||
</blockquote>
|
||||
<p>导出为<code>Xmind</code>新版文件。</p>
|
||||
<pre class="hljs"><code>mindMap.export(<span class="hljs-string">'xmind'</span>, <span class="hljs-string">'文件名'</span>)
|
||||
</code></pre>
|
||||
<h2>导入</h2>
|
||||
<p>目前支持从<code>.smm</code>、<code>.json</code>、<code>.xmind</code>、<code>.xlsx</code>、<code>.md</code>格式的文件导入。</p>
|
||||
<h3>导入smm、json</h3>
|
||||
|
||||
@@ -108,4 +108,15 @@ MindMap.usePlugin(ExportPDF)
|
||||
node// svg节点
|
||||
str// svg字符串
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
### xmind(name)
|
||||
|
||||
> v0.6.6+,需要额外注册一个ExportXMind插件
|
||||
|
||||
```js
|
||||
import ExportXMind from 'simple-mind-map/src/plugins/ExportXMind.js'
|
||||
MindMap.usePlugin(ExportXMind)
|
||||
```
|
||||
|
||||
导出为`xmind`文件类型,异步方法,返回一个`Promise`实例,返回的数据为一个`zip`压缩包的`data:url`数据,可以直接下载。
|
||||
@@ -87,6 +87,14 @@ MindMap.usePlugin(ExportPDF)
|
||||
str<span class="hljs-comment">// svg字符串</span>
|
||||
}
|
||||
</code></pre>
|
||||
<h3>xmind(name)</h3>
|
||||
<blockquote>
|
||||
<p>v0.6.6+,需要额外注册一个ExportXMind插件</p>
|
||||
</blockquote>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> ExportXMind <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/plugins/ExportXMind.js'</span>
|
||||
MindMap.usePlugin(ExportXMind)
|
||||
</code></pre>
|
||||
<p>导出为<code>xmind</code>文件类型,异步方法,返回一个<code>Promise</code>实例,返回的数据为一个<code>zip</code>压缩包的<code>data:url</code>数据,可以直接下载。</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -108,7 +108,9 @@
|
||||
|
||||
## 请作者喝杯咖啡
|
||||
|
||||
开源不易,如果本项目有帮助到你的话,可以请作者喝杯咖啡哟~
|
||||
开源不易,如果本项目有帮助到你的话,可以考虑请作者喝杯咖啡哟~
|
||||
|
||||
> 厚椰乳一盒 + 纯牛奶半盒 + 冰块 + 咖啡液 = 生椰拿铁 yyds
|
||||
|
||||
> 转账请备注【思维导图】。你的头像和名字将会出现在下面。
|
||||
|
||||
@@ -129,4 +131,20 @@
|
||||
<img src="../../../../assets/avatar/小土渣的宇宙.jpeg" 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/qp.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>qp</p>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/ZXR.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>ZXR</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 style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/suka.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>suka</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -8,19 +8,19 @@
|
||||
</blockquote>
|
||||
<h2>特性</h2>
|
||||
<ul>
|
||||
<li><input type="checkbox" id="checkbox18" checked="true" /><label for="checkbox18">插件化架构,除核心功能外,其他功能作为插件提供,按需使用,减小整体体积</label></li>
|
||||
<li><input type="checkbox" id="checkbox19" checked="true" /><label for="checkbox19">支持逻辑结构图、思维导图、组织结构图、目录组织图、时间轴、鱼骨图六种结构</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">支持快捷键</label></li>
|
||||
<li><input type="checkbox" id="checkbox22" checked="true" /><label for="checkbox22">节点内容支持图片、图标、超链接、备注、标签、概要</label></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>
|
||||
<li><input type="checkbox" id="checkbox25" checked="true" /><label for="checkbox25">支持右键和Ctrl+左键两种多选方式</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><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="checkbox29" checked="true" /><label for="checkbox29">支持小地图、支持水印</label></li>
|
||||
<li><input type="checkbox" id="checkbox30" checked="true" /><label for="checkbox30">支持关联线</label></li>
|
||||
<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>
|
||||
</ul>
|
||||
<h2>仓库目录介绍</h2>
|
||||
<p>1.<code>simple-mind-map</code></p>
|
||||
@@ -28,11 +28,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="checkbox31" checked="true" /><label for="checkbox31">工具栏,支持插入节点、删除节点;编辑节点图片、图标、超链接、备注、标签、概要</label></li>
|
||||
<li><input type="checkbox" id="checkbox32" checked="true" /><label for="checkbox32">侧边栏,基础样式设置面板、节点样式设置面板、大纲面板、主题选择面板、结构选择面板</label></li>
|
||||
<li><input type="checkbox" id="checkbox33" checked="true" /><label for="checkbox33">导入导出功能;数据默认保存在浏览器本地存储,也支持直接创建、打开、编辑电脑本地文件</label></li>
|
||||
<li><input type="checkbox" id="checkbox34" checked="true" /><label for="checkbox34">右键菜单,支持展开、收起、整理布局等操作</label></li>
|
||||
<li><input type="checkbox" id="checkbox35" checked="true" /><label for="checkbox35">底部栏,支持节点数量、字数统计;支持切换编辑和只读模式;支持放大缩小;支持全屏切换;支持小地图</label></li>
|
||||
<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>
|
||||
</ul>
|
||||
<p>提供文档页面服务。</p>
|
||||
<p>3.<code>dist</code></p>
|
||||
@@ -69,7 +69,10 @@
|
||||
<h2>License</h2>
|
||||
<p><a href="https://opensource.org/licenses/MIT">MIT</a></p>
|
||||
<h2>请作者喝杯咖啡</h2>
|
||||
<p>开源不易,如果本项目有帮助到你的话,可以请作者喝杯咖啡哟~</p>
|
||||
<p>开源不易,如果本项目有帮助到你的话,可以考虑请作者喝杯咖啡哟~</p>
|
||||
<blockquote>
|
||||
<p>厚椰乳一盒 + 纯牛奶半盒 + 冰块 + 咖啡液 = 生椰拿铁 yyds</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>转账请备注【思维导图】。你的头像和名字将会出现在下面。</p>
|
||||
</blockquote>
|
||||
@@ -88,6 +91,22 @@
|
||||
<img src="../../../../assets/avatar/小土渣的宇宙.jpeg" 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/qp.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>qp</p>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/ZXR.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>ZXR</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 style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/suka.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>suka</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -82,4 +82,22 @@
|
||||
|
||||
移动节点到画布中心。
|
||||
|
||||
目前如果是存在缩放的情况下回到中心会重置缩放。
|
||||
目前如果是存在缩放的情况下回到中心会重置缩放。
|
||||
|
||||
### expandToNodeUid(uid, callback)
|
||||
|
||||
> v0.6.7+
|
||||
|
||||
- `uid`:节点uid
|
||||
|
||||
- `callback`:展开完成的回调函数
|
||||
|
||||
展开到指定uid的节点。
|
||||
|
||||
### findNodeByUid(uid)
|
||||
|
||||
> v0.6.7+
|
||||
|
||||
- `uid`:节点uid
|
||||
|
||||
根据uid找到对应的节点实例。
|
||||
@@ -51,6 +51,27 @@
|
||||
</blockquote>
|
||||
<p>移动节点到画布中心。</p>
|
||||
<p>目前如果是存在缩放的情况下回到中心会重置缩放。</p>
|
||||
<h3>expandToNodeUid(uid, callback)</h3>
|
||||
<blockquote>
|
||||
<p>v0.6.7+</p>
|
||||
</blockquote>
|
||||
<ul>
|
||||
<li>
|
||||
<p><code>uid</code>:节点uid</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>callback</code>:展开完成的回调函数</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>展开到指定uid的节点。</p>
|
||||
<h3>findNodeByUid(uid)</h3>
|
||||
<blockquote>
|
||||
<p>v0.6.7+</p>
|
||||
</blockquote>
|
||||
<ul>
|
||||
<li><code>uid</code>:节点uid</li>
|
||||
</ul>
|
||||
<p>根据uid找到对应的节点实例。</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -148,6 +148,34 @@ copyNodeTree({}, node)
|
||||
|
||||
将`blob`数据转成`data:url`数据。
|
||||
|
||||
#### parseDataUrl(data)
|
||||
|
||||
> v0.6.6+
|
||||
|
||||
解析`data:url`数据,返回:
|
||||
|
||||
```js
|
||||
{
|
||||
type,// 数据的文件类型
|
||||
base64// base64数据
|
||||
}
|
||||
```
|
||||
|
||||
#### getImageSize(src)
|
||||
|
||||
> v0.6.6+
|
||||
|
||||
- `src`:图片的url
|
||||
|
||||
获取图片的大小。返回:
|
||||
|
||||
```js
|
||||
{
|
||||
width,
|
||||
height
|
||||
}
|
||||
```
|
||||
|
||||
## 在canvas中模拟css的背景属性
|
||||
|
||||
引入:
|
||||
|
||||
@@ -93,6 +93,29 @@
|
||||
<p>v0.5.9+</p>
|
||||
</blockquote>
|
||||
<p>将<code>blob</code>数据转成<code>data:url</code>数据。</p>
|
||||
<h4>parseDataUrl(data)</h4>
|
||||
<blockquote>
|
||||
<p>v0.6.6+</p>
|
||||
</blockquote>
|
||||
<p>解析<code>data:url</code>数据,返回:</p>
|
||||
<pre class="hljs"><code>{
|
||||
type,<span class="hljs-comment">// 数据的文件类型</span>
|
||||
base64<span class="hljs-comment">// base64数据</span>
|
||||
}
|
||||
</code></pre>
|
||||
<h4>getImageSize(src)</h4>
|
||||
<blockquote>
|
||||
<p>v0.6.6+</p>
|
||||
</blockquote>
|
||||
<ul>
|
||||
<li><code>src</code>:图片的url</li>
|
||||
</ul>
|
||||
<p>获取图片的大小。返回:</p>
|
||||
<pre class="hljs"><code>{
|
||||
width,
|
||||
height
|
||||
}
|
||||
</code></pre>
|
||||
<h2>在canvas中模拟css的背景属性</h2>
|
||||
<p>引入:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> drawBackgroundImageToCanvas <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/utils/simulateCSSBackgroundInCanvas'</span>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> v0.2.7+
|
||||
|
||||
提供导入`XMind`文件的方法。
|
||||
提供导入和导出`XMind`文件的方法。
|
||||
|
||||
## 引入
|
||||
|
||||
@@ -30,6 +30,8 @@ simpleMindMap.xmind
|
||||
|
||||
### xmind.transformXmind(content)
|
||||
|
||||
> v0.6.6+版本该方法改为异步方法,返回一个Promise实例
|
||||
|
||||
转换`xmind`数据,`.xmind`文件本质上是一个压缩包,改成`zip`后缀可以解压缩,里面存在一个`content.json`文件,如果你自己解析出了这个文件,那么可以把这个文件内容传递给这个方法进行转换,转换后的数据,可以使用`mindMap.setData(data)`来将返回的数据渲染到画布上
|
||||
|
||||
`content`:`.xmind`压缩包内的`content.json`文件内容
|
||||
@@ -40,4 +42,14 @@ simpleMindMap.xmind
|
||||
|
||||
针对`xmind8`版本的数据解析,因为该版本的`.xmind`文件内没有`content.json`,对应的是`content.xml`。
|
||||
|
||||
`content`:`.xmind`压缩包内的`content.xml`文件内容
|
||||
`content`:`.xmind`压缩包内的`content.xml`文件内容
|
||||
|
||||
### transformToXmind(data, name)
|
||||
|
||||
> v0.6.6+
|
||||
|
||||
- `data`:`simple-mind-map`思维导图数据,可以通过`mindMap.getData()`方法获取。
|
||||
|
||||
- `name`:要导出的文件名。
|
||||
|
||||
将`simple-mind-map`数据转为`xmind`文件。该方法为异步方法,返回一个`Promise`实例,返回的数据是一个`blob`类型的`zip`压缩包数据,你可以自行下载为文件。
|
||||
@@ -4,7 +4,7 @@
|
||||
<blockquote>
|
||||
<p>v0.2.7+</p>
|
||||
</blockquote>
|
||||
<p>提供导入<code>XMind</code>文件的方法。</p>
|
||||
<p>提供导入和导出<code>XMind</code>文件的方法。</p>
|
||||
<h2>引入</h2>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> xmind <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/parse/xmind.js'</span>
|
||||
</code></pre>
|
||||
@@ -18,6 +18,9 @@
|
||||
<p>解析<code>.xmind</code>文件,返回解析后的数据,可以使用<code>mindMap.setData(data)</code>来将返回的数据渲染到画布上</p>
|
||||
<p><code>file</code>:<code>File</code>对象</p>
|
||||
<h3>xmind.transformXmind(content)</h3>
|
||||
<blockquote>
|
||||
<p>v0.6.6+版本该方法改为异步方法,返回一个Promise实例</p>
|
||||
</blockquote>
|
||||
<p>转换<code>xmind</code>数据,<code>.xmind</code>文件本质上是一个压缩包,改成<code>zip</code>后缀可以解压缩,里面存在一个<code>content.json</code>文件,如果你自己解析出了这个文件,那么可以把这个文件内容传递给这个方法进行转换,转换后的数据,可以使用<code>mindMap.setData(data)</code>来将返回的数据渲染到画布上</p>
|
||||
<p><code>content</code>:<code>.xmind</code>压缩包内的<code>content.json</code>文件内容</p>
|
||||
<h3>xmind.transformOldXmind(content)</h3>
|
||||
@@ -26,6 +29,19 @@
|
||||
</blockquote>
|
||||
<p>针对<code>xmind8</code>版本的数据解析,因为该版本的<code>.xmind</code>文件内没有<code>content.json</code>,对应的是<code>content.xml</code>。</p>
|
||||
<p><code>content</code>:<code>.xmind</code>压缩包内的<code>content.xml</code>文件内容</p>
|
||||
<h3>transformToXmind(data, name)</h3>
|
||||
<blockquote>
|
||||
<p>v0.6.6+</p>
|
||||
</blockquote>
|
||||
<ul>
|
||||
<li>
|
||||
<p><code>data</code>:<code>simple-mind-map</code>思维导图数据,可以通过<code>mindMap.getData()</code>方法获取。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>name</code>:要导出的文件名。</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>将<code>simple-mind-map</code>数据转为<code>xmind</code>文件。该方法为异步方法,返回一个<code>Promise</code>实例,返回的数据是一个<code>blob</code>类型的<code>zip</code>压缩包数据,你可以自行下载为文件。</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||