# 组织树与脑图操作能力一致 Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** 让“组织树模式”和“脑图模式”只是展示形式不同，组织节点和人员节点的操作能力完全一致。

**Architecture:** 保留 `SystemPermissionView.vue` 作为唯一组织动作编排层，继续复用现有 `nodeActions(node)`、`runNodeAction(actionKey, node)`、各类 drawer 打开函数和删除/授权函数。`OrgPermissionTree.vue` 只负责展示组织树、选中节点、发出节点菜单事件，不新增第二套业务动作逻辑。

**Tech Stack:** Vue 3 `<script setup>`、现有静态 Node test、Vite、现有 `OrgPermissionTree.vue` / `SystemPermissionView.vue` 组件体系。

---

## File Structure

- Modify: `frontend/src/components/OrgPermissionTree.vue`
  - 增加右侧“节点操作”入口。
  - 继续支持组织树节点右键菜单。
  - 通过 `node-contextmenu` emit 当前节点和菜单坐标，不直接执行业务动作。
  - 调整右侧 header 按钮布局，让“节点操作”和“新增人员”共存。

- Modify: `frontend/src/components/OrgPermissionTree.test.js`
  - 锁定组织树存在“节点操作”按钮。
  - 锁定组织树节点右键仍然 emit `open-menu`。
  - 锁定组织树不新增第二套动作函数，只 emit `node-contextmenu`。

- Modify: `frontend/src/views/SystemPermissionView.vue`
  - 复用现有 `openContextMenu(eventPayload)` 和 `runNodeAction(actionKey, node)`。
  - 必要时给当前节点操作入口提供更稳定的菜单坐标处理。
  - 不复制 `nodeActions` 到子组件。

- Modify: `frontend/src/views/SystemPermissionView.test.js`
  - 锁定组织树仍绑定 `@node-contextmenu="openContextMenu"`。
  - 锁定组织树的“节点操作”最终仍进入同一套 `contextMenuActions` / `runNodeAction`。

- Modify: `frontend/src/styles/main.css` if needed
  - 如果组件 scoped 样式不够覆盖全局表格/抽屉样式，补少量全局样式。
  - 不引入新的视觉体系。

---

## Task 1: 锁定组织树动作入口契约

**Files:**
- Modify: `frontend/src/components/OrgPermissionTree.test.js`

- [ ] **Step 1: 写失败测试，要求组织树有显式节点操作入口**

在 `OrgPermissionTree.test.js` 末尾追加：

```js
describe("OrgPermissionTree node action parity", () => {
  it("exposes a visible current-node action entry that emits the shared node context menu event", () => {
    assert.match(source, /节点操作/);
    assert.match(source, /@click="openSelectedNodeMenu"/);
    assert.match(source, /function openSelectedNodeMenu\(event\)/);
    assert.match(source, /emit\("node-contextmenu"/);
    assert.doesNotMatch(source, /function runNodeAction\(/);
    assert.doesNotMatch(source, /function nodeActions\(/);
  });
});
```

- [ ] **Step 2: 运行测试确认失败**

Run:

```bash
cd /Users/souplearn/Gitlab/py/ForgeFlow-ERP/frontend
node src/components/OrgPermissionTree.test.js
```

Expected:

```text
not ok ... exposes a visible current-node action entry ...
```

失败原因应是当前源码没有 `节点操作`、`openSelectedNodeMenu`。

- [ ] **Step 3: 不修改生产代码，先提交测试是不需要的**

此任务只建立红灯，不单独提交。

---

## Task 2: 在组织树右侧 header 增加“节点操作”入口

**Files:**
- Modify: `frontend/src/components/OrgPermissionTree.vue`

- [ ] **Step 1: 修改右侧 header 按钮区**

将当前右侧 header 中的单个“新增人员”按钮：

```vue
<button
  class="primary-button"
  type="button"
  :disabled="!canAddEmployee"
  @click="addEmployee"
>
  新增人员
</button>
```

替换为：

```vue
<div class="employee-head-actions">
  <button
    class="ghost-button node-action-button"
    type="button"
    :disabled="!selectedNode"
    @click="openSelectedNodeMenu"
  >
    节点操作
  </button>
  <button
    class="primary-button"
    type="button"
    :disabled="!canAddEmployee"
    @click="addEmployee"
  >
    新增人员
  </button>
</div>
```

- [ ] **Step 2: 添加 `openSelectedNodeMenu` 函数**

在 `openNodeMenu(payload)` 下方添加：

```js
function openSelectedNodeMenu(event) {
  if (!selectedNode.value) {
    return;
  }
  const rect = event.currentTarget?.getBoundingClientRect?.();
  emit("node-contextmenu", {
    node: selectedNode.value,
    x: rect ? rect.left : event.clientX,
    y: rect ? rect.bottom + 6 : event.clientY
  });
}
```

说明：这里仍然只 emit `node-contextmenu`，由父组件现有 `openContextMenu` 接管菜单，不在子组件里实现动作。

- [ ] **Step 3: 添加 scoped 样式**

在 `.employee-head` 附近增加：

```css
.employee-head-actions {
  display: inline-flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 10px;
}

.node-action-button {
  border: 1px solid rgba(148, 163, 184, 0.5);
  background: #ffffff;
  color: #334155;
}

.node-action-button:hover:not(:disabled) {
  background: #eff6ff;
  color: #1d4ed8;
}
```

- [ ] **Step 4: 运行组件测试确认 Task 1 变绿**

Run:

```bash
cd /Users/souplearn/Gitlab/py/ForgeFlow-ERP/frontend
node src/components/OrgPermissionTree.test.js
```

Expected:

```text
# pass ...
```

---

## Task 3: 锁定父页面复用同一套节点菜单

**Files:**
- Modify: `frontend/src/views/SystemPermissionView.test.js`

- [ ] **Step 1: 写测试确认组织树和脑图共用 `openContextMenu`**

在 `SystemPermissionView.test.js` 末尾追加：

```js
describe("SystemPermissionView organization view action parity", () => {
  it("routes both mind map and tree node actions through the same context menu action pipeline", () => {
    assert.match(source, /<OrgMindMap[\s\S]*?@node-contextmenu="openContextMenu"/);
    assert.match(source, /<OrgPermissionTree[\s\S]*?@node-contextmenu="openContextMenu"/);
    assert.match(source, /const contextMenuActions = computed\(\(\) => \(contextMenu\.node \? nodeActions\(contextMenu\.node\) : \[\]\)\);/);
    assert.match(source, /function runNodeAction\(actionKey, node\)/);
    assert.match(source, /openOrgRenameDrawer\(node\)/);
    assert.match(source, /openOrgManagerDrawer\(node\)/);
    assert.match(source, /openOrgCreateDrawer\(node,\s*"DEPARTMENT"\)/);
    assert.match(source, /openOrgEmployeeDrawer\(node\)/);
  });
});
```

- [ ] **Step 2: 运行测试**

Run:

```bash
cd /Users/souplearn/Gitlab/py/ForgeFlow-ERP/frontend
node src/views/SystemPermissionView.test.js
```

Expected:

```text
# pass ...
```

如果失败，说明当前源码实际函数名或字符串不同；不要新增第二套函数，先核对现有 `nodeActions` 和 `runNodeAction`，再调整测试为真实函数名。

---

## Task 4: 优化上下文菜单打开位置与组织树使用体验

**Files:**
- Modify: `frontend/src/views/SystemPermissionView.vue`

- [ ] **Step 1: 检查现有 `openContextMenu`**

确认当前函数类似：

```js
function openContextMenu(eventPayload) {
  selectedNode.value = eventPayload.node;
  contextMenu.visible = true;
  contextMenu.x = eventPayload.x;
  contextMenu.y = eventPayload.y;
  contextMenu.node = eventPayload.node;
}
```

- [ ] **Step 2: 改为带视口保护的位置计算**

将 `openContextMenu` 替换为：

```js
function openContextMenu(eventPayload) {
  selectedNode.value = eventPayload.node;
  const menuWidth = 220;
  const menuHeight = 280;
  const viewportWidth = window.innerWidth || 1280;
  const viewportHeight = window.innerHeight || 720;
  contextMenu.visible = true;
  contextMenu.x = Math.max(12, Math.min(Number(eventPayload.x || 12), viewportWidth - menuWidth - 12));
  contextMenu.y = Math.max(12, Math.min(Number(eventPayload.y || 12), viewportHeight - menuHeight - 12));
  contextMenu.node = eventPayload.node;
}
```

说明：组织树右侧“节点操作”按钮会把菜单开在按钮下方；如果靠右或靠下，菜单不应跑出屏幕。

- [ ] **Step 3: 运行父页面测试**

Run:

```bash
cd /Users/souplearn/Gitlab/py/ForgeFlow-ERP/frontend
node src/views/SystemPermissionView.test.js
```

Expected:

```text
# pass ...
```

---

## Task 5: 手动浏览器验收

**Files:**
- No code changes.

- [ ] **Step 1: 打开系统权限管理页面**

Use in-app Browser:

```text
http://localhost:5173/system-permissions
```

- [ ] **Step 2: 验收组织树模式下组织节点操作**

操作：

1. 切换到“组织树模式”。
2. 点击左侧“总公司”节点。
3. 点击右侧“节点操作”。

Expected:

```text
菜单出现，包含：重命名、添加该节点的主管、新增分公司节点。
```

- [ ] **Step 3: 验收分公司节点操作**

操作：

1. 点击“旭升车间”或“嘉恒车间”。
2. 点击“节点操作”。

Expected:

```text
菜单出现，包含：重命名、添加该节点的主管、新增部门节点、删除节点。
```

- [ ] **Step 4: 验收部门节点操作**

操作：

1. 点击“旭升加工部”或“嘉恒生产部”。
2. 点击“节点操作”。

Expected:

```text
菜单出现，包含：重命名、添加该节点的主管、新增小组节点、新增人员、删除节点。
```

- [ ] **Step 5: 验收右键入口**

操作：

1. 在组织树左侧任意组织节点上右键。

Expected:

```text
弹出的菜单内容和右侧“节点操作”按钮一致。
```

- [ ] **Step 6: 验收人员操作仍正常**

操作：

1. 在右侧人员列表点击“人员授权”。
2. 确认弹窗仍为卡片式角色授权界面。
3. 关闭弹窗。

Expected:

```text
人员授权弹窗打开正常，不受节点操作入口影响。
```

---

## Task 6: 回归验证与提交

**Files:**
- All modified files from earlier tasks.

- [ ] **Step 1: 运行关键测试**

Run:

```bash
cd /Users/souplearn/Gitlab/py/ForgeFlow-ERP/frontend
node src/components/OrgPermissionTree.test.js
node src/views/SystemPermissionView.test.js
node src/utils/orgTreeEmployees.test.js
node src/App.test.js
```

Expected:

```text
# fail 0
```

- [ ] **Step 2: 运行构建**

Run:

```bash
cd /Users/souplearn/Gitlab/py/ForgeFlow-ERP/frontend
npm run build
```

Expected:

```text
✓ built
```

允许出现当前项目既有的大 chunk warning，不作为阻断。

- [ ] **Step 3: 检查 diff**

Run:

```bash
cd /Users/souplearn/Gitlab/py/ForgeFlow-ERP
git diff --stat
git diff -- frontend/src/components/OrgPermissionTree.vue frontend/src/components/OrgPermissionTree.test.js frontend/src/views/SystemPermissionView.vue frontend/src/views/SystemPermissionView.test.js
```

Expected:

```text
只包含组织树动作入口、菜单位置保护、相关测试和少量样式。
```

- [ ] **Step 4: 等用户确认后提交 main**

Run only after user confirms:

```bash
cd /Users/souplearn/Gitlab/py/ForgeFlow-ERP
git add frontend/src/components/OrgPermissionTree.vue frontend/src/components/OrgPermissionTree.test.js frontend/src/views/SystemPermissionView.vue frontend/src/views/SystemPermissionView.test.js
git commit -m "补齐组织树节点操作能力"
git push origin main
```

Expected:

```text
main -> main
```

- [ ] **Step 5: 同步 BH_DEV**

Run only after main push succeeds:

```bash
cd /Users/souplearn/Gitlab/py/ForgeFlow-ERP
git switch BH_DEV
git pull --ff-only origin BH_DEV
git cherry-pick <main_commit_hash>
```

Expected:

```text
[BH_DEV <hash>] 补齐组织树节点操作能力
```

- [ ] **Step 6: 在 BH_DEV 运行同样验证并推送**

Run:

```bash
cd /Users/souplearn/Gitlab/py/ForgeFlow-ERP/frontend
node src/components/OrgPermissionTree.test.js
node src/views/SystemPermissionView.test.js
node src/utils/orgTreeEmployees.test.js
node src/App.test.js
npm run build
cd ..
git push origin BH_DEV
```

Expected:

```text
BH_DEV -> BH_DEV
```

---

## Self-Review

**Spec coverage:**  
已覆盖“组织树和脑图只是展示不同，功能一致”的核心要求。组织树右键入口和显式“节点操作”入口都会进入现有 `openContextMenu`，菜单项仍由 `nodeActions` 根据节点类型动态决定。

**Placeholder scan:**  
未使用 TBD/TODO/后续补充。每个任务都有具体文件、代码片段、命令和预期结果。

**Type consistency:**  
计划中使用的函数名与当前代码一致：`openContextMenu`、`nodeActions`、`runNodeAction`、`openOrgCreateDrawer`、`openOrgRenameDrawer`、`openOrgManagerDrawer`、`openOrgEmployeeDrawer`、`openAuthorizeDrawer`、`removeOrgEmployee`、`removeOrgNode`。

