|
|
@@ -0,0 +1,285 @@
|
|
|
+# 职位列表名称链接 + 关联人员 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:** 在 `list_posts` 对本页职位批量查询 `sys_user_post` + `sys_user`,向每行附加只读 `boundUserNames`。前端职位名称用 `el-button link`,新增「关联人员」列全部展示并换行;关联用户保存后刷新列表。
|
|
|
+
|
|
|
+**Tech Stack:** FastAPI / SQLAlchemy / Vue 3 / Element Plus / pytest
|
|
|
+
|
|
|
+**Spec:** `docs/superpowers/specs/2026-08-05-post-list-name-link-bound-users-design.md`
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## File map
|
|
|
+
|
|
|
+| File | Responsibility |
|
|
|
+|------|----------------|
|
|
|
+| `backend/app/crud/post.py` | `_row_to_dict` 增加 `boundUserNames`;批量 enrichment;`list_posts` 调用 |
|
|
|
+| `backend/tests/test_post_list_bound_users.py` | 空入参与展示名逻辑、列表 enrichment 调用回归 |
|
|
|
+| `web-admin/src/types/api.d.ts` | `PostItem.boundUserNames` |
|
|
|
+| `web-admin/src/views/system/post/index.vue` | 名称链接、关联人员列、保存后刷新 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 1: 后端批量查询关联人员姓名(TDD)
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Create: `backend/tests/test_post_list_bound_users.py`
|
|
|
+- Modify: `backend/app/crud/post.py`
|
|
|
+
|
|
|
+- [ ] **Step 1: Write failing tests**
|
|
|
+
|
|
|
+```python
|
|
|
+"""职位列表 boundUserNames enrichment。"""
|
|
|
+import inspect
|
|
|
+
|
|
|
+from app.crud import post as post_crud
|
|
|
+
|
|
|
+
|
|
|
+class _DummySession:
|
|
|
+ def query(self, *args, **kwargs): # pragma: no cover
|
|
|
+ raise AssertionError("空入参不应访问数据库")
|
|
|
+
|
|
|
+
|
|
|
+def test_bound_user_names_by_post_ids_empty():
|
|
|
+ db = _DummySession()
|
|
|
+ assert post_crud._bound_user_names_by_post_ids(db, [], "zkxw") == {}
|
|
|
+ assert post_crud._bound_user_names_by_post_ids(db, ["p1"], "") == {}
|
|
|
+
|
|
|
+
|
|
|
+def test_user_display_name_prefers_nick():
|
|
|
+ assert post_crud._user_display_name("张三", "zhangsan") == "张三"
|
|
|
+ assert post_crud._user_display_name("", "zhangsan") == "zhangsan"
|
|
|
+ assert post_crud._user_display_name(" ", "u1") == "u1"
|
|
|
+ assert post_crud._user_display_name(None, None) == ""
|
|
|
+
|
|
|
+
|
|
|
+def test_list_posts_enriches_bound_user_names():
|
|
|
+ source = inspect.getsource(post_crud.list_posts)
|
|
|
+ assert "_bound_user_names_by_post_ids" in source
|
|
|
+ assert "bound_user_names" in source or "boundUserNames" in source
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: Run tests — expect FAIL**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd backend && source venv/bin/activate && pytest tests/test_post_list_bound_users.py -v
|
|
|
+```
|
|
|
+
|
|
|
+Expected: FAIL(`_bound_user_names_by_post_ids` / `_user_display_name` 未定义)
|
|
|
+
|
|
|
+- [ ] **Step 3: Implement helpers + list enrichment**
|
|
|
+
|
|
|
+在 `backend/app/crud/post.py`:
|
|
|
+
|
|
|
+1. Import `SysUser`, `SysUserPost` from `app.models.models`。
|
|
|
+2. 增加:
|
|
|
+
|
|
|
+```python
|
|
|
+def _user_display_name(nick_name: Optional[str], user_no: Optional[str]) -> str:
|
|
|
+ n = (nick_name or "").strip()
|
|
|
+ if n:
|
|
|
+ return n
|
|
|
+ return (user_no or "").strip()
|
|
|
+
|
|
|
+
|
|
|
+def _bound_user_names_by_post_ids(
|
|
|
+ db: Session, post_ids: List[str], tenant_no: str
|
|
|
+) -> Dict[str, List[str]]:
|
|
|
+ """批量查询职位关联用户显示名(nick_name 优先,否则 user_no)。"""
|
|
|
+ out: Dict[str, List[str]] = {}
|
|
|
+ ids = [pid for pid in (post_ids or []) if pid]
|
|
|
+ tenant = (tenant_no or "").strip()
|
|
|
+ if not ids or not tenant:
|
|
|
+ return out
|
|
|
+ rows = (
|
|
|
+ db.query(SysUserPost.post_id, SysUser.nick_name, SysUser.user_no)
|
|
|
+ .join(
|
|
|
+ SysUser,
|
|
|
+ and_(
|
|
|
+ SysUser.id == SysUserPost.user_id,
|
|
|
+ SysUser.del_flag == 0,
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ .filter(
|
|
|
+ and_(
|
|
|
+ SysUserPost.post_id.in_(ids),
|
|
|
+ SysUserPost.tenant_no == tenant,
|
|
|
+ SysUserPost.del_flag == 0,
|
|
|
+ )
|
|
|
+ )
|
|
|
+ .all()
|
|
|
+ )
|
|
|
+ for post_id, nick_name, user_no in rows:
|
|
|
+ name = _user_display_name(nick_name, user_no)
|
|
|
+ if not name:
|
|
|
+ continue
|
|
|
+ out.setdefault(post_id, []).append(name)
|
|
|
+ return out
|
|
|
+```
|
|
|
+
|
|
|
+3. 修改 `_row_to_dict`:
|
|
|
+
|
|
|
+```python
|
|
|
+def _row_to_dict(p: SysPost, bound_user_names: Optional[List[str]] = None) -> Dict[str, Any]:
|
|
|
+ return {
|
|
|
+ "id": p.id,
|
|
|
+ "postName": p.post_name,
|
|
|
+ "postCode": p.post_code,
|
|
|
+ "status": p.status,
|
|
|
+ "tenantNo": p.tenant_no,
|
|
|
+ "orderNum": p.order_num if p.order_num is not None else 0,
|
|
|
+ "remark": p.remark or "",
|
|
|
+ "boundUserNames": list(bound_user_names or []),
|
|
|
+ "createTime": p.create_time.strftime("%Y-%m-%d %H:%M:%S") if p.create_time else "",
|
|
|
+ "updateTime": p.update_time.strftime("%Y-%m-%d %H:%M:%S") if p.update_time else "",
|
|
|
+ }
|
|
|
+```
|
|
|
+
|
|
|
+4. 修改 `list_posts` 返回前:
|
|
|
+
|
|
|
+```python
|
|
|
+ name_map = _bound_user_names_by_post_ids(
|
|
|
+ db, [x.id for x in rows], tenant_no or ""
|
|
|
+ )
|
|
|
+ return [
|
|
|
+ _row_to_dict(x, bound_user_names=name_map.get(x.id, [])) for x in rows
|
|
|
+ ], int(total)
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 4: Run tests — expect PASS**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd backend && source venv/bin/activate && pytest tests/test_post_list_bound_users.py -v
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 5: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add backend/app/crud/post.py backend/tests/test_post_list_bound_users.py
|
|
|
+git commit -m "feat(post): enrich post list with boundUserNames"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 2: 前端类型 + 列表 UI
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `web-admin/src/types/api.d.ts`(`Api.Post.PostItem`)
|
|
|
+- Modify: `web-admin/src/views/system/post/index.vue`
|
|
|
+
|
|
|
+- [ ] **Step 1: 扩展类型**
|
|
|
+
|
|
|
+在 `PostItem` 中增加:
|
|
|
+
|
|
|
+```ts
|
|
|
+boundUserNames?: string[]
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 职位名称改为链接**
|
|
|
+
|
|
|
+将「职位名称」列改为:
|
|
|
+
|
|
|
+```vue
|
|
|
+<el-table-column
|
|
|
+ prop="postName"
|
|
|
+ label="职位名称"
|
|
|
+ min-width="140"
|
|
|
+ align="center"
|
|
|
+ header-align="center"
|
|
|
+>
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button link type="primary" @click="handleView(row)">
|
|
|
+ {{ row.postName || '-' }}
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+</el-table-column>
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 3: 新增「关联人员」列(编码与状态之间)**
|
|
|
+
|
|
|
+```vue
|
|
|
+<el-table-column
|
|
|
+ label="关联人员"
|
|
|
+ min-width="180"
|
|
|
+ align="center"
|
|
|
+ header-align="center"
|
|
|
+>
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span class="post-bound-users-cell">{{
|
|
|
+ (row.boundUserNames || []).length
|
|
|
+ ? (row.boundUserNames || []).join('、')
|
|
|
+ : '—'
|
|
|
+ }}</span>
|
|
|
+ </template>
|
|
|
+</el-table-column>
|
|
|
+```
|
|
|
+
|
|
|
+scoped 样式:
|
|
|
+
|
|
|
+```scss
|
|
|
+.post-bound-users-cell {
|
|
|
+ display: inline-block;
|
|
|
+ max-width: 100%;
|
|
|
+ white-space: normal;
|
|
|
+ word-break: break-word;
|
|
|
+ line-height: 1.45;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 4: 关联用户保存后刷新列表**
|
|
|
+
|
|
|
+在 `handlePostBindSubmit` 成功分支增加 `await loadData()`:
|
|
|
+
|
|
|
+```ts
|
|
|
+await fetchUpdatePostBoundUsers(postBindPostId.value, [...postBindUserCheckedKeys.value])
|
|
|
+postBindDialogVisible.value = false
|
|
|
+await loadData()
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 5: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add web-admin/src/types/api.d.ts web-admin/src/views/system/post/index.vue
|
|
|
+git commit -m "feat(web-admin): post list name link and bound users column"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 3: 图谱更新与手工验收
|
|
|
+
|
|
|
+- [ ] **Step 1: 更新 graphify**
|
|
|
+
|
|
|
+```bash
|
|
|
+./scripts/update-graph.sh all
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 手工验收清单**
|
|
|
+
|
|
|
+1. 打开 `/#/system-manage/system/post`。
|
|
|
+2. 点击职位名称 → 打开查看弹窗。
|
|
|
+3. 有关联用户的职位,「关联人员」列显示全部姓名;列窄时换行。
|
|
|
+4. 无关联用户显示 `—`。
|
|
|
+5. 「关联用户」保存后列表人名更新。
|
|
|
+
|
|
|
+- [ ] **Step 3: Commit graphify(若有变更)**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add backend/graphify-out web-admin/graphify-out
|
|
|
+git commit -m "chore: update graphify after post list enrichment"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Spec coverage
|
|
|
+
|
|
|
+| Spec 项 | Task |
|
|
|
+|---------|------|
|
|
|
+| 名称快捷链接 → handleView | Task 2 |
|
|
|
+| boundUserNames 列表 enrichment | Task 1 |
|
|
|
+| 全部姓名、换行 | Task 2 |
|
|
|
+| 关联用户保存后刷新 | Task 2 |
|
|
|
+| Create/Update 不写该字段 | 无需改 API body(只读 enrichment) |
|