2026-07-07-qc-inbound-sibling-navigate-design.md 11 KB

入库质检 · 同协议样本点击跳转与按需插入卡片

日期: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) 生成):

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。

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 分支:

<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
全文设计 待审核 —