17 KiB
工艺路线管理模块设计规格
概述
实现需求文档 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张子表数据批量克隆到 route_step_* 子表。返回完整 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):
- 校验 route_id 存在且属于当前租户
- 从
biz_process读取工艺基础信息 - 插入
biz_process_route_step(克隆字段:process_code, category_id, process_name, description, equipment_name, estimated_duration, sop_ids) - 从
biz_process_reagent等4张子表批量读取并插入对应biz_process_route_step_* - 返回完整 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:表格列(采集名称、单位),同上交互
- 试剂 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 路由条目 |
风险与注意事项
- 跨容器拖拽:左栏(HTML5 Drag)→ 中栏(vue-draggable-plus)的跨容器拖入是核心难点。需在 drop 事件中正确获取 process_id 并调用 API。参考项目中 FlowDesigner.vue 的 HTML5 drag 实现。
- 大弹窗性能:90vw 弹窗内三栏 + 多个子表可能数据量大。步骤列表应限制每页展示(暂无分页需求,路线步骤通常 < 50)。
- 工艺库删除影响:步骤的 process_id 引用工艺库,但工艺被删除后步骤保留(process_id 可 null)。右栏编辑时不依赖 process_id 回查,所有数据已克隆到步骤中。
- 子表更新策略:使用完整替换(del旧 + insert新),避免复杂 diff。注意 UUID 生成和 tenant_no 继承。