vue2-mind-map
Version:
`vue2-mind-map` 是一个功能丰富的思维导图可视化组件。该组件提供了完整的思维导图展示和交互功能,支持多种数据格式输入、灵活的布局配置、丰富的样式定制和流畅的用户交互体验。目前基于 Vue 2 构建。
541 lines (436 loc) • 17.4 kB
Markdown
# vue2-mind-map 组件使用文档
`vue2-mind-map` 是一个功能丰富的思维导图可视化组件。该组件提供了完整的思维导图展示和交互功能,支持多种数据格式输入、灵活的布局配置、丰富的样式定制和流畅的用户交互体验。目前基于 Vue 2 构建。
### TODO
- 支持 Vue 3
- 触摸屏支持
- 操作历史及回滚
- 小地图
## 数据结构说明
### TreeNode(树形数据结构)
用于描述具有层级关系的思维导图节点,通过 `children` 数组直接表示父子关系:
- **id** `string` (可选) - 节点唯一标识符
- **position** `object` (可选) - 节点位置坐标,格式:`{ x: number, y: number }`
- **size** `object` (可选) - 节点尺寸,格式:`{ x: number, y: number }`
- **children** `array` (可选) - 子节点数组,每个元素都是 TreeNode 结构
- **collapsed** `boolean` (可选) - 节点是否折叠,默认为 `false`
- **content** `string|HTMLElement|array|function` (可选) - 节点显示内容
- **[自定义属性]** `any` - 可以添加任意自定义属性
**示例**:
```javascript
const treeData = {
id: 'root',
content: '根节点',
children: [
{
id: 'child1',
content: '子节点1',
collapsed: false,
children: [
{ id: 'grandchild1', content: '孙节点1' }
]
},
{ id: 'child2', content: '子节点2' }
]
}
```
### FlatNode(扁平数据结构)
用于描述扁平化的思维导图节点,通过 `parentId` 间接表示父子关系:
- **id** `string` (可选) - 节点唯一标识符
- **parentId** `string` (可选) - 父节点ID,根节点可以不设置
- **position** `object` (可选) - 节点位置坐标,格式:`{ x: number, y: number }`
- **size** `object` (可选) - 节点尺寸,格式:`{ x: number, y: number }`
- **collapsed** `boolean` (可选) - 节点是否折叠,默认为 `false`
- **content** `string|HTMLElement|array|function` (可选) - 节点显示内容
- **[自定义属性]** `any` - 可以添加任意自定义属性
**示例**:
```javascript
const flatData = [
{ id: 'root', content: '根节点' },
{ id: 'child1', parentId: 'root', content: '子节点1' },
{ id: 'child2', parentId: 'root', content: '子节点2' },
{ id: 'grandchild1', parentId: 'child1', content: '孙节点1' }
]
```
## 配置选项说明
### 对齐配置 (MindMapAlign)
控制思维导图节点的对齐方式:
- **mode** `string` - 对齐模式,可选值:
- `"cross-axis-start-edge"` - 交叉轴起始边对齐(默认)
- `"cross-axis-center"` - 交叉轴居中对齐
- `"node-center"` - 以指定节点为中心对齐
- **targetNodeId** `string` (可选) - 目标节点ID,仅在 `mode` 为 `"node-center"` 时使用
### 拖拽配置 (MindMapDragOptions)
控制思维导图的拖拽行为:
- **enableNodeDrag** `boolean` (可选) - 是否启用节点拖拽,默认 `false`
- **enableCanvasDrag** `boolean` (可选) - 是否启用画布拖拽,默认 `true`
- **dragNodeButton** `number|array` (可选) - 拖拽节点的鼠标按钮,默认左键(0)
- **dragCanvasButton** `number|array` (可选) - 拖拽画布的鼠标按钮,默认右键(2)和中键(1)
- **useThrottle** `boolean` (可选) - 是否使用节流减少事件触发频率,默认 `true`
**鼠标按钮值**:左键=0,中键=1,右键=2
### 缩放配置 (MindMapZoomOptions)
控制思维导图的缩放行为:
- **enableWheelZoom** `boolean` (可选) - 是否启用滚轮缩放,默认 `true`
- **zoomStep** `number` (可选) - 每次缩放步长,默认 `0.1` (10%)
- **invertWheelDirection** `boolean` (可选) - 是否反转滚轮方向,默认 `false`
- **minZoom** `number` (可选) - 最小缩放比例,默认 `0.1` (10%)
- **maxZoom** `number` (可选) - 最大缩放比例,默认 `5.0` (500%)
## Props
### 基础配置
**`data`** (必填)
- 类型: TreeNode 对象 或 FlatNode 数组
- 描述: 思维导图的数据源
**`direction`**
- 类型: `"horizontal" | "vertical"`
- 默认值: `"horizontal"`
- 描述: 思维导图的布局方向
### 样式配置
**`line-style`**
- 类型: `"straight" | "curved" | "zigzag"`
- 默认值: `"curved"`
- 描述: 连接线的样式
**`line-color`**
- 类型: `String`
- 默认值: `"#000000"`
- 描述: 连接线的颜色
**`line-width`**
- 类型: `Number`
- 默认值: `1`
- 描述: 连接线的宽度,单位像素
**`node-border-width`**
- 类型: `Number`
- 默认值: `1`
- 描述: 节点边框的宽度,单位像素
**`node-bg-color`**
- 类型: `String`
- 默认值: `"#FFFFFF"`
- 描述: 节点背景颜色
**`node-border-color`**
- 类型: `String`
- 默认值: `"#000000"`
- 描述: 节点边框颜色
### 布局配置
**`align`**
- 类型: MindMapAlign 配置对象
- 默认值: `{ mode: "cross-axis-start-edge" }`
- 描述: 思维导图对齐方式配置
**`sibling-separation`**
- 类型: `Number`
- 默认值: `25`
- 描述: 同级节点之间的间距,单位像素
**`level-separation`**
- 类型: `Number`
- 默认值: `50`
- 描述: 层级之间的间距,单位像素
**`initial-offset`**
- 类型: `Object`
- 默认值: `undefined`
- 描述: 思维导图的初始偏移量,格式:`{ x: number, y: number }`
### 交互配置
**`drag-options`**
- 类型: MindMapDragOptions 配置对象
- 默认值: `{ enableNodeDrag: false, enableCanvasDrag: true, dragCanvasButton: [2, 1], useThrottle: true }`
- 描述: 拖拽相关配置
**`zoom-options`**
- 类型: MindMapZoomOptions 配置对象
- 默认值: `{ enableWheelZoom: true, zoomStep: 0.1, invertWheelDirection: false, minZoom: 0.1, maxZoom: 5.0 }`
- 描述: 缩放相关配置
## 方法 (Methods)
你可以通过获取 `MindMap.vue` 组件的引用 (ref) 来调用其公开方法。
**示例**:
```vue
<template>
<mind-map ref="mindMapRef" :data="mindMapData" />
</template>
<script>
export default {
mounted() {
// 调用 rebuild 方法
this.$refs.mindMapRef.rebuild();
}
};
</script>
```
### 布局和渲染控制
**`rebuild()`**
- 描述: 重置思维导图布局并重新渲染
- 返回: `void`
**`clear()`**
- 描述: 清空思维导图实例的所有内容和状态
- 返回: `void`
**`setHorizontal(isHorizontal)`**
- 参数: `isHorizontal: boolean` - 布局方向,`true` 为水平,`false` 为垂直
- 描述: 设置思维导图布局方向
- 返回: `void`
### 缩放控制
**`zoomIn(cx?, cy?)`**
- 参数:
- `cx?: number` - 缩放中心点的 X 坐标(可选)
- `cy?: number` - 缩放中心点的 Y 坐标(可选)
- 描述: 放大思维导图
- 返回: `void`
**`zoomOut(cx?, cy?)`**
- 参数:
- `cx?: number` - 缩放中心点的 X 坐标(可选)
- `cy?: number` - 缩放中心点的 Y 坐标(可选)
- 描述: 缩小思维导图
- 返回: `void`
**`resetZoom(cx?, cy?)`**
- 参数:
- `cx?: number` - 缩放中心点的 X 坐标(可选)
- `cy?: number` - 缩放中心点的 Y 坐标(可选)
- 描述: 重置思维导图缩放比例到 1.0
- 返回: `void`
### 节点操作
**`setNodeSize(nodeId, size)`**
- 参数:
- `nodeId: string` - 要设置尺寸的节点 ID
- `size: { width?: number; height?: number }` - 新的节点尺寸
- 描述: 设置指定节点的尺寸
- 返回: `boolean | undefined` - 如果节点找到并成功设置尺寸,则返回 `true`;否则返回 `false`
**`focusOnNode(nodeId)`**
- 参数: `nodeId: string` - 节点ID
- 描述: 将指定节点聚焦到视口中心
- 返回: `boolean | undefined` - 如果节点找到并成功聚焦,则返回 `true`;否则返回 `false`
**`toggleNodeCollapse(nodeId)`**
- 参数: `nodeId: string` - 节点ID
- 描述: 切换节点的折叠状态
- 返回: `boolean | undefined` - 如果节点找到并成功切换状态,则返回 `true`;否则返回 `false`
**`setNodeCollapsed(nodeId, collapsed)`**
- 参数:
- `nodeId: string` - 节点ID
- `collapsed: boolean` - 要设置的折叠状态
- 描述: 设置节点的折叠状态
- 返回: `boolean | undefined` - 如果节点找到并成功设置状态,则返回 `true`;否则返回 `false`
**`getNodeCollapsed(nodeId)`**
- 参数: `nodeId: string` - 节点ID
- 描述: 获取节点的折叠状态
- 返回: `boolean | undefined` - 节点的折叠状态,如果节点不存在则返回 `undefined`
**`expandAllNodes()`**
- 描述: 展开所有节点
- 返回: `number` - 成功展开的节点数量
**`collapseAllNodes()`**
- 描述: 折叠所有非根节点
- 返回: `number` - 成功折叠的节点数量
**`updateNodeContents(nodeIds?)`**
- 参数: `nodeIds?: string[]` - 要更新内容的节点 ID 数组,不传参时默认更新所有节点
- 描述: 更新指定节点的内容,此方法会根据节点的 `content` 属性或外部传入的 `contentCallback` 来重新解析和设置节点内部的内容
- 返回: `Promise<number>` - 成功更新的节点个数
## 插槽 (Slots)
### 默认插槽
- **名称**: `default`(默认插槽)
- **作用域**: `{ node: TreeNode | FlatNode }`
- **描述**: 自定义节点内容的显示。你可以通过这个作用域插槽来为每个节点提供自定义的 HTML 结构。
- **`node`**: 当前正在渲染的节点对象,包含节点的 ID、位置、子节点等信息。也就是你通过 prop data 传入的节点。
**基础示例**:
```vue
<template>
<mind-map :data="mindMapData">
<template v-slot="{ node }">
<div class="custom-node">
<strong>{{ node.id }}</strong>
<p v-if="node.data && node.data.description">{{ node.data.description }}</p>
</div>
</template>
</mind-map>
</template>
```
**条件渲染示例**:
```vue
<template>
<mind-map :data="mindMapData">
<template v-slot="{ node }">
<!-- 方式1: 使用 v-show 控制显示 -->
<div v-show="slotIndex === 1" class="node-style-1">
样式1 - {{ node.id }}
</div>
<div v-show="slotIndex === 2" class="node-style-2">
样式2 - {{ node.id }}
</div>
<!-- 方式2: 使用 v-if 条件渲染 -->
<div v-if="node.type === 'important'" class="important-node">
重要节点: {{ node.id }}
</div>
<div v-else class="normal-node">
普通节点: {{ node.id }}
</div>
</template>
</mind-map>
</template>
```
**注意事项**:
- 插槽内容会被缓存以提高性能,每个节点对应一个 Vue 实例
- 插槽渲染失败时会显示错误信息,并回退到默认渲染
## 事件 (Events)
你可以通过监听 `MindMap.vue` 组件的事件来响应思维导图的状态变化。
### 画布拖拽事件
**`canvas-drag-start`**
- **触发时机**: 当画布开始被拖拽时触发
- **事件数据**: `MindMapCanvasEvent` 对象
- `originalEvent: MouseEvent` - 原始鼠标事件
- `button: MouseButton | null` - 按下的鼠标按钮
- `mouseX: number` - 拖拽开始时鼠标在容器内的 X 坐标
- `mouseY: number` - 拖拽开始时鼠标在容器内的 Y 坐标
- **描述**: 此事件在画布拖拽开始时触发,可以用于记录拖拽开始状态或执行相关初始化操作
**`canvas-drag`**
- **触发时机**: 当画布正在被拖拽时触发
- **事件数据**: `MindMapCanvasEvent` 对象
- `originalEvent: MouseEvent` - 原始鼠标事件
- `button: MouseButton | null` - 按下的鼠标按钮
- `mouseX: number` - 当前鼠标在容器内的 X 坐标
- `mouseY: number` - 当前鼠标在容器内的 Y 坐标
- `deltaX: number` - X 轴拖拽距离
- `deltaY: number` - Y 轴拖拽距离
- **描述**: 此事件在画布拖拽过程中持续触发,可以用于同步画布位置状态或执行跟随操作
**`canvas-drag-end`**
- **触发时机**: 当画布拖拽结束时触发
- **事件数据**: `MindMapCanvasEvent` 对象
- `originalEvent: MouseEvent` - 原始鼠标事件
- `button: MouseButton | null` - 按下的鼠标按钮
- `mouseX: number` - 拖拽结束时鼠标在容器内的 X 坐标
- `mouseY: number` - 拖拽结束时鼠标在容器内的 Y 坐标
- **描述**: 此事件在画布拖拽结束时触发,可以用于保存最终状态或执行清理操作
**使用示例**:
```vue
<template>
<mind-map
:data="mindMapData"
@canvas-drag-start="handleCanvasDragStart"
@canvas-drag="handleCanvasDrag"
@canvas-drag-end="handleCanvasDragEnd"
/>
</template>
<script>
export default {
methods: {
handleCanvasDragStart(event) {
console.log('画布开始拖拽:', event.mouseX, event.mouseY);
},
handleCanvasDrag(event) {
console.log('画布拖拽中:', `移动了 ${event.deltaX}, ${event.deltaY}`);
},
handleCanvasDragEnd(event) {
console.log('画布拖拽结束');
}
}
};
</script>
```
### 缩放事件
**`zoom-change`**
- **触发时机**: 当思维导图的缩放比例发生变化时触发
- **事件数据**: `MindMapZoomEvent` 对象
- `originalEvent?: WheelEvent` - 原始滚轮事件(如果是滚轮触发的缩放)
- `scale: Vector` - 当前的缩放比例向量 {x, y}
- `mouseX: number` - 缩放中心在容器内的 X 坐标
- `mouseY: number` - 缩放中心在容器内的 Y 坐标
- **描述**: 此事件在缩放操作(放大、缩小、重置)完成后触发,可以用于同步缩放状态或执行相关联的操作
**使用示例**:
```vue
<template>
<mind-map
:data="mindMapData"
@zoom-change="handleZoomChange"
/>
</template>
<script>
export default {
methods: {
handleZoomChange(event) {
console.log('缩放比例变化:', `${event.scale.x * 100}%`);
console.log('缩放中心:', event.mouseX, event.mouseY);
// 同步外部缩放状态
this.currentZoomLevel = event.scale.x;
}
}
};
</script>
```
### 容器尺寸变化事件
**`container-resize`**
- **触发时机**: 当思维导图容器尺寸发生变化时触发
- **事件数据**: `MindMapContainerResizeEvent` 对象
- `width: number` - 新的容器宽度
- `height: number` - 新的容器高度
- **描述**: 此事件在容器尺寸变化时触发,可以用于响应式布局调整或执行相关的重新计算操作
**使用示例**:
```vue
<template>
<mind-map
:data="mindMapData"
@container-resize="handleContainerResize"
/>
</template>
<script>
export default {
methods: {
handleContainerResize(event) {
console.log('容器尺寸变化:', `${event.width} x ${event.height}`);
// 根据新尺寸调整相关组件
this.adjustUIForNewSize(event.width, event.height);
},
adjustUIForNewSize(width, height) {
// 执行尺寸相关的调整逻辑
}
}
};
</script>
```
### 布局更新事件
**`layout-updated`**
- **触发时机**: 当思维导图的布局发生变化并更新完成后触发(例如,从水平切换到垂直布局,或重新计算布局后)
- **事件数据**: `MindMapLayoutEvent` 对象
- `direction: 'horizontal' | 'vertical'` - 当前的布局方向
- **描述**: 此事件在思维导图完成布局计算和渲染后触发,可以用于同步外部状态或执行需要在布局稳定后进行的操作
**使用示例**:
```vue
<template>
<mind-map
:data="mindMapData"
@layout-updated="handleLayoutUpdated"
/>
</template>
<script>
export default {
methods: {
handleLayoutUpdated(event) {
console.log('布局已更新,当前方向:', event.direction);
// 执行布局更新后的逻辑
}
}
};
</script>
```
### 节点折叠状态变化事件
**`node-collapse-changed`**
- **触发时机**: 当任何节点的折叠/展开状态发生变化时触发
- **事件数据**: `MindMapNodeCollapseEvent` 对象
- `nodeId: string` - 折叠状态发生变化的节点 ID
- `collapsed: boolean` - 当前的折叠状态,`true` 表示已折叠,`false` 表示已展开
- `node: TreeNode | FlatNode` - 被折叠/展开的节点对应的原始数据
- **描述**: 此事件在调用折叠相关方法(如 `toggleNodeCollapse`、`setNodeCollapsed`、`expandAllNodes`、`collapseAllNodes`)时触发,可以用于同步外部状态或记录操作历史
**使用示例**:
```vue
<template>
<mind-map
:data="mindMapData"
@node-collapse-changed="handleNodeCollapseChanged"
/>
</template>
<script>
export default {
methods: {
handleNodeCollapseChanged(event) {
console.log(`节点 ${event.nodeId} ${event.collapsed ? '折叠' : '展开'}了`);
console.log('节点数据:', event.node);
// 同步外部状态
this.updateNodeState(event.nodeId, event.collapsed);
},
updateNodeState(nodeId, collapsed) {
// 更新外部数据或状态管理
}
}
};
</script>
```
**批量操作事件**:
当调用 `expandAllNodes()` 或 `collapseAllNodes()` 方法时,每个状态发生变化的节点都会单独触发一次 `node-collapse-changed` 事件。