2026-05-19-process-route-management-design.md 17 KB

工艺路线管理模块设计规格

概述

实现需求文档 2.3.8 节「工艺路线配置」。在生产管理菜单下的「工艺库管理」之后新增「工艺路线管理」菜单。

核心功能:维护工艺路线基础信息(路线编码、名称、产品类型、版本号),支持同编码多版本管理(轻量版:is_current 标记当前版本),并通过拖拽式三栏弹窗配置路线中的工艺步骤和子表数据。

设计决策

决策项 选择 理由
版本号 同编码多版本 + is_current 标记当前版本 轻量版:同 route_code 可有多条记录,仅一个可标记为当前版本。无发布/草稿状态机
子表范围 完整复刻4张子表(试剂/耗材/产出物/数据采集) 从工艺库拖入时克隆默认数据,用户可覆盖编辑
排列模式 线性排列(A→B→C→D) 卡片从上到下,无需分支逻辑
可编辑范围 全部字段可编辑(基础信息+4张子表) 右侧面板展示完整工艺信息供修改
保存方式 实时保存,拖入即创建后端记录 每步操作即时持久化,弹窗关闭无需额外保存
UI方案 方案A:单弹窗 + vue-draggable-plus 项目中已有3处成熟用法,跨栏拖拽通过HTML5 Drag API组合实现
步骤删除 支持删除,软删除步骤+级联软删除子表 中栏卡片上有删除按钮,确认后删除

依赖

依赖模块 用途 状态
细胞产品类型管理(biz_cell_product_type) 产品类型下拉 已实现
工艺工序分类(biz_process_category) 工艺分组展示 已实现
工艺库(biz_process + 4张子表) 拖入时的数据源和克隆来源 已实现
SOP文档(biz_sop_document) 步骤关联SOP 已实现
vue-draggable-plus@0.6.0 卡片排序拖拽 已安装
sortablejs@1.15.7 vue-draggable-plus 底层引擎 已安装

SQL 表(6 张新建)

所有表遵循项目公共字段规范:id VARCHAR(64) UUID → 业务字段 → tenant_no → order_num → remark → create_by → create_dept_id → create_time → update_by → update_time → del_flag。

1. biz_process_route — 工艺路线主表

字段 类型 说明
id VARCHAR(64) PK UUID
route_code VARCHAR(64) NOT NULL 路线编码
route_name VARCHAR(100) NOT NULL 路线名称
product_type_id VARCHAR(64) DEFAULT NULL 细胞产品类型ID(引用 biz_cell_product_type)
version VARCHAR(50) DEFAULT NULL 版本号(用户手填文本,如 V1.0、V2.0)
is_current CHAR(1) DEFAULT '0' 是否当前版本(0-否 1-是)。同 route_code 下仅一条记录 is_current=1
+ 公共字段

索引:idx_route_tenant(tenant_no, del_flag)

2. biz_process_route_step — 路线工序步骤

字段 类型 说明
id VARCHAR(64) PK UUID
route_id VARCHAR(64) NOT NULL 所属路线ID
process_id VARCHAR(64) DEFAULT NULL 来源工艺库工艺ID(工艺被删时可null)
step_order INT DEFAULT NULL 步骤顺序(拖拽排序时自动计算)
process_code VARCHAR(64) DEFAULT NULL 工序代码(从工艺库克隆,可编辑)
category_id VARCHAR(64) DEFAULT NULL 工序分类ID(可编辑)
process_name VARCHAR(100) DEFAULT NULL 工序名称(可编辑)
description VARCHAR(500) DEFAULT NULL 描述(可编辑)
equipment_name VARCHAR(200) DEFAULT NULL 预计使用设备(可编辑)
estimated_duration VARCHAR(100) DEFAULT NULL 预估耗时(可编辑)
sop_ids VARCHAR(1000) DEFAULT NULL 关联SOP文档ID(可编辑,逗号分隔)
+ 公共字段

索引:idx_prs_route(route_id, del_flag)、idx_prs_tenant(tenant_no, del_flag)

3. biz_process_route_step_reagent — 步骤试剂

字段完全镜像 biz_process_reagent,但外键改为 step_id(关联 biz_process_route_step.id):

字段 类型 说明
id VARCHAR(64) PK UUID
step_id VARCHAR(64) NOT NULL 所属步骤ID
reagent_code VARCHAR(64) DEFAULT NULL 试剂编码
reagent_name VARCHAR(100) DEFAULT NULL 试剂名称
spec VARCHAR(200) DEFAULT NULL 规格
standard_usage VARCHAR(100) DEFAULT NULL 标准用量
unit VARCHAR(64) DEFAULT NULL 单位
is_key_material CHAR(1) DEFAULT '0' 是否关键物料(0-否 1-是)
+ 公共字段

索引:idx_prsr_step(step_id, del_flag)

4. biz_process_route_step_consumable — 步骤耗材

字段完全镜像 biz_process_consumable,外键改为 step_id:

字段 类型 说明
id VARCHAR(64) PK UUID
step_id VARCHAR(64) NOT NULL 所属步骤ID
consumable_code VARCHAR(64) DEFAULT NULL 耗材编码
consumable_name VARCHAR(100) DEFAULT NULL 耗材名称
spec_model VARCHAR(200) DEFAULT NULL 规格型号
standard_usage VARCHAR(100) DEFAULT NULL 标准用量
unit VARCHAR(64) DEFAULT NULL 单位
is_required CHAR(1) DEFAULT '1' 是否必需(1-必须 0-可选)
remark VARCHAR(512) DEFAULT NULL 备注
+ 公共字段(不含 remark)

索引:idx_prsc_step(step_id, del_flag)

5. biz_process_route_step_output — 步骤产出物

字段完全镜像 biz_process_output,外键改为 step_id:

字段 类型 说明
id VARCHAR(64) PK UUID
step_id VARCHAR(64) NOT NULL 所属步骤ID
output_type_id VARCHAR(64) DEFAULT NULL 产出类型ID
output_name VARCHAR(200) DEFAULT NULL 产出物名称
output_barcode VARCHAR(200) DEFAULT NULL 产出物条码
+ 公共字段

索引:idx_prso_step(step_id, del_flag)

6. biz_process_route_step_data_collection — 步骤数据采集

字段完全镜像 biz_process_data_collection,外键改为 step_id:

字段 类型 说明
id VARCHAR(64) PK UUID
step_id VARCHAR(64) NOT NULL 所属步骤ID
collection_name VARCHAR(100) DEFAULT NULL 采集信息名称
unit VARCHAR(64) DEFAULT NULL 单位
+ 公共字段

索引:idx_prsdc_step(step_id, del_flag)

菜单数据

在「工艺库管理」(order_num=6)之后、「SOP文档」(order_num=7)之前插入,SOP 文档 order_num 改为 8:

INSERT INTO sys_menu (id, menu_name, parent_id, menu_type, path, component, perms, is_frame, visible, status, tenant_no, order_num, remark, create_by, create_dept_id, create_time, update_by, update_time, del_flag)
VALUES ('90000000000000000000000000000010', '工艺路线管理', '90000000000000000000000000000001', 'C', 'process-route', '/production/process-route/index', 'production:process-route:list', 1, '1', '1', 'zkxw', 7, NULL, NULL, '', '2026-05-19 00:00:00', NULL, '2026-05-19 00:00:00', 0);

-- SOP文档 order_num 从 7 改为 8
UPDATE sys_menu SET order_num = 8 WHERE id = '90000000000000000000000000000004';

后端 API

路由前缀:/api/admin/production/route/

路线主表 CRUD

方法 路径 说明
GET /list 路线分页列表。支持筛选:route_name(模糊)、route_code(精确)、product_type_id。排序:order_num ASC, create_time DESC。带 tenant_no 隔离。
POST /create 创建路线主表。必填:route_code, route_name。自动填充 tenant_no。默认 is_current=0。
PUT /update 更新路线主表信息(编码、名称、产品类型、版本号、备注)。
PUT /set-current 设置指定路线为当前版本。逻辑:先将同 route_code + tenant_no 下所有记录 is_current 置 0,再将目标记录 is_current 置 1。
DELETE /delete/{id} 软删除路线(del_flag=1)+ 级联软删除所有步骤及步骤子表。
GET /detail/{id} 获取路线详情:主表 + 步骤列表(按 step_order ASC)+ 每个步骤的4张子表数据。

步骤操作(实时保存)

方法 路径 说明
POST /step/add 从工艺库拖入创建步骤。入参:route_id, process_id, step_order。后端读取 biz_process 基础信息克隆到 step 表,读取4张子表数据批量克隆到 routestep* 子表。返回完整 step 对象(含子表)。
PUT /step/update 更新步骤基础信息(右侧面板字段修改时触发)。
PUT /step/reorder 批量重排步骤顺序。入参:route_id, step_ids[](新顺序数组)。遍历更新 step_order。
DELETE /step/delete/{id} 软删除步骤(del_flag=1)+ 级联软删除该步骤的4张子表数据。
PUT /step/reagent/update 更新步骤试剂子表。入参:step_id, items[](完整替换)。
PUT /step/consumable/update 更新步骤耗材子表。入参:step_id, items[](完整替换)。
PUT /step/output/update 更新步骤产出子表。入参:step_id, items[](完整替换)。
PUT /step/data-collection/update 更新步骤数据采集子表。入参:step_id, items[](完整替换)。

关键后端逻辑

拖入克隆(step/add):

  1. 校验 route_id 存在且属于当前租户
  2. 从 biz_process 读取工艺基础信息
  3. 插入 biz_process_route_step(克隆字段:process_code, category_id, process_name, description, equipment_name, estimated_duration, sop_ids)
  4. 从 biz_process_reagent 等4张子表批量读取并插入对应 biz_process_route_step_*
  5. 返回完整 step 对象

级联软删除:

  • 删除路线 → 遍历所有步骤设 del_flag=1 → 遍历每步子表设 del_flag=1
  • 删除步骤 → 遍历该步骤子表设 del_flag=1

子表更新(完整替换策略):

  • 接收 items[] 后,将现有记录 del_flag=1,然后批量插入新记录
  • 避免逐条 diff,简化逻辑

前端设计

文件清单

文件 操作 说明
web-admin/src/views/production/process-route/index.vue 新建 路线列表页 + 路线图配置弹窗
web-admin/src/api/production.ts 追加 route 相关 API 函数
web-admin/src/router/modules/sqxp.ts 追加 process-route 路由条目

列表页

遵循标准列表模板(参考 src/views/system/unit/index.vue):

  • 搜索栏:路线名称(模糊)、路线编码(精确)、产品类型(下拉,数据来自细胞产品类型管理列表 API)
  • 工具栏:新增按钮(单一 primary)
  • 表格列:路线名称、路线编码、产品类型名称、版本号、当前版本(is_current=1 时显示绿色 el-tag 标签)、备注、创建时间
  • 操作列(fixed="right", min-width ≥ 260px):路线图配置、编辑、设为当前版本(仅 is_current=0 时显示)、删除
  • 新增/编辑弹窗:标准表单(label-position="top"),字段:路线编码、路线名称、产品类型(下拉)、版本号(文本)、备注

路线图配置弹窗

弹窗属性:width="90vw",destroy-on-close,标题显示路线名称和版本号

三栏布局(flex):

┌──────────────────────────────────────────────────────────────┐
│  路线图配置 - {路线名称} v{版本号}                     [关闭] │
├────────────┬──────────────────────┬──────────────────────────┤
│  工艺库    │  工艺流程布局        │  工艺详情                │
│  (240px)   │  (flex:1)            │  (360px)                 │
│            │                      │                          │
│ [分类折叠] │  ┌────────────┐     │  工序名称: [___]         │
│ > 细胞培养 │  │ 步骤1: xxx │ ←拖  │  工序分类: [下拉]        │
│   [卡片]   │  └────────────┘     │  描述:     [___]         │
│   [卡片]   │  ┌────────────┐     │  设备:     [___]         │
│ > 病毒转染 │  │ 步骤2: xxx │     │  预估耗时: [___]         │
│   [卡片]   │  └────────────┘     │  SOP:      [选择]        │
│ > ...      │                      │  ──────────────          │
│            │  [+ 拖入工艺卡片]    │  [试剂][耗材][产出][采集] │
│            │                      │  (子表tab切换编辑)       │
└────────────┴──────────────────────┴──────────────────────────┘

左栏 — 工艺库面板(240px 固定宽度):

  • 使用 el-collapse 按工序分类(biz_process_category)分组展示
  • 每组内展示工艺小卡片:工序名称 + 编码
  • 卡片使用 HTML5 draggable="true" + @dragstart 传递 process_id
  • 无分类的工艺归入"未分类"组
  • 仅展示当前租户未删除的工艺

中栏 — 流程布局区(flex:1 自适应):

  • 使用 vue-draggable-plus(VueDraggable 组件)包裹步骤卡片列表
  • 每张步骤卡片显示:步骤序号(圆角标签)+ 工艺名称 + 操作按钮(删除图标)
  • 支持内部排序拖拽(handle 拖拽手柄,使用 <el-icon><Rank /></el-icon>)
  • 空白区域通过 @dragover.prevent + @drop 接收从左栏拖入的工艺卡片
  • 拖入时调用 POST /step/add,即时创建后端记录
  • 排序变化时调用 PUT /step/reorder
  • 点击卡片 → 选中态(高亮边框)→ 触发右栏加载详情
  • 卡片上的删除按钮 → 确认弹窗 → DELETE /step/delete/{id} → 从列表移除

右栏 — 工艺详情编辑面板(360px 固定宽度):

  • 顶部:工艺基础信息表单
    • 工序名称(el-input)
    • 工序分类(el-select,下拉数据来自工艺工序分类)
    • 描述(el-input textarea)
    • 设备名称(el-input)
    • 预估耗时(el-input)
    • SOP文档(el-select multiple,数据来自 SOP 文档列表)
    • 字段修改时 @change 触发 PUT /step/update 实时保存
  • 底部:4 个 el-tab-pane 切换子表编辑
    • 试剂 tab:表格列(试剂编码、名称、规格、标准用量、单位、是否关键物料),底部"添加"按钮新增行,每行有删除按钮。修改后实时保存 PUT /step/reagent/update
    • 耗材 tab:表格列(耗材编码、名称、规格型号、标准用量、单位、是否必需、备注),同上交互
    • 产出 tab:表格列(产出类型[下拉选择产出物类型]、名称、条码),同上交互
    • 数据采集 tab:表格列(采集名称、单位),同上交互
  • 未选中任何步骤时显示空状态提示:"请从左侧工艺库拖入工艺,或点击已添加的步骤查看详情"

数据流

拖入流程:
  左栏卡片 dragstart(process_id)
  → 中栏 drop 事件
  → POST /step/add {route_id, process_id, step_order: list.length + 1}
  → 后端克隆工艺+子表 → 返回完整 step
  → 前端追加到步骤列表

排序流程:
  中栏内 vue-draggable-plus 拖拽排序
  → @end 回调
  → PUT /step/reorder {route_id, step_ids: [...]}

编辑流程:
  右栏字段 @change
  → PUT /step/update {step_id, ...fields}
  
  右栏子表增删改
  → PUT /step/{sub-table}/update {step_id, items: [...]}

删除步骤:
  卡片删除按钮 → ElMessageBox.confirm
  → DELETE /step/delete/{step_id}
  → 前端从列表移除

点击步骤:
  中栏卡片 click
  → 设置 selectedStepId
  → 右栏加载该步骤详情(从列表数据中取,无需额外 API)

实现文件清单

层 文件 操作
SQL docs/sql/production_process_route.sql 新建:6张表DDL + 菜单INSERT + SOP order_num UPDATE
Model backend/app/models/models.py 追加 6 个 SQLAlchemy Model 类
Model backend/app/models/__init__.py 追加 6 个 export
CRUD backend/app/crud/process_route.py 新建:路线CRUD + 步骤CRUD + 子表CRUD + 克隆逻辑
API backend/app/api/production.py 追加 route 子路由(挂载到 production router 下)
Schema backend/app/schemas/process_route.py 新建:Pydantic v2 schemas
前端 API web-admin/src/api/production.ts 追加 route 相关函数(~15个 API 函数)
前端页面 web-admin/src/views/production/process-route/index.vue 新建:列表页 + 三栏拖拽配置弹窗
路由 web-admin/src/router/modules/sqxp.ts 追加 process-route 路由条目

风险与注意事项

  1. 跨容器拖拽:左栏(HTML5 Drag)→ 中栏(vue-draggable-plus)的跨容器拖入是核心难点。需在 drop 事件中正确获取 process_id 并调用 API。参考项目中 FlowDesigner.vue 的 HTML5 drag 实现。
  2. 大弹窗性能:90vw 弹窗内三栏 + 多个子表可能数据量大。步骤列表应限制每页展示(暂无分页需求,路线步骤通常 < 50)。
  3. 工艺库删除影响:步骤的 process_id 引用工艺库,但工艺被删除后步骤保留(process_id 可 null)。右栏编辑时不依赖 process_id 回查,所有数据已克隆到步骤中。
  4. 子表更新策略:使用完整替换(del旧 + insert新),避免复杂 diff。注意 UUID 生成和 tenant_no 继承。