# 工艺路线管理模块设计规格 ## 概述 实现需求文档 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: ```sql 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)**: 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 拖拽手柄,使用 ``) - 空白区域通过 `@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 继承。