Compare commits

...

47 Commits
0.6.3 ... 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
wanglin2
a48e52f1f4 打包0.6.5 2023-06-28 16:37:19 +08:00
wanglin2
f8a345d8de Doc: update 2023-06-28 16:28:47 +08:00
wanglin2
d900c2f122 Feat:节点图片支持拖拽调整大小 2023-06-28 15:03:15 +08:00
wanglin2
443a714549 Feat:支持配置鼠标滚轮方向对应的缩放行为 2023-06-27 16:46:37 +08:00
wanglin2
74618a8a2b Feat:打包后的库支持获取内置常量、主题等数据 2023-06-27 16:27:46 +08:00
wanglin2
a25bd4c556 Fix:修复xmind导入报错 2023-06-27 16:15:42 +08:00
wanglin2
efe205ae70 打包0.6.4-fix.1 2023-06-27 09:08:45 +08:00
wanglin2
a0d7473b1f Doc: update 2023-06-27 09:05:39 +08:00
wanglin2
7821781f20 Feat:鼠标滚轮缩放时默认以鼠标当前位置为中心进行缩放,可以通过配置关闭该特性 2023-06-27 09:05:25 +08:00
街角小林
b90d4ddf8a Merge pull request #152 from F-star/feat/scale-in-wheel-curor
Feat: 支持滚轮情况下,以光标为中心进行缩放
2023-06-27 08:47:40 +08:00
Hao Huang
314562c167 Feat: 支持滚轮情况下,以光标为中心进行缩放 2023-06-26 13:32:33 +00:00
wanglin2
5f0a9a7ce2 打包0.6.4 2023-06-26 16:20:38 +08:00
wanglin2
98e27841ad Doc: update 2023-06-26 16:15:20 +08:00
wanglin2
1b6467728c Feat:优化指定中心点缩放 2023-06-26 15:57:45 +08:00
119 changed files with 3245 additions and 212 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

@@ -8,13 +8,21 @@ import Drag from './src/plugins/Drag.js'
import Select from './src/plugins/Select.js'
import AssociativeLine from './src/plugins/AssociativeLine'
import RichText from './src/plugins/RichText'
import NodeImgAdjust from './src/plugins/NodeImgAdjust.js'
import TouchEvent from './src/plugins/TouchEvent.js'
import xmind from './src/parse/xmind.js'
import markdown from './src/parse/markdown.js'
import icons from './src/svg/icons.js'
import * as constants from './src/constants/constant.js'
import themes from './src/themes/index.js'
import * as defaultTheme from './src/themes/default.js'
MindMap.xmind = xmind
MindMap.markdown = markdown
MindMap.iconList = icons.nodeIconList
MindMap.constants = constants
MindMap.themes = themes
MindMap.defaultTheme = defaultTheme
MindMap
.usePlugin(MiniMap)
@@ -26,5 +34,7 @@ MindMap
.usePlugin(Select)
.usePlugin(AssociativeLine)
.usePlugin(RichText)
.usePlugin(TouchEvent)
.usePlugin(NodeImgAdjust)
export default MindMap

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.3",
"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

@@ -13,7 +13,9 @@ export const defaultOpt = {
// 主题配置,会和所选择的主题进行合并
themeConfig: {},
// 放大缩小的增量比例
scaleRatio: 0.1,
scaleRatio: 0.2,
// 鼠标缩放是否以鼠标当前位置为中心点,否则以画布中心点
mouseScaleCenterUseMousePosition: true,
// 最多显示几个标签
maxTag: 5,
// 导出图片时的内边距
@@ -59,6 +61,8 @@ export const defaultOpt = {
mousewheelAction: CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM, // zoom放大缩小、move上下移动
// 当mousewheelAction设为move时可以通过该属性控制鼠标滚动一下视图移动的步长单位px
mousewheelMoveStep: 100,
// 当mousewheelAction设为zoom时默认向前滚动是缩小向后滚动是放大如果该属性设为true那么会反过来
mousewheelZoomActionReverse: false,
// 默认插入的二级节点的文字
defaultInsertSecondLevelNodeText: '二级节点',
// 默认插入的二级以下节点的文字

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()
@@ -869,13 +872,14 @@ class Render {
}
// 设置节点图片
setNodeImage(node, { url, title, width, height }) {
setNodeImage(node, { url, title, width, height, custom = false }) {
this.setNodeDataRender(node, {
image: url,
imageTitle: title || '',
imageSize: {
width,
height
height,
custom
}
})
}

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

@@ -17,6 +17,15 @@ function createImgNode() {
node.on('dblclick', e => {
this.mindMap.emit('node_img_dblclick', this, e)
})
node.on('mouseenter', e => {
this.mindMap.emit('node_img_mouseenter', this, node, e)
})
node.on('mouseleave', e => {
this.mindMap.emit('node_img_mouseleave', this, node, e)
})
node.on('mousemove', e => {
this.mindMap.emit('node_img_mousemove', this, node, e)
})
return {
node,
width: imgSize[0],
@@ -26,9 +35,12 @@ function createImgNode() {
// 获取图片显示宽高
function getImgShowSize() {
const { custom, width, height } = this.nodeData.data.imageSize
// 如果是自定义了图片的宽高,那么不受最大宽高限制
if (custom) return [width, height]
return resizeImgSize(
this.nodeData.data.imageSize.width,
this.nodeData.data.imageSize.height,
width,
height,
this.mindMap.themeConfig.imgMaxWidth,
this.mindMap.themeConfig.imgMaxHeight
)
@@ -89,7 +101,7 @@ function createRichTextNode() {
el.style.maxWidth = this.mindMap.opt.textAutoWrapWidth + 'px'
this.mindMap.el.appendChild(div)
let { width, height } = el.getBoundingClientRect()
width = Math.ceil(width)
width = Math.ceil(width) + 1// 修复getBoundingClientRect方法对实际宽度是小数的元素获取到的值是整数导致宽度不够文本发生换行的问题
height = Math.ceil(height)
g.attr('data-width', width)
g.attr('data-height', height)

View File

@@ -60,29 +60,38 @@ class View {
})
// 放大缩小视图
this.mindMap.event.on('mousewheel', (e, dir, event, isTouchPad) => {
let {
customHandleMousewheel,
mousewheelAction,
mouseScaleCenterUseMousePosition,
mousewheelMoveStep,
mousewheelZoomActionReverse
} = this.mindMap.opt
// 是否自定义鼠标滚轮事件
if (
this.mindMap.opt.customHandleMousewheel &&
typeof this.mindMap.opt.customHandleMousewheel === 'function'
customHandleMousewheel &&
typeof customHandleMousewheel === 'function'
) {
return this.mindMap.opt.customHandleMousewheel(e)
return customHandleMousewheel(e)
}
if (
this.mindMap.opt.mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM
) {
// 鼠标滚轮事件控制缩放
if (mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM) {
let cx = mouseScaleCenterUseMousePosition ? e.clientX : undefined
let cy = mouseScaleCenterUseMousePosition ? e.clientY : undefined
switch (dir) {
// 鼠标滚轮,向上和向左,都是缩小
case CONSTANTS.DIR.UP:
case CONSTANTS.DIR.LEFT:
this.narrow()
mousewheelZoomActionReverse ? this.enlarge(cx, cy, isTouchPad) : this.narrow(cx, cy, isTouchPad)
break
// 鼠标滚轮,向下和向右,都是放大
case CONSTANTS.DIR.DOWN:
case CONSTANTS.DIR.RIGHT:
this.enlarge()
mousewheelZoomActionReverse ? this.narrow(cx, cy, isTouchPad) : this.enlarge(cx, cy, isTouchPad)
break
}
} else {
let step = this.mindMap.opt.mousewheelMoveStep
} else {// 鼠标滚轮事件控制画布移动
let step = mousewheelMoveStep
if (isTouchPad) {
step = 5
}
@@ -190,40 +199,45 @@ class View {
}
// 缩小
narrow() {
let scale
if (this.scale - this.mindMap.opt.scaleRatio > 0.1) {
scale = this.scale - this.mindMap.opt.scaleRatio
} else {
scale = 0.1
}
this.scaleInCenter(this.mindMap.width / 2, this.mindMap.height / 2, scale)
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() {
const scale = this.scale + this.mindMap.opt.scaleRatio
this.scaleInCenter(this.mindMap.width / 2, this.mindMap.height / 2, scale)
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)
}
// 基于画布中心进行缩放
scaleInCenter(cx, cy, scale) {
// 基于指定中心进行缩放cxcy 可不指定,此时会使用画布中心点
scaleInCenter(scale, cx, cy) {
if (cx === undefined || cy === undefined) {
cx = this.mindMap.width / 2
cy = this.mindMap.height / 2
}
const prevScale = this.scale
const dx = (cx - this.x) * (1 - scale / prevScale)
const dy = (cy - this.y) * (1 - scale / prevScale)
this.x += dx;
this.y += dy;
const ratio = 1 - scale / prevScale
const dx = (cx - this.x) * ratio
const dy = (cy - this.y) * ratio
this.x += dx
this.y += dy
this.scale = scale
}
// 设置缩放
setScale(scale) {
this.scale = scale
setScale(scale, cx, cy) {
if (cx !== undefined && cy !== undefined) {
this.scaleInCenter(scale, cx, cy)
} else {
this.scale = scale
}
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,18 +39,20 @@ 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
}
// 节点备注
if (node.notes) {
newNode.data.note = (node.notes.realHTML || node.notes.plain).content
let notesData = node.notes.realHTML || node.notes.plain
newNode.data.note = notesData ? notesData.content || '' : ''
}
// 超链接
if (node.href && /^https?:\/\//.test(node.href)) {
@@ -54,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 (
@@ -69,6 +113,7 @@ const transformXmind = content => {
}
}
walk(nodeTree, newTree)
await Promise.all(waitLoadImageList)
return newTree
}
@@ -157,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

@@ -0,0 +1,223 @@
// 节点图片大小调整插件
import { resizeImgSizeByOriginRatio } from '../utils/index'
import btnsSvg from '../svg/btns'
class NodeImgAdjust {
// 构造函数
constructor({ mindMap }) {
this.mindMap = mindMap
this.resizeBtnSize = 26 // 调整按钮的大小
this.handleEl = null // 自定义元素,用来渲染临时图片、调整按钮
this.isShowHandleEl = false // 自定义元素是否在显示中
this.node = null // 当前节点实例
this.img = null // 当前节点的图片节点
this.rect = null // 当前图片节点的尺寸信息
this.isMousedown = false // 当前是否是按住调整按钮状态
this.currentImgWidth = 0 // 当前拖拽实时图片的大小
this.currentImgHeight = 0
this.isAdjusted = false // 是否是拖拽结束后的渲染期间
this.bindEvent()
}
// 监听事件
bindEvent() {
this.onNodeImgMouseleave = this.onNodeImgMouseleave.bind(this)
this.onNodeImgMousemove = this.onNodeImgMousemove.bind(this)
this.onMousemove = this.onMousemove.bind(this)
this.onMouseup = this.onMouseup.bind(this)
this.onRenderEnd = this.onRenderEnd.bind(this)
this.mindMap.on('node_img_mouseleave', this.onNodeImgMouseleave)
this.mindMap.on('node_img_mousemove', this.onNodeImgMousemove)
this.mindMap.on('mousemove', this.onMousemove)
this.mindMap.on('mouseup', this.onMouseup)
this.mindMap.on('node_mouseup', this.onMouseup)
this.mindMap.on('node_tree_render_end', this.onRenderEnd)
}
// 解绑事件
unBindEvent() {
this.mindMap.off('node_img_mouseleave', this.onNodeImgMouseleave)
this.mindMap.off('node_img_mousemove', this.onNodeImgMousemove)
this.mindMap.off('mousemove', this.onMousemove)
this.mindMap.off('mouseup', this.onMouseup)
this.mindMap.off('node_mouseup', this.onMouseup)
this.mindMap.off('node_tree_render_end', this.onRenderEnd)
}
// 节点图片鼠标移动事件
onNodeImgMousemove(node, img) {
// 如果当前正在拖动调整中那么直接返回
if (this.isMousedown || this.isAdjusted) return
// 如果在当前节点内移动,以及自定义元素已经是显示状态,那么直接返回
if (this.node === node && this.isShowHandleEl) return
// 更新当前节点信息
this.node = node
this.img = img
this.rect = this.img.rbox()
// 显示自定义元素
this.showHandleEl()
}
// 节点图片鼠标移出事件
onNodeImgMouseleave() {
if (this.isMousedown) return
this.hideHandleEl()
}
// 隐藏节点实际的图片
hideNodeImage() {
if (!this.img) return
this.img.hide()
}
// 显示节点实际的图片
showNodeImage() {
if (!this.img) return
this.img.show()
}
// 显示自定义元素
showHandleEl() {
if (!this.handleEl) {
this.createResizeBtnEl()
}
this.setHandleElRect()
document.body.appendChild(this.handleEl)
this.isShowHandleEl = true
}
// 隐藏自定义元素
hideHandleEl() {
if (!this.isShowHandleEl) return
this.isShowHandleEl = false
document.body.removeChild(this.handleEl)
this.handleEl.style.backgroundImage = ``
this.handleEl.style.width = 0
this.handleEl.style.height = 0
this.handleEl.style.left = 0
this.handleEl.style.top = 0
}
// 设置自定义元素尺寸位置信息
setHandleElRect() {
let { width, height, x, y } = this.rect
this.handleEl.style.left = `${x}px`
this.handleEl.style.top = `${y}px`
this.currentImgWidth = width
this.currentImgHeight = height
this.updateHandleElSize()
}
// 更新自定义元素宽高
updateHandleElSize() {
this.handleEl.style.width = `${this.currentImgWidth}px`
this.handleEl.style.height = `${this.currentImgHeight}px`
}
// 创建调整按钮元素
createResizeBtnEl() {
// 容器元素
this.handleEl = document.createElement('div')
this.handleEl.style.cssText = `
pointer-events: none;
position: fixed;
background-size: cover;
`
// 调整按钮元素
const btnEl = document.createElement('div')
btnEl.innerHTML = btnsSvg.imgAdjust
btnEl.style.cssText = `
position: absolute;
right: 0;
bottom: 0;
pointer-events: auto;
background-color: rgba(0, 0, 0, 0.3);
width: ${this.resizeBtnSize}px;
height: ${this.resizeBtnSize}px;
display: flex;
justify-content: center;
align-items: center;
cursor: nwse-resize;
`
this.handleEl.appendChild(btnEl)
// 给按钮元素绑定事件
btnEl.addEventListener('mouseenter', () => {
// 移入按钮,会触发节点图片的移出事件,所以需要再次显示按钮
this.showHandleEl()
})
btnEl.addEventListener('mouseleave', () => {
// 移除按钮,需要隐藏按钮
if (this.isMousedown) return
this.hideHandleEl()
})
btnEl.addEventListener('mousedown', e => {
this.onMousedown(e)
})
}
// 鼠标按钮按下事件
onMousedown() {
this.isMousedown = true
// 隐藏节点实际图片
this.hideNodeImage()
// 将节点图片渲染到自定义元素上
this.handleEl.style.backgroundImage = `url(${this.node.nodeData.data.image})`
}
// 鼠标移动
onMousemove(e) {
if (!this.isMousedown) return
e.preventDefault()
// 计算当前拖拽位置对应的图片的实时大小
let { width: imageOriginWidth, height: imageOriginHeight } =
this.node.nodeData.data.imageSize
let newWidth = e.clientX - this.rect.x
let newHeight = e.clientY - this.rect.y
if (newWidth <= 0 || newHeight <= 0) return
let [actWidth, actHeight] = resizeImgSizeByOriginRatio(
imageOriginWidth,
imageOriginHeight,
newWidth,
newHeight
)
this.currentImgWidth = actWidth
this.currentImgHeight = actHeight
this.updateHandleElSize()
}
// 鼠标松开
onMouseup() {
if (!this.isMousedown) return
// 显示节点实际图片
this.showNodeImage()
// 隐藏自定义元素
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 / scaleX,
height: this.currentImgHeight / scaleY,
custom: true // 代表自定义了图片大小
})
this.isAdjusted = true
this.isMousedown = false
}
// 渲染完成事件
onRenderEnd() {
if (!this.isAdjusted) return
this.isAdjusted = false
}
// 插件被移除前做的事情
beforePluginRemove() {
this.unBindEvent()
}
}
NodeImgAdjust.instanceName = 'nodeImgAdjust'
export default NodeImgAdjust

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

@@ -50,16 +50,20 @@ class TouchEvent {
} else if (len === 2) {
let touch1 = e.touches[0]
let touch2 = e.touches[1]
let distance = Math.sqrt(
Math.pow(touch1.clientX - touch2.clientX, 2) +
Math.pow(touch1.clientY - touch2.clientY, 2)
)
let ox = touch1.clientX - touch2.clientX
let oy = touch1.clientY - touch2.clientY
let distance = Math.sqrt(Math.pow(ox, 2) + Math.pow(oy, 2))
// 以两指中心点进行缩放
let { x: touch1ClientX, y: touch1ClientY } = this.mindMap.toPos(touch1.clientX, touch1.clientY)
let { x: touch2ClientX, y: touch2ClientY } = this.mindMap.toPos(touch2.clientX, touch2.clientY)
let cx = (touch1ClientX + touch2ClientX) / 2
let cy = (touch1ClientY + touch2ClientY) / 2
if (distance > this.doubleTouchmoveDistance) {
// 放大
this.mindMap.view.enlarge()
this.mindMap.view.enlarge(cx, cy)
} else {
// 缩小
this.mindMap.view.narrow()
this.mindMap.view.narrow(cx, cy)
}
this.doubleTouchmoveDistance = distance
}
@@ -82,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

@@ -4,7 +4,11 @@ const open = `<svg t="1618141562310" class="icon" viewBox="0 0 1024 1024" versio
// 收缩按钮
const close = `<svg t="1618141589243" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13611" width="200" height="200"><path d="M512 105.472c225.28 0 407.04 181.76 407.04 407.04s-181.76 407.04-407.04 407.04-407.04-181.76-407.04-407.04 181.76-407.04 407.04-407.04z m0-74.24c-265.216 0-480.768 215.552-480.768 480.768s215.552 480.768 480.768 480.768 480.768-215.552 480.768-480.768-215.552-480.768-480.768-480.768z" p-id="13612"></path><path d="M252.928 474.624h518.144v74.24h-518.144z" p-id="13613"></path></svg>`
// 图片调整按钮
const imgAdjust = `<svg width="12px" height="12px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M1008.128 614.4a25.6 25.6 0 0 0-27.648 5.632l-142.848 142.848L259.072 186.88 401.92 43.52A25.6 25.6 0 0 0 384 0h-358.4a25.6 25.6 0 0 0-25.6 25.6v358.4a25.6 25.6 0 0 0 43.52 17.92l143.36-142.848 578.048 578.048-142.848 142.848a25.6 25.6 0 0 0 17.92 43.52h358.4a25.6 25.6 0 0 0 25.6-25.6v-358.4a25.6 25.6 0 0 0-15.872-25.088z" /></svg>`
export default {
open,
close
close,
imgAdjust
}

View File

@@ -50,6 +50,26 @@ export const bfsWalk = (root, callback) => {
}
}
// 按原比例缩放图片
export const resizeImgSizeByOriginRatio = (
width,
height,
newWidth,
newHeight
) => {
let arr = []
let nRatio = width / height
let mRatio = newWidth / newHeight
if (nRatio > mRatio) {
// 固定高度
arr = [nRatio * newHeight, newHeight]
} else {
// 固定宽度
arr = [newWidth, newWidth / nRatio]
}
return arr
}
// 缩放图片尺寸
export const resizeImgSize = (width, height, maxWidth, maxHeight) => {
let nRatio = width / height
@@ -137,7 +157,12 @@ export const copyRenderTree = (tree, root, removeActiveState = false) => {
}
// 复制节点树数据
export const copyNodeTree = (tree, root, removeActiveState = false, keepId = false) => {
export const copyNodeTree = (
tree,
root,
removeActiveState = false,
keepId = false
) => {
tree.data = simpleDeepClone(root.nodeData ? root.nodeData.data : root.data)
// 去除节点id因为节点id不能重复
if (tree.data.id && !keepId) delete tree.data.id
@@ -188,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')
@@ -236,8 +273,8 @@ export const degToRad = deg => {
return deg * (Math.PI / 180)
}
// 驼峰转连字符
export const camelCaseToHyphen = (str) => {
// 驼峰转连字符
export const camelCaseToHyphen = str => {
return str.replace(/([a-z])([A-Z])/g, (...args) => {
return args[1] + '-' + args[2].toLowerCase()
})
@@ -258,11 +295,8 @@ export const measureText = (text, { italic, bold, fontSize, fontFamily }) => {
}
measureTextContext.save()
measureTextContext.font = font
const {
width,
actualBoundingBoxAscent,
actualBoundingBoxDescent
} = measureTextContext.measureText(text)
const { width, actualBoundingBoxAscent, actualBoundingBoxDescent } =
measureTextContext.measureText(text)
measureTextContext.restore()
const height = actualBoundingBoxAscent + actualBoundingBoxDescent
return { width, height }
@@ -270,7 +304,9 @@ export const measureText = (text, { italic, bold, fontSize, fontFamily }) => {
// 拼接font字符串
export const joinFontStr = ({ italic, bold, fontSize, fontFamily }) => {
return `${italic ? 'italic ' : ''} ${bold ? 'bold ' : ''} ${fontSize}px ${fontFamily} `
return `${italic ? 'italic ' : ''} ${
bold ? 'bold ' : ''
} ${fontSize}px ${fontFamily} `
}
// 在下一个事件循环里执行任务
@@ -336,7 +372,7 @@ export const checkNodeOuter = (mindMap, node) => {
// 提取html字符串里的纯文本
let getTextFromHtmlEl = null
export const getTextFromHtml = (html) => {
export const getTextFromHtml = html => {
if (!getTextFromHtmlEl) {
getTextFromHtmlEl = document.createElement('div')
}
@@ -345,13 +381,13 @@ export const getTextFromHtml = (html) => {
}
// 将blob转成data:url
export const readBlob = (blob) => {
export const readBlob = blob => {
return new Promise((resolve, reject) => {
let reader = new FileReader()
reader.onload = (evt) => {
reader.onload = evt => {
resolve(evt.target.result)
}
reader.onerror = (err) => {
reader.onerror = err => {
reject(err)
}
reader.readAsDataURL(blob)
@@ -360,11 +396,31 @@ export const readBlob = (blob) => {
// 将dom节点转换成html字符串
let nodeToHTMLWrapEl = null
export const nodeToHTML = (node) => {
export const nodeToHTML = node => {
if (!nodeToHTMLWrapEl) {
nodeToHTMLWrapEl = document.createElement('div')
}
nodeToHTMLWrapEl.innerHTML = ''
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

@@ -43,7 +43,10 @@ export default {
associativeLineWidth: 'Width',
associativeLineColor: 'Color',
associativeLineActiveWidth: 'Active width',
associativeLineActiveColor: 'Active color'
associativeLineActiveColor: 'Active color',
mousewheelZoomActionReverse: 'Mouse Wheel Zoom',
mousewheelZoomActionReverse1: 'Zoom out forward and zoom in back',
mousewheelZoomActionReverse2: 'Zoom in forward and zoom out back'
},
color: {
moreColor: 'More color'

View File

@@ -43,7 +43,10 @@ export default {
associativeLineWidth: '粗细',
associativeLineColor: '颜色',
associativeLineActiveWidth: '激活粗细',
associativeLineActiveColor: '激活颜色'
associativeLineActiveColor: '激活颜色',
mousewheelZoomActionReverse: '鼠标滚轮缩放',
mousewheelZoomActionReverse1: '向前缩小向后放大',
mousewheelZoomActionReverse2: '向前放大向后缩小'
},
color: {
moreColor: '更多颜色'

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

@@ -31,6 +31,7 @@ let APIList = [
'watermark',
'associativeLine',
'touchEvent',
'nodeImgAdjust',
'xmind',
'markdown',
'utils'

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,33 @@
# 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.
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.
## 0.6.4-fix.1
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.
Fix: 1.Fixed an issue where the default value of the zoom center point was not updated after changing the canvas size.
## 0.6.4
New: 1.The default is to scale at the center point of the canvas. 2.Optimize the scaling of both fingers on the mobile end, with the center position of the two fingers as the center point for scaling.
## 0.6.3
Fix: 1.Fix the issue where the summary node will respond to inserting node shortcuts.

View File

@@ -1,6 +1,20 @@
<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. 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>
<p>Fix: 1.Fixed an issue where the default value of the zoom center point was not updated after changing the canvas size.</p>
<h2>0.6.4</h2>
<p>New: 1.The default is to scale at the center point of the canvas. 2.Optimize the scaling of both fingers on the mobile end, with the center position of the two fingers as the center point for scaling.</p>
<h2>0.6.3</h2>
<p>Fix: 1.Fix the issue where the summary node will respond to inserting node shortcuts.</p>
<p>New: 1.Support custom node content.</p>

View File

@@ -45,6 +45,7 @@ const mindMap = new MindMap({
| customHandleMousewheelv0.4.3+ | Function | null | User-defined mouse wheel event processing can pass a function, and the callback parameter is the event object | |
| mousewheelActionv0.4.3+ | String | zoom | The behavior of the mouse wheel, `zoom`(Zoom in and out)、`move`(Move up and down). If `customHandleMousewheel` passes a custom function, this property will not take effect | |
| mousewheelMoveStepv0.4.3+ | Number | 100 | When the `mousewheelAction` is set to `move`, you can use this attribute to control the step length of the view movement when the mouse scrolls. The unit is `px` | |
| mousewheelZoomActionReversev0.6.5+ | Boolean | false | When `mousewheelAction` is set to `zoom`, the default scrolling forward is to zoom out, and scrolling backward is to zoom in. If this property is set to true, it will be reversed | |
| defaultInsertSecondLevelNodeTextv0.4.7+ | String | 二级节点 | Text of the default inserted secondary node | |
| defaultInsertBelowSecondLevelNodeTextv0.4.7+ | String | 分支主题 | Text for nodes below the second level inserted by default | |
| expandBtnStylev0.5.0+ | Object | { color: '#808080', fill: '#fff' } | Expand the color of the stow button | |
@@ -69,6 +70,7 @@ const mindMap = new MindMap({
| beforeTextEditv0.6.0+ | Function/null | null | The callback method before the node is about to enter editing. If the method returns a value other than true, the editing will be canceled. The function can return a value or a promise, and the callback parameter is the node instance | |
| isUseCustomNodeContentv0.6.3+ | Boolean | false | Whether to customize node content | |
| customCreateNodeContentv0.6.3+ | Function/null | null | If `isUseCustomNodeContent` is set to `true`, then this option needs to be used to pass in a method that receives the node instance `node` as a parameter (if you want to obtain data for that node, you can use `node.nodeData.data`). You need to return the custom node content element, which is the DOM node. If a node does not require customization, you can return `null` | |
| mouseScaleCenterUseMousePositionv0.6.4-fix.1+ | Boolean | true | Is the mouse zoom centered around the current position of the mouse, otherwise centered around the canvas | |
### Watermark config
@@ -233,6 +235,9 @@ Listen to an event. Event list:
| hide_text_edit | Node text edit box close event | textEditNode (text edit box DOM node), activeNodeList (current list of active nodes) |
| scale | Zoom event | scale (zoom ratio) |
| node_img_dblclickv0.2.15+ | Node image double-click event | this (node instance), e (event object) |
| node_img_mouseenterv0.6.5+ | Node image mouseenter event | thisnode instance、imgNodeimg node、eevent object |
| node_img_mouseleavev0.6.5+ | Node image mouseleave event | thisnode instance、imgNodeimg node、eevent object |
| node_img_mousemovev0.6.5+ | Node image mousemove event | thisnode instance、imgNodeimg node、eevent object |
| node_tree_render_endv0.2.16+ | Node tree render end event | |
| rich_text_selection_changev0.4.0+ | Available when the `RichText` plugin is registered. Triggered when the text selection area changes when the node is edited | hasRangeWhether there is a selection、rectInfoSize and location information of the selected area、formatInfoText formatting information of the selected area |
| transforming-dom-to-imagesv0.4.0+ | Available when the `RichText` plugin is registered. When there is a `DOM` node in `svg`, the `DOM` node will be converted to an image when exporting to an image. This event will be triggered during the conversion process. You can use this event to prompt the user about the node to which you are currently converting | indexIndex of the node currently converted to、lenTotal number of nodes to be converted |

View File

@@ -176,6 +176,13 @@
<td></td>
</tr>
<tr>
<td>mousewheelZoomActionReversev0.6.5+</td>
<td>Boolean</td>
<td>false</td>
<td>When <code>mousewheelAction</code> is set to <code>zoom</code>, the default scrolling forward is to zoom out, and scrolling backward is to zoom in. If this property is set to true, it will be reversed</td>
<td></td>
</tr>
<tr>
<td>defaultInsertSecondLevelNodeTextv0.4.7+</td>
<td>String</td>
<td>二级节点</td>
@@ -343,6 +350,13 @@
<td>If <code>isUseCustomNodeContent</code> is set to <code>true</code>, then this option needs to be used to pass in a method that receives the node instance <code>node</code> as a parameter (if you want to obtain data for that node, you can use <code>node.nodeData.data</code>). You need to return the custom node content element, which is the DOM node. If a node does not require customization, you can return <code>null</code></td>
<td></td>
</tr>
<tr>
<td>mouseScaleCenterUseMousePositionv0.6.4-fix.1+</td>
<td>Boolean</td>
<td>true</td>
<td>Is the mouse zoom centered around the current position of the mouse, otherwise centered around the canvas</td>
<td></td>
</tr>
</tbody>
</table>
<h3>Watermark config</h3>
@@ -646,6 +660,21 @@ poor performance and should be used sparingly.</p>
<td>this (node instance), e (event object)</td>
</tr>
<tr>
<td>node_img_mouseenterv0.6.5+</td>
<td>Node image mouseenter event</td>
<td>thisnode instanceimgNodeimg nodeeevent object</td>
</tr>
<tr>
<td>node_img_mouseleavev0.6.5+</td>
<td>Node image mouseleave event</td>
<td>thisnode instanceimgNodeimg nodeeevent object</td>
</tr>
<tr>
<td>node_img_mousemovev0.6.5+</td>
<td>Node image mousemove event</td>
<td>thisnode instanceimgNodeimg nodeeevent object</td>
</tr>
<tr>
<td>node_tree_render_endv0.2.16+</td>
<td>Node tree render end event</td>
<td></td>

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

@@ -0,0 +1,16 @@
# NodeImgAdjust plugin
> v0.6.5+
This plugin provides the function of dragging and adjusting the size of images within nodes.
## Register
```js
import MindMap from 'simple-mind-map'
import NodeImgAdjust from 'simple-mind-map/src/plugins/NodeImgAdjust.js'
MindMap.usePlugin(NodeImgAdjust)
```
After registration and instantiation of `MindMap`, the instance can be obtained through `mindMap.nodeImgAdjust`.

View File

@@ -0,0 +1,27 @@
<template>
<div>
<h1>NodeImgAdjust plugin</h1>
<blockquote>
<p>v0.6.5+</p>
</blockquote>
<p>This plugin provides the function of dragging and adjusting the size of images within nodes.</p>
<h2>Register</h2>
<pre class="hljs"><code><span class="hljs-keyword">import</span> MindMap <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map&#x27;</span>
<span class="hljs-keyword">import</span> NodeImgAdjust <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/plugins/NodeImgAdjust.js&#x27;</span>
MindMap.usePlugin(NodeImgAdjust)
</code></pre>
<p>After registration and instantiation of <code>MindMap</code>, the instance can be obtained through <code>mindMap.nodeImgAdjust</code>.</p>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>

View File

@@ -112,6 +112,8 @@ cd web
npm run buildLibrary
```
The packaging entry is `simple-mind-map/full.js`, which will introduce all plugins by default. If you don't need all plugins, you can modify the file to only introduce the plugins you need, which can reduce the size of the packaged file.
The `package.json` file in the `simple-mind-map` library provides two export
fields:

View File

@@ -80,6 +80,7 @@ simple-mind-map. This uses the same packaging tool as the sample project web.</p
<pre class="hljs"><code><span class="hljs-built_in">cd</span> web
npm run buildLibrary
</code></pre>
<p>The packaging entry is <code>simple-mind-map/full.js</code>, which will introduce all plugins by default. If you don't need all plugins, you can modify the file to only introduce the plugins you need, which can reduce the size of the packaged file.</p>
<p>The <code>package.json</code> file in the <code>simple-mind-map</code> library provides two export
fields:</p>
<pre class="hljs"><code>{

View File

@@ -10,6 +10,20 @@ import {walk, ...} from 'simple-mind-map/src/utils'
### Methods
#### resizeImgSizeByOriginRatio(width, height, newWidth, newHeight)
> v0.6.5+
`width`: The original width of the image
`height`The original height of the image
`newWidth`Width to zoom in to
`newHeight`Height to zoom in to
Scale the image proportionally. Zoom to the specified size of `newWidth` and `newHeight` while maintaining the original aspect ratio of the image.
#### walk(root, parent, beforeCallback, afterCallback, isRoot, layerIndex = 0, index = 0)
Depth-first traversal of a tree
@@ -139,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

@@ -6,6 +6,15 @@
<pre class="hljs"><code><span class="hljs-keyword">import</span> {walk, ...} <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/utils&#x27;</span>
</code></pre>
<h3>Methods</h3>
<h4>resizeImgSizeByOriginRatio(width, height, newWidth, newHeight)</h4>
<blockquote>
<p>v0.6.5+</p>
</blockquote>
<p><code>width</code>: The original width of the image</p>
<p><code>height</code>The original height of the image</p>
<p><code>newWidth</code>Width to zoom in to</p>
<p><code>newHeight</code>Height to zoom in to</p>
<p>Scale the image proportionally. Zoom to the specified size of <code>newWidth</code> and <code>newHeight</code> while maintaining the original aspect ratio of the image.</p>
<h4>walk(root, parent, beforeCallback, afterCallback, isRoot, layerIndex = 0, index = 0)</h4>
<p>Depth-first traversal of a tree</p>
<p><code>root</code>: the root node of the tree to be traversed</p>
@@ -89,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

@@ -35,11 +35,19 @@ Translate the `y` direction to a specific position
Revert to the default transformation
### narrow()
### narrow(cx, cy)
- `cx`:v0.6.4+Zoom to the specified position on the canvas, default to the center point of the canvas
- `cy`:v0.6.4+Zoom to the specified position on the canvas, default to the center point of the canvas
Zoom out
### enlarge()
### enlarge(cx, cy)
- `cx`:v0.6.4+Zoom to the specified position on the canvas, default to the center point of the canvas
- `cy`:v0.6.4+Zoom to the specified position on the canvas, default to the center point of the canvas
Zoom in
@@ -56,8 +64,12 @@ Get the current transform data, can be used for display
Dynamically set transform data, transform data can be obtained through the
getTransformData method"
### setScale(scale)
### setScale(scale, cx, cy)
> v0.2.17+
- `cx`:v0.6.4+Zoom to the specified position on the canvas, default to the center point of the canvas
- `cy`:v0.6.4+Zoom to the specified position on the canvas, default to the center point of the canvas
Setting Zoom

View File

@@ -25,9 +25,25 @@ through <code>mindMap.view</code></p>
<p>Translate the <code>y</code> direction to a specific position</p>
<h3>reset()</h3>
<p>Revert to the default transformation</p>
<h3>narrow()</h3>
<h3>narrow(cx, cy)</h3>
<ul>
<li>
<p><code>cx</code>:v0.6.4+Zoom to the specified position on the canvas, default to the center point of the canvas</p>
</li>
<li>
<p><code>cy</code>:v0.6.4+Zoom to the specified position on the canvas, default to the center point of the canvas</p>
</li>
</ul>
<p>Zoom out</p>
<h3>enlarge()</h3>
<h3>enlarge(cx, cy)</h3>
<ul>
<li>
<p><code>cx</code>:v0.6.4+Zoom to the specified position on the canvas, default to the center point of the canvas</p>
</li>
<li>
<p><code>cy</code>:v0.6.4+Zoom to the specified position on the canvas, default to the center point of the canvas</p>
</li>
</ul>
<p>Zoom in</p>
<h3>getTransformData()</h3>
<blockquote>
@@ -40,10 +56,18 @@ through <code>mindMap.view</code></p>
</blockquote>
<p>Dynamically set transform data, transform data can be obtained through the
getTransformData method&quot;</p>
<h3>setScale(scale)</h3>
<h3>setScale(scale, cx, cy)</h3>
<blockquote>
<p>v0.2.17+</p>
</blockquote>
<ul>
<li>
<p><code>cx</code>:v0.6.4+Zoom to the specified position on the canvas, default to the center point of the canvas</p>
</li>
<li>
<p><code>cy</code>:v0.6.4+Zoom to the specified position on the canvas, default to the center point of the canvas</p>
</li>
</ul>
<p>Setting Zoom</p>
</div>

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

@@ -46,7 +46,8 @@ export default [
{ path: 'xmind', title: 'XMind解析' },
{ path: 'deploy', title: '部署' },
{ path: 'client', title: '客户端' },
{ path: 'touchEvent', title: 'TouchEvent插件' }
{ path: 'touchEvent', title: 'TouchEvent插件' },
{ path: 'nodeImgAdjust', title: 'NodeImgAdjust插件' }
]
},
{
@@ -75,7 +76,8 @@ export default [
{ path: 'watermark', title: 'Watermark plugin' },
{ path: 'xmind', title: 'XMind parse' },
{ path: 'deploy', title: 'Deploy' },
{ path: 'touchEvent', title: 'TouchEvent plugin' }
{ path: 'touchEvent', title: 'TouchEvent plugin' },
{ path: 'nodeImgAdjust', title: 'NodeImgAdjust plugin' }
]
}
]

View File

@@ -1,5 +1,33 @@
# 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.修复极少数情况下当节点文本的宽度为小数时显示发生换行的问题。
新增1.打包后的库支持获取内置常量、主题等数据。 2.支持配置鼠标滚轮方向对应的缩放行为。 3.节点图片支持拖拽调整大小。
## 0.6.4-fix.1
新增1.鼠标滚轮缩放时默认以鼠标当前位置为中心进行缩放,可以通过配置关闭该特性。
修复1.修复改变了画布大小后缩放中心点默认值不随之更新的问题。
## 0.6.4
新增1.默认以画布中心点进行缩放。 2.优化移动端双指缩放,以双指中心位置为中心点进行缩放。
## 0.6.3
修复1.修复概要节点会响应插入节点快捷键的问题。

View File

@@ -1,6 +1,20 @@
<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文件导入报错的问题 2.修复极少数情况下当节点文本的宽度为小数时显示发生换行的问题</p>
<p>新增1.打包后的库支持获取内置常量主题等数据 2.支持配置鼠标滚轮方向对应的缩放行为 3.节点图片支持拖拽调整大小</p>
<h2>0.6.4-fix.1</h2>
<p>新增1.鼠标滚轮缩放时默认以鼠标当前位置为中心进行缩放可以通过配置关闭该特性</p>
<p>修复1.修复改变了画布大小后缩放中心点默认值不随之更新的问题</p>
<h2>0.6.4</h2>
<p>新增1.默认以画布中心点进行缩放 2.优化移动端双指缩放以双指中心位置为中心点进行缩放</p>
<h2>0.6.3</h2>
<p>修复1.修复概要节点会响应插入节点快捷键的问题</p>
<p>新增1.支持自定义节点内容</p>

View File

@@ -45,6 +45,7 @@ const mindMap = new MindMap({
| customHandleMousewheelv0.4.3+ | Function | null | 自定义鼠标滚轮事件处理,可以传一个函数,回调参数为事件对象 | |
| mousewheelActionv0.4.3+ | String | zoom | 鼠标滚轮的行为,`zoom`(放大缩小)、`move`(上下移动)。如果`customHandleMousewheel`传了自定义函数,这个属性不生效 | |
| mousewheelMoveStepv0.4.3+ | Number | 100 | 当`mousewheelAction`设为`move`时,可以通过该属性控制鼠标滚动一下视图移动的步长,单位`px` | |
| mousewheelZoomActionReversev0.6.5+ | Boolean | false | 当mousewheelAction设为zoom时默认向前滚动是缩小向后滚动是放大如果该属性设为true那么会反过来 | |
| defaultInsertSecondLevelNodeTextv0.4.7+ | String | 二级节点 | 默认插入的二级节点的文字 | |
| defaultInsertBelowSecondLevelNodeTextv0.4.7+ | String | 分支主题 | 默认插入的二级以下节点的文字 | |
| expandBtnStylev0.5.0+ | Object | { color: '#808080', fill: '#fff' } | 展开收起按钮的颜色 | |
@@ -69,6 +70,7 @@ const mindMap = new MindMap({
| beforeTextEditv0.6.0+ | Function/null | null | 节点即将进入编辑前的回调方法如果该方法返回true以外的值那么将取消编辑函数可以返回一个值或一个Promise回调参数为节点实例 | |
| isUseCustomNodeContentv0.6.3+ | Boolean | false | 是否自定义节点内容 | |
| customCreateNodeContentv0.6.3+ | Function/null | null | 如果`isUseCustomNodeContent`设为`true`,那么需要使用该选项传入一个方法,接收节点实例`node`为参数(如果要获取该节点的数据,可以通过`node.nodeData.data`需要返回自定义节点内容元素也就是DOM节点如果某个节点不需要自定义那么返回`null`即可 | |
| mouseScaleCenterUseMousePositionv0.6.4-fix.1+ | Boolean | true | 鼠标缩放是否以鼠标当前位置为中心点,否则以画布中心点 | |
### 水印配置
@@ -228,6 +230,9 @@ mindMap.setTheme('主题名称')
| hide_text_edit | 节点文本编辑框关闭事件 | textEditNode文本编辑框DOM节点、activeNodeList当前激活的所有节点列表 |
| scale | 放大缩小事件 | scale缩放比例 |
| node_img_dblclickv0.2.15+ | 节点内图片的双击事件 | this节点实例、e事件对象 |
| node_img_mouseenterv0.6.5+ | 节点内图片的鼠标移入事件 | this节点实例、imgNode图片节点、e事件对象 |
| node_img_mouseleavev0.6.5+ | 节点内图片的鼠标移出事件 | this节点实例、imgNode图片节点、e事件对象 |
| node_img_mousemovev0.6.5+ | 节点内图片的鼠标移动事件 | this节点实例、imgNode图片节点、e事件对象 |
| node_tree_render_endv0.2.16+ | 节点树渲染完毕事件 | |
| rich_text_selection_changev0.4.0+ | 当注册了`RichText`插件时可用。当节点编辑时,文本选区发生改变时触发 | hasRange是否存在选区、rectInfo选区的尺寸和位置信息、formatInfo选区的文本格式化信息 |
| transforming-dom-to-imagesv0.4.0+ | 当注册了`RichText`插件时可用。当`svg`中存在`DOM`节点时,导出为图片时会将`DOM`节点转换为图片,转换过程中会触发该事件,可用通过该事件给用户提示,告知目前转换到的节点 | index当前转换到的节点索引、len一共需要转换的节点数量 |

View File

@@ -176,6 +176,13 @@
<td></td>
</tr>
<tr>
<td>mousewheelZoomActionReversev0.6.5+</td>
<td>Boolean</td>
<td>false</td>
<td>当mousewheelAction设为zoom时默认向前滚动是缩小向后滚动是放大如果该属性设为true那么会反过来</td>
<td></td>
</tr>
<tr>
<td>defaultInsertSecondLevelNodeTextv0.4.7+</td>
<td>String</td>
<td>二级节点</td>
@@ -343,6 +350,13 @@
<td>如果<code>isUseCustomNodeContent</code>设为<code>true</code>那么需要使用该选项传入一个方法接收节点实例<code>node</code>为参数如果要获取该节点的数据可以通过<code>node.nodeData.data</code>需要返回自定义节点内容元素也就是DOM节点如果某个节点不需要自定义那么返回<code>null</code>即可</td>
<td></td>
</tr>
<tr>
<td>mouseScaleCenterUseMousePositionv0.6.4-fix.1+</td>
<td>Boolean</td>
<td>true</td>
<td>鼠标缩放是否以鼠标当前位置为中心点否则以画布中心点</td>
<td></td>
</tr>
</tbody>
</table>
<h3>水印配置</h3>
@@ -639,6 +653,21 @@ mindMap.setTheme(<span class="hljs-string">&#x27;主题名称&#x27;</span>)
<td>this节点实例e事件对象</td>
</tr>
<tr>
<td>node_img_mouseenterv0.6.5+</td>
<td>节点内图片的鼠标移入事件</td>
<td>this节点实例imgNode图片节点e事件对象</td>
</tr>
<tr>
<td>node_img_mouseleavev0.6.5+</td>
<td>节点内图片的鼠标移出事件</td>
<td>this节点实例imgNode图片节点e事件对象</td>
</tr>
<tr>
<td>node_img_mousemovev0.6.5+</td>
<td>节点内图片的鼠标移动事件</td>
<td>this节点实例imgNode图片节点e事件对象</td>
</tr>
<tr>
<td>node_tree_render_endv0.2.16+</td>
<td>节点树渲染完毕事件</td>
<td></td>

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

@@ -0,0 +1,16 @@
# NodeImgAdjust插件
> v0.6.5+
该插件提供拖拽调整节点内图片大小的功能。
## 注册
```js
import MindMap from 'simple-mind-map'
import NodeImgAdjust from 'simple-mind-map/src/plugins/NodeImgAdjust.js'
MindMap.usePlugin(NodeImgAdjust)
```
注册完且实例化`MindMap`后可通过`mindMap.nodeImgAdjust`获取到该实例。

View File

@@ -0,0 +1,27 @@
<template>
<div>
<h1>NodeImgAdjust插件</h1>
<blockquote>
<p>v0.6.5+</p>
</blockquote>
<p>该插件提供拖拽调整节点内图片大小的功能</p>
<h2>注册</h2>
<pre class="hljs"><code><span class="hljs-keyword">import</span> MindMap <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map&#x27;</span>
<span class="hljs-keyword">import</span> NodeImgAdjust <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/plugins/NodeImgAdjust.js&#x27;</span>
MindMap.usePlugin(NodeImgAdjust)
</code></pre>
<p>注册完且实例化<code>MindMap</code>后可通过<code>mindMap.nodeImgAdjust</code>获取到该实例</p>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>

View File

@@ -106,6 +106,8 @@ cd web
npm run buildLibrary
```
打包入口为`simple-mind-map/full.js`,默认会引入所有插件,如果你不需要所有插件的话,那么可以修改该文件,只引入你需要的插件,这样可以减少打包后的文件体积。
`simple-mind-map`库的`package.json`文件提供了两个导出字段:
```json

Some files were not shown because too many files have changed in this diff Show More