完善组织树操作与空响应处理

This commit is contained in:
汤学会
2026-06-15 04:27:33 +08:00
parent c42d190f4b
commit 4c06295535
9 changed files with 701 additions and 19 deletions
@@ -0,0 +1,493 @@
# 组织树与脑图操作能力一致 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`。