# 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` (可选) - 是否启用节点拖拽，默认 `true`
- **enableCanvasDrag** `boolean` (可选) - 是否启用画布拖拽，默认 `true`
- **dragNodeButton** `number|array` (可选) - 拖拽节点的鼠标按钮，默认左键(0)
- **dragCanvasButton** `number|array` (可选) - 拖拽画布的鼠标按钮，默认右键(2)和中键(1)
- **useThrottle** `boolean` (可选) - 是否使用节流减少事件触发频率，默认 `true`
- **throttleWait** `number` (可选) - 节流函数的等待时间，默认 `16` 毫秒

**鼠标按钮值**：左键=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%)
- **useThrottle** `boolean` (可选) - 是否使用节流减少事件触发频率，默认 `true`
- **throttleWait** `number` (可选) - 节流函数的等待时间，默认 `16` 毫秒

### 全屏配置 (FullscreenOptions)

控制思维导图的全屏行为：

- **fullscreenBgColor** `string` (可选) - 全屏模式下的背景颜色，默认 `#FFFFFF`
- **fullscreenMode** `string` (可选) - 全屏模式，可选值：
  - `"native"` - 使用浏览器原生全屏 API（默认）
  - `"pseudo"` - 使用 CSS 模拟全屏效果

## 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: true, enableCanvasDrag: true, dragNodeButton: 0, dragCanvasButton: [2, 1], useThrottle: true, throttleWait: 16 }`
- 描述: 拖拽相关配置。`dragNodeButton` 和 `dragCanvasButton` 的值可以是单个鼠标按钮或按钮数组 (左键=0, 中键=1, 右键=2)。

**`zoom-options`**
- 类型: `MindMapZoomOptions` 配置对象
- 默认值: `{ enableWheelZoom: true, zoomStep: 0.1, invertWheelDirection: false, minZoom: 0.1, maxZoom: 5.0, useThrottle: true, throttleWait: 16 }`
- 描述: 缩放相关配置。

**`full-screen-options`**
- 类型: `FullscreenOptions` 配置对象
- 默认值: `{ fullscreenBgColor: '#FFFFFF', fullscreenMode: 'native' }`
- 描述: 全屏相关配置。

## 方法 (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>` - 成功更新的节点个数

### 全屏控制

**`enterFullscreen(mode?)`**
- 参数: 
  - `mode?: "native" | "pseudo"` (可选) - 全屏模式 ('native' 或 'pseudo')。如果未提供，则使用组件 prop 中配置的默认模式。
- 描述: 进入全屏模式。
- 返回: `Promise<boolean | undefined>` - 成功时 resolve true，失败时 reject error。

**`exitFullscreen()`**
- 描述: 退出全屏模式。会自动检测并退出当前激活的全屏模式 (原生或模拟)。
- 返回: `Promise<boolean | undefined>` - 成功时 resolve true，失败时 reject error。

**`toggleFullscreen(mode?)`**
- 参数: 
  - `mode?: "native" | "pseudo"` (可选) - 全屏模式 ('native' 或 'pseudo')。如果未提供，则使用组件 prop 中配置的默认模式。
- 描述: 切换全屏状态。
- 返回: `Promise<boolean | undefined>` - 成功时 resolve 新的全屏状态 (true 为进入，false 为退出)。

## 插槽 (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` 事件。