提交
This commit is contained in:
1 parent
731bbf878a
commit
f4bbe3e06e
56 files changed
+13599
-11335
No files matched your search
@@ -0,0 +1,61 @@
|
||||
# 同协议样本点击跳转与按需插入卡片 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:** 后端新增 `GET /qc-work-orders/by-sample` 精确查工单并 enrich;前端 `useInboundSiblingNavigation` 管理插入卡与滚动;`QcWorkOrderSampleGroup` 发 `sibling-navigate` 事件;`QcWorkOrderCardList` 渲染插入卡。
|
||||
|
||||
**Tech Stack:** FastAPI + SQLAlchemy · Vue 3 + Element Plus · 现有 `QcWorkOrderSampleGroup` 卡片体系
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-07-07-qc-inbound-sibling-navigate-design.md`
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 后端 by-sample 查询
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/app/crud/qc_work_order.py`
|
||||
- Modify: `backend/app/api/qc.py`
|
||||
|
||||
- [ ] 在 `qc_work_order.py` 新增 `list_work_orders_by_sample`(tenant + 数据权限 + 列表 enrich)
|
||||
- [ ] 在 `qc.py` 注册 `GET /qc-work-orders/by-sample`(置于 `/{work_order_id}` 之前),权限 `require_any_menu_code("biz:qcWorkOrder:list", "biz:myQcTask:list")`
|
||||
|
||||
### Task 2: 前端 API 与 link 模型
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/api/qc-work-order.ts`
|
||||
- Modify: `web-admin/src/utils/qc-work-order-inbound-sample-cards.ts`
|
||||
|
||||
- [ ] 新增 `fetchQcWorkOrdersBySample`
|
||||
- [ ] `QcInboundProtocolSiblingLink` 增加 `sampleCode`;`buildProtocolSiblingLinksFromSamples` / `buildLinksFromVisibleGroups` 写入
|
||||
|
||||
### Task 3: useInboundSiblingNavigation composable
|
||||
|
||||
**Files:**
|
||||
- Create: `web-admin/src/views/quality-inspection/qc-work-order/composables/useInboundSiblingNavigation.ts`
|
||||
|
||||
- [ ] 实现 `navigateToSibling` / `clearInsertedCards` / `insertedCardsAfter`
|
||||
- [ ] manage 模式拉 inspection batches;mine 模式按 sampleCode keyword 过滤我的任务
|
||||
|
||||
### Task 4: 集成 Tab 页与 CardList
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/views/quality-inspection/qc-work-order/composables/useQcWorkOrderTabPage.ts`
|
||||
- Modify: `web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue`
|
||||
- Modify: `web-admin/src/views/quality-inspection/qc-work-order/tabs/QcWorkOrderTabPanel.vue`
|
||||
|
||||
- [ ] `loadData` 开头 `clearInsertedCards`
|
||||
- [ ] CardList 入库分支渲染插入卡 + 合并 preset tasks/batches
|
||||
|
||||
### Task 5: SampleGroup 可点击芯片
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSampleGroup.vue`
|
||||
|
||||
- [ ] 芯片改 button、emit `sibling-navigate`、`isInsertedSibling` 标识、loading 禁用
|
||||
|
||||
### Task 6: 验证
|
||||
|
||||
- [ ] `pnpm lint`(改动文件)
|
||||
- [ ] `./scripts/update-graph.sh frontend`
|
||||
@@ -0,0 +1,280 @@
|
||||
# 入库质检 · 同协议样本点击跳转与按需插入卡片
|
||||
|
||||
> 日期:2026-07-07
|
||||
> 状态:已批准(2026-07-07)
|
||||
> 范围:`web-admin` 【质检工单】【我的质检任务】入库类 Tab(细胞存储、科研捐赠);后端按样本编码查工单小接口
|
||||
> 关联:`2026-06-25-qc-work-order-protocol-group-design.md`(历史协议组方案,现网未接入列表)、`2026-06-16-qc-work-order-tab-split-design.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与目标
|
||||
|
||||
### 1.1 问题
|
||||
|
||||
细胞存储、科研捐赠入库质检列表中,样本卡标题栏展示 **「同协议样本」** 胶囊(实验室编号 + 样本类型),数据来自后端 `protocolSiblingSamples` enrich。
|
||||
|
||||
现状:
|
||||
|
||||
- 胶囊为纯展示 `<span>`,**不可点击**;
|
||||
- 虽已有 `scrollToInboundSampleCard(anchorId)` 工具函数,但未绑定到 UI;
|
||||
- 兄弟样本常**不在当前列表**(分页、筛选、我的任务无指派等),无法跳转查看。
|
||||
|
||||
### 1.2 目标
|
||||
|
||||
1. 「同协议样本」胶囊可点击,跳转到对应样本的质检卡片。
|
||||
2. 目标样本**已在当前列表 DOM 中**:平滑滚动 + 高亮(复用 `scrollToInboundSampleCard`)。
|
||||
3. 目标样本**不在当前列表**:在**点击来源样本卡正下方**按需插入一张完整样本卡,滚动并高亮。
|
||||
4. 【质检工单】与【我的质检任务】**共用**同一套导航逻辑与卡片组件。
|
||||
|
||||
### 1.3 非目标
|
||||
|
||||
- 不改变出库、临时、「所有」Tab 中非入库样本卡的布局(「所有」Tab 中入库样本组若已有同协议芯片,一并支持本能力)。
|
||||
- 不恢复/接入 `QcWorkOrderProtocolGroup` 协议组外层卡片。
|
||||
- 不改变列表分页语义(插入卡不计入 `total`、不触发服务端翻页)。
|
||||
- 不做「自动修改筛选条件以定位兄弟样本」。
|
||||
- 本期不做弹窗/抽屉替代插入卡的方案。
|
||||
|
||||
---
|
||||
|
||||
## 2. 已确认产品决策
|
||||
|
||||
| 决策点 | 结论 |
|
||||
|--------|------|
|
||||
| **我的质检任务 · 插入卡内容** | **选项 A**:仍插入完整工单卡;任务区可为空;操作区仅「查看」等只读能力(与现网 `showWorkOrderViewOnly` 一致) |
|
||||
| **插入位置** | 紧贴**点击来源**那张样本卡下方,不按全局排序重排 |
|
||||
| **点击当前样本芯片** | 仅高亮,不重复插入、不请求接口 |
|
||||
| **插入卡生命周期** | Tab 切换、搜索/重置、翻页、`loadData()` 重载时**清空**所有插入卡 |
|
||||
| **去重** | 同一 `sampleCode` 已在主列表或已插入 → 只滚动到已有卡,不重复插入 |
|
||||
| **无质检工单** | Toast:`该样本暂无质检工单`(不插入空壳卡) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 交互设计
|
||||
|
||||
### 3.1 芯片样式与状态
|
||||
|
||||
| 状态 | 表现 |
|
||||
|------|------|
|
||||
| 当前样本(`is-current`) | 保持现有高亮样式;`cursor: default`;点击仅触发高亮动画 |
|
||||
| 可跳转样本 | `cursor: pointer`;hover 边框/底色略加深;`role="button"` + 键盘可聚焦 |
|
||||
| 加载中(拉取兄弟样本) | 来源卡下方显示细条 loading(`v-loading` 或 skeleton),禁止重复点击 |
|
||||
|
||||
### 3.2 点击流程
|
||||
|
||||
```
|
||||
用户点击芯片(非当前)
|
||||
→ 解析 targetSampleCode(来自 link.sampleCode)
|
||||
→ 若 document.getElementById(anchorId) 存在
|
||||
→ scrollToInboundSampleCard(anchorId)
|
||||
→ 否则
|
||||
→ 若 insertedCards 或主列表已含该 sampleCode → 滚动到已有 anchor
|
||||
→ 否则 POST/GET 按样本拉工单
|
||||
→ 有工单:在来源卡下插入 SampleGroup,再滚动高亮
|
||||
→ 无工单:ElMessage.info
|
||||
```
|
||||
|
||||
### 3.3 插入卡视觉区分
|
||||
|
||||
插入卡在标题栏副标题区增加轻量 muted 文案:**「同协议关联」**(或同等语义 Tag),与主列表卡区分;其余结构与普通 `QcWorkOrderSampleGroup` 一致。
|
||||
|
||||
### 3.4 我的质检任务 · 插入卡
|
||||
|
||||
- `pageMode=mine` → `showWorkOrderViewOnly=true`,`showWorkOrderOps=false`。
|
||||
- `presetTasksByWorkOrderId`:仅填入**当前用户**在该工单下的任务;无任务则为 `{}` 或空数组 → 任务区自然为空。
|
||||
- 仍展示工单元信息、触发来源 Tab、查看弹窗入口。
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据与 API
|
||||
|
||||
### 4.1 前端 Link 模型扩展
|
||||
|
||||
`QcInboundProtocolSiblingLink` 增加 `sampleCode`(拉数主键;`anchorId` 仍由 `buildInboundSampleCardAnchorId(sampleCode)` 生成):
|
||||
|
||||
```ts
|
||||
export type QcInboundProtocolSiblingLink = {
|
||||
sampleCode: string
|
||||
anchorId: string
|
||||
labStorageCode: string
|
||||
sampleTypeName: string
|
||||
}
|
||||
```
|
||||
|
||||
`buildProtocolSiblingLinksFromSamples` 从 `QcProtocolSiblingSample.sampleCode` 写入。
|
||||
|
||||
### 4.2 新增后端接口(推荐)
|
||||
|
||||
**`GET /api/web/admin/qc-work-orders/by-sample`**
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `sampleCode` | 是 | 样本编码,精确匹配 |
|
||||
| `receptionSource` | 是 | `cell_storage` \| `research_donation`,与 Tab 一致 |
|
||||
|
||||
**响应**:`{ list: QcWorkOrder[] }`(0~N 条,同一样本可有多条工单:接收 + 生产过程等)
|
||||
|
||||
**查询规则**:
|
||||
|
||||
- `tenant_no` = 当前用户租户
|
||||
- `del_flag = 0`
|
||||
- `work_order_type = inbound`
|
||||
- `sample_code` 精确匹配
|
||||
- `reception_source` 匹配
|
||||
- 应用数据权限(与列表一致)
|
||||
- 列表 enrich:`_enrich_work_order_list_items`、`_enrich_work_order_qc_targets`、`_enrich_unfinished_badge_counts` 等,**含 `protocolSiblingSamples`**
|
||||
|
||||
**不用现有 `keyword` 的原因**:列表关键词为 `LIKE` 模糊匹配,不可靠。
|
||||
|
||||
### 4.3 我的任务 · 任务数据
|
||||
|
||||
插入卡渲染前,对返回的 `workOrder.id` 列表调用现有「我的任务」查询(可批量 by workOrderIds,或逐工单 `fetchMyQcTaskList` 带 workOrderId 过滤——以实现时现有 API 能力为准),仅合并指派给当前用户的任务到 `presetTasksByWorkOrderId`。
|
||||
|
||||
若无批量接口,首期可接受:插入后对每个 workOrderId 并行请求 mine tasks(通常 1~2 条工单,可接受)。
|
||||
|
||||
### 4.4 质检工单 · 批次数据
|
||||
|
||||
`pageMode=manage` 时,插入卡工单 ID 走现有 `fetchQcInspectionBatchesBatch` 写入 `presetBatchesByWorkOrderId`。
|
||||
|
||||
---
|
||||
|
||||
## 5. 前端架构
|
||||
|
||||
### 5.1 状态:composable `useInboundSiblingNavigation`
|
||||
|
||||
建议新建 `composables/useInboundSiblingNavigation.ts`,由 `useQcWorkOrderTabPage` 持有并下传 `QcWorkOrderCardList`。
|
||||
|
||||
```ts
|
||||
type InsertedInboundSiblingCard = {
|
||||
sampleCode: string
|
||||
anchorId: string
|
||||
insertedAfterAnchorId: string
|
||||
group: QcWorkOrderSampleGroupModel
|
||||
protocolSiblingLinks: QcInboundProtocolSiblingLink[]
|
||||
showProtocolPaymentAtGroup: boolean
|
||||
loading?: boolean
|
||||
isInserted: true
|
||||
}
|
||||
|
||||
// 状态
|
||||
insertedCards: Ref<InsertedInboundSiblingCard[]>
|
||||
navigating: Ref<boolean>
|
||||
|
||||
// 方法
|
||||
async function navigateToSibling(payload: {
|
||||
targetSampleCode: string
|
||||
sourceAnchorId: string
|
||||
receptionSource: string
|
||||
protocolSiblingLinks: QcInboundProtocolSiblingLink[]
|
||||
}): Promise<void>
|
||||
|
||||
function clearInsertedCards(): void
|
||||
```
|
||||
|
||||
**`navigateToSibling` 逻辑**:
|
||||
|
||||
1. 计算 `targetAnchorId`。
|
||||
2. 若 DOM 存在或 `insertedCards`/主列表 `groupKey` 已含 → `scrollToInboundSampleCard`。
|
||||
3. 否则 `fetchQcWorkOrdersBySample` → `groupInboundQcWorkOrders` → 构建 `protocolSiblingLinks`(复用 `buildProtocolSiblingLinksFromSamples`)→ push 到 `insertedCards`(`insertedAfterAnchorId = sourceAnchorId`)→ nextTick 后滚动。
|
||||
|
||||
**清空时机**:`loadData` 开头、`handleTabChange`、`handleReset`、翻页事件。
|
||||
|
||||
### 5.2 渲染顺序(`QcWorkOrderCardList`)
|
||||
|
||||
入库 grouped 分支:
|
||||
|
||||
```vue
|
||||
<template v-for="item in inboundSampleListItems" :key="item.group.groupKey">
|
||||
<QcWorkOrderSampleGroup ... @sibling-navigate="onSiblingNavigate" />
|
||||
<QcWorkOrderSampleGroup
|
||||
v-for="ins in insertedCardsAfter(item.cardAnchorId)"
|
||||
:key="'ins-' + ins.sampleCode"
|
||||
:is-inserted-sibling="true"
|
||||
...
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
「所有」Tab 中 `kind === 'sample-group'` 的入库项同样支持插入(`resolveProtocolSiblingLinksForGroup` 已有 links)。
|
||||
|
||||
### 5.3 组件事件
|
||||
|
||||
`QcWorkOrderSampleGroup`:
|
||||
|
||||
- 芯片改为 `<button type="button">` 或带 `@click` 的可聚焦元素;
|
||||
- 当前芯片 `@click` → 仅 `scrollToInboundSampleCard(cardAnchorId)`;
|
||||
- 非当前 → `emit('sibling-navigate', { targetSampleCode, protocolSiblingLinks })`;
|
||||
- 新增 prop `isInsertedSibling?: boolean` 控制「同协议关联」标识。
|
||||
|
||||
### 5.4 文件改动清单
|
||||
|
||||
| 路径 | 改动 |
|
||||
|------|------|
|
||||
| `web-admin/src/utils/qc-work-order-inbound-sample-cards.ts` | link 增加 `sampleCode` |
|
||||
| `web-admin/src/views/.../QcWorkOrderSampleGroup.vue` | 可点击芯片 + emit |
|
||||
| `web-admin/src/views/.../QcWorkOrderCardList.vue` | 插入卡渲染、事件桥接 |
|
||||
| `web-admin/src/views/.../composables/useInboundSiblingNavigation.ts` | **新建** |
|
||||
| `web-admin/src/views/.../composables/useQcWorkOrderTabPage.ts` | 集成 composable、清空、下传 props |
|
||||
| `web-admin/src/api/qc-work-order.ts` | `fetchQcWorkOrdersBySample` |
|
||||
| `backend/app/crud/qc_work_order.py` | `list_work_orders_by_sample` |
|
||||
| `backend/app/api/qc.py`(或 qc_work_order 路由模块) | 注册 GET 路由 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 边界与错误处理
|
||||
|
||||
| 场景 | 处理 |
|
||||
|------|------|
|
||||
| 协议有样本、尚无质检工单 | Toast,不插入 |
|
||||
| 接口 403 / 无数据权限 | Toast:`无权查看该样本质检信息` |
|
||||
| 网络失败 | Toast + 不插入;`navigating` 复位 |
|
||||
| 连续快速点击不同芯片 | 防抖或队列;以最后一次为准 |
|
||||
| 插入后用户翻页 | 插入卡清空(已确认) |
|
||||
| 细胞存储 `paymentSummary` | 插入卡 `showProtocolPaymentAtGroup` 按 `buildInboundSampleListItems` 同规则(协议内 payment host) |
|
||||
| 特殊样本 | 插入卡继承 enrich 的 `isSpecial` / `specialRemark`,与主列表一致 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 测试要点
|
||||
|
||||
### 7.1 质检工单 · 细胞存储
|
||||
|
||||
1. 同协议 2 样本均在当前页 → 点击芯片 → 滚动高亮,无新卡。
|
||||
2. 仅 1 样本在列表 → 点击另一芯片 → 下方插入卡,可查看工单。
|
||||
3. 换页后插入卡消失。
|
||||
4. 无工单样本(若可造数据)→ Toast。
|
||||
|
||||
### 7.2 我的质检任务 · 科研捐赠
|
||||
|
||||
1. 兄弟样本无我的任务 → 插入卡任务区为空,可「查看」工单。
|
||||
2. 兄弟样本有部分我的任务 → 任务区仅显示我的任务。
|
||||
|
||||
### 7.3 可访问性
|
||||
|
||||
- 芯片可 Tab 聚焦;Enter 触发跳转。
|
||||
|
||||
---
|
||||
|
||||
## 8. 与历史设计的关系
|
||||
|
||||
`2026-06-25-qc-work-order-protocol-group-design.md` 规划协议组 + 样本 Tab;现网为**每样本独立卡 + 标题栏同协议芯片**。本需求在现架构上补**跨卡导航**,不推翻当前列表形态。若未来接入协议组,导航可内聚为组内 Tab 切换,插入卡逻辑可废弃。
|
||||
|
||||
---
|
||||
|
||||
## 9. 实现顺序建议
|
||||
|
||||
1. 后端 `by-sample` 接口 + 前端 API 封装
|
||||
2. `sampleCode` 写入 link 模型
|
||||
3. `useInboundSiblingNavigation` + 仅滚动(列表内互跳)
|
||||
4. 插入卡渲染 + 拉数
|
||||
5. 我的任务 presetTasks 合并
|
||||
6. 样式(pointer、插入标识、loading)
|
||||
7. 联调与边界用例
|
||||
|
||||
---
|
||||
|
||||
## 10. 审核记录
|
||||
|
||||
| 项 | 确认人 | 日期 |
|
||||
|----|--------|------|
|
||||
| 我的任务插入卡策略(选项 A) | 产品/用户 | 2026-07-07 |
|
||||
| 全文设计 | 待审核 | — |
|
||||
Reference in new issue
Block a user