494 lines
12 KiB
Markdown
494 lines
12 KiB
Markdown
# 组织树与脑图操作能力一致 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`。
|
||
|