zlwu 4 месяцев назад
Родитель
Сommit
4daf990fab
39 измененных файлов с 1269 добавлено и 1081 удалено
  1. 10 0
      backend/app/crud/production_task.py
  2. 0 0
      backend/app/graphify-out/cache/ast/2850750ab62e28748dc49e5769694b8a2308c026a7ac70f86d67982f75290ded.json
  3. 595 584
      backend/graphify-out/.graphify_analysis.json
  4. 122 122
      backend/graphify-out/.graphify_extract.json
  5. 73 68
      backend/graphify-out/GRAPH_REPORT.md
  6. 1 1
      backend/graphify-out/graph.html
  7. 112 112
      backend/graphify-out/graph.json
  8. 1 1
      backend/graphify-out/manifest.json
  9. 70 66
      web-admin/graphify-out/.graphify_analysis.json
  10. 43 36
      web-admin/graphify-out/.graphify_extract.json
  11. 5 5
      web-admin/graphify-out/GRAPH_REPORT.md
  12. 1 1
      web-admin/graphify-out/graph.html
  13. 11 10
      web-admin/graphify-out/manifest.json
  14. 1 0
      web-admin/src/graphify-out/cache/ast/00eb8469953643a3c3cfba32b243aa6652fbdd8d852bc7e8513e89dbd7630548.json
  15. 0 0
      web-admin/src/graphify-out/cache/ast/29f0d7f63ca8062bcf4ea156a390b92c941be0891899b715e5f8aefdc82d0635.json
  16. 0 0
      web-admin/src/graphify-out/cache/ast/2b8f6cbf829f1472f4f5f6b2fb3c882085f932f03a762e9ee33389b9d20ba239.json
  17. 0 0
      web-admin/src/graphify-out/cache/ast/32583408ea3016c8701bdc44cce5c1323958d79f4b862a58236f9f0cd3758ebb.json
  18. 0 0
      web-admin/src/graphify-out/cache/ast/366e6f9a2dea604b81467606e638d15bfcf1a30f2a92489b0e4457eec37f976f.json
  19. 0 0
      web-admin/src/graphify-out/cache/ast/42e4a90a8f73359b4e0181772bcde64c4248f192d1ed604fad3c0ad6a4850217.json
  20. 0 0
      web-admin/src/graphify-out/cache/ast/57cc3ba80f162e1122461769890c1e1d1acedd7beb835fa74722151838c4e2f7.json
  21. 0 0
      web-admin/src/graphify-out/cache/ast/607f814d315332a54267c0eb5d3d5e08c1de2f63e5cff5e96e07618f2db57c9d.json
  22. 0 0
      web-admin/src/graphify-out/cache/ast/89263039e9bb13578bae782556e679d102df051c88b26a490274f7d3c7fbdba5.json
  23. 1 0
      web-admin/src/graphify-out/cache/ast/9dbb2b716ebfaeb608d62b840eb51b0f0de3f295c219e1510e162c435c646052.json
  24. 0 0
      web-admin/src/graphify-out/cache/ast/a659bb387b0297ed7791585c2ad430eed193dd5c94534e0d4ba68ef7f2118ca3.json
  25. 0 0
      web-admin/src/graphify-out/cache/ast/afc53ee5b030d413f9183e68618d0e53ac7b8b27881cea9b8dbf85ec7d71c8d3.json
  26. 0 0
      web-admin/src/graphify-out/cache/ast/b22e9912d60cbaac0ce4c6d5236c13947504d175e6c40402df51541ecb4bf3b1.json
  27. 0 0
      web-admin/src/graphify-out/cache/ast/bae03facd4defefef248e6487218a59a1e8859f6072b05cb173080ae8d28b9c9.json
  28. 0 0
      web-admin/src/graphify-out/cache/ast/c7c993aa902f22415f7c31b077186964f1e06f1340a2f1726eaf19c90e40deaa.json
  29. 0 0
      web-admin/src/graphify-out/cache/ast/d04eb091178fbb8a06bdee4d982551fcb59f3317415418a73ad118491f9a8d58.json
  30. 0 0
      web-admin/src/graphify-out/cache/ast/df352739a8f47bbd2334bb660316a35ef36309a1789088a64fbf662ecb7ceddb.json
  31. 0 0
      web-admin/src/graphify-out/cache/ast/f5fe1179bc158e2e8d230b5c7fc9f11d07aeb6fd7938292ab534872294cb11fa.json
  32. 75 0
      web-admin/src/views/production/production-work-order/StepDetailActionBar.vue
  33. 10 58
      web-admin/src/views/production/production-work-order/StepDetailPanel.vue
  34. 105 5
      web-admin/src/views/production/production-work-order/TaskExecutionDialog.vue
  35. 2 2
      web-admin/src/views/production/production-work-order/TaskInputEnhancedTable.vue
  36. 6 4
      web-admin/src/views/production/production-work-order/TaskOutputList.vue
  37. 2 2
      web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue
  38. 6 4
      web-admin/src/views/production/production-work-order/TaskPaperAttachmentsPanel.vue
  39. 17 0
      web-admin/src/views/production/production-work-order/useTaskStepEditor.ts

+ 10 - 0
backend/app/crud/production_task.py

@@ -1788,6 +1788,16 @@ def update_task_status(
         concl = (conclusion or "").strip()
         if concl not in ("cg", "sb"):
             raise ValueError("须选择生产结论(成功或失败)")
+        if previous_status != "ywc":
+            incomplete_step_count = db.query(BizProductionTaskStep).filter(
+                and_(
+                    BizProductionTaskStep.task_id == task_id,
+                    BizProductionTaskStep.del_flag == 0,
+                    BizProductionTaskStep.step_status != "ywc",
+                )
+            ).count()
+            if incomplete_step_count > 0:
+                raise ValueError("尚有未完成的工艺步骤,请完成全部步骤后再完成任务")
         task.conclusion = concl
         if remark is not None:
             task.remark = (remark or "").strip() or None

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
backend/app/graphify-out/cache/ast/2850750ab62e28748dc49e5769694b8a2308c026a7ac70f86d67982f75290ded.json


Разница между файлами не показана из-за своего большого размера
+ 595 - 584
backend/graphify-out/.graphify_analysis.json


Разница между файлами не показана из-за своего большого размера
+ 122 - 122
backend/graphify-out/.graphify_extract.json


+ 73 - 68
backend/graphify-out/GRAPH_REPORT.md

@@ -1,11 +1,11 @@
 # Graph Report - app  (2026-06-26)
 
 ## Corpus Check
-- Large corpus: 212 files · ~255,263 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
+- Large corpus: 212 files · ~255,288 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
 
 ## Summary
-- 4350 nodes · 23145 edges · 52 communities detected
-- Extraction: 32% EXTRACTED · 68% INFERRED · 0% AMBIGUOUS · INFERRED: 15664 edges (avg confidence: 0.59)
+- 4350 nodes · 23146 edges · 53 communities detected
+- Extraction: 32% EXTRACTED · 68% INFERRED · 0% AMBIGUOUS · INFERRED: 15665 edges (avg confidence: 0.59)
 - Token cost: 0 input · 0 output
 
 ## Community Hubs (Navigation)
@@ -55,12 +55,13 @@
 - [[_COMMUNITY_Community 43|Community 43]]
 - [[_COMMUNITY_Community 44|Community 44]]
 - [[_COMMUNITY_Community 45|Community 45]]
-- [[_COMMUNITY_Community 48|Community 48]]
+- [[_COMMUNITY_Community 46|Community 46]]
 - [[_COMMUNITY_Community 49|Community 49]]
 - [[_COMMUNITY_Community 50|Community 50]]
 - [[_COMMUNITY_Community 51|Community 51]]
 - [[_COMMUNITY_Community 52|Community 52]]
 - [[_COMMUNITY_Community 53|Community 53]]
+- [[_COMMUNITY_Community 54|Community 54]]
 
 ## God Nodes (most connected - your core abstractions)
 1. `success_response()` - 658 edges
@@ -90,7 +91,7 @@
 
 ### Community 0 - "Community 0"
 Cohesion: 0.01
-Nodes (864): attachment_download_url(), create_dept(), create_dict_data(), create_dict_type(), create_express_company(), create_item_unit(), create_menu(), create_post() (+856 more)
+Nodes (863): attachment_download_url(), create_dept(), create_dict_data(), create_dict_type(), create_express_company(), create_item_unit(), create_menu(), create_post() (+855 more)
 
 ### Community 1 - "Community 1"
 Cohesion: 0.01
@@ -102,76 +103,76 @@ Nodes (492): BatchRefundFinanceSubmitBody, 细胞存储 — 采集协议单 / 
 
 ### Community 3 - "Community 3"
 Cohesion: 0.01
-Nodes (358): _normalize_date_bound(), _persist_protocol_sub_resources(), list_ethnics_for_select(), get_designer_data(), apply_data_scope(), _get_child_dept_ids(), get_user_dept_id(), _resolve_user_role_scopes() (+350 more)
+Nodes (324): _normalize_date_bound(), _persist_protocol_sub_resources(), list_ethnics_for_select(), apply_data_scope(), _get_child_dept_ids(), get_user_dept_id(), _resolve_user_role_scopes(), format_date() (+316 more)
 
 ### Community 4 - "Community 4"
 Cohesion: 0.01
-Nodes (311): generate_uuid(), batch_stock_in(), batch_stock_out(), consumable_code_exists(), create_consumable(), _dec(), get_consumable(), _is_enabled_status() (+303 more)
+Nodes (319): generate_uuid(), batch_stock_in(), batch_stock_out(), consumable_code_exists(), create_consumable(), _dec(), get_consumable(), _is_enabled_status() (+311 more)
 
 ### Community 5 - "Community 5"
 Cohesion: 0.02
-Nodes (183): eq_unicode(), like_unicode(), MySQL 字符串比较/模糊匹配时统一排序规则,避免 Illegal mix of collations。, 跨表字符串等值比较(JOIN / WHERE)时统一为 utf8mb4_unicode_ci。, 将列显式设为 utf8mb4_unicode_ci。, unicode_ci(), batch_review(), _dec() (+175 more)
+Nodes (233): eq_unicode(), like_unicode(), MySQL 字符串比较/模糊匹配时统一排序规则,避免 Illegal mix of collations。, 跨表字符串等值比较(JOIN / WHERE)时统一为 utf8mb4_unicode_ci。, 将列显式设为 utf8mb4_unicode_ci。, unicode_ci(), batch_review(), _dec() (+225 more)
 
 ### Community 6 - "Community 6"
-Cohesion: 0.03
-Nodes (176): batch_discard(), batch_options(), _batch_row_to_dict(), batch_stock_in(), batch_stock_out(), _create_batch_and_usage(), delete_batch(), _discard_batch() (+168 more)
+Cohesion: 0.02
+Nodes (173): auto_create_payment(), get_protocol_nos_used_in_protocols(), create_dict_type(), dict_type_code_exists(), get_dict_type(), list_dict_types(), 字典类型 CRUD(sys_dict_type), _row_to_dict() (+165 more)
 
 ### Community 7 - "Community 7"
 Cohesion: 0.02
-Nodes (77): _error_json(), lifespan(), 数据库异常:返回 JSON 并保留 CORS 头,避免浏览器只报跨域错误, 未捕获异常:统一 JSON 500,确保跨域场景下仍有 Access-Control-Allow-Origin, sqlalchemy_exception_handler(), unhandled_exception_handler(), BaseHTTPMiddleware, parse_admin_request_json() (+69 more)
+Nodes (111): get_designer_data(), _error_json(), 数据库异常:返回 JSON 并保留 CORS 头,避免浏览器只报跨域错误, 未捕获异常:统一 JSON 500,确保跨域场景下仍有 Access-Control-Allow-Origin, sqlalchemy_exception_handler(), unhandled_exception_handler(), BaseHTTPMiddleware, parse_admin_request_json() (+103 more)
 
 ### Community 8 - "Community 8"
-Cohesion: 0.04
-Nodes (102): _fmt_dt(), get_handover_by_schedule(), get_outbound_handover(), get_outbound_handover_view(), _handover_row_to_dict(), _handover_view_dict(), _parse_handover_time(), 供输注计划 detail 聚合的交接只读信息。 (+94 more)
+Cohesion: 0.03
+Nodes (160): batch_discard(), batch_options(), _batch_row_to_dict(), batch_stock_in(), batch_stock_out(), _create_batch_and_usage(), delete_batch(), _discard_batch() (+152 more)
 
 ### Community 9 - "Community 9"
-Cohesion: 0.04
-Nodes (93): _avatar_url_field(), _build_current_user_payload(), _build_user_info_payload(), get_current_user_profile(), get_web_auth_menu_codes(), _jwt_user_payload(), logout(), 公共接口(Web 登录、刷新 Token、登出;用户模型对齐 docs/sql/zkxw.sql 基线) (+85 more)
-
-### Community 10 - "Community 10"
 Cohesion: 0.05
 Nodes (100): AccessLogCreateBody, AccessLogUpdateBody, CleanRoomCreateBody, CleanRoomUpdateBody, ExhibitionAppointmentCreateBody, ExhibitionAppointmentUpdateBody, OccupancyCreateBody, 运营支撑 API(洁净室、进出记录、仓储设施、储位、展厅预约) (+92 more)
 
-### Community 11 - "Community 11"
-Cohesion: 0.04
-Nodes (84): batch_create_sterilize_logs(), batch_create_wash_logs(), batch_discard_garments(), batch_soft_delete_garments(), _count_map(), create_garment(), create_sterilize_log(), create_wash_log() (+76 more)
-
-### Community 12 - "Community 12"
+### Community 10 - "Community 10"
 Cohesion: 0.04
-Nodes (84): auto_create_payment(), complete_application(), create_basic_experiment_sale_schedules(), create_infusion_plan(), create_schedule(), _create_schedule_products(), _format_yyyy_mm_dd(), _generate_application_no() (+76 more)
+Nodes (75): _avatar_url_field(), _build_current_user_payload(), _build_user_info_payload(), get_current_user_profile(), get_web_auth_menu_codes(), _jwt_user_payload(), logout(), 公共接口(Web 登录、刷新 Token、登出;用户模型对齐 docs/sql/zkxw.sql 基线) (+67 more)
 
-### Community 13 - "Community 13"
-Cohesion: 0.08
-Nodes (56): allocate_next_biz_no(), _format_biz_no(), _prefix_for(), 业务协议号自动生成:XBCC / JZCC + YYYYMMDD + 6 位序号, 原子分配下一个业务协议号。     须在 create 事务内调用;使用 MySQL LAST_INSERT_ID 保证并发安全。, create_collection_protocol(), _active_link_query(), assert_biz_no_available() (+48 more)
+### Community 11 - "Community 11"
+Cohesion: 0.05
+Nodes (63): generate_id(), optional_date(), parse_date(), 将字符串 / date / datetime 转为 date 或 None, camel_to_snake(), get_model_attr(), HistoryCRUD, allocate_next_biz_no() (+55 more)
 
-### Community 14 - "Community 14"
+### Community 12 - "Community 12"
 Cohesion: 0.07
 Nodes (54): get_next_cell_product_code(), _ancestor_link_from_cell_product_input(), _ancestor_sample_chain_link(), _apply_cell_product_status_change(), _build_ancestor_chain(), _build_descendant_chain(), _build_facility_path_label(), build_split_product_codes() (+46 more)
 
-### Community 15 - "Community 15"
+### Community 13 - "Community 13"
 Cohesion: 0.11
 Nodes (39): _amount_for_row(), _batch_map(), deduct_for_task(), _deduct_task_usage_legacy_batch(), _display_remark(), _fmt_date_val(), _linked_qc_task_no_map(), list_linked_task_usages() (+31 more)
 
-### Community 16 - "Community 16"
+### Community 14 - "Community 14"
+Cohesion: 0.11
+Nodes (35): batch_create_sterilize_logs(), batch_create_wash_logs(), batch_discard_garments(), batch_soft_delete_garments(), _count_map(), create_garment(), create_sterilize_log(), create_wash_log() (+27 more)
+
+### Community 15 - "Community 15"
 Cohesion: 0.12
 Nodes (11): get_wechat_pay_client(), get_wechat_pay_v2(), 微信支付工具模块  支持微信支付 V3 API 文档参考: https://pay.weixin.qq.com/docs/v3/apis/index.ht, 统一下单接口 (V3)          Args:             out_trade_no: 商户订单号             total, 申请退款          Args:             out_trade_no: 商户订单号             total_amount, 小程序调起支付的二次签名参数(与公众号 JSAPI 规则一致), 统一下单 (V2 API)          Args:             out_trade_no: 商户订单号             tot, 生成签名 V3 (SHA256 with RSA) (+3 more)
 
+### Community 16 - "Community 16"
+Cohesion: 0.17
+Nodes (21): batch_stock_in(), batch_stock_out(), code_exists(), create_material(), _dec(), _fmt_dt(), get_material(), _is_enabled_status() (+13 more)
+
 ### Community 17 - "Community 17"
 Cohesion: 0.16
 Nodes (22): _code_exists(), create_material(), _dec(), _fmt_dt(), get_material(), get_material_entity(), get_my_usages(), _is_enabled_status() (+14 more)
 
 ### Community 18 - "Community 18"
+Cohesion: 0.18
+Nodes (15): create_collection_region(), create_hospital(), get_collection_region(), get_hospital(), _hospital_names_by_region_ids(), _hospital_to_dict(), list_collection_regions(), list_hospitals() (+7 more)
+
+### Community 19 - "Community 19"
 Cohesion: 0.25
 Nodes (13): batch_issue_protocol_nos(), batch_revoke_protocol_nos(), create_protocol_no(), get_protocol_no(), list_protocol_nos(), _parse_issued_time(), protocol_no_exists(), 采集协议号池 CRUD(biz_collection_protocol_no) (+5 more)
 
-### Community 19 - "Community 19"
+### Community 20 - "Community 20"
 Cohesion: 0.26
 Nodes (12): batch_issue_sample_codes(), batch_revoke_sample_codes(), create_sample_code(), get_sample_code(), list_sample_codes(), _parse_issued_time(), 样本编码池 CRUD(biz_sample_code), _resolve_issued_user_display_name() (+4 more)
 
-### Community 20 - "Community 20"
-Cohesion: 0.27
-Nodes (11): _accept_conclusion_label(), _base_query(), _ensure_notification(), _fmt_dt(), generate_sms_content(), list_notifications(), _normalize_date_bound(), resolve_notification() (+3 more)
-
 ### Community 21 - "Community 21"
 Cohesion: 0.24
 Nodes (9): 微信消息通知服务  支持微信小程序模板消息推送  使用方式:     from app.core.wechat_message import Wech, 发送模板消息                  Args:             openid: 用户openid             templ, send_delivery_notice(), send_delivery_warning(), send_order_status(), send_payment_success(), send_refund_result(), send_urge_notification() (+1 more)
@@ -181,8 +182,8 @@ Cohesion: 0.23
 Nodes (12): Config, ProductBase, ProductCategoryBase, ProductCategoryCreate, ProductCategoryResponse, ProductCreate, ProductResponse, ProductUnitBase (+4 more)
 
 ### Community 23 - "Community 23"
-Cohesion: 0.31
-Nodes (11): create_equipment(), _dec(), equipment_code_exists(), equipment_is_referenced(), get_equipment(), list_equipments(), _norm_status(), _parse_date() (+3 more)
+Cohesion: 0.27
+Nodes (11): create_consumable_pack(), get_consumable_pack(), get_pack_detail(), _item_row_to_dict(), list_all_packs(), list_consumable_packs(), pack_code_exists(), 耗材包 CRUD(biz_consumable_pack + biz_consumable_pack_item) (+3 more)
 
 ### Community 24 - "Community 24"
 Cohesion: 0.24
@@ -206,16 +207,16 @@ Nodes (8): create_customer(), get_customer(), id_card_taken(), list_customers(),
 
 ### Community 29 - "Community 29"
 Cohesion: 0.31
-Nodes (8): create_dict_type(), dict_type_code_exists(), get_dict_type(), list_dict_types(), 字典类型 CRUD(sys_dict_type), _row_to_dict(), soft_delete_dict_type(), update_dict_type()
+Nodes (8): create_express_company(), express_code_exists(), get_express_company(), list_express_companies(), 快递公司 CRUD(biz_express_company), _row_to_dict(), soft_delete_express_company(), update_express_company()
 
 ### Community 30 - "Community 30"
-Cohesion: 0.31
-Nodes (8): create_sop_document(), get_sop_document(), list_sop_documents(), SOP文档 CRUD(biz_sop_document), _row_to_dict(), soft_delete_sop_document(), sop_code_exists(), update_sop_document()
-
-### Community 31 - "Community 31"
 Cohesion: 0.38
 Nodes (9): create_check_item(), get_check_item(), item_code_exists(), list_active_check_items(), list_check_items(), _order_query(), _row_to_dict(), soft_delete_check_item() (+1 more)
 
+### Community 31 - "Community 31"
+Cohesion: 0.27
+Nodes (8): create_dict_data(), get_dict_data(), list_dict_data(), list_dict_data_options(), 字典数据 CRUD(sys_dict_data), _row_to_dict(), soft_delete_dict_data(), update_dict_data()
+
 ### Community 32 - "Community 32"
 Cohesion: 0.38
 Nodes (9): create_env_detect_item(), get_env_detect_item(), item_code_exists(), list_active_env_detect_items(), list_env_detect_items(), _order_query(), _row_to_dict(), soft_delete_env_detect_item() (+1 more)
@@ -242,94 +243,98 @@ Nodes (7): create_post(), get_post(), list_posts(), post_code_exists(), _row_to_
 
 ### Community 38 - "Community 38"
 Cohesion: 0.36
-Nodes (7): create_site(), get_site(), list_sites(), _row_to_dict(), site_code_exists(), soft_delete_site(), update_site()
+Nodes (7): create_tenant(), get_tenant(), list_tenants(), _row_to_dict(), soft_delete_tenant(), tenant_no_exists(), update_tenant()
 
 ### Community 39 - "Community 39"
+Cohesion: 0.36
+Nodes (7): create_site(), get_site(), list_sites(), _row_to_dict(), site_code_exists(), soft_delete_site(), update_site()
+
+### Community 40 - "Community 40"
 Cohesion: 0.29
 Nodes (2): BaseModel, SoftDeleteModel
 
-### Community 40 - "Community 40"
+### Community 41 - "Community 41"
 Cohesion: 0.38
 Nodes (6): derive_legacy_status_from_three(), derive_three_status_from_legacy(), 阶段A兼容:从旧 status 推导三字段。     约束:旧 status 本身混合了支付/配送/订单生命周期,因此只能尽力推断;     若业务侧已有更精确, 阶段A兼容:从三字段反推旧 status(用于旧前端/旧筛选参数兼容)。, ThreeStatus, _to_decimal()
 
-### Community 41 - "Community 41"
+### Community 42 - "Community 42"
 Cohesion: 0.5
 Nodes (3): BaseSettings, Config, Settings
 
-### Community 42 - "Community 42"
+### Community 43 - "Community 43"
 Cohesion: 0.5
 Nodes (2): _auto_fill_create_dept_id(), ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID
 
-### Community 43 - "Community 43"
+### Community 44 - "Community 44"
 Cohesion: 0.67
 Nodes (2): get_menu_list(), 运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,5 个一级菜单分组)
 
-### Community 44 - "Community 44"
+### Community 45 - "Community 45"
 Cohesion: 1.0
 Nodes (1): 请求级别的用户上下文变量  通过 contextvars 在请求处理链路中传递当前用户信息, 供 SQLAlchemy 事件监听器等底层机制使用,避免逐层手动传
 
-### Community 45 - "Community 45"
+### Community 46 - "Community 46"
 Cohesion: 1.0
 Nodes (1): API 路由包(骨架:公共鉴权 + 静态菜单 + 运营端占位)
 
-### Community 48 - "Community 48"
+### Community 49 - "Community 49"
 Cohesion: 1.0
 Nodes (1): 发送订单状态通知                  Args:             openid: 用户openid             ord
 
-### Community 49 - "Community 49"
+### Community 50 - "Community 50"
 Cohesion: 1.0
 Nodes (1): 发送支付成功通知                  Args:             openid: 用户openid             ord
 
-### Community 50 - "Community 50"
+### Community 51 - "Community 51"
 Cohesion: 1.0
 Nodes (1): 发送配送通知                  Args:             openid: 配送员openid             task
 
-### Community 51 - "Community 51"
+### Community 52 - "Community 52"
 Cohesion: 1.0
 Nodes (1): 发送配送时效预警                  Args:             openid: 配送员openid             ta
 
-### Community 52 - "Community 52"
+### Community 53 - "Community 53"
 Cohesion: 1.0
 Nodes (1): 发送退款结果通知                  Args:             openid: 用户openid             ord
 
-### Community 53 - "Community 53"
+### Community 54 - "Community 54"
 Cohesion: 1.0
 Nodes (1): 发送催单通知给配送员                  Args:             deliver_openid: 配送员openid
 
 ## Knowledge Gaps
 - **131 isolated node(s):** `刷新会话 TTL(refreshToken 续期;已登录用户每次 API 校验通过后滑动续期)`, `写入 refreshToken(用于 one-time 使用/吊销)`, `根据 token 获取当前用户(包含 Redis 会话校验;会话滑动续期)`, `登录凭据 RSA 解密(与前端 JSEncrypt PKCS#1 v1.5 兼容)。`, `加载 RSA 密钥对:优先环境变量,否则使用内置固定默认密钥。` (+126 more)
   These have ≤1 connection - possible missing edges or undocumented components.
-- **Thin community `Community 39`** (8 nodes): `BaseModel`, `.to_dict()`, `get_active()`, `base.py`, `SoftDeleteModel`, `.restore()`, `.soft_delete()`, `__tablename__()`
+- **Thin community `Community 40`** (8 nodes): `BaseModel`, `.to_dict()`, `get_active()`, `base.py`, `SoftDeleteModel`, `.restore()`, `.soft_delete()`, `__tablename__()`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 42`** (4 nodes): `_auto_fill_create_dept_id()`, `get_db()`, `database.py`, `ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID`
+- **Thin community `Community 43`** (4 nodes): `_auto_fill_create_dept_id()`, `get_db()`, `database.py`, `ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 43`** (3 nodes): `get_menu_list()`, `menu.py`, `运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,5 个一级菜单分组)`
+- **Thin community `Community 44`** (3 nodes): `get_menu_list()`, `menu.py`, `运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,5 个一级菜单分组)`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 44`** (2 nodes): `context.py`, `请求级别的用户上下文变量  通过 contextvars 在请求处理链路中传递当前用户信息, 供 SQLAlchemy 事件监听器等底层机制使用,避免逐层手动传`
+- **Thin community `Community 45`** (2 nodes): `context.py`, `请求级别的用户上下文变量  通过 contextvars 在请求处理链路中传递当前用户信息, 供 SQLAlchemy 事件监听器等底层机制使用,避免逐层手动传`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 45`** (2 nodes): `__init__.py`, `API 路由包(骨架:公共鉴权 + 静态菜单 + 运营端占位)`
+- **Thin community `Community 46`** (2 nodes): `__init__.py`, `API 路由包(骨架:公共鉴权 + 静态菜单 + 运营端占位)`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 48`** (1 nodes): `发送订单状态通知                  Args:             openid: 用户openid             ord`
+- **Thin community `Community 49`** (1 nodes): `发送订单状态通知                  Args:             openid: 用户openid             ord`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 49`** (1 nodes): `发送支付成功通知                  Args:             openid: 用户openid             ord`
+- **Thin community `Community 50`** (1 nodes): `发送支付成功通知                  Args:             openid: 用户openid             ord`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 50`** (1 nodes): `发送配送通知                  Args:             openid: 配送员openid             task`
+- **Thin community `Community 51`** (1 nodes): `发送配送通知                  Args:             openid: 配送员openid             task`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 51`** (1 nodes): `发送配送时效预警                  Args:             openid: 配送员openid             ta`
+- **Thin community `Community 52`** (1 nodes): `发送配送时效预警                  Args:             openid: 配送员openid             ta`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 52`** (1 nodes): `发送退款结果通知                  Args:             openid: 用户openid             ord`
+- **Thin community `Community 53`** (1 nodes): `发送退款结果通知                  Args:             openid: 用户openid             ord`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 53`** (1 nodes): `发送催单通知给配送员                  Args:             deliver_openid: 配送员openid`
+- **Thin community `Community 54`** (1 nodes): `发送催单通知给配送员                  Args:             deliver_openid: 配送员openid`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
 
 ## Suggested Questions
 _Questions this graph is uniquely positioned to answer:_
 
-- **Why does `SysUser` connect `Community 1` to `Community 0`, `Community 2`, `Community 3`, `Community 4`, `Community 5`, `Community 6`, `Community 8`, `Community 9`, `Community 10`, `Community 15`, `Community 17`, `Community 18`, `Community 19`, `Community 24`, `Community 25`?**
+- **Why does `SysUser` connect `Community 1` to `Community 0`, `Community 2`, `Community 3`, `Community 4`, `Community 5`, `Community 6`, `Community 8`, `Community 9`, `Community 10`, `Community 13`, `Community 17`, `Community 19`, `Community 20`, `Community 24`, `Community 25`?**
   _High betweenness centrality (0.120) - this node is a cross-community bridge._
-- **Why does `generate_uuid()` connect `Community 4` to `Community 0`, `Community 1`, `Community 2`, `Community 3`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 9`, `Community 10`, `Community 11`, `Community 13`, `Community 14`, `Community 15`, `Community 17`, `Community 18`, `Community 19`, `Community 20`, `Community 23`, `Community 24`, `Community 25`, `Community 26`, `Community 27`, `Community 28`, `Community 29`, `Community 30`, `Community 31`, `Community 32`, `Community 33`, `Community 34`, `Community 35`, `Community 36`, `Community 37`, `Community 38`?**
+- **Why does `generate_uuid()` connect `Community 4` to `Community 0`, `Community 1`, `Community 2`, `Community 3`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 9`, `Community 10`, `Community 11`, `Community 12`, `Community 13`, `Community 14`, `Community 16`, `Community 17`, `Community 18`, `Community 19`, `Community 20`, `Community 23`, `Community 24`, `Community 25`, `Community 26`, `Community 27`, `Community 28`, `Community 29`, `Community 30`, `Community 31`, `Community 32`, `Community 33`, `Community 34`, `Community 35`, `Community 36`, `Community 37`, `Community 38`, `Community 39`?**
   _High betweenness centrality (0.064) - this node is a cross-community bridge._
-- **Why does `success_response()` connect `Community 0` to `Community 1`, `Community 3`, `Community 9`, `Community 43`, `Community 14`?**
+- **Why does `success_response()` connect `Community 0` to `Community 1`, `Community 3`, `Community 7`, `Community 10`, `Community 44`, `Community 12`?**
   _High betweenness centrality (0.047) - this node is a cross-community bridge._
 - **Are the 714 inferred relationships involving `str` (e.g. with `unhandled_exception_handler()` and `get_current_user_by_token()`) actually correct?**
   _`str` has 714 INFERRED edges - model-reasoned connections that need verification._

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
backend/graphify-out/graph.html


Разница между файлами не показана из-за своего большого размера
+ 112 - 112
backend/graphify-out/graph.json


+ 1 - 1
backend/graphify-out/manifest.json

@@ -97,7 +97,7 @@
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/material_usage.py": 1781427187.4456425,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_material_usage.py": 1781513732.7796721,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/sop_document.py": 1779265721.9928973,
-  "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_task.py": 1782472559.9628916,
+  "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_task.py": 1782483658.3968706,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_third_party_item.py": 1779265661.482352,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/reagent.py": 1779512669.9847593,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/collection_protocol.py": 1782195334.7812605,

+ 70 - 66
web-admin/graphify-out/.graphify_analysis.json

@@ -3128,201 +3128,204 @@
       "views_production_production_work_order_productionworkordercardlist_vue"
     ],
     "258": [
-      "views_production_consumable_pack_index_vue"
+      "views_production_production_work_order_stepdetailactionbar_vue"
     ],
     "259": [
-      "views_production_consumable_index_vue"
+      "views_production_consumable_pack_index_vue"
     ],
     "260": [
-      "views_production_cleanroom_garment_index_vue"
+      "views_production_consumable_index_vue"
     ],
     "261": [
-      "views_production_production_material_index_vue"
+      "views_production_cleanroom_garment_index_vue"
     ],
     "262": [
-      "views_production_production_material_productionmaterialstockchecktab_vue"
+      "views_production_production_material_index_vue"
     ],
     "263": [
-      "views_production_production_material_productionmaterialpreptab_vue"
+      "views_production_production_material_productionmaterialstockchecktab_vue"
     ],
     "264": [
-      "views_production_sample_reception_completion_index_vue"
+      "views_production_production_material_productionmaterialpreptab_vue"
     ],
     "265": [
-      "views_production_equipment_index_vue"
+      "views_production_sample_reception_completion_index_vue"
     ],
     "266": [
-      "views_production_process_index_vue"
+      "views_production_equipment_index_vue"
     ],
     "267": [
-      "views_production_process_category_index_vue"
+      "views_production_process_index_vue"
     ],
     "268": [
-      "views_production_process_category_outputtypetab_vue"
+      "views_production_process_category_index_vue"
     ],
     "269": [
-      "views_index_index_vue"
+      "views_production_process_category_outputtypetab_vue"
     ],
     "270": [
-      "views_quality_inspection_qc_work_order_qcworkordertaskresultpanel_vue"
+      "views_index_index_vue"
     ],
     "271": [
-      "views_quality_inspection_qc_work_order_qcworkorderconfigurebatches_vue"
+      "views_quality_inspection_qc_work_order_qcworkordertaskresultpanel_vue"
     ],
     "272": [
-      "views_quality_inspection_qc_work_order_qcworkorderfeesummarybar_vue"
+      "views_quality_inspection_qc_work_order_qcworkorderconfigurebatches_vue"
     ],
     "273": [
-      "views_quality_inspection_qc_work_order_view_dialogs_outboundqcdiagnosisattachments_vue"
+      "views_quality_inspection_qc_work_order_qcworkorderfeesummarybar_vue"
     ],
     "274": [
-      "views_quality_inspection_sample_reception_check_item_index_vue"
+      "views_quality_inspection_qc_work_order_view_dialogs_outboundqcdiagnosisattachments_vue"
     ],
     "275": [
-      "views_quality_inspection_lab_test_item_index_vue"
+      "views_quality_inspection_sample_reception_check_item_index_vue"
     ],
     "276": [
-      "views_quality_inspection_lab_test_item_thirdpartycompanytab_vue"
+      "views_quality_inspection_lab_test_item_index_vue"
     ],
     "277": [
-      "views_quality_inspection_my_qc_material_usage_index_vue"
+      "views_quality_inspection_lab_test_item_thirdpartycompanytab_vue"
     ],
     "278": [
-      "views_quality_inspection_pre_transit_sample_index_vue"
+      "views_quality_inspection_my_qc_material_usage_index_vue"
     ],
     "279": [
-      "views_quality_inspection_pre_transit_sample_pretransitprotocolcardlist_vue"
+      "views_quality_inspection_pre_transit_sample_index_vue"
     ],
     "280": [
-      "views_quality_inspection_sample_reception_index_vue"
+      "views_quality_inspection_pre_transit_sample_pretransitprotocolcardlist_vue"
     ],
     "281": [
-      "views_quality_inspection_sample_reception_samplereceptioncardlist_vue"
+      "views_quality_inspection_sample_reception_index_vue"
     ],
     "282": [
-      "views_quality_inspection_qc_task_index_vue"
+      "views_quality_inspection_sample_reception_samplereceptioncardlist_vue"
     ],
     "283": [
-      "views_quality_inspection_storage_release_review_index_vue"
+      "views_quality_inspection_qc_task_index_vue"
     ],
     "284": [
-      "views_quality_inspection_qc_material_usage_index_vue"
+      "views_quality_inspection_storage_release_review_index_vue"
     ],
     "285": [
-      "views_quality_inspection_third_party_company_index_vue"
+      "views_quality_inspection_qc_material_usage_index_vue"
     ],
     "286": [
-      "views_exception_500_index_vue"
+      "views_quality_inspection_third_party_company_index_vue"
     ],
     "287": [
-      "views_exception_403_index_vue"
+      "views_exception_500_index_vue"
     ],
     "288": [
-      "views_exception_404_index_vue"
+      "views_exception_403_index_vue"
     ],
     "289": [
-      "views_pilot_service_cell_product_type_index_vue"
+      "views_exception_404_index_vue"
     ],
     "290": [
-      "views_pilot_service_cell_product_type_cellproductpricetab_vue"
+      "views_pilot_service_cell_product_type_index_vue"
     ],
     "291": [
-      "views_pilot_service_cell_product_type_cellproductspectab_vue"
+      "views_pilot_service_cell_product_type_cellproductpricetab_vue"
     ],
     "292": [
-      "views_pilot_service_infusion_schedule_management_index_vue"
+      "views_pilot_service_cell_product_type_cellproductspectab_vue"
     ],
     "293": [
-      "views_pilot_service_infusion_schedule_management_scheduleviewdialog_vue"
+      "views_pilot_service_infusion_schedule_management_index_vue"
     ],
     "294": [
-      "views_pilot_service_channel_service_order_index_vue"
+      "views_pilot_service_infusion_schedule_management_scheduleviewdialog_vue"
     ],
     "295": [
-      "views_pilot_service_channel_service_order_serviceordertxntable_vue"
+      "views_pilot_service_channel_service_order_index_vue"
     ],
     "296": [
-      "views_pilot_service_channel_service_order_serviceordertxndialog_vue"
+      "views_pilot_service_channel_service_order_serviceordertxntable_vue"
     ],
     "297": [
-      "views_pilot_service_channel_index_vue"
+      "views_pilot_service_channel_service_order_serviceordertxndialog_vue"
     ],
     "298": [
-      "views_pilot_service_clinical_refund_confirm_index_vue"
+      "views_pilot_service_channel_index_vue"
     ],
     "299": [
-      "views_pilot_service_outbound_release_review_outboundreleaseviewdialog_vue"
+      "views_pilot_service_clinical_refund_confirm_index_vue"
     ],
     "300": [
-      "views_pilot_service_cell_product_price_index_vue"
+      "views_pilot_service_outbound_release_review_outboundreleaseviewdialog_vue"
     ],
     "301": [
-      "views_pilot_service_checkup_management_index_vue"
+      "views_pilot_service_cell_product_price_index_vue"
     ],
     "302": [
-      "views_pilot_service_clinical_payment_info_index_vue"
+      "views_pilot_service_checkup_management_index_vue"
     ],
     "303": [
-      "views_pilot_service_project_price_system_index_vue"
+      "views_pilot_service_clinical_payment_info_index_vue"
     ],
     "304": [
-      "views_pilot_service_cell_product_spec_index_vue"
+      "views_pilot_service_project_price_system_index_vue"
     ],
     "305": [
-      "views_basic_info_sample_code_index_vue"
+      "views_pilot_service_cell_product_spec_index_vue"
     ],
     "306": [
-      "views_basic_info_collection_protocol_no_index_vue"
+      "views_basic_info_sample_code_index_vue"
     ],
     "307": [
-      "views_basic_info_collection_region_index_vue"
+      "views_basic_info_collection_protocol_no_index_vue"
     ],
     "308": [
-      "views_basic_info_collection_region_collectionprotocolnotab_vue"
+      "views_basic_info_collection_region_index_vue"
     ],
     "309": [
-      "assets_svg_loading_ts"
+      "views_basic_info_collection_region_collectionprotocolnotab_vue"
     ],
     "310": [
-      "store_modules_qc_menu_badge_ts"
+      "assets_svg_loading_ts"
     ],
     "311": [
-      "store_modules_worktab_ts"
+      "store_modules_qc_menu_badge_ts"
     ],
     "312": [
-      "store_modules_table_ts"
+      "store_modules_worktab_ts"
     ],
     "313": [
-      "store_modules_menu_ts"
+      "store_modules_table_ts"
     ],
     "314": [
-      "store_modules_setting_ts"
+      "store_modules_menu_ts"
     ],
     "315": [
-      "store_modules_production_menu_badge_ts"
+      "store_modules_setting_ts"
     ],
     "316": [
-      "store_modules_user_ts"
+      "store_modules_production_menu_badge_ts"
     ],
     "317": [
-      "router_routesalias_ts"
+      "store_modules_user_ts"
     ],
     "318": [
-      "router_core_index_ts"
+      "router_routesalias_ts"
     ],
     "319": [
-      "router_modules_sqxp_ts"
+      "router_core_index_ts"
     ],
     "320": [
-      "router_modules_index_ts"
+      "router_modules_sqxp_ts"
     ],
     "321": [
-      "router_modules_exception_ts"
+      "router_modules_index_ts"
     ],
     "322": [
-      "router_routes_staticroutes_ts"
+      "router_modules_exception_ts"
     ],
     "323": [
+      "router_routes_staticroutes_ts"
+    ],
+    "324": [
       "router_routes_asyncroutes_ts"
     ]
   },
@@ -3650,7 +3653,8 @@
     "320": 1.0,
     "321": 1.0,
     "322": 1.0,
-    "323": 1.0
+    "323": 1.0,
+    "324": 1.0
   },
   "gods": [
     {

+ 43 - 36
web-admin/graphify-out/.graphify_extract.json

@@ -13543,7 +13543,7 @@
       "label": "for()",
       "file_type": "code",
       "source_file": "views/production/production-work-order/StepDetailPanel.vue",
-      "source_location": "L999"
+      "source_location": "L952"
     },
     {
       "id": "views_production_production_work_order_completeproductiontaskdialog_vue",
@@ -13760,28 +13760,28 @@
       "label": "onStepListDrop()",
       "file_type": "code",
       "source_file": "views/production/production-work-order/TaskExecutionDialog.vue",
-      "source_location": "L448"
+      "source_location": "L490"
     },
     {
       "id": "production_work_order_taskexecutiondialog_handleforwardsuccess",
       "label": "handleForwardSuccess()",
       "file_type": "code",
       "source_file": "views/production/production-work-order/TaskExecutionDialog.vue",
-      "source_location": "L453"
+      "source_location": "L495"
     },
     {
       "id": "production_work_order_taskexecutiondialog_handleclose",
       "label": "handleClose()",
       "file_type": "code",
       "source_file": "views/production/production-work-order/TaskExecutionDialog.vue",
-      "source_location": "L459"
+      "source_location": "L501"
     },
     {
       "id": "production_work_order_taskexecutiondialog_stepseqclass",
       "label": "stepSeqClass()",
       "file_type": "code",
       "source_file": "views/production/production-work-order/TaskExecutionDialog.vue",
-      "source_location": "L470"
+      "source_location": "L512"
     },
     {
       "id": "views_production_production_work_order_edittaskinfodialog_vue",
@@ -13823,70 +13823,70 @@
       "label": "outputCardKey()",
       "file_type": "code",
       "source_file": "views/production/production-work-order/TaskOutputList.vue",
-      "source_location": "L157"
+      "source_location": "L159"
     },
     {
       "id": "production_work_order_taskoutputlist_displaytext",
       "label": "displayText()",
       "file_type": "code",
       "source_file": "views/production/production-work-order/TaskOutputList.vue",
-      "source_location": "L161"
+      "source_location": "L163"
     },
     {
       "id": "production_work_order_taskoutputlist_statustagtype",
       "label": "statusTagType()",
       "file_type": "code",
       "source_file": "views/production/production-work-order/TaskOutputList.vue",
-      "source_location": "L166"
+      "source_location": "L168"
     },
     {
       "id": "production_work_order_taskoutputlist_statuslabel",
       "label": "statusLabel()",
       "file_type": "code",
       "source_file": "views/production/production-work-order/TaskOutputList.vue",
-      "source_location": "L176"
+      "source_location": "L178"
     },
     {
       "id": "production_work_order_taskoutputlist_handleadd",
       "label": "handleAdd()",
       "file_type": "code",
       "source_file": "views/production/production-work-order/TaskOutputList.vue",
-      "source_location": "L186"
+      "source_location": "L188"
     },
     {
       "id": "production_work_order_taskoutputlist_handleedit",
       "label": "handleEdit()",
       "file_type": "code",
       "source_file": "views/production/production-work-order/TaskOutputList.vue",
-      "source_location": "L192"
+      "source_location": "L194"
     },
     {
       "id": "production_work_order_taskoutputlist_handledelete",
       "label": "handleDelete()",
       "file_type": "code",
       "source_file": "views/production/production-work-order/TaskOutputList.vue",
-      "source_location": "L198"
+      "source_location": "L200"
     },
     {
       "id": "production_work_order_taskoutputlist_formatdateonly",
       "label": "formatDateOnly()",
       "file_type": "code",
       "source_file": "views/production/production-work-order/TaskOutputList.vue",
-      "source_location": "L207"
+      "source_location": "L209"
     },
     {
       "id": "production_work_order_taskoutputlist_enrichoutputrow",
       "label": "enrichOutputRow()",
       "file_type": "code",
       "source_file": "views/production/production-work-order/TaskOutputList.vue",
-      "source_location": "L213"
+      "source_location": "L215"
     },
     {
       "id": "production_work_order_taskoutputlist_handledialogconfirm",
       "label": "handleDialogConfirm()",
       "file_type": "code",
       "source_file": "views/production/production-work-order/TaskOutputList.vue",
-      "source_location": "L228"
+      "source_location": "L230"
     },
     {
       "id": "views_production_production_work_order_dispatchtaskdialog_vue",
@@ -14014,6 +14014,13 @@
       "source_file": "views/production/production-work-order/WorkOrderTaskStepSubList.vue",
       "source_location": "L136"
     },
+    {
+      "id": "views_production_production_work_order_stepdetailactionbar_vue",
+      "label": "StepDetailActionBar.vue",
+      "file_type": "code",
+      "source_file": "views/production/production-work-order/StepDetailActionBar.vue",
+      "source_location": "L1"
+    },
     {
       "id": "views_production_production_work_order_productionworkordertabbar_vue",
       "label": "ProductionWorkOrderTabBar.vue",
@@ -41859,7 +41866,7 @@
       "relation": "contains",
       "confidence": "EXTRACTED",
       "source_file": "views/production/production-work-order/StepDetailPanel.vue",
-      "source_location": "L999",
+      "source_location": "L952",
       "weight": 1.0
     },
     {
@@ -42336,7 +42343,7 @@
       "relation": "contains",
       "confidence": "EXTRACTED",
       "source_file": "views/production/production-work-order/TaskExecutionDialog.vue",
-      "source_location": "L448",
+      "source_location": "L490",
       "weight": 1.0
     },
     {
@@ -42345,7 +42352,7 @@
       "relation": "contains",
       "confidence": "EXTRACTED",
       "source_file": "views/production/production-work-order/TaskExecutionDialog.vue",
-      "source_location": "L453",
+      "source_location": "L495",
       "weight": 1.0
     },
     {
@@ -42354,7 +42361,7 @@
       "relation": "contains",
       "confidence": "EXTRACTED",
       "source_file": "views/production/production-work-order/TaskExecutionDialog.vue",
-      "source_location": "L459",
+      "source_location": "L501",
       "weight": 1.0
     },
     {
@@ -42363,7 +42370,7 @@
       "relation": "contains",
       "confidence": "EXTRACTED",
       "source_file": "views/production/production-work-order/TaskExecutionDialog.vue",
-      "source_location": "L470",
+      "source_location": "L512",
       "weight": 1.0
     },
     {
@@ -42435,7 +42442,7 @@
       "relation": "contains",
       "confidence": "EXTRACTED",
       "source_file": "views/production/production-work-order/TaskOutputList.vue",
-      "source_location": "L157",
+      "source_location": "L159",
       "weight": 1.0
     },
     {
@@ -42444,7 +42451,7 @@
       "relation": "contains",
       "confidence": "EXTRACTED",
       "source_file": "views/production/production-work-order/TaskOutputList.vue",
-      "source_location": "L161",
+      "source_location": "L163",
       "weight": 1.0
     },
     {
@@ -42453,7 +42460,7 @@
       "relation": "contains",
       "confidence": "EXTRACTED",
       "source_file": "views/production/production-work-order/TaskOutputList.vue",
-      "source_location": "L166",
+      "source_location": "L168",
       "weight": 1.0
     },
     {
@@ -42462,7 +42469,7 @@
       "relation": "contains",
       "confidence": "EXTRACTED",
       "source_file": "views/production/production-work-order/TaskOutputList.vue",
-      "source_location": "L176",
+      "source_location": "L178",
       "weight": 1.0
     },
     {
@@ -42471,7 +42478,7 @@
       "relation": "contains",
       "confidence": "EXTRACTED",
       "source_file": "views/production/production-work-order/TaskOutputList.vue",
-      "source_location": "L186",
+      "source_location": "L188",
       "weight": 1.0
     },
     {
@@ -42480,7 +42487,7 @@
       "relation": "contains",
       "confidence": "EXTRACTED",
       "source_file": "views/production/production-work-order/TaskOutputList.vue",
-      "source_location": "L192",
+      "source_location": "L194",
       "weight": 1.0
     },
     {
@@ -42489,7 +42496,7 @@
       "relation": "contains",
       "confidence": "EXTRACTED",
       "source_file": "views/production/production-work-order/TaskOutputList.vue",
-      "source_location": "L198",
+      "source_location": "L200",
       "weight": 1.0
     },
     {
@@ -42498,7 +42505,7 @@
       "relation": "contains",
       "confidence": "EXTRACTED",
       "source_file": "views/production/production-work-order/TaskOutputList.vue",
-      "source_location": "L207",
+      "source_location": "L209",
       "weight": 1.0
     },
     {
@@ -42507,7 +42514,7 @@
       "relation": "contains",
       "confidence": "EXTRACTED",
       "source_file": "views/production/production-work-order/TaskOutputList.vue",
-      "source_location": "L213",
+      "source_location": "L215",
       "weight": 1.0
     },
     {
@@ -42516,7 +42523,7 @@
       "relation": "contains",
       "confidence": "EXTRACTED",
       "source_file": "views/production/production-work-order/TaskOutputList.vue",
-      "source_location": "L228",
+      "source_location": "L230",
       "weight": 1.0
     },
     {
@@ -58422,7 +58429,7 @@
       "confidence": "INFERRED",
       "confidence_score": 0.8,
       "source_file": "views/production/production-work-order/StepDetailPanel.vue",
-      "source_location": "L1001",
+      "source_location": "L954",
       "weight": 1.0
     },
     {
@@ -58572,7 +58579,7 @@
       "confidence": "INFERRED",
       "confidence_score": 0.8,
       "source_file": "views/production/production-work-order/TaskExecutionDialog.vue",
-      "source_location": "L456",
+      "source_location": "L498",
       "weight": 1.0
     },
     {
@@ -58582,7 +58589,7 @@
       "confidence": "INFERRED",
       "confidence_score": 0.8,
       "source_file": "views/production/production-work-order/TaskExecutionDialog.vue",
-      "source_location": "L460",
+      "source_location": "L502",
       "weight": 1.0
     },
     {
@@ -58632,7 +58639,7 @@
       "confidence": "INFERRED",
       "confidence_score": 0.8,
       "source_file": "views/production/production-work-order/TaskOutputList.vue",
-      "source_location": "L204",
+      "source_location": "L206",
       "weight": 1.0
     },
     {
@@ -58642,7 +58649,7 @@
       "confidence": "INFERRED",
       "confidence_score": 0.8,
       "source_file": "views/production/production-work-order/TaskOutputList.vue",
-      "source_location": "L239",
+      "source_location": "L241",
       "weight": 1.0
     },
     {
@@ -58652,7 +58659,7 @@
       "confidence": "INFERRED",
       "confidence_score": 0.8,
       "source_file": "views/production/production-work-order/TaskOutputList.vue",
-      "source_location": "L241",
+      "source_location": "L243",
       "weight": 1.0
     },
     {

+ 5 - 5
web-admin/graphify-out/GRAPH_REPORT.md

@@ -1,10 +1,10 @@
 # Graph Report - src  (2026-06-26)
 
 ## Corpus Check
-- Large corpus: 814 files · ~540,541 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
+- Large corpus: 815 files · ~540,864 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
 
 ## Summary
-- 2677 nodes · 3702 edges · 57 communities detected
+- 2678 nodes · 3702 edges · 57 communities detected
 - Extraction: 78% EXTRACTED · 22% INFERRED · 0% AMBIGUOUS · INFERRED: 815 edges (avg confidence: 0.8)
 - Token cost: 0 input · 0 output
 
@@ -357,11 +357,11 @@ Nodes (2): emptyTabCounts(), useQcWorkOrderTabUnfinishedCounts()
 _Questions this graph is uniquely positioned to answer:_
 
 - **Why does `get()` connect `Community 0` to `Community 1`, `Community 2`, `Community 3`, `Community 4`, `Community 6`, `Community 7`, `Community 8`, `Community 10`, `Community 13`, `Community 14`, `Community 15`, `Community 17`, `Community 19`, `Community 20`, `Community 21`, `Community 22`, `Community 23`, `Community 24`, `Community 25`, `Community 26`, `Community 29`, `Community 30`, `Community 32`, `Community 36`, `Community 38`, `Community 51`, `Community 58`, `Community 59`?**
-  _High betweenness centrality (0.306) - this node is a cross-community bridge._
+  _High betweenness centrality (0.309) - this node is a cross-community bridge._
 - **Why does `push()` connect `Community 1` to `Community 0`, `Community 2`, `Community 3`, `Community 4`, `Community 6`, `Community 7`, `Community 8`, `Community 11`, `Community 12`, `Community 13`, `Community 14`, `Community 15`, `Community 16`, `Community 19`, `Community 21`, `Community 22`, `Community 26`, `Community 39`, `Community 41`?**
-  _High betweenness centrality (0.186) - this node is a cross-community bridge._
+  _High betweenness centrality (0.178) - this node is a cross-community bridge._
 - **Why does `handleDynamicRoutes()` connect `Community 5` to `Community 25`, `Community 26`, `Community 7`?**
-  _High betweenness centrality (0.048) - this node is a cross-community bridge._
+  _High betweenness centrality (0.055) - this node is a cross-community bridge._
 - **Are the 275 inferred relationships involving `get()` (e.g. with `buildNodeCodePathFromFlat()` and `collectLeafFacilityNodesInTreeOrder()`) actually correct?**
   _`get()` has 275 INFERRED edges - model-reasoned connections that need verification._
 - **Are the 68 inferred relationships involving `push()` (e.g. with `refreshQcMenuBadges()` and `collectLeafFacilityNodesInTreeOrder()`) actually correct?**

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
web-admin/graphify-out/graph.html


+ 11 - 10
web-admin/graphify-out/manifest.json

@@ -17,8 +17,8 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/common/index.ts": 1781272556.3597305,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/directive/directive.d.ts": 1781272556.3601494,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/api/api.d.ts": 1781272556.3606477,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts": 1782473324.5558708,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/auto-imports.d.ts": 1782472704.88409,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts": 1782481552.487545,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/auto-imports.d.ts": 1782481539.8774757,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/store/index.ts": 1781272556.364766,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/router/index.ts": 1782394569.8806834,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/locales/index.ts": 1781272556.3657222,
@@ -368,22 +368,23 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-task/index.vue": 1781272556.812188,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/reagent/index.vue": 1781563767.359068,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue": 1782472598.0379121,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts": 1782473925.746376,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/StepDetailPanel.vue": 1782473990.105439,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts": 1782483672.6912673,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/StepDetailPanel.vue": 1782483107.7971935,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/CompleteProductionTaskDialog.vue": 1781563767.3525257,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskInputEnhancedTable.vue": 1781585388.6972075,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskInputEnhancedTable.vue": 1782482871.5840545,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/DispatchQcWorkOrderDialog.vue": 1782359900.6139107,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/SampleReceptionDialog.vue": 1782377442.3260844,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskExecutionDialog.vue": 1782473972.7166908,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskExecutionDialog.vue": 1782483435.694966,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/EditTaskInfoDialog.vue": 1781577178.2471955,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputList.vue": 1781585388.699347,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputList.vue": 1782483404.200688,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/DispatchTaskDialog.vue": 1782472566.9576142,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskPaperAttachmentsPanel.vue": 1781530960.805927,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskPaperAttachmentsPanel.vue": 1782483404.2007074,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/WorkOrderTaskSubList.vue": 1782472596.5978415,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue": 1782469964.0441203,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue": 1782481811.443201,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue": 1782472589.6603115,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ForwardTaskDialog.vue": 1781563767.3550124,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/WorkOrderTaskStepSubList.vue": 1782473928.6698763,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/WorkOrderTaskStepSubList.vue": 1782474252.3707378,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/StepDetailActionBar.vue": 1782483101.2067354,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderTabBar.vue": 1782403774.1374023,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/DispatchStepDialog.vue": 1782472889.9876838,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/tabs/OutboundPilotTab.vue": 1781583381.1088736,

+ 1 - 0
web-admin/src/graphify-out/cache/ast/00eb8469953643a3c3cfba32b243aa6652fbdd8d852bc7e8513e89dbd7630548.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_stepdetailactionbar_vue", "label": "StepDetailActionBar.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/StepDetailActionBar.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
web-admin/src/graphify-out/cache/ast/29f0d7f63ca8062bcf4ea156a390b92c941be0891899b715e5f8aefdc82d0635.json


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
web-admin/src/graphify-out/cache/ast/2b8f6cbf829f1472f4f5f6b2fb3c882085f932f03a762e9ee33389b9d20ba239.json


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
web-admin/src/graphify-out/cache/ast/32583408ea3016c8701bdc44cce5c1323958d79f4b862a58236f9f0cd3758ebb.json


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
web-admin/src/graphify-out/cache/ast/366e6f9a2dea604b81467606e638d15bfcf1a30f2a92489b0e4457eec37f976f.json


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
web-admin/src/graphify-out/cache/ast/42e4a90a8f73359b4e0181772bcde64c4248f192d1ed604fad3c0ad6a4850217.json


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
web-admin/src/graphify-out/cache/ast/57cc3ba80f162e1122461769890c1e1d1acedd7beb835fa74722151838c4e2f7.json


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
web-admin/src/graphify-out/cache/ast/607f814d315332a54267c0eb5d3d5e08c1de2f63e5cff5e96e07618f2db57c9d.json


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
web-admin/src/graphify-out/cache/ast/89263039e9bb13578bae782556e679d102df051c88b26a490274f7d3c7fbdba5.json


+ 1 - 0
web-admin/src/graphify-out/cache/ast/9dbb2b716ebfaeb608d62b840eb51b0f0de3f295c219e1510e162c435c646052.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_taskinputenhancedtable_vue", "label": "TaskInputEnhancedTable.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskInputEnhancedTable.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
web-admin/src/graphify-out/cache/ast/a659bb387b0297ed7791585c2ad430eed193dd5c94534e0d4ba68ef7f2118ca3.json


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
web-admin/src/graphify-out/cache/ast/afc53ee5b030d413f9183e68618d0e53ac7b8b27881cea9b8dbf85ec7d71c8d3.json


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
web-admin/src/graphify-out/cache/ast/b22e9912d60cbaac0ce4c6d5236c13947504d175e6c40402df51541ecb4bf3b1.json


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
web-admin/src/graphify-out/cache/ast/bae03facd4defefef248e6487218a59a1e8859f6072b05cb173080ae8d28b9c9.json


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
web-admin/src/graphify-out/cache/ast/c7c993aa902f22415f7c31b077186964f1e06f1340a2f1726eaf19c90e40deaa.json


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
web-admin/src/graphify-out/cache/ast/d04eb091178fbb8a06bdee4d982551fcb59f3317415418a73ad118491f9a8d58.json


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
web-admin/src/graphify-out/cache/ast/df352739a8f47bbd2334bb660316a35ef36309a1789088a64fbf662ecb7ceddb.json


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
web-admin/src/graphify-out/cache/ast/f5fe1179bc158e2e8d230b5c7fc9f11d07aeb6fd7938292ab534872294cb11fa.json


+ 75 - 0
web-admin/src/views/production/production-work-order/StepDetailActionBar.vue

@@ -0,0 +1,75 @@
+<template>
+  <div v-if="!readonly" class="step-detail-action-bar">
+    <template v-if="editor.mode.value === 'execution' && editor.selectedStep.value">
+      <el-tag
+        :type="editor.stepStatusTag(editor.selectedStep.value.stepStatus || 'dzx').type"
+        size="small"
+      >
+        {{ editor.stepStatusTag(editor.selectedStep.value.stepStatus || 'dzx').label }}
+      </el-tag>
+      <el-button
+        v-if="
+          editor.mode.value === 'execution' &&
+          editor.canEditStep(editor.selectedStep.value) &&
+          editor.selectedStep.value.stepStatus === 'dzx'
+        "
+        class="btn-main task-exec-toolbar-btn"
+        type="primary"
+        :loading="editor.stepActionLoading.value"
+        :disabled="editor.manualSaveLoading.value"
+        @click="editor.handleStartStep(editor.selectedStep.value)"
+      >
+        <el-icon><VideoPlay /></el-icon>执行此步骤
+      </el-button>
+      <el-button
+        v-if="
+          editor.mode.value === 'execution' &&
+          editor.canEditStep(editor.selectedStep.value) &&
+          editor.selectedStep.value.stepStatus === 'jxz'
+        "
+        class="btn-main task-exec-toolbar-btn"
+        type="success"
+        :loading="editor.stepActionLoading.value"
+        :disabled="editor.manualSaveLoading.value"
+        @click="editor.handleCompleteStep(editor.selectedStep.value)"
+      >
+        <el-icon><Check /></el-icon>完成步骤
+      </el-button>
+    </template>
+    <el-button
+      v-if="
+        editor.selectedStep.value &&
+        (editor.canEditStep(editor.selectedStep.value) ||
+          (editor.canEditStepDispatch() && !readonly))
+      "
+      class="task-exec-toolbar-btn task-exec-save-btn"
+      type="danger"
+      :loading="editor.manualSaveLoading.value"
+      :disabled="editor.stepActionLoading.value"
+      @click="editor.handleManualSave"
+    >
+      <el-icon><Check /></el-icon>保存
+    </el-button>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { Check, VideoPlay } from '@element-plus/icons-vue'
+  import { useTaskStepEditor } from './useTaskStepEditor'
+
+  defineOptions({ name: 'StepDetailActionBar' })
+
+  defineProps<{
+    editor: ReturnType<typeof useTaskStepEditor>
+    readonly?: boolean
+  }>()
+</script>
+
+<style scoped lang="scss">
+  .step-detail-action-bar {
+    display: flex;
+    align-items: center;
+    flex-wrap: wrap;
+    gap: 8px;
+  }
+</style>

+ 10 - 58
web-admin/src/views/production/production-work-order/StepDetailPanel.vue

@@ -2,59 +2,9 @@
 <template>
   <div class="route-config-right">
     <template v-if="editor.selectedStep.value">
-      <div class="route-config-right-header">
+      <div v-if="!hideHeader" class="route-config-right-header">
         <h3>步骤详情</h3>
-        <div v-if="!readonly" class="step-status-bar">
-          <template v-if="editor.mode.value === 'execution' && editor.selectedStep.value">
-            <el-tag
-              :type="editor.stepStatusTag(editor.selectedStep.value.stepStatus || 'dzx').type"
-              size="small"
-            >
-              {{ editor.stepStatusTag(editor.selectedStep.value.stepStatus || 'dzx').label }}
-            </el-tag>
-            <el-button
-              v-if="
-                editor.mode.value === 'execution' &&
-                editor.canEditStep(editor.selectedStep.value) &&
-                editor.selectedStep.value.stepStatus === 'dzx'
-              "
-              class="btn-main task-exec-toolbar-btn"
-              type="primary"
-              :loading="editor.stepActionLoading.value"
-              :disabled="editor.manualSaveLoading.value"
-              @click="editor.handleStartStep(editor.selectedStep.value)"
-            >
-              <el-icon><VideoPlay /></el-icon>执行此步骤
-            </el-button>
-            <el-button
-              v-if="
-                editor.mode.value === 'execution' &&
-                editor.canEditStep(editor.selectedStep.value) &&
-                editor.selectedStep.value.stepStatus === 'jxz'
-              "
-              class="btn-main task-exec-toolbar-btn"
-              type="success"
-              :loading="editor.stepActionLoading.value"
-              :disabled="editor.manualSaveLoading.value"
-              @click="editor.handleCompleteStep(editor.selectedStep.value)"
-            >
-              <el-icon><Check /></el-icon>完成步骤
-            </el-button>
-          </template>
-          <el-button
-            v-if="
-              editor.canEditStep(editor.selectedStep.value) ||
-              (editor.canEditStepDispatch() && !readonly)
-            "
-            class="btn-main task-exec-toolbar-btn"
-            type="primary"
-            :loading="editor.manualSaveLoading.value"
-            :disabled="editor.stepActionLoading.value"
-            @click="editor.handleManualSave"
-          >
-            <el-icon><Check /></el-icon>保存
-          </el-button>
-        </div>
+        <StepDetailActionBar :editor="editor" :readonly="readonly" />
       </div>
       <div class="route-config-right-body">
         <el-form label-position="top" class="route-config-step-form">
@@ -934,16 +884,19 @@
 
 <script setup lang="ts">
   import { computed } from 'vue'
-  import { Check, Delete, Plus, Rank, VideoPlay } from '@element-plus/icons-vue'
+  import { Delete, Plus, Rank } from '@element-plus/icons-vue'
   import ProtocolAttachmentReadonly from '@/views/cell-storage/protocol/ProtocolAttachmentReadonly.vue'
   import { downloadAttachmentFile } from '@/utils/download-attachment'
   import { SHOW_PROCESS_REAGENT_CONSUMABLE } from '@/utils/process-material-ui'
   import { formatPlannedStartDate } from '@/utils/production-labels'
+  import StepDetailActionBar from './StepDetailActionBar.vue'
   import { useTaskStepEditor } from './useTaskStepEditor'
 
   const props = defineProps<{
     editor: ReturnType<typeof useTaskStepEditor>
     readonly?: boolean
+    /** 任务执行弹窗:标题与按钮栏由外层固定,面板仅渲染表单内容 */
+    hideHeader?: boolean
   }>()
 
   /** 弹窗可编辑但当前步骤非本人负责(mine 执行)时为只读 */
@@ -1026,11 +979,10 @@
 <style scoped lang="scss">
   @use '../../../styles/route-config' as *;
 
-  .step-status-bar {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-    margin-left: 12px;
+  .route-config-right-header {
+    :deep(.step-detail-action-bar) {
+      margin-left: 12px;
+    }
   }
 
   /* 4 列栅格下各占半行(2/4 列) */

+ 105 - 5
web-admin/src/views/production/production-work-order/TaskExecutionDialog.vue

@@ -326,14 +326,24 @@
                 :items="editor.taskOutputs.value"
                 :loading="false"
                 :disabled="readonly"
-                :saving="editor.outputSaving.value"
+                hide-save-footer
                 :unit-options="editor.itemUnitOptions.value"
                 :unit-options-loading="editor.itemUnitOptionsLoading.value"
                 @update:items="(items: Record<string, any>[]) => (editor.taskOutputs.value = items)"
                 @search-unit="(q: string) => editor.searchItemUnitOptions(q)"
-                @save="editor.saveTaskOutputs"
               />
             </div>
+            <div v-if="!readonly" class="execution-detail-step-footer">
+              <el-button
+                class="task-exec-toolbar-btn task-exec-save-btn"
+                type="danger"
+                :loading="editor.outputSaving.value"
+                @click="editor.saveTaskOutputs"
+              >
+                <el-icon><Check /></el-icon>
+                保存
+              </el-button>
+            </div>
           </template>
 
           <!-- 线下纸质文档 -->
@@ -345,16 +355,47 @@
               <TaskPaperAttachmentsPanel
                 :attachments="editor.taskAttachments.value"
                 :disabled="readonly"
-                :saving="editor.attachmentSaving.value"
+                hide-save-footer
                 @update:attachments="(items) => (editor.taskAttachments.value = items)"
-                @save="editor.saveTaskAttachments"
               />
             </div>
+            <div v-if="!readonly" class="execution-detail-step-footer">
+              <el-button
+                class="task-exec-toolbar-btn task-exec-save-btn"
+                type="danger"
+                :loading="editor.attachmentSaving.value"
+                @click="editor.saveTaskAttachments"
+              >
+                <el-icon><Check /></el-icon>
+                保存
+              </el-button>
+            </div>
           </template>
 
           <!-- 步骤详情 -->
           <template v-else>
-            <StepDetailPanel :editor="editor" :readonly="readonly" />
+            <template v-if="editor.selectedStep.value">
+              <div class="execution-detail-header">
+                <h3>
+                  步骤详情
+                  <span
+                    v-if="editor.selectedStep.value.processName"
+                    class="execution-detail-subtitle"
+                  >
+                    — {{ editor.selectedStep.value.processName }}
+                  </span>
+                </h3>
+              </div>
+            </template>
+            <div class="execution-detail-body execution-detail-body--step">
+              <StepDetailPanel :editor="editor" :readonly="readonly" hide-header />
+            </div>
+            <div
+              v-if="editor.selectedStep.value && !readonly"
+              class="execution-detail-step-footer"
+            >
+              <StepDetailActionBar :editor="editor" :readonly="readonly" />
+            </div>
           </template>
         </div>
       </div>
@@ -384,6 +425,7 @@
   import { VueDraggable as draggable } from 'vue-draggable-plus'
   import { useTaskStepEditor } from './useTaskStepEditor'
   import StepDetailPanel from './StepDetailPanel.vue'
+  import StepDetailActionBar from './StepDetailActionBar.vue'
   import TaskInputEnhancedTable from './TaskInputEnhancedTable.vue'
   import TaskOutputList from './TaskOutputList.vue'
   import TaskPaperAttachmentsPanel from './TaskPaperAttachmentsPanel.vue'
@@ -794,6 +836,7 @@
     align-items: center;
     justify-content: space-between;
     flex-shrink: 0;
+    background: #fff;
     h3 {
       margin: 0;
       font-size: 14px;
@@ -801,6 +844,19 @@
       color: #1e293b;
     }
   }
+  .execution-detail-subtitle {
+    font-weight: 500;
+    color: var(--mint-text-muted, #64748b);
+  }
+  .execution-detail-step-footer {
+    flex-shrink: 0;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 12px 16px;
+    border-top: 1px solid #e5e7eb;
+    background: #fff;
+  }
   .execution-detail-actions {
     display: flex;
     gap: 8px;
@@ -810,6 +866,18 @@
     min-height: 0;
     overflow-y: auto;
     padding: 16px;
+
+    &.execution-detail-body--step {
+      padding: 0;
+      overflow: hidden;
+      display: flex;
+      flex-direction: column;
+
+      :deep(.route-config-right) {
+        flex: 1;
+        min-height: 0;
+      }
+    }
   }
 
   .section-bar {
@@ -863,5 +931,37 @@
         font-size: 14px;
       }
     }
+
+    .task-exec-save-btn.el-button:not(.el-button--text):not(.el-button--link) {
+      --el-button-bg-color: #dc2626;
+      --el-button-border-color: #dc2626;
+      --el-button-hover-bg-color: #b91c1c;
+      --el-button-hover-border-color: #b91c1c;
+      --el-button-active-bg-color: #991b1b;
+      --el-button-active-border-color: #991b1b;
+      color: #fff !important;
+      background: #dc2626 !important;
+      border-color: #dc2626 !important;
+
+      &:hover,
+      &:focus {
+        color: #fff !important;
+        background: #b91c1c !important;
+        border-color: #b91c1c !important;
+      }
+
+      &:active {
+        color: #fff !important;
+        background: #991b1b !important;
+        border-color: #991b1b !important;
+      }
+
+      &.is-disabled,
+      &.is-disabled:hover {
+        color: #fff !important;
+        background: #fca5a5 !important;
+        border-color: #fca5a5 !important;
+      }
+    }
   }
 </style>

+ 2 - 2
web-admin/src/views/production/production-work-order/TaskInputEnhancedTable.vue

@@ -160,8 +160,8 @@
 
     <div v-if="!disabled" class="list-save-footer">
       <el-button
-        class="btn-main task-exec-toolbar-btn"
-        type="primary"
+        class="task-exec-toolbar-btn task-exec-save-btn"
+        type="danger"
         :loading="saving"
         @click="emit('save')"
       >

+ 6 - 4
web-admin/src/views/production/production-work-order/TaskOutputList.vue

@@ -94,10 +94,10 @@
       </div>
     </div>
 
-    <div v-if="!disabled" class="list-save-footer">
+    <div v-if="!disabled && !hideSaveFooter" class="list-save-footer">
       <el-button
-        class="btn-main task-exec-toolbar-btn"
-        type="primary"
+        class="task-exec-toolbar-btn task-exec-save-btn"
+        type="danger"
         :loading="saving"
         @click="emit('save')"
       >
@@ -130,8 +130,10 @@
       unitOptionsLoading: boolean
       disabled?: boolean
       saving?: boolean
+      /** 任务执行弹窗:保存按钮由外层底栏承载 */
+      hideSaveFooter?: boolean
     }>(),
-    { disabled: false, saving: false }
+    { disabled: false, saving: false, hideSaveFooter: false }
   )
 
   const emit = defineEmits<{

+ 2 - 2
web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue

@@ -114,7 +114,7 @@
             v-model="formData.viability"
             :min="0"
             :max="100"
-            :precision="1"
+            :step="0.01"
             controls-position="right"
             style="width: 100%"
           />
@@ -227,7 +227,7 @@
                   v-model="row.viability"
                   :min="0"
                   :max="100"
-                  :precision="1"
+                  :step="0.01"
                   controls-position="right"
                   style="width: 100%"
                 />

+ 6 - 4
web-admin/src/views/production/production-work-order/TaskPaperAttachmentsPanel.vue

@@ -18,10 +18,10 @@
       />
     </template>
 
-    <div v-if="!disabled" class="list-save-footer">
+    <div v-if="!disabled && !hideSaveFooter" class="list-save-footer">
       <el-button
-        class="btn-main task-exec-toolbar-btn"
-        type="primary"
+        class="task-exec-toolbar-btn task-exec-save-btn"
+        type="danger"
         :loading="saving"
         @click="emit('save')"
       >
@@ -46,8 +46,10 @@
       attachments: FinanceAttachmentItem[]
       disabled?: boolean
       saving?: boolean
+      /** 任务执行弹窗:保存按钮由外层底栏承载 */
+      hideSaveFooter?: boolean
     }>(),
-    { disabled: false, saving: false }
+    { disabled: false, saving: false, hideSaveFooter: false }
   )
 
   const emit = defineEmits<{

+ 17 - 0
web-admin/src/views/production/production-work-order/useTaskStepEditor.ts

@@ -1595,6 +1595,18 @@ export function useTaskStepEditor() {
     return null
   }
 
+  function validateAllStepsComplete(): string | null {
+    if (!stepList.value.length) return null
+    const pending = stepList.value.filter((s) => s.stepStatus !== 'ywc')
+    if (!pending.length) return null
+    const preview = pending
+      .slice(0, 3)
+      .map((s) => s.processName || '未命名步骤')
+      .join('、')
+    const suffix = pending.length > 3 ? ` 等 ${pending.length} 个步骤` : ''
+    return `尚有未完成的工艺步骤(${preview}${suffix}),请完成全部步骤后再完成任务`
+  }
+
   const completeConclusionVisible = ref(false)
   const completeConclusionIsEdit = ref(false)
   const completeConclusionInitial = reactive({ conclusion: '', remark: '' })
@@ -1612,6 +1624,11 @@ export function useTaskStepEditor() {
         await ElMessageBox.alert(ioErr, '无法完成任务', { type: 'warning' })
         return
       }
+      const stepErr = validateAllStepsComplete()
+      if (stepErr) {
+        await ElMessageBox.alert(stepErr, '无法完成任务', { type: 'warning' })
+        return
+      }
     }
     completeConclusionVisible.value = true
   }

Некоторые файлы не были показаны из-за большого количества измененных файлов