# 质检工单 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 " ``` 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 按钮 + `` + **共用**配置检测弹窗 - 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 执行。