Files
zkxw/docs/superpowers/plans/2026-06-16-qc-work-order-tab-split.md
T
2026-06-16 12:36:29 +08:00

368 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 质检工单 Tab 拆分与分型展示 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:** 将质检工单与我的质检任务从 3 Tab 拆为 6 Tab,服务端强制按子类型过滤;Tab 子页面拷贝再改;六份独立查看弹窗;CardList 共用 + cardMetaConfig props。
**Architecture:** `tabs/*` 六份子页面从现有入库/出库/临时区块拷贝;`view-dialogs/*` 六份独立查看界面(不共用 Host);`QcWorkOrderTaskSubList` 与配置检测弹窗共用;`QcWorkOrderCardList` + SampleGroup 共用,props 传 `cardMetaConfig`;检测任务 sections 抽 `useQcWorkOrderTaskViewSections` 供六弹窗 import。后端 `list_work_orders` / `list_mine_tasks` 增加 `receptionSource` 过滤。
**Tech Stack:** Vue 3 + Element Plus(web-admin)、FastAPI + SQLAlchemy(backend)、TypeScript strict:false
**Spec:** `docs/superpowers/specs/2026-06-16-qc-work-order-tab-split-design.md`
---
## File Map
| 文件 | 职责 |
|------|------|
| `web-admin/src/utils/qc-work-order-tabs.ts` | **新建** Tab 常量、`toListQueryParams` |
| `web-admin/src/utils/qc-work-order-card-meta.ts` | **新建** 六 Tab 的 `cardMetaConfig`(CardList props) |
| `web-admin/src/views/.../qc-work-order/tabs/*.vue` | **新建×6** 从 index 拷贝的 Tab 子页面 |
| `web-admin/src/views/.../qc-work-order/view-dialogs/*.vue` | **新建×6** 独立查看弹窗 |
| `web-admin/src/views/.../qc-work-order/composables/useQcWorkOrderTaskViewSections.ts` | **新建** 检测任务 sections(六弹窗共用) |
| `backend/app/crud/qc_work_order.py` | `list_work_orders` + `reception_source` |
| `backend/app/crud/qc_task.py` | `list_mine_tasks` + `reception_source` / `biz_type` |
| `QcWorkOrderCardList.vue` / `SingleCard.vue` / `SampleGroup.vue` | 接收 `cardMetaConfig` props |
| `QcWorkOrderViewDialogHost.vue` | **删除**(逻辑迁入六弹窗) |
| `OutboundQcViewDialog.vue` | **删除**(拷贝为三个 outbound 弹窗后删) |
| `useQcWorkOrderViewDialog.ts` | **删除**(拆入弹窗 + task composable) |
| `qc-work-order/index.vue` | Tab 壳 + 共用配置检测弹窗 |
| `my-qc-task/index.vue` | Tab 壳,import 同一套 `tabs/*` |
---
### Task 1: Tab 配置模块(前端基础)
**Files:**
- Create: `web-admin/src/utils/qc-work-order-tabs.ts`
- [ ] **Step 1: 创建 Tab 配置**
```typescript
// web-admin/src/utils/qc-work-order-tabs.ts
export type QcWorkOrderTabKey =
| 'inbound_cell_storage'
| 'inbound_research_donation'
| 'outbound_pilot_service'
| 'outbound_basic_experiment'
| 'outbound_clinical_application'
| 'temporary'
export type QcWorkOrderTabConfig = {
key: QcWorkOrderTabKey
label: string
group: 'inbound' | 'outbound' | 'temporary'
workOrderType: 'inbound' | 'outbound' | 'temporary'
receptionSource?: 'cell_storage' | 'research_donation'
bizType?: 'pilot_service' | 'basic_experiment' | 'clinical_application'
listLayout: 'inbound-grouped' | 'standalone'
}
export const QC_WORK_ORDER_TABS: QcWorkOrderTabConfig[] = [
{
key: 'inbound_cell_storage',
label: '采集入库',
group: 'inbound',
workOrderType: 'inbound',
receptionSource: 'cell_storage',
listLayout: 'inbound-grouped'
},
{
key: 'inbound_research_donation',
label: '捐赠入库',
group: 'inbound',
workOrderType: 'inbound',
receptionSource: 'research_donation',
listLayout: 'inbound-grouped'
},
{
key: 'outbound_pilot_service',
label: '中试',
group: 'outbound',
workOrderType: 'outbound',
bizType: 'pilot_service',
listLayout: 'standalone'
},
{
key: 'outbound_basic_experiment',
label: '基础实验',
group: 'outbound',
workOrderType: 'outbound',
bizType: 'basic_experiment',
listLayout: 'standalone'
},
{
key: 'outbound_clinical_application',
label: '临床应用',
group: 'outbound',
workOrderType: 'outbound',
bizType: 'clinical_application',
listLayout: 'standalone'
},
{
key: 'temporary',
label: '临时',
group: 'temporary',
workOrderType: 'temporary',
listLayout: 'standalone'
}
]
export const DEFAULT_QC_WORK_ORDER_TAB: QcWorkOrderTabKey = 'inbound_cell_storage'
export function getQcWorkOrderTab(key: string): QcWorkOrderTabConfig | undefined {
return QC_WORK_ORDER_TABS.find((t) => t.key === key)
}
export function parseQcWorkOrderTabKey(raw?: string | null): QcWorkOrderTabKey {
const k = (raw || '').trim()
if (getQcWorkOrderTab(k)) return k as QcWorkOrderTabKey
// 兼容旧 query:inbound / outbound / temporary
if (k === 'inbound') return 'inbound_cell_storage'
if (k === 'outbound') return 'outbound_pilot_service'
if (k === 'temporary') return 'temporary'
return DEFAULT_QC_WORK_ORDER_TAB
}
export function toWorkOrderListQuery(tab: QcWorkOrderTabConfig) {
return {
workOrderType: tab.workOrderType,
receptionSource: tab.receptionSource,
bizType: tab.bizType
}
}
export function resolveTabKeyFromWorkOrder(row: {
workOrderType?: string
receptionSource?: string
bizType?: string
}): QcWorkOrderTabKey {
const wt = row.workOrderType || 'inbound'
if (wt === 'temporary') return 'temporary'
if (wt === 'outbound') {
const bt = row.bizType || 'pilot_service'
if (bt === 'basic_experiment') return 'outbound_basic_experiment'
if (bt === 'clinical_application') return 'outbound_clinical_application'
return 'outbound_pilot_service'
}
return row.receptionSource === 'research_donation'
? 'inbound_research_donation'
: 'inbound_cell_storage'
}
```
- [ ] **Step 2: 本地类型检查**
Run: `cd web-admin && pnpm exec vue-tsc --noEmit 2>&1 | head -20`
Expected: 无新增错误(新文件未被引用时可能无输出)
---
### Task 2: 后端列表 API — `receptionSource` 过滤
**Files:**
- Modify: `backend/app/crud/qc_work_order.py`(`list_work_orders`)
- Modify: `backend/app/api/qc.py`(`GET /qc-work-orders`)
- [ ] **Step 1: CRUD 增加参数与过滤**
在 `list_work_orders` 签名增加 `reception_source: Optional[str] = None`,在 `biz_type` 过滤之后:
```python
rs = (reception_source or "").strip()
if rs:
q0 = q0.filter(BizQcWorkOrder.reception_source == rs)
```
- [ ] **Step 2: API 路由暴露参数**
```python
receptionSource: Optional[str] = Query(None, alias="receptionSource"),
# ...
reception_source=receptionSource,
```
- [ ] **Step 3: 手动验证**
启动 backend 后请求(需有效 token):
```bash
curl -s 'http://127.0.0.1:8000/api/admin/qc-work-orders?workOrderType=inbound&receptionSource=cell_storage&page=1&pageSize=5' -H "Authorization: Bearer <token>"
```
Expected: 返回列表中每条 `receptionSource` 均为 `cell_storage`(或空默认为 cell_storage 的工单)
---
### Task 3: 后端我的任务 API — `receptionSource` / `bizType` 过滤
**Files:**
- Modify: `backend/app/crud/qc_task.py`(`list_mine_tasks`)
- Modify: `backend/app/api/qc.py`(`GET /qc-tasks/mine`)
- [ ] **Step 1: CRUD 增加过滤**
`list_mine_tasks` 签名增加 `reception_source`、`biz_type`;在 `qc_biz_type` 过滤后:
```python
rs = (reception_source or "").strip()
if rs:
q0 = q0.filter(BizQcWorkOrder.reception_source == rs)
bt_out = (biz_type or "").strip()
if bt_out:
q0 = q0.filter(BizQcWorkOrder.biz_type == bt_out)
```
- [ ] **Step 2: API 暴露 Query 参数并传入**
---
### Task 4: 前端 API 类型
**Files:**
- Modify: `web-admin/src/api/qc-work-order.ts`
- Modify: `web-admin/src/api/qc-task.ts`
- [ ] **Step 1: 列表查询参数增加 `receptionSource?: string`**
- [ ] **Step 2: `fetchMyQcTaskList` 参数增加 `receptionSource?: string`、`bizType?: string`**
---
### Task 5: `cardMetaConfig`(CardList props)
**Files:**
- Create: `web-admin/src/utils/qc-work-order-card-meta.ts`
- [ ] **Step 1: 导出六份 `QcWorkOrderCardMetaConfig`**
含:`groupContextBuilder`(入库组头)、`metaFields`(卡片 dl)、`showProtocolFeeSummary`、`showOutboundFeeSummary`、`listLayout`。
- [ ] **Step 2: 出库 meta 不得含 `receptionSource`**
---
### Task 6–8: CardList / SingleCard / SampleGroup 接 props
**Files:**
- Modify: `QcWorkOrderSingleCard.vue`、`QcWorkOrderSampleGroup.vue`、`QcWorkOrderCardList.vue`
- [ ] **Step 1: 新增 prop `cardMetaConfig: QcWorkOrderCardMetaConfig`**
- [ ] **Step 2: 删除 `activeTypeTab === 'outbound'` 等分支,改为渲染 `cardMetaConfig.metaFields`**
- [ ] **Step 3: CardList 按 `cardMetaConfig.listLayout` 决定是否样本分组**
---
### Task 9: 检测任务 composable(六弹窗共用)
**Files:**
- Create: `web-admin/src/views/quality-inspection/qc-work-order/composables/useQcWorkOrderTaskViewSections.ts`
- [ ] **Step 1: 从 `useQcWorkOrderViewDialog.ts` 抽出 `buildTaskTypeSections`、附件 slot、物料 slot 逻辑**
- [ ] **Step 2: 导出 `useQcWorkOrderTaskViewSections(tasks)` 供六弹窗 import**
---
### Task 10: 六份独立查看弹窗(拷贝再改)
**Files:**
- Create: `view-dialogs/InboundCellStorageViewDialog.vue` ← 拷 `QcWorkOrderViewDialogHost` + `useQcWorkOrderViewDialog`
- Create: `view-dialogs/InboundResearchDonationViewDialog.vue` ← 拷上,删协议缴费
- Create: `view-dialogs/OutboundPilotViewDialog.vue` ← 拷 `OutboundQcViewDialog`,仅中试 sections
- Create: `view-dialogs/OutboundBasicExperimentViewDialog.vue` ← 拷出库弹窗,删患者/输注
- Create: `view-dialogs/OutboundClinicalViewDialog.vue` ← 拷出库弹窗,临床 + 缴费
- Create: `view-dialogs/TemporaryQcViewDialog.vue` ← 拷入库弹窗,仅产品 + 任务
- [ ] **Step 1: 先落地 `InboundCellStorageViewDialog` + `OutboundPilotViewDialog`,行为与现网一致**
- [ ] **Step 2: 拷贝出其余四份并按 Spec §6.1 删减区块**
- [ ] **Step 3: 各弹窗内 import Task 9 composable**
---
### Task 11: 六 Tab 子页面(拷贝再改)
**Files:**
- Create: `tabs/InboundCellStorageTab.vue` ← 从 `index.vue` 剪出原入库 Tab(筛选+列表+本 Tab 弹窗)
- Create: `tabs/InboundResearchDonationTab.vue` ← 拷贝,改 API 参数与 cardMeta
- Create: `tabs/OutboundPilotTab.vue` ← 剪出原出库 Tab
- Create: `tabs/OutboundBasicExperimentTab.vue`、`tabs/OutboundClinicalTab.vue` ← 拷贝出库 Tab
- Create: `tabs/TemporaryTab.vue` ← 剪出原临时 Tab
每个 Tab 文件内:
- 写死 `toWorkOrderListQuery(tabConfig)`
- 传入对应 `cardMetaConfig`
- 挂载对应 `view-dialogs/*ViewDialog.vue`
- prop `pageMode?: 'manage' | 'mine'`
- [ ] **Step 1: 先完成 `InboundCellStorageTab` + `OutboundPilotTab` + `TemporaryTab`**
- [ ] **Step 2: 拷贝完成捐赠 + 两个 outbound 变体**
---
### Task 12: `index.vue` / `my-qc-task/index.vue` Tab 壳
**Files:**
- Modify: `qc-work-order/index.vue` — 6 Tab 按钮 + `<component :is="tabComponent" />` + **共用**配置检测弹窗
- Modify: `my-qc-task/index.vue` — 同上,传 `page-mode="mine"`
- [ ] **Step 1: `index.vue` 移除原筛选/列表大段,改为动态加载 `tabs/*`**
- [ ] **Step 2: `my-qc-task` import 同一套 `tabs/*`,不重复维护**
- [ ] **Step 3: 删除 `QcWorkOrderViewDialogHost.vue`、`OutboundQcViewDialog.vue`、`useQcWorkOrderViewDialog.ts`**
- [ ] **Step 4: 更新 `qc-mine-task-groups.ts` TabKey 与 API 过滤**
---
### Task 13: 列表 enrich 字段(若缺)
**Files:**
- Modify: `backend/app/crud/qc_work_order.py`
- [ ] **Step 1: 确认 `basic_experiment` 列表含 `projectTitle`、`customerName`**
---
### Task 14: 知识图谱与验收
- [ ] **Step 1: 六 Tab 数据隔离 + 六弹窗独立打开验收**
- [ ] **Step 2: `./scripts/update-graph.sh frontend`**
---
## Spec Coverage Self-Review
| Spec § | Task |
|--------|------|
| §2 Tab 定义 | Task 1, 11, 12 |
| §2.2 数据隔离 | Task 2, 3, 11 |
| §4 组件边界 | Task 5–12 |
| §5 卡片矩阵 | Task 5–8, 11 |
| §6 查看弹窗 | Task 9, 10, 12 |
| §7 Tab 子页面 | Task 11, 12 |
| §8 我的任务 | Task 12 |
| §9 后端 API | Task 2, 3, 13 |
| §11 验收 | Task 14 |
---
## Execution Handoff
Plan complete and saved to `docs/superpowers/plans/2026-06-16-qc-work-order-tab-split.md`.
**两种执行方式:**
1. **Subagent-Driven(推荐)** — 按 Task 逐条派发子 agent,每步完成后 review
2. **Inline Execution** — 本会话按 Task 1→13 连续实现,关键节点暂停确认
请告知选择哪种方式;若直接开始实现,将从 Task 1 执行。