Compare commits

...

33 Commits
0.6.5 ... 0.6.6

Author SHA1 Message Date
wanglin2
9ecb199608 更新群二维码 2023-07-17 22:16:08 +08:00
wanglin2
e093cb1741 Merge branch 'feature' into main 2023-07-13 09:50:01 +08:00
wanglin2
a63a92c423 打包 2023-07-13 09:48:30 +08:00
wanglin2
2c4f065626 新增文档首页 2023-07-13 09:44:55 +08:00
wanglin2
ae5d4dd2a6 Merge branch 'feature' into main 2023-07-11 16:36:51 +08:00
wanglin2
e574883e5f Doc: update 2023-07-11 16:31:48 +08:00
wanglin2
1bf60c49c7 Merge branch 'feature' into main 2023-07-11 14:52:35 +08:00
wanglin2
ddc173cf84 update README 2023-07-11 14:52:15 +08:00
wanglin2
2c71c7d102 Merge branch 'feature' into main 2023-07-11 14:44:01 +08:00
wanglin2
f6cb08bdaa update Doc and README 2023-07-11 14:08:00 +08:00
wanglin2
05728de21b Merge branch 'feature' into main 2023-07-10 16:08:26 +08:00
wanglin2
b51027f641 Doc: update 2023-07-10 16:07:48 +08:00
wanglin2
ea4fdf8290 更新群二维码 2023-07-10 09:16:20 +08:00
wanglin2
55796b4e39 Merge branch 'feature' into main 2023-07-06 17:13:07 +08:00
wanglin2
e534c3138d Doc: update 2023-07-06 17:12:42 +08:00
wanglin2
e185abd223 Merge branch 'feature' into main 2023-07-06 10:49:19 +08:00
wanglin2
3b73f72866 打包0.6.6 2023-07-06 10:45:44 +08:00
wanglin2
0db2f47133 Doc: update 2023-07-06 10:37:02 +08:00
wanglin2
80c7ec0fac Demo:更新结构示意图 2023-07-06 10:05:00 +08:00
wanglin2
780ce363de Fix:修复切换结构后展开收起按钮的隐藏占位元素不更新的问题 2023-07-06 09:31:58 +08:00
wanglin2
eaa8929457 Fix:修复除了向右生长的结构,其他结构鼠标移入展开收起按钮位置时不会触发按钮显示的问题 2023-07-06 09:12:18 +08:00
wanglin2
ad0f62a5ac Demo:增加竖向时间轴图片 2023-07-05 18:11:02 +08:00
wanglin2
31f21ce013 Feat:新增竖向时间轴 2023-07-05 16:49:32 +08:00
wanglin2
7a20ce2f79 Fix:修复二级节点拖拽成3级节点时节点边框样式未更新的问题 2023-07-05 14:21:15 +08:00
wanglin2
b98e7a97ec Demo:支持导出xmind文件 2023-07-05 14:00:57 +08:00
wanglin2
0bb50b3371 Feat:1.支持导出为xmind新版文件;2.导入xmind新版文件支持处理图片 2023-07-05 13:58:55 +08:00
wanglin2
a03091b28c Fix:修复拖拽移动一个节点为另一个节点的子节点时该节点的父节点指向未更新的问题 2023-07-04 09:10:32 +08:00
wanglin2
6c33984b8c 优化:刚创建的节点默认全选方便删除默认文本 2023-07-04 08:42:31 +08:00
wanglin2
28a4be0631 Fix:TouchEvent插件去除派发click事件,解决移动端点击超链接会打开两个窗口的问题 2023-07-03 22:39:47 +08:00
wanglin2
b8a3be7a62 优化触控板缩放画布时幅度过大的问题 2023-07-03 22:26:11 +08:00
wanglin2
259d4028f3 更新群二维码 2023-07-03 08:42:37 +08:00
wanglin2
fb1251afc1 Doc: update 2023-06-29 13:51:57 +08:00
wanglin2
06e3fd428a Fix:修复缩放情况下调整图片大小不正确的问题 2023-06-28 18:22:43 +08:00
94 changed files with 2518 additions and 144 deletions

View File

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

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 184 KiB

View File

@@ -194,7 +194,7 @@ class MindMap {
this.opt.layout = layout
this.view.reset()
this.renderer.setLayout()
this.render()
this.render(null, CONSTANTS.CHANGE_LAYOUT)
}
// 执行命令

View File

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

View File

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

View File

@@ -4,6 +4,7 @@ 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'
@@ -25,6 +26,8 @@ const layouts = {
[CONSTANTS.LAYOUT.TIMELINE]: Timeline,
// 时间轴2
[CONSTANTS.LAYOUT.TIMELINE2]: Timeline,
// 竖向时间轴
[CONSTANTS.LAYOUT.VERTICAL_TIMELINE]: VerticalTimeline,
// 鱼骨图
[CONSTANTS.LAYOUT.FISHBONE]: Fishbone,
}
@@ -693,11 +696,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()

View File

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

View File

@@ -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,11 @@ class Node {
if (!this.group) {
return
}
let { enableNodeTransitionMove, nodeTransitionMoveDuration, alwaysShowExpandBtn } =
this.mindMap.opt
let {
enableNodeTransitionMove,
nodeTransitionMoveDuration,
alwaysShowExpandBtn
} = this.mindMap.opt
if (alwaysShowExpandBtn) {
// 需要移除展开收缩按钮
if (this._expandBtn && this.nodeData.children.length <= 0) {
@@ -543,6 +554,10 @@ class Node {
this.needLayout = false
this.layout()
}
if (this.needRerenderExpandBtnPlaceholderRect) {
this.needRerenderExpandBtnPlaceholderRect = false
this.renderExpandBtnPlaceholderRect()
}
this.update()
}
// 子节点
@@ -578,7 +593,7 @@ class Node {
delete this.nodeData.inserting
this.active()
setTimeout(() => {
this.mindMap.emit('node_dblclick', this)
this.mindMap.emit('node_dblclick', this, null, true)
}, 0)
}
}
@@ -783,7 +798,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)

View File

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

View File

@@ -48,6 +48,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 +69,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,16 +84,18 @@ 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
}

View File

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

View File

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

View File

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

View File

@@ -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},${

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View 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

View File

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

View File

@@ -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)
}
// 格式化当前选中的文本

View File

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

View File

@@ -213,6 +213,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 +404,23 @@ 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
})
}
})
}

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -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=1689210173189') format('woff2'),
url('iconfont.woff?t=1689210173189') format('woff'),
url('iconfont.ttf?t=1689210173189') format('truetype');
}
.iconfont {
@@ -13,6 +13,42 @@
-moz-osx-font-smoothing: grayscale;
}
.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";
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -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'),
}
// 主题图片映射

View File

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

View File

@@ -484,5 +484,11 @@ export const downTypeList = [
type: 'md',
icon: 'iconmarkdown',
desc: '便于其他软件打开'
},
{
name: 'XMind',
type: 'xmind',
icon: 'iconxmind',
desc: 'XMind格式'
}
]

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
<div class="headerContainer">
<div class="left">
<div class="title">
<img src="../../../assets/img/logo.png" alt="">
<img src="../../../assets/img/logo2.png" alt="">
SimpleMindMap
</div>
</div>
@@ -130,7 +130,7 @@ export default {
font-size: 14px;
&:hover {
color: #42b883;
color: #1ea59a;
}
}

View File

@@ -108,7 +108,7 @@ export default {
}
&.active {
color: #42b883;
color: #1ea59a;
}
}
}

View File

@@ -1,5 +1,17 @@
# Changelog
## 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.

View File

@@ -1,8 +1,14 @@
<template>
<div>
<h1>Changelog</h1>
<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>

View File

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

View File

@@ -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">&#x27;simple-mind-map/src/plugins/ExportXMind.js&#x27;</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>

View File

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

View File

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

View File

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

View File

@@ -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">&#x27;simple-mind-map/src/utils/simulateCSSBackgroundInCanvas&#x27;</span>

View File

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

View File

@@ -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">&#x27;simple-mind-map/src/parse/xmind.js&#x27;</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>

View File

@@ -1,5 +1,17 @@
# Changelog
## 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.修复极少数情况下当节点文本的宽度为小数时显示发生换行的问题。

View File

@@ -1,8 +1,14 @@
<template>
<div>
<h1>Changelog</h1>
<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>

View File

@@ -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鱼骨图、verticalTimelinev0.6.6+竖向时间轴)
可以在实例化`simple-mind-map`时通过选项指定使用的结构:

View File

@@ -1,7 +1,7 @@
<template>
<div>
<h1>结构</h1>
<p><code>simple-mind-map</code>目前支持四种结构logicalStructure逻辑结构图mindMap思维导图organizationStructure组织结构图catalogOrganization目录组织图timeline时间轴timeline2时间轴2fishbone鱼骨图</p>
<p><code>simple-mind-map</code>目前支持四种结构logicalStructure逻辑结构图mindMap思维导图organizationStructure组织结构图catalogOrganization目录组织图timeline时间轴timeline2时间轴2fishbone鱼骨图verticalTimelinev0.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>

View File

@@ -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`格式的文件导入。

View File

@@ -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">&#x27;pdf&#x27;</span>, <span class="hl
<p>导出为<code>markdown</code>文件只要传递默认的三个参数即可</p>
<pre class="hljs"><code>mindMap.export(<span class="hljs-string">&#x27;md&#x27;</span>, <span class="hljs-literal">true</span>, <span class="hljs-string">&#x27;文件名&#x27;</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">&#x27;xmind&#x27;</span>, <span class="hljs-string">&#x27;文件名&#x27;</span>)
</code></pre>
<h2>导入</h2>
<p>目前支持从<code>.smm</code><code>.json</code><code>.xmind</code><code>.xlsx</code><code>.md</code>格式的文件导入</p>
<h3>导入smmjson</h3>

View File

@@ -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`数据,可以直接下载。

View File

@@ -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">&#x27;simple-mind-map/src/plugins/ExportXMind.js&#x27;</span>
MindMap.usePlugin(ExportXMind)
</code></pre>
<p>导出为<code>xmind</code>文件类型异步方法返回一个<code>Promise</code>实例返回的数据为一个<code>zip</code>压缩包的<code>data:url</code>数据可以直接下载</p>
</div>
</template>

View File

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

View File

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

View File

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

View File

@@ -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">&#x27;simple-mind-map/src/utils/simulateCSSBackgroundInCanvas&#x27;</span>

View File

@@ -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`压缩包数据,你可以自行下载为文件。

View File

@@ -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">&#x27;simple-mind-map/src/parse/xmind.js&#x27;</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>

View File

@@ -27,6 +27,7 @@ import MiniMap from 'simple-mind-map/src/plugins/MiniMap.js'
import Watermark from 'simple-mind-map/src/plugins/Watermark.js'
import KeyboardNavigation from 'simple-mind-map/src/plugins/KeyboardNavigation.js'
import ExportPDF from 'simple-mind-map/src/plugins/ExportPDF.js'
import ExportXMind from 'simple-mind-map/src/plugins/ExportXMind.js'
import Export from 'simple-mind-map/src/plugins/Export.js'
import Drag from 'simple-mind-map/src/plugins/Drag.js'
import Select from 'simple-mind-map/src/plugins/Select.js'
@@ -66,6 +67,7 @@ MindMap
.usePlugin(Drag)
.usePlugin(KeyboardNavigation)
.usePlugin(ExportPDF)
.usePlugin(ExportXMind)
.usePlugin(Export)
.usePlugin(Select)
.usePlugin(AssociativeLine)

View File

@@ -150,7 +150,6 @@ export default {
async handleXmind(file) {
try {
let data = await xmind.parseXmindFile(file.raw)
console.log(data);
this.$bus.$emit('setData', data)
this.$message.success('导入成功')
} catch (error) {

View File

@@ -0,0 +1,41 @@
<template>
<div class="indexContainer">
<Header></Header>
<Block1></Block1>
<Block2></Block2>
<Block3></Block3>
<Block4></Block4>
<Block5></Block5>
</div>
</template>
<script>
import Header from './components/Header.vue'
import Block1 from './components/Block1.vue'
import Block2 from './components/Block2.vue'
import Block3 from './components/Block3.vue'
import Block4 from './components/Block4.vue'
import Block5 from './components/Block5.vue'
export default {
components: {
Header,
Block1,
Block2,
Block3,
Block4,
Block5
},
data() {
return {}
},
created() {},
methods: {}
}
</script>
<style lang="less" scoped>
.indexContainer {
}
</style>

View File

@@ -0,0 +1,206 @@
<template>
<div class="block1Container" :style="{ height: height + 'px' }">
<div class="blockContent">
<div class="infoBox">
<div class="blockTitle">Simple mind map</div>
<Split></Split>
<div class="infoList">
<p class="infoRow">是一个思维导图库</p>
<p class="infoRow">同时也是一个思维导图软件</p>
<p class="infoRow">开源免费强大...</p>
</div>
<div class="desc">
无论你是开发者还是使用者只要喜欢思维导图都能在这个项目里找到你需要的
</div>
<div class="btnBox">
<div class="btn" @click="useOnline">在线使用</div>
<div class="btn btn2" @click="jumpDoc">开发文档</div>
</div>
</div>
<div class="picBox">
<div class="animation1"></div>
<div class="animation2"></div>
<div class="animation3"></div>
<div class="pic"></div>
</div>
</div>
</div>
</template>
<script>
import Split from './Split.vue'
export default {
components: {
Split
},
data() {
return {
height: 0
}
},
created() {
window.addEventListener('resize', this.onResize)
this.onResize()
},
mounted() {},
beforeDestroy() {
window.removeEventListener('resize', this.onResize)
},
methods: {
onResize() {
this.height = window.innerHeight
},
useOnline() {
this.$router.push('/')
},
jumpDoc() {
this.$router.push('/doc/zh/')
}
}
}
</script>
<style lang="less" scoped>
.block1Container {
background-color: #f0f9fa;
border-radius: 0 0 0 450px;
display: flex;
justify-content: center;
align-items: center;
.blockContent {
width: 100%;
max-width: 1140px;
display: flex;
align-items: center;
justify-content: space-between;
.infoBox {
.blockTitle {
font-size: 16px;
color: #1e3547;
margin-bottom: 10px;
}
.infoList {
margin-top: 20px;
.infoRow {
font-size: 45px;
color: #1e3547;
font-weight: 700;
margin-bottom: 20px;
}
}
.desc {
color: #828f99;
font-size: 20px;
line-height: 1.5;
}
.btnBox {
display: flex;
align-items: center;
margin-top: 20px;
.btn {
height: 44px;
padding: 0 20px;
line-height: 44px;
cursor: pointer;
background: #1ea59a;
border-color: #1ea59a;
color: #fff;
font-weight: 600;
font-size: 15px;
border-radius: 5px;
transition: all 0.5s;
margin-right: 10px;
&:hover {
transform: translateY(-4px);
}
&.btn2 {
background-color: #f5828b;
}
}
}
}
.picBox {
position: relative;
.pic {
width: 500px;
height: 500px;
background-image: url('../../../assets/img/block1.png');
background-size: cover;
}
.animation1 {
width: 38px;
height: 38px;
border: 7px solid #f5828b;
border-radius: 50%;
position: absolute;
right: -50px;
bottom: 86px;
animation-name: zoom1;
animation-duration: 3s;
animation-iteration-count: infinite;
animation-direction: alternate;
box-shadow: 0 12px 50px 0 rgba(0, 0, 0, 0.14);
}
.animation2 {
border-radius: 50%;
background-color: #1ea59a;
box-shadow: 0 20px 30px 0 rgba(48, 61, 114, 0.4);
position: absolute;
width: 25px;
height: 25px;
top: -60px;
right: 60px;
animation: spin 2s infinite alternate;
bottom: 60px;
}
.animation3 {
border-radius: 50%;
background-color: #25233a;
box-shadow: 0 20px 30px 0 rgba(245, 130, 139, 0.4);
position: absolute;
width: 25px;
height: 25px;
bottom: 50px;
left: 0px;
animation: spin 3s infinite alternate;
}
}
}
}
@keyframes zoom1 {
0% {
transform: scale(0.9);
}
100% {
transform: scale(1.5);
}
}
@keyframes spin {
0% {
transform: translateY(0);
}
100% {
transform: translateY(40px);
}
}
</style>

View File

@@ -0,0 +1,263 @@
<template>
<div class="block2Container">
<div class="blockContent">
<div class="blockTitle">为什么选择Simple mind map</div>
<div class="dataList">
<div class="dataItem" v-for="(item, index) in dataList" :key="index">
<div class="iconBox">
<span class="icon iconfont" :class="[item.icon]"></span>
</div>
<div class="dataValue">{{ item.value }}</div>
</div>
</div>
<div class="desc">
如果你是开发者Simple mind
map提供了一个功能完善的 js 思维导图库不依赖任何框架你可以使用它来快速完成Web思维导图产品的开发
</div>
<div class="desc">
如果你是使用者Simple mind
map提供了一个完整的思维导图软件支持在线和客户端两种使用方式所有功能完全免费
</div>
<div class="functionList">
<div
class="functionItem"
v-for="(item, index) in functionList"
:key="index"
>
<div class="icon iconfont" :class="[item.icon]"></div>
<div class="info">
<div class="name">{{ item.name }}</div>
<div class="value">{{ item.value }}</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
dataList: [
{
icon: 'iconstar',
value: 'Github star数量450+'
},
{
icon: 'iconfork',
value: 'Github fork数量100+'
},
{
icon: 'iconxiazai',
value: 'npm总下载次数10000+'
},
{
icon: 'iconteamwork',
value: '代码贡献者6+'
}
],
functionList: [
{
icon: 'iconjingzi',
name: '主题',
value: '内置多种主题,允许高度自定义样式,支持注册新主题。'
},
{
icon: 'iconjiegou',
name: '结构',
value:
'支持常见的逻辑结构图、思维导图、组织结构图、目录组织图、时间轴、鱼骨图结构。'
},
{
icon: 'iconjianpan',
name: '快捷键',
value: '常用操作支持快捷键,方便使用。'
},
{
icon: 'iconzitixiahuaxian',
name: '富文本',
value:
'节点支持普通文本和富文本两种类型,通过富文本可以创建样式丰富的节点文本内容。'
},
{
icon: 'iconimage',
name: '图片',
value: '选中任一节点,选择上传图片,让内容达到图文并茂。'
},
{
icon: 'icongaikuozonglan',
name: '概要',
value: '补充表述几个节点之间的关系。'
},
{
icon: 'iconxiaolian',
name: '图标',
value: '通过添加图标来让节点内容更丰富。'
},
{
icon: 'iconchaolianjie',
name: '超链接',
value: '节点可插入超链接,鼠标点击即可实现跳转。'
},
{
icon: 'iconflow-Mark',
name: '备注',
value: '详细的内容可以放在备注中,节点内显得更简单明了。'
},
{
icon: 'iconbiaoqian',
name: '标签',
value: '如果添加带颜色的标签,来突出要表达的重点。'
},
{
icon: 'iconlianjiexian',
name: '关联线',
value: '通过添加关联线来表明节点之间的关联关系。'
},
{
icon: 'iconmouseL',
name: '拖动',
value: '画布和节点都可以进行拖动。'
},
{
icon: 'icondaohang',
name: '导航器',
value: '通过导航器可以方便知道当前画布处于思维导图的哪个部分。'
},
{
icon: 'icondaochu',
name: '导入导出',
value: '支持多种文件格式的导入和导出。'
},
{
icon: 'iconshuiyin',
name: '水印',
value: '内置支持水印功能,防止隐私泄露。'
},
{
icon: 'iconwithdraw',
name: '前进后退',
value: '不小心误操作删除或修改内容支持一键撤回或恢复。'
},
{
icon: 'iconfuhao-dagangshu',
name: '大纲',
value: '根据大纲编辑思维导图,让内容更详细也不容易出错。'
},
{
icon: 'iconshezhi',
name: '丰富的设置',
value: '提供了丰富的功能设置,可以选择合适你的操作行为。'
}
]
}
}
}
</script>
<style lang="less" scoped>
.block2Container {
display: flex;
justify-content: center;
align-items: center;
.blockContent {
padding: 100px 0;
width: 100%;
max-width: 1140px;
.dataList {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 40px;
.dataItem {
box-shadow: 0 5px 30px -10px rgba(0, 0, 0, 0.1);
border-radius: 20px;
margin-right: 30px;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
&:last-of-type {
margin-right: 0;
}
.iconBox {
width: 55px;
height: 55px;
border-radius: 10px;
background-color: rgba(30, 165, 154, 0.1);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
.icon {
font-size: 30px;
color: #1ea59a;
}
}
.dataValue {
color: #1e3547;
font-weight: 700;
}
}
}
.blockTitle {
font-size: 30px;
font-weight: 700;
color: #1e3547;
text-align: center;
margin-bottom: 40px;
}
.desc {
color: #828f99;
font-size: 17px;
line-height: 1.7;
}
.functionList {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
margin-top: 60px;
.functionItem {
display: flex;
width: 30%;
margin-bottom: 50px;
.icon {
width: 50px;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
font-size: 50px;
margin-right: 24px;
color: #1ea59a;
}
.info {
.name {
margin-bottom: 5px;
color: #1e3547;
font-weight: 600;
font-size: 18px;
}
.value {
font-size: 14px;
color: #828f99;
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,102 @@
<template>
<div class="block3Container">
<div class="blockContent">
<div class="picBox"></div>
<div class="infoBox">
<div class="infoTitle">客户端</div>
<div class="info">支持WindowsMacLinux平台</div>
<div class="info">
在线版数据默认保存在浏览器缓存里同时也可以操作电脑本地文件但是在线版受限于网络环境访问可能比较慢多个文件切换也不够方便所以提供客户端版本功能简单但不简陋
</div>
<div class="btnList">
<div class="btn">
<a href="https://pan.baidu.com/s/1huasEbKsGNH2Af68dvWiOg?pwd=3bp3" target="_blank">百度网盘下载</a>
</div>
<div class="btn btn2">
<a href="https://github.com/wanglin2/mind-map/releases" target="_blank">Github下载</a>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
methods: {}
}
</script>
<style lang="less" scoped>
.block3Container {
background-color: #f0f9fa;
border-radius: 0 0 350px 0;
display: flex;
justify-content: center;
align-items: center;
.blockContent {
width: 100%;
max-width: 1140px;
height: 520px;
display: flex;
align-items: center;
.picBox {
width: 500px;
height: 500px;
background-image: url('../../../assets/img/block3.png');
flex-shrink: 0;
}
.infoBox {
.infoTitle {
font-weight: 700;
color: #1e3547;
font-size: 40px;
}
.info {
color: #828f99;
font-size: 16px;
line-height: 1.7;
margin-top: 20px;
}
.btnList {
display: flex;
align-items: center;
.btn {
height: 44px;
cursor: pointer;
background: #1ea59a;
border-color: #1ea59a;
border-radius: 5px;
transition: all 0.5s;
margin-right: 10px;
margin-top: 20px;
&:hover {
transform: translateY(-4px);
}
&.btn2 {
background-color: #f5828b;
}
a {
height: 100%;
padding: 0 20px;
line-height: 44px;
color: #fff;
font-weight: 600;
font-size: 15px;
text-decoration: none;
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,140 @@
<template>
<div class="block4Container">
<div class="blockContent">
<div class="infoBox">
<div class="infoTitle">JavaScript库</div>
<div class="info">
simple-mind-map是一个简单&强大的Web思维导图库不依赖任何特定框架可以帮助你快速开发思维导图产品
</div>
<div class="info">
使用非常简单只需三步即可渲染出一个思维导图
</div>
<div class="info">第一步安装</div>
<div class="codeBox">
<pre><code class="language-bash" ref="code1">
npm i simple-mind-map
</code></pre>
</div>
<div class="info">第二步引入</div>
<div class="codeBox">
<pre><code class="language-javascript" ref="code2">
import MindMap from "simple-mind-map"
</code></pre>
</div>
<div class="info">第三步实例化</div>
<div class="codeBox">
<pre><code class="language-javascript" ref="code3">
const mindMap = new MindMap({
// 提供一个宽高不为0的容器元素
el: document.getElementById('mindMapContainer'),
// 思维导图数据
data: {
"data": {
"text": "根节点"
},
"children": []
}
})
</code></pre>
</div>
<div class="btnList">
<div class="btn" @click="jumpDoc">查看更多</div>
</div>
</div>
<div class="picBox"></div>
</div>
</div>
</template>
<script>
import hljs from 'highlight.js/lib/core'
import javascript from 'highlight.js/lib/languages/javascript'
import bash from 'highlight.js/lib/languages/bash'
import 'highlight.js/styles/github.css'
hljs.registerLanguage('javascript', javascript)
hljs.registerLanguage('bash', bash)
export default {
mounted() {
hljs.highlightElement(this.$refs.code1)
hljs.highlightElement(this.$refs.code2)
hljs.highlightElement(this.$refs.code3)
},
methods: {
jumpDoc() {
this.$router.push('/doc/zh/')
}
}
}
</script>
<style lang="less" scoped>
.block4Container {
display: flex;
justify-content: center;
align-items: center;
.blockContent {
width: 100%;
max-width: 1140px;
display: flex;
align-items: center;
padding: 50px 0;
.picBox {
width: 500px;
height: 500px;
background-image: url('../../../assets/img/block4.png');
flex-shrink: 0;
}
.infoBox {
margin-right: 50px;
.infoTitle {
font-weight: 700;
color: #1e3547;
font-size: 40px;
}
.info {
color: #828f99;
font-size: 16px;
line-height: 1.7;
margin-top: 20px;
}
.codeBox {
font-size: 16px;
margin-top: 10px;
}
.btnList {
display: flex;
align-items: center;
.btn {
height: 44px;
cursor: pointer;
background: #1ea59a;
border-color: #1ea59a;
border-radius: 5px;
transition: all 0.5s;
margin-right: 10px;
margin-top: 20px;
height: 100%;
padding: 0 20px;
line-height: 44px;
color: #fff;
font-weight: 600;
font-size: 15px;
&:hover {
transform: translateY(-4px);
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,229 @@
<template>
<div class="block5Container">
<div class="blockContent">
<div class="infoBox">
<div class="infoTitle">街角小林出品</div>
<div class="infoDesc">
90六年+前端开发工程师热爱前端写作开源
</div>
<div class="linkBtnList">
<div class="linkBtn">
<a href="https://github.com/wanglin2" target="_blank">
<span class="linkBtnIcon iconfont icongithub"></span>
</a>
</div>
<div class="linkBtn">
<a href="https://juejin.cn/user/325111170756279" target="_blank">
<span class="linkBtnIcon text">掘金</span>
</a>
</div>
<div class="linkBtn">
<a
href="https://segmentfault.com/u/jiejiaoxiaolin/articles"
target="_blank"
>
<span class="linkBtnIcon text">思否</span>
</a>
</div>
<div class="linkBtn">
<a href="http://lxqnsys.com/" target="_blank">
<span class="linkBtnIcon iconfont iconwangzhan"></span>
</a>
</div>
<div class="linkBtn">
<a
href="https://www.zhihu.com/people/wang-lin-49-43-65"
target="_blank"
>
<span class="linkBtnIcon iconfont iconshejiaotubiao-10"></span>
</a>
</div>
<div class="linkBtn">
<a
href="https://blog.csdn.net/sinat_33488770?type=blog"
target="_blank"
>
<span class="linkBtnIcon iconfont iconcsdn"></span>
</a>
</div>
</div>
</div>
<div class="linkBox">
<div class="linkTitle">更多作品</div>
<div class="linkList">
<div class="linkItem" v-for="item in linkList" :key="item.name">
<a :href="item.url" target="_blank">{{ item.name }}</a>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
linkList: [
{
name: 'CodeRun',
url: 'https://github.com/wanglin2/code-run'
},
{
name: 'TinyWhiteboard',
url: 'https://github.com/wanglin2/tiny_whiteboard'
},
{
name: 'Mark.js',
url: 'https://github.com/wanglin2/markjs'
},
{
name: 'WebMapEngine',
url: 'https://github.com/wanglin2/web_map_demo'
},
{
name: 'SimpleNoviceGuide',
url: 'https://github.com/wanglin2/simple-novice-guide'
},
{
name: 'CanvasEditor',
url: 'https://github.com/wanglin2/canvas-editor-demo'
},
{
name: 'JsonTreeView',
url: 'https://github.com/wanglin2/json-tree-view'
},
{
name: 'SimpleFlowChart',
url: 'https://github.com/wanglin2/simple-flow-chart'
},
{
name: 'VideoTimeLine',
url: 'https://github.com/wanglin2/VideoTimeLine'
},
{
name: 'MarkdownEditor',
url: 'https://github.com/wanglin2/markdown_editor_sync_scroll_demo'
},
{
name: 'AssociationLine',
url: 'https://github.com/wanglin2/AssociationLineDemo'
},
{
name: 'HandPaintedStyle',
url: 'https://github.com/wanglin2/handPaintedStyle'
}
]
}
},
methods: {}
}
</script>
<style lang="less" scoped>
.block5Container {
background-color: #f0f9fa;
border-radius: 450px 0 0 0;
display: flex;
justify-content: center;
align-items: center;
.blockContent {
width: 100%;
max-width: 1140px;
height: 350px;
display: flex;
padding-top: 100px;
.infoBox {
.infoTitle {
font-size: 20px;
font-weight: 700;
color: #1e3547;
line-height: 1.4;
margin-bottom: 20px;
}
.infoDesc {
color: #828f99;
font-size: 16px;
}
.linkBtnList {
margin-top: 30px;
display: flex;
align-items: center;
.linkBtn {
width: 38px;
height: 38px;
border-radius: 50%;
overflow: hidden;
background-color: #f0f9fa;
border: 2px solid #dbf1f1;
margin-right: 10px;
a {
width: 100%;
height: 100%;
text-decoration: none;
display: flex;
justify-content: center;
align-items: center;
transition: all 0.5s;
&:hover {
background-color: #1ea59a;
.linkBtnIcon {
color: #fff;
}
}
.linkBtnIcon {
font-size: 20px;
color: #1e3547;
transition: all 0.5s;
&.text {
font-size: 13px;
}
}
}
}
}
}
.linkBox {
margin-left: 150px;
.linkTitle {
font-size: 20px;
font-weight: 700;
color: #1e3547;
line-height: 1.4;
margin-bottom: 20px;
}
.linkList {
display: flex;
flex-wrap: wrap;
.linkItem {
margin-right: 20px;
margin-bottom: 10px;
a {
color: #828f99;
font-size: 15px;
transition: all 0.5s;
text-decoration: none;
&:hover {
color: #1ea59a;
}
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,167 @@
<template>
<div class="indexHeaderContainer" :class="{ active: active }">
<div class="headerContent">
<div class="logoBox">
<span class="logo"></span>
<span class="title">SimpleMindMap</span>
</div>
<div class="nav">
<div
class="navItem"
@click="jumpTop"
:class="{
active: activeTab === 'home'
}"
>
首页
</div>
<div class="navItem" @click="useOnline">在线使用</div>
<div
class="navItem"
@click="jumpClient"
:class="{
active: activeTab === 'client'
}"
>
客户端
</div>
<div class="navItem" @click="jumpDoc">开发文档</div>
<div class="navItem">
<a href="https://github.com/wanglin2/mind-map" target="_blank"
>Github</a
>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
active: false,
activeTab: 'home',
clientEl: null
}
},
created() {
window.addEventListener('scroll', this.onScroll)
},
mounted() {
this.clientEl = document.querySelector('.block3Container')
},
beforeDestroy() {
window.removeEventListener('scroll', this.onScroll)
},
methods: {
onScroll() {
this.active = window.scrollY > 0
let offsetTop = this.clientEl.offsetTop
if (
window.scrollY + window.innerHeight >= offsetTop &&
window.scrollY <= offsetTop + this.clientEl.offsetHeight
) {
this.activeTab = 'client'
} else {
this.activeTab = 'home'
}
},
useOnline() {
this.$router.push('/')
},
jumpDoc() {
this.$router.push('/doc/zh/')
},
jumpTop() {
window.scrollTo(0, 0)
},
jumpClient() {
window.scrollTo(0, this.clientEl.offsetTop - 76)
}
}
}
</script>
<style lang="less" scoped>
.indexHeaderContainer {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 76px;
transition: all 0.5s;
background-color: transparent;
z-index: 999;
&.active {
background-color: #fff;
box-shadow: 0 5px 30px -10px rgba(0, 0, 0, 0.1);
}
.headerContent {
height: 100%;
max-width: 1140px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
.logoBox {
display: flex;
align-items: center;
.logo {
width: 22px;
height: 22px;
background-image: url('../../../assets/img/logo2.png');
background-size: cover;
margin-right: 5px;
}
.title {
color: #000;
font-size: 20px;
}
}
.nav {
display: flex;
align-items: center;
.navItem {
color: #828f99;
font-size: 15px;
transition: all 0.5s;
margin-right: 40px;
cursor: pointer;
&:last-of-type {
margin-right: 0;
}
&.active {
color: #1ea59a;
}
&:hover {
color: #1ea59a;
}
a {
text-decoration: none;
color: #828f99;
&:hover {
color: #1ea59a;
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,16 @@
<template>
<div class="splitContainer"></div>
</template>
<script>
export default {}
</script>
<style lang="less" scoped>
.splitContainer {
width: 122px;
height: 15px;
background-image: url('../../../assets/img/split.png');
background-size: cover;
}
</style>

View File

@@ -3,6 +3,7 @@ import VueRouter from 'vue-router'
import EditPage from '@/pages/Edit/Index'
import DocPage from '@/pages/Doc/Index'
import routerList from '@/pages/Doc/routerList'
import IndexPage from '@/pages/Index/Index'
// 处理没有翻译的章节路由
const handleRouterList = () => {
@@ -26,6 +27,11 @@ handleRouterList()
Vue.use(VueRouter)
const routes = [
{
path: '/index',
name: 'Index',
component: IndexPage
},
{
path: '/',
name: 'Edit',