Files
zkxw/docs/superpowers/specs/2026-05-24-cell-product-source-chain-design.md
T
2026-05-24 16:58:51 +08:00

11 KiB
Raw Blame History

细胞产品库 — 来源链路展示设计

日期:2026-05-24 状态:待审核 方案:A(自定义 Vue 组件 + CSS 绘制连线) Demo:docs/demo-source-chain-graph.html

1. 需求概述

细胞产品库列表页(/#/production/cell-product)当前仅展示产品基础信息,缺少来源追溯能力。需要:

  1. 列表新增来源信息列:来源类型、来源协议号、来源工单号、来源任务号
  2. 列表新增来源类型筛选:下拉筛选(全部/生产入库/手动录入)
  3. 查看弹窗新增链路图:横向 DAG 可视化,展示从样本到当前产品的祖先链 + 当前产品到子代产品的后代链,双向递归到顶点/终点
  4. 链路图连线标注:连线上方显示工单号、任务号

数据模型

溯源链路涉及 5 张核心表:

biz_collection_protocol (采集协议单)
  ↓ collection_protocol_id
biz_protocol_sample (协议样本)
  ↓ protocol_sample_id + collection_protocol_id
biz_sample_reception (样本接收记录)
  ↓ reception_id + protocol_sample_id + collection_protocol_id
biz_production_work_order (生产工单) ← 冗余存储 protocol_no, sample_code
  ↓ work_order_id
biz_production_task (生产任务)
  ↓ task_id
biz_cell_product (细胞产品) ← production_task_id 关联到任务

后代链路通过投入物/产出物表关联:

父细胞产品 (biz_cell_product.id=A)
  ↓ biz_production_task_input(input_type='cell_product', input_id=A, task_id=T)
biz_production_task (id=T)
  ↓ biz_production_task_output(task_id=T, cell_product_id=B, C, ...)
子细胞产品们 (biz_cell_product.id=B, C, ...)

2. 后端改造

2.1 列表 API 增强

接口:GET /web/admin/cell-products(已有)

改造文件:backend/app/crud/cell_product.py

2.1.1 _enrich_product_dicts() 增加来源富化

在现有富化逻辑(productTypeName, specName, storageSlotName)之后,增加来源信息的批量富化查询:

  1. 收集所有非空 production_task_id
  2. 一次查询 biz_production_task,获取 task_no 和 work_order_id
  3. 一次查询 biz_production_work_order,获取 work_order_no、protocol_no、reception_source
  4. 映射回每条产品记录

新增返回字段:

字段名 类型 来源路径 说明
sourceTypeLabel str source_type 枚举映射 来源类型中文
sourceProtocolNo str task → work_order → protocol_no 来源协议号
sourceWorkOrderNo str task → work_order → work_order_no 来源工单号
sourceTaskNo str task → task_no 来源生产任务号
sourceReceptionSource str work_order → reception_source cell_storage/research_donation

source_type 枚举映射:

  • production → "生产入库"
  • manual → "手动录入"

2.1.2 list_cell_products() 增加筛选参数

入参新增 source_type: Optional[str] = None,在查询条件中增加:

if source_type:
    query = query.filter(BizCellProduct.source_type == source_type)

2.1.3 Schema 更新

CellProductListQuery 新增 source_type: Optional[str] = None。

API 路由层 production.py 从 query params 读取 source_type 传入 service。

2.2 新增链路 API

接口:GET /web/admin/cell-products/{product_id}/chain

新文件:无(在 backend/app/crud/cell_product.py 中新增函数,在 backend/app/api/production.py 中新增路由)

2.2.1 返回数据结构

{
  "current": {
    "id": "uuid",
    "productCode": "CP001",
    "productName": "脐带间充质干细胞",
    "nodeType": "cell_product",
    "productStatus": "frozen",
    "passage": "P2",
    "isCurrent": true
  },
  "ancestors": [
    {
      "node": {
        "id": "uuid",
        "productCode": "CP000",
        "productName": "...",
        "nodeType": "cell_product",
        "productStatus": "frozen",
        "passage": "P1",
        "isCurrent": false
      },
      "edge": {
        "workOrderNo": "WO001",
        "workOrderType": "cell_storage",
        "taskNo": "T001",
        "taskName": "制备任务"
      },
      "parent": null
    }
  ],
  "descendants": [
    {
      "node": {
        "id": "uuid",
        "productCode": "CP002",
        "productName": "...",
        "nodeType": "cell_product",
        "productStatus": "frozen",
        "passage": "P3",
        "isCurrent": false
      },
      "edge": {
        "workOrderNo": "WO002",
        "workOrderType": "outbound",
        "taskNo": "T002",
        "taskName": "出库生产"
      },
      "children": []
    }
  ]
}

当追溯链路中遇到样本节点(非细胞产品)时,nodeType = "sample",包含 sampleCode 和 sampleType 字段。

2.2.2 递归查询逻辑

祖先方向(从当前产品向上追溯):

  1. 从 biz_cell_product.production_task_id 获取任务
  2. 从 biz_production_task.work_order_id 获取工单(含 protocol_no, sample_code)
  3. 检查该任务的投入物:biz_production_task_input(input_type='cell_product') 是否有父产品
  4. 如有父产品,递归继续(将父产品作为新的起点)
  5. 如投入物是样本(input_type='sample'),创建样本节点,终止递归
  6. 如 production_task_id 为空或 source_type='manual',终止递归

子代方向(从当前产品向下追溯):

  1. 查询 biz_production_task_input(input_type='cell_product', input_id=当前产品id) 找到所有引用此产品的任务
  2. 对每个任务,查询 biz_production_task_output 获取产出的细胞产品列表
  3. 对每个产出产品,递归继续(1对多分支)
  4. 如无任务引用此产品,终止递归

性能保护:最大递归深度 10 层。

2.2.3 路由注册

在 backend/app/api/production.py Section 11(细胞产品库)中新增路由,放在 GET /{product_id} 之后。

3. 前端改造

3.1 列表页

文件:web-admin/src/views/production/cell-product/index.vue

3.1.1 新增表格列

在 productStatus(状态)列之后,新增 4 列:

prop label min-width 渲染
sourceTypeLabel 来源类型 120 <el-tag>:生产入库=success、手动录入=info
sourceProtocolNo 来源协议号 160 文本,无值显示 "—"
sourceWorkOrderNo 来源工单号 140 文本,无值显示 "—"
sourceTaskNo 来源任务号 140 文本,无值显示 "—"

3.1.2 新增搜索筛选

搜索栏增加「来源类型」下拉,位于 productStatus 筛选之后:

<el-select v-model="searchForm.sourceType" placeholder="来源类型" clearable>
  <el-option label="生产入库" value="production" />
  <el-option label="手动录入" value="manual" />
</el-select>

3.1.3 查看弹窗基础信息

在现有 viewSections 的「基本信息」section 中追加 2 个 ViewField:

field label 渲染
sourceTypeLabel 来源类型 tagType 映射
sourceProtocolNo 来源协议号 文本

3.2 链路图组件

新文件:web-admin/src/views/production/cell-product/SourceChainGraph.vue

3.2.1 组件职责

  • 接收 productId prop
  • 调用 GET /web/admin/cell-products/{id}/chain 获取链路数据
  • 渲染横向 DAG:祖先 → 当前 → 子代
  • 支持节点点击查看详情(Element Plus Popover)

3.2.2 组件 Props

prop type 说明
productId string 当前细胞产品 ID
visible boolean 是否显示(由父组件控制)

3.2.3 布局结构

SourceChainGraph
├── 祖先区域(水平 flex,从左到右:最早祖先 → ... → 当前节点)
│   ├── ChainNode(递归渲染祖先链)
│   └── ChainConnector(连线 + 工单/任务标签)
├── 当前节点(高亮样式,角标「当前」)
└── 子代区域(支持分支,水平 flex-wrap)
    ├── ChainNode(递归渲染子代链,1对多用 flex-wrap)
    └── ChainConnector(连线 + 工单/任务标签)

3.2.4 节点样式

节点类型 背景 边框 内容
样本 #eff6ff(浅蓝) #93c5fd 编码、类型
细胞产品 #ecfdf5(浅绿) #a7f3d0 编码、名称、代次
当前产品 #ecfdf5 #10b981(2px 加粗) 编码、名称、代次 + 角标「当前」

节点尺寸:148×60px,内容过长用 text-overflow: ellipsis 截断。

3.2.5 连线样式

  • 水平线:2px border,颜色 #d1d5db
  • 箭头:CSS border 三角形
  • 连线上方标签:绝对定位 <span>,显示工单号和任务号,字号 10px
  • 分叉线:从父节点右侧引出竖线 + 横线到各子节点

3.2.6 查看弹窗集成

在 index.vue 的 SystemViewDialog 中新增第三个 section「来源链路」,使用 template #sourceChain 自定义插槽:

<SystemViewDialog ...>
  <!-- 基本信息 section -->
  <!-- 产品图片 section -->
  <!-- 来源链路 section (新增) -->
  <template #sourceChain>
    <SourceChainGraph :product-id="currentRow.id" :visible="viewDialogVisible" />
  </template>
</SystemViewDialog>

SystemViewDialog 需支持第三个 section(通过 viewSections 配置中增加 slot: 'sourceChain')。

3.3 API 层

文件:web-admin/src/api/cell-product.ts

新增接口函数:

export function fetchCellProductChain(productId: string) {
  return request.get({ url: `/web/admin/cell-products/${productId}/chain` })
}

fetchCellProductList 的参数类型新增 sourceType?: string。

4. 影响范围

后端文件

文件 改动类型
backend/app/crud/cell_product.py 修改:_enrich_product_dicts 增加来源富化,新增 get_product_chain() 递归函数
backend/app/schemas/cell_product.py 修改:CellProductListQuery 增加 source_type 参数
backend/app/api/production.py 修改:列表路由读取 source_type 参数,新增 chain 路由

前端文件

文件 改动类型
web-admin/src/views/production/cell-product/index.vue 修改:新增表格列、搜索筛选、查看弹窗字段
web-admin/src/views/production/cell-product/SourceChainGraph.vue 新增:链路图组件
web-admin/src/api/cell-product.ts 修改:新增 fetchCellProductChain,fetchCellProductList 参数增加 sourceType

不涉及的文件

  • 数据库无表结构变更(无新表、无新字段)
  • 不涉及其他模块(订单、协议单、质检等)

5. 约束与边界

  1. 手动录入产品(起源节点):无 production_task_id,所以祖先链为空。但后续如果被用于生产,子代链路仍然会展示。只有既无祖先又无子代的孤立产品才显示空状态提示「该产品暂无来源及衍生链路信息」
  2. 最大递归深度:10 层,防止异常数据导致无限递归
  3. 性能:链路 API 单次查询,预估节点数 ≤ 50,响应时间 < 500ms
  4. 科研捐赠来源:通过 reception_source='research_donation' 标识,链路追溯路径相同
  5. 子代追踪:依赖 biz_production_task_input(input_type='cell_product') 记录,如投入物未正确录入则无法追踪子代
  6. 配色:严格使用 Fresh Mint 色系(#10b981、#ecfdf5),样本节点使用蓝色系(#eff6ff、#93c5fd)做区分