ForgeFlow-ERP/docs/superpowers/plans/2026-06-15-org-tree-action-parity.md
2026-06-15 04:27:33 +08:00

12 KiB
Raw Blame History

组织树与脑图操作能力一致 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 末尾追加:

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:

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

Expected:

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 中的单个“新增人员”按钮:

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

替换为:

<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) 下方添加:

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 附近增加:

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

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

Expected:

# pass ...

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

Files:

  • Modify: frontend/src/views/SystemPermissionView.test.js

  • Step 1: 写测试确认组织树和脑图共用 openContextMenu

SystemPermissionView.test.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:

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

Expected:

# pass ...

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


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

Files:

  • Modify: frontend/src/views/SystemPermissionView.vue

  • Step 1: 检查现有 openContextMenu

确认当前函数类似:

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 替换为:

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:

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

Expected:

# pass ...

Task 5: 手动浏览器验收

Files:

  • No code changes.

  • Step 1: 打开系统权限管理页面

Use in-app Browser:

http://localhost:5173/system-permissions
  • Step 2: 验收组织树模式下组织节点操作

操作:

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

Expected:

菜单出现,包含:重命名、添加该节点的主管、新增分公司节点。
  • Step 3: 验收分公司节点操作

操作:

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

Expected:

菜单出现,包含:重命名、添加该节点的主管、新增部门节点、删除节点。
  • Step 4: 验收部门节点操作

操作:

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

Expected:

菜单出现,包含:重命名、添加该节点的主管、新增小组节点、新增人员、删除节点。
  • Step 5: 验收右键入口

操作:

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

Expected:

弹出的菜单内容和右侧“节点操作”按钮一致。
  • Step 6: 验收人员操作仍正常

操作:

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

Expected:

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

Task 6: 回归验证与提交

Files:

  • All modified files from earlier tasks.

  • Step 1: 运行关键测试

Run:

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:

# fail 0
  • Step 2: 运行构建

Run:

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

Expected:

✓ built

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

  • Step 3: 检查 diff

Run:

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:

只包含组织树动作入口、菜单位置保护、相关测试和少量样式。
  • Step 4: 等用户确认后提交 main

Run only after user confirms:

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:

main -> main
  • Step 5: 同步 BH_DEV

Run only after main push succeeds:

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:

[BH_DEV <hash>] 补齐组织树节点操作能力
  • Step 6: 在 BH_DEV 运行同样验证并推送

Run:

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:

BH_DEV -> BH_DEV

Self-Review

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

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

Type consistency:
计划中使用的函数名与当前代码一致:openContextMenunodeActionsrunNodeActionopenOrgCreateDraweropenOrgRenameDraweropenOrgManagerDraweropenOrgEmployeeDraweropenAuthorizeDrawerremoveOrgEmployeeremoveOrgNode