zlwu hai 5 meses
pai
achega
53d3b21f57
Modificáronse 41 ficheiros con 2080 adicións e 1492 borrados
  1. 1 1
      backend/app.pid
  2. 0 0
      backend/app/graphify-out/cache/ast/0247ee806f924d335e056a4c74cf7c5cfe6f0a0d9abd2a511b6313dbbc7bbae8.json
  3. 0 0
      backend/app/graphify-out/cache/ast/1f8598dd1cc652f3b3c733fa593a8c91b47635c9d9c60173c25f9d64749d6fb1.json
  4. 0 0
      backend/app/graphify-out/cache/ast/1ffda554e8d34ed3588dde33a6da29c5ae2046aaa9d3be8380036d7777a3274d.json
  5. 1 0
      backend/app/graphify-out/cache/ast/3764badc8bbd06cb50c572c8c1fb7b27843f722af4a0a40cea8d4a7c45359d65.json
  6. 0 0
      backend/app/graphify-out/cache/ast/623854ad30cc18e2a6f75f777f741fa6eacd1d53d057dd1e4aa1f13b0a798278.json
  7. 0 0
      backend/app/graphify-out/cache/ast/6b588b66cb4db67617fbb04f9a7ebd54e1271bd68231569fd7ee9d1502e63ca4.json
  8. 0 0
      backend/app/graphify-out/cache/ast/6cc6de0ece2ad6a19f0f3a9a8c4c31498dd118183cb1d9e75086df4028165186.json
  9. 0 0
      backend/app/graphify-out/cache/ast/99c6e7a946ac58e457b113696530f54266c1d950e8e5276a35a8dfcfd6de855e.json
  10. 451 404
      backend/graphify-out/.graphify_analysis.json
  11. 276 143
      backend/graphify-out/.graphify_extract.json
  12. 88 88
      backend/graphify-out/GRAPH_REPORT.md
  13. 1 1
      backend/graphify-out/graph.html
  14. 121 112
      backend/graphify-out/graph.json
  15. 8 6
      backend/graphify-out/manifest.json
  16. 369 390
      web-admin/graphify-out/.graphify_analysis.json
  17. 177 113
      web-admin/graphify-out/.graphify_extract.json
  18. 107 67
      web-admin/graphify-out/GRAPH_REPORT.md
  19. 1 1
      web-admin/graphify-out/graph.html
  20. 11 11
      web-admin/graphify-out/manifest.json
  21. 0 0
      web-admin/src/graphify-out/cache/ast/06e8cbafff75ab1b3038690ecf398fcfd47e125ac19af34353c8f86f000e6319.json
  22. 1 0
      web-admin/src/graphify-out/cache/ast/0ba3126fb414db1cb60cac56cfdeef0009037746227a23ccf8f45189b0e46704.json
  23. 1 0
      web-admin/src/graphify-out/cache/ast/4d5ec2b6baeee6160e0a47abe2c705d8021e4a3b23079b90bd5da8713b20c9a2.json
  24. 1 0
      web-admin/src/graphify-out/cache/ast/7310426cab33026e9fc1a9a939228914f2f9a0f388503f3f9a96ec4b8f5bec3d.json
  25. 0 0
      web-admin/src/graphify-out/cache/ast/736d57d953c189f69e6909383ad11720f05f7ea892f4aed6be1a140c6ce97bee.json
  26. 1 0
      web-admin/src/graphify-out/cache/ast/8c799aa9e77ed13d10ce8cb47f7bfbcf342bf461cdde928692d4fee5c83190da.json
  27. 0 0
      web-admin/src/graphify-out/cache/ast/91e603def28fab255ff52f5194293aff57fa13e7bc2d3df892d96a66584afb17.json
  28. 0 0
      web-admin/src/graphify-out/cache/ast/9bcd2e9117669a13a7f9c3de6a2daac127dc85c37c3c88802b0196b4e0644bd8.json
  29. 1 0
      web-admin/src/graphify-out/cache/ast/a6b15f9f2e87ea18eadcad72a962911feb4e66452299a8248677bc2ebeb58cc5.json
  30. 1 0
      web-admin/src/graphify-out/cache/ast/a6e633ee565a3d3465ed1895f8071bf126e93d04c1d8cb9f7c6e763958567435.json
  31. 108 80
      web-admin/src/views/cell-storage/protocol/ProtocolFormDialog.vue
  32. 2 0
      web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue
  33. 2 2
      web-admin/src/views/production/cell-product/index.vue
  34. 133 10
      web-admin/src/views/production/process-route/index.vue
  35. 3 3
      web-admin/src/views/production/process/index.vue
  36. 4 0
      web-admin/src/views/production/production-work-order/DispatchTaskDialog.vue
  37. 131 7
      web-admin/src/views/production/production-work-order/StepDetailPanel.vue
  38. 9 3
      web-admin/src/views/quality-inspection/lab-test-item/index.vue
  39. 9 5
      web-admin/src/views/quality-inspection/sample-reception/index.vue
  40. 47 43
      web-admin/src/views/quality-inspection/third-party-company/index.vue
  41. 14 2
      web-admin/src/views/research-donation/list/DonationFormDialog.vue

+ 1 - 1
backend/app.pid

@@ -1 +1 @@
-77442
+98717

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
backend/app/graphify-out/cache/ast/0247ee806f924d335e056a4c74cf7c5cfe6f0a0d9abd2a511b6313dbbc7bbae8.json


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
backend/app/graphify-out/cache/ast/1f8598dd1cc652f3b3c733fa593a8c91b47635c9d9c60173c25f9d64749d6fb1.json


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
backend/app/graphify-out/cache/ast/1ffda554e8d34ed3588dde33a6da29c5ae2046aaa9d3be8380036d7777a3274d.json


+ 1 - 0
backend/app/graphify-out/cache/ast/3764badc8bbd06cb50c572c8c1fb7b27843f722af4a0a40cea8d4a7c45359d65.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_models_init_py", "label": "__init__.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/__init__.py", "source_location": "L1"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_backend_app_models_init_py", "target": "app_models_base", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/__init__.py", "source_location": "L2", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_models_init_py", "target": "app_models_models", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/__init__.py", "source_location": "L3", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_models_init_py", "target": "app_models_research_donation", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/__init__.py", "source_location": "L52", "weight": 1.0}], "raw_calls": []}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
backend/app/graphify-out/cache/ast/623854ad30cc18e2a6f75f777f741fa6eacd1d53d057dd1e4aa1f13b0a798278.json


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
backend/app/graphify-out/cache/ast/6b588b66cb4db67617fbb04f9a7ebd54e1271bd68231569fd7ee9d1502e63ca4.json


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
backend/app/graphify-out/cache/ast/6cc6de0ece2ad6a19f0f3a9a8c4c31498dd118183cb1d9e75086df4028165186.json


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
backend/app/graphify-out/cache/ast/99c6e7a946ac58e457b113696530f54266c1d950e8e5276a35a8dfcfd6de855e.json


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 451 - 404
backend/graphify-out/.graphify_analysis.json


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 276 - 143
backend/graphify-out/.graphify_extract.json


+ 88 - 88
backend/graphify-out/GRAPH_REPORT.md

@@ -1,12 +1,12 @@
 # Graph Report - app  (2026-05-22)
 
 ## Corpus Check
-- 135 files · ~120,233 words
+- 137 files · ~121,901 words
 - Verdict: corpus is large enough that graph structure adds value.
 
 ## Summary
-- 2411 nodes · 9466 edges · 54 communities detected
-- Extraction: 42% EXTRACTED · 58% INFERRED · 0% AMBIGUOUS · INFERRED: 5506 edges (avg confidence: 0.63)
+- 2434 nodes · 9565 edges · 54 communities detected
+- Extraction: 42% EXTRACTED · 58% INFERRED · 0% AMBIGUOUS · INFERRED: 5568 edges (avg confidence: 0.63)
 - Token cost: 0 input · 0 output
 
 ## Community Hubs (Navigation)
@@ -66,16 +66,16 @@
 - [[_COMMUNITY_Community 55|Community 55]]
 
 ## God Nodes (most connected - your core abstractions)
-1. `success_response()` - 380 edges
-2. `error_response()` - 305 edges
-3. `SysUser` - 217 edges
-4. `BizQcMaterialUsage` - 197 edges
-5. `BizMaterialStockLog` - 193 edges
-6. `BizQcTaskResult` - 193 edges
-7. `BizQcMaterial` - 193 edges
-8. `BizMaterial` - 190 edges
-9. `BizQcItemParam` - 188 edges
-10. `generate_uuid()` - 131 edges
+1. `success_response()` - 384 edges
+2. `error_response()` - 307 edges
+3. `SysUser` - 218 edges
+4. `BizQcMaterialUsage` - 199 edges
+5. `BizMaterialStockLog` - 195 edges
+6. `BizQcTaskResult` - 195 edges
+7. `BizQcMaterial` - 195 edges
+8. `BizMaterial` - 192 edges
+9. `BizQcItemParam` - 190 edges
+10. `generate_uuid()` - 134 edges
 
 ## Surprising Connections (you probably didn't know these)
 - `统一记录 API 请求/响应出入参的中间件` --uses--> `SysApiRequestLog`  [INFERRED]
@@ -93,51 +93,51 @@
 
 ### Community 0 - "Community 0"
 Cohesion: 0.01
-Nodes (402): AttachmentDownloadUrlBody, DeptCreateBody, DeptUpdateBody, DictDataCreateBody, DictDataUpdateBody, DictTypeCreateBody, DictTypeUpdateBody, ExpressCompanyCreateBody (+394 more)
+Nodes (401): AttachmentDownloadUrlBody, DeptCreateBody, DeptUpdateBody, DictDataCreateBody, DictDataUpdateBody, DictTypeCreateBody, DictTypeUpdateBody, ExpressCompanyCreateBody (+393 more)
 
 ### Community 1 - "Community 1"
 Cohesion: 0.02
-Nodes (385): attachment_download_url(), create_dept(), create_dict_data(), create_dict_type(), create_express_company(), create_item_unit(), create_menu(), create_post() (+377 more)
+Nodes (407): attachment_download_url(), create_dept(), create_dict_data(), create_dict_type(), create_express_company(), create_item_unit(), create_menu(), create_post() (+399 more)
 
 ### Community 2 - "Community 2"
 Cohesion: 0.05
-Nodes (272): AuditCreateBody, BatchDetailIdsBody, BatchRefundFinanceSubmitBody, FinanceConfirmBody, FullmoonTodoInsuranceBody, ProtocolCreateBody, ProtocolUpdateBody, 细胞存储 — 采集协议单 / 审核 / 财务确认 / 分期预警 API (+264 more)
+Nodes (266): AuditCreateBody, BatchDetailIdsBody, BatchRefundFinanceSubmitBody, FinanceConfirmBody, FullmoonTodoInsuranceBody, ProtocolCreateBody, ProtocolUpdateBody, 细胞存储 — 采集协议单 / 审核 / 财务确认 / 分期预警 API (+258 more)
 
 ### Community 3 - "Community 3"
 Cohesion: 0.02
-Nodes (157): generate_uuid(), batch_stock_in(), batch_stock_out(), _code_exists(), create_material(), _dec(), _fmt_dt(), get_material() (+149 more)
+Nodes (213): list_collection_regions(), get_designer_data(), apply_data_scope(), _get_child_dept_ids(), get_user_dept_id(), _resolve_user_role_scopes(), generate_uuid(), delete() (+205 more)
 
 ### Community 4 - "Community 4"
-Cohesion: 0.03
-Nodes (138): create_collection_protocol(), delete_collection_protocol(), _ensure_customer_for_mother(), get_collection_protocol_detail(), _get_current_user_id(), _get_tenant_no(), list_collection_protocol_audits(), list_collection_protocols() (+130 more)
+Cohesion: 0.04
+Nodes (112): _format_attachment_file_size(), get_protocol_nos_used_in_protocols(), urls_to_attachment_list(), create_dict_type(), dict_type_code_exists(), get_dict_type(), list_dict_types(), _row_to_dict() (+104 more)
 
 ### Community 5 - "Community 5"
-Cohesion: 0.04
-Nodes (106): _format_attachment_file_size(), get_protocol_nos_used_in_protocols(), urls_to_attachment_list(), _accept_conclusion_label(), _attach_reception_detail(), complete_production_work_order(), create_production_work_order_from_reception(), _enrich_wo_items() (+98 more)
+Cohesion: 0.05
+Nodes (85): _persist_protocol_sub_resources(), _add_months_yyyymmdd(), align_fq_payment_details_payload(), _apply_submit_receipt_finance(), _apply_submit_refund_finance(), attachments_to_urls(), _audit_to_dict(), batch_submit_receipt_finance() (+77 more)
 
 ### Community 6 - "Community 6"
-Cohesion: 0.03
-Nodes (55): get_designer_data(), BaseStorage, convert_heic_to_jpeg(), get_storage(), LocalStorage, MinIOStorage, OSSStorage, process_image_content() (+47 more)
+Cohesion: 0.05
+Nodes (43): ProcessDefCreateBody, ProcessDefUpdateBody, PublishVersionBody, 审批意见:前端传 commentText,兼容 comment。, StartInstanceBody, TaskActionBody, BaseStorage, convert_heic_to_jpeg() (+35 more)
 
 ### Community 7 - "Community 7"
-Cohesion: 0.05
-Nodes (88): AccessLogCreateBody, AccessLogUpdateBody, CleanRoomCreateBody, CleanRoomUpdateBody, ExhibitionAppointmentCreateBody, ExhibitionAppointmentUpdateBody, list_storage_slots(), 运营支撑 API(洁净室、进出记录、仓储设施、储位、展厅预约) (+80 more)
+Cohesion: 0.04
+Nodes (51): BaseHTTPMiddleware, parse_admin_request_json(), 运营 Web(/web/admin)JSON 请求体解析。  与 ApiCryptoMiddleware 配合:中间件未改写 body 时,在路由内解密 `, 读取 JSON body;dict 时走 resolve_request_payload(明文 / 密文 / data 包装)。      Returns:, ApiCryptoError, _b64url_decode(), _b64url_encode(), decrypt_json() (+43 more)
 
 ### Community 8 - "Community 8"
-Cohesion: 0.04
-Nodes (65): list_roles_for_select(), _avatar_url_field(), _build_user_info_payload(), get_web_auth_menu_codes(), _jwt_user_payload(), logout(), 公共接口(Web 登录、刷新 Token、登出;用户模型对齐 docs/sql/zkxw.sql 基线), 登出:吊销 Redis 会话(若已配置 REDIS_HOST)。 (+57 more)
+Cohesion: 0.05
+Nodes (59): list_roles_for_select(), _avatar_url_field(), _build_user_info_payload(), get_web_auth_menu_codes(), _jwt_user_payload(), logout(), 公共接口(Web 登录、刷新 Token、登出;用户模型对齐 docs/sql/zkxw.sql 基线), 登出:吊销 Redis 会话(若已配置 REDIS_HOST)。 (+51 more)
 
 ### Community 9 - "Community 9"
-Cohesion: 0.32
-Nodes (52): ConsumableCreateBody, ConsumablePackCreateBody, ConsumablePackUpdateBody, ConsumableUpdateBody, OutputTypeCreateBody, OutputTypeUpdateBody, PackItemInput, ProcessCategoryCreateBody (+44 more)
+Cohesion: 0.07
+Nodes (66): AccessLogCreateBody, AccessLogUpdateBody, CleanRoomCreateBody, CleanRoomUpdateBody, ExhibitionAppointmentCreateBody, ExhibitionAppointmentUpdateBody, OccupancyCreateBody, 运营支撑 API(洁净室、进出记录、仓储设施、储位、展厅预约) (+58 more)
 
 ### Community 10 - "Community 10"
-Cohesion: 0.11
-Nodes (42): create_collection_protocol(), _active_link_query(), assert_biz_no_available(), create_convert_link(), get_convert_partner(), get_partner_sample_codes(), has_active_convert_link(), upsert_convert_link_for_to_storage() (+34 more)
+Cohesion: 0.32
+Nodes (52): ConsumableCreateBody, ConsumablePackCreateBody, ConsumablePackUpdateBody, ConsumableUpdateBody, OutputTypeCreateBody, OutputTypeUpdateBody, PackItemInput, ProcessCategoryCreateBody (+44 more)
 
 ### Community 11 - "Community 11"
-Cohesion: 0.08
-Nodes (36): BaseHTTPMiddleware, parse_admin_request_json(), 运营 Web(/web/admin)JSON 请求体解析。  与 ApiCryptoMiddleware 配合:中间件未改写 body 时,在路由内解密 `, 读取 JSON body;dict 时走 resolve_request_payload(明文 / 密文 / data 包装)。      Returns:, ApiCryptoError, _b64url_decode(), _b64url_encode(), decrypt_json() (+28 more)
+Cohesion: 0.14
+Nodes (36): _audit_to_dict(), batch_delete_donations(), _collection_to_dict(), convert_to_storage(), _create_collection(), create_donation(), _create_party(), _create_samples() (+28 more)
 
 ### Community 12 - "Community 12"
 Cohesion: 0.16
@@ -148,49 +148,49 @@ 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 14 - "Community 14"
-Cohesion: 0.21
-Nodes (19): _consumable_row_to_dict(), create_process(), _create_sub_consumable(), _create_sub_data_collection(), _create_sub_output(), _create_sub_reagent(), _data_collection_row_to_dict(), get_process() (+11 more)
+Cohesion: 0.11
+Nodes (28): get_next_cell_product_code(), _build_facility_path_label(), build_split_product_codes(), create_cell_product(), create_cell_products_batch(), create_cell_products_by_quantity(), delete_cell_product(), _enrich_product_dicts() (+20 more)
 
 ### Community 15 - "Community 15"
+Cohesion: 0.16
+Nodes (26): add_step(), copy_step(), create_route(), _create_step_consumable(), _create_step_data_collection(), _create_step_output(), _create_step_reagent(), get_route() (+18 more)
+
+### Community 16 - "Community 16"
 Cohesion: 0.17
 Nodes (12): BusinessException, InvalidStatusTransitionException, log_delivery_operation(), log_operation(), log_order_operation(), log_settlement_operation(), log_user_operation(), OrderLockedException (+4 more)
 
-### Community 16 - "Community 16"
+### Community 17 - "Community 17"
+Cohesion: 0.21
+Nodes (19): _consumable_row_to_dict(), create_process(), _create_sub_consumable(), _create_sub_data_collection(), _create_sub_output(), _create_sub_reagent(), _data_collection_row_to_dict(), get_process() (+11 more)
+
+### Community 18 - "Community 18"
 Cohesion: 0.18
 Nodes (18): company_code_exists(), create_company(), get_company(), list_companies(), list_company_options(), _row_to_dict(), soft_delete_company(), update_company() (+10 more)
 
-### Community 17 - "Community 17"
+### Community 19 - "Community 19"
 Cohesion: 0.2
 Nodes (14): deduct_for_task(), _display_remark(), list_usages(), list_usages_by_task(), _parse_receiver_purpose_from_remark(), 查询质检任务关联的物料使用记录(task_complete)。, 撤销任务此前 task_complete 扣减,便于重新填报物料使用。, 兼容旧数据:领用人/用途曾拼接在 remark 中。 (+6 more)
 
-### Community 18 - "Community 18"
+### Community 20 - "Community 20"
 Cohesion: 0.25
 Nodes (13): code_exists(), create_material(), _dec(), _fmt_dt(), get_material(), list_materials(), _norm_status(), options_list() (+5 more)
 
-### Community 19 - "Community 19"
+### Community 21 - "Community 21"
 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 20 - "Community 20"
+### Community 22 - "Community 22"
 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 21 - "Community 21"
+### Community 23 - "Community 23"
 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)
 
-### Community 22 - "Community 22"
+### Community 24 - "Community 24"
 Cohesion: 0.23
 Nodes (12): Config, ProductBase, ProductCategoryBase, ProductCategoryCreate, ProductCategoryResponse, ProductCreate, ProductResponse, ProductUnitBase (+4 more)
 
-### Community 23 - "Community 23"
-Cohesion: 0.27
-Nodes (10): create_collection_region(), create_hospital(), get_collection_region(), get_hospital(), _hospital_to_dict(), list_hospitals(), soft_delete_collection_region(), soft_delete_hospital() (+2 more)
-
-### Community 24 - "Community 24"
-Cohesion: 0.3
-Nodes (10): create_consumable_pack(), get_consumable_pack(), get_pack_detail(), _item_row_to_dict(), list_all_packs(), list_consumable_packs(), pack_code_exists(), _row_to_dict() (+2 more)
-
 ### Community 25 - "Community 25"
 Cohesion: 0.27
 Nodes (11): _build_tree(), _cascade_soft_delete(), _collect_with_ancestors(), create_dept(), get_dept(), list_depts(), list_depts_tree(), 保留匹配节点及其全部上级,供关键词检索后仍能正确组树。 (+3 more)
@@ -204,65 +204,65 @@ Cohesion: 0.31
 Nodes (8): consumable_code_exists(), create_consumable(), get_consumable(), list_consumables(), 耗材 CRUD(biz_consumable), _row_to_dict(), soft_delete_consumable(), update_consumable()
 
 ### Community 28 - "Community 28"
-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 29 - "Community 29"
-Cohesion: 0.31
-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.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"
+### Community 29 - "Community 29"
 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"
+### Community 30 - "Community 30"
 Cohesion: 0.31
 Nodes (8): category_code_exists(), create_process_category(), get_process_category(), list_process_categories(), 工艺工序分类 CRUD(biz_process_category), _row_to_dict(), soft_delete_process_category(), update_process_category()
 
-### Community 33 - "Community 33"
+### Community 31 - "Community 31"
 Cohesion: 0.31
 Nodes (8): create_item_unit(), get_item_unit(), list_item_units(), 物品单位 CRUD(biz_item_unit), _row_to_dict(), soft_delete_item_unit(), unit_code_exists(), update_item_unit()
 
-### Community 34 - "Community 34"
+### Community 32 - "Community 32"
+Cohesion: 0.31
+Nodes (8): create_customer(), get_customer(), id_card_taken(), list_customers(), 客户 CRUD(biz_customer), _row_to_dict(), soft_delete_customer(), update_customer()
+
+### Community 33 - "Community 33"
 Cohesion: 0.31
 Nodes (8): create_cell_sample_type(), get_cell_sample_type(), list_cell_sample_types(), 细胞样本类型 CRUD(biz_cell_sample_type), _row_to_dict(), soft_delete_cell_sample_type(), type_code_exists(), update_cell_sample_type()
 
-### Community 35 - "Community 35"
+### Community 34 - "Community 34"
 Cohesion: 0.31
 Nodes (8): create_output_type(), get_output_type(), list_output_types(), 工艺产出物类型 CRUD(biz_output_type), _row_to_dict(), soft_delete_output_type(), type_code_exists(), update_output_type()
 
-### Community 36 - "Community 36"
+### Community 35 - "Community 35"
 Cohesion: 0.36
 Nodes (7): create_post(), get_post(), list_posts(), post_code_exists(), _row_to_dict(), soft_delete_post(), update_post()
 
-### Community 37 - "Community 37"
-Cohesion: 0.36
-Nodes (7): create_dict_type(), dict_type_code_exists(), get_dict_type(), list_dict_types(), _row_to_dict(), soft_delete_dict_type(), update_dict_type()
-
-### Community 38 - "Community 38"
+### Community 36 - "Community 36"
 Cohesion: 0.36
 Nodes (7): create_tenant(), get_tenant(), list_tenants(), _row_to_dict(), soft_delete_tenant(), tenant_no_exists(), update_tenant()
 
-### Community 39 - "Community 39"
+### Community 37 - "Community 37"
 Cohesion: 0.36
 Nodes (7): create_reagent(), get_reagent(), list_reagents(), reagent_code_exists(), _row_to_dict(), soft_delete_reagent(), update_reagent()
 
-### Community 40 - "Community 40"
+### 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()
 
-### Community 41 - "Community 41"
+### Community 39 - "Community 39"
 Cohesion: 0.29
 Nodes (2): BaseModel, SoftDeleteModel
 
-### Community 42 - "Community 42"
+### Community 40 - "Community 40"
 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"
+Cohesion: 0.6
+Nodes (5): _dec(), _fmt_dt(), list_stock_logs(), _row_to_dict(), _user_nick_name_map()
+
+### Community 42 - "Community 42"
+Cohesion: 0.4
+Nodes (4): list_ethnics_for_select(), list_ethnics(), 民族码表 CRUD(全局 sys_ethnic), _row_to_dict()
+
 ### Community 43 - "Community 43"
 Cohesion: 0.5
 Nodes (3): BaseSettings, Config, Settings
@@ -310,7 +310,7 @@ Nodes (1): 发送催单通知给配送员                  Args:             del
 ## Knowledge Gaps
 - **90 isolated node(s):** `刷新会话 TTL(refreshToken 续期;已登录用户每次 API 校验通过后滑动续期)`, `写入 refreshToken(用于 one-time 使用/吊销)`, `根据 token 获取当前用户(包含 Redis 会话校验;会话滑动续期)`, `登录凭据 RSA 解密(与前端 JSEncrypt PKCS#1 v1.5 兼容)。`, `Base64 密文 -> JSON 对象(须含 username、password、ts 毫秒时间戳)。` (+85 more)
   These have ≤1 connection - possible missing edges or undocumented components.
-- **Thin community `Community 41`** (8 nodes): `BaseModel`, `.to_dict()`, `get_active()`, `base.py`, `SoftDeleteModel`, `.restore()`, `.soft_delete()`, `__tablename__()`
+- **Thin community `Community 39`** (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 44`** (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.
@@ -336,17 +336,17 @@ Nodes (1): 发送催单通知给配送员                  Args:             del
 ## Suggested Questions
 _Questions this graph is uniquely positioned to answer:_
 
-- **Why does `generate_uuid()` connect `Community 3` to `Community 0`, `Community 1`, `Community 2`, `Community 4`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 10`, `Community 11`, `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`, `Community 40`?**
-  _High betweenness centrality (0.124) - this node is a cross-community bridge._
-- **Why does `SysUser` connect `Community 0` to `Community 2`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 9`, `Community 12`, `Community 17`, `Community 19`, `Community 20`?**
-  _High betweenness centrality (0.083) - this node is a cross-community bridge._
-- **Why does `success_response()` connect `Community 1` to `Community 0`, `Community 4`, `Community 6`, `Community 7`, `Community 8`, `Community 12`, `Community 45`?**
-  _High betweenness centrality (0.067) - this node is a cross-community bridge._
-- **Are the 379 inferred relationships involving `success_response()` (e.g. with `web_login_rsa_public_key()` and `web_login()`) actually correct?**
-  _`success_response()` has 379 INFERRED edges - model-reasoned connections that need verification._
-- **Are the 351 inferred relationships involving `str` (e.g. with `extract_login_credentials()` and `._make_request()`) actually correct?**
-  _`str` has 351 INFERRED edges - model-reasoned connections that need verification._
-- **Are the 304 inferred relationships involving `error_response()` (e.g. with `_json_error_response()` and `web_login()`) actually correct?**
-  _`error_response()` has 304 INFERRED edges - model-reasoned connections that need verification._
-- **Are the 215 inferred relationships involving `SysUser` (e.g. with `数据权限过滤(V0.3:基于角色 data_scope 的查询级行级过滤)  data_scope 值:   1 = 全部数据   2 = 本部门数据   3` and `从 SysUser 读取用户的 dept_id。`) actually correct?**
-  _`SysUser` has 215 INFERRED edges - model-reasoned connections that need verification._
+- **Why does `generate_uuid()` connect `Community 3` to `Community 0`, `Community 1`, `Community 2`, `Community 4`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 9`, `Community 11`, `Community 14`, `Community 15`, `Community 17`, `Community 18`, `Community 19`, `Community 20`, `Community 21`, `Community 22`, `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`?**
+  _High betweenness centrality (0.118) - this node is a cross-community bridge._
+- **Why does `SysUser` connect `Community 0` to `Community 2`, `Community 4`, `Community 6`, `Community 8`, `Community 9`, `Community 10`, `Community 12`, `Community 19`, `Community 21`, `Community 22`?**
+  _High betweenness centrality (0.080) - this node is a cross-community bridge._
+- **Why does `success_response()` connect `Community 1` to `Community 0`, `Community 3`, `Community 8`, `Community 42`, `Community 12`, `Community 45`, `Community 14`?**
+  _High betweenness centrality (0.066) - this node is a cross-community bridge._
+- **Are the 383 inferred relationships involving `success_response()` (e.g. with `web_login_rsa_public_key()` and `web_login()`) actually correct?**
+  _`success_response()` has 383 INFERRED edges - model-reasoned connections that need verification._
+- **Are the 353 inferred relationships involving `str` (e.g. with `extract_login_credentials()` and `._make_request()`) actually correct?**
+  _`str` has 353 INFERRED edges - model-reasoned connections that need verification._
+- **Are the 306 inferred relationships involving `error_response()` (e.g. with `_json_error_response()` and `web_login()`) actually correct?**
+  _`error_response()` has 306 INFERRED edges - model-reasoned connections that need verification._
+- **Are the 216 inferred relationships involving `SysUser` (e.g. with `数据权限过滤(V0.3:基于角色 data_scope 的查询级行级过滤)  data_scope 值:   1 = 全部数据   2 = 本部门数据   3` and `从 SysUser 读取用户的 dept_id。`) actually correct?**
+  _`SysUser` has 216 INFERRED edges - model-reasoned connections that need verification._

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1 - 1
backend/graphify-out/graph.html


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 121 - 112
backend/graphify-out/graph.json


+ 8 - 6
backend/graphify-out/manifest.json

@@ -25,8 +25,8 @@
   "/Users/wuzilong/work/00code/zkxw/backend/app/core/wechat_message.py": 1778578258.0458734,
   "/Users/wuzilong/work/00code/zkxw/backend/app/models/material.py": 1779283385.245847,
   "/Users/wuzilong/work/00code/zkxw/backend/app/models/qc_param.py": 1779022399.0175831,
-  "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py": 1779341971.6717553,
-  "/Users/wuzilong/work/00code/zkxw/backend/app/models/__init__.py": 1779206668.0388827,
+  "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py": 1779418048.5388381,
+  "/Users/wuzilong/work/00code/zkxw/backend/app/models/__init__.py": 1779418058.876012,
   "/Users/wuzilong/work/00code/zkxw/backend/app/models/research_donation.py": 1779177348.5945773,
   "/Users/wuzilong/work/00code/zkxw/backend/app/models/qc_material.py": 1779284411.5179992,
   "/Users/wuzilong/work/00code/zkxw/backend/app/models/base.py": 1778578258.0821667,
@@ -36,11 +36,12 @@
   "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/price_list.py": 1778578258.096609,
   "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/user.py": 1778578258.099944,
   "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/production_work_order.py": 1779206897.0519433,
+  "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/storage_occupancy.py": 1779418054.7652438,
   "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/quotation.py": 1778578258.0991468,
   "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/product_parameter_value.py": 1778578258.0980055,
   "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/product_review_media.py": 1778578258.0986822,
   "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/product_parameter.py": 1778578258.0977957,
-  "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/production_task.py": 1779402943.8622077,
+  "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/production_task.py": 1779418120.989224,
   "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/product_detail_media.py": 1778578258.0973988,
   "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/product_banner.py": 1778578258.097102,
   "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/region.py": 1778578258.099429,
@@ -69,12 +70,13 @@
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py": 1779341248.2062187,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/material.py": 1779283386.3879013,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/dict_type.py": 1779265621.5212462,
+  "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_occupancy.py": 1779418213.0446064,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_third_party_company.py": 1779265648.2051165,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/material_stock_log.py": 1779281903.0942652,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/tenant.py": 1779265611.0532296,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/consumable.py": 1779265707.1662717,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/sop_document.py": 1779265721.9928973,
-  "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_task.py": 1779402989.6753793,
+  "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_task.py": 1779415585.5702748,
   "/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": 1779265718.524432,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/collection_protocol.py": 1779329214.3583212,
@@ -84,7 +86,7 @@
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/express_company.py": 1779265659.3453207,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_lab_test_item.py": 1779265673.3888667,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/role.py": 1779265616.3701816,
-  "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py": 1779341969.895009,
+  "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py": 1779421014.4336343,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/menu.py": 1779265729.0381103,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/collection_region.py": 1779277867.6408322,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/sample_reception_check_item.py": 1779007909.2933013,
@@ -119,7 +121,7 @@
   "/Users/wuzilong/work/00code/zkxw/backend/app/api/collection_protocol.py": 1779265813.1605325,
   "/Users/wuzilong/work/00code/zkxw/backend/app/api/basic_info.py": 1779280842.8447173,
   "/Users/wuzilong/work/00code/zkxw/backend/app/api/__init__.py": 1779230551.0712175,
-  "/Users/wuzilong/work/00code/zkxw/backend/app/api/operations.py": 1779271459.2836037,
+  "/Users/wuzilong/work/00code/zkxw/backend/app/api/operations.py": 1779418318.1375492,
   "/Users/wuzilong/work/00code/zkxw/backend/app/api/menu.py": 1779263225.4964793,
   "/Users/wuzilong/work/00code/zkxw/backend/app/api/admin.py": 1779265690.8423955,
   "/Users/wuzilong/work/00code/zkxw/backend/app/api/research_donation.py": 1779266149.3047824,

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 369 - 390
web-admin/graphify-out/.graphify_analysis.json


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 177 - 113
web-admin/graphify-out/.graphify_extract.json


+ 107 - 67
web-admin/graphify-out/GRAPH_REPORT.md

@@ -1,11 +1,11 @@
 # Graph Report - src  (2026-05-22)
 
 ## Corpus Check
-- Large corpus: 580 files · ~301,189 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
+- Large corpus: 580 files · ~303,483 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
 
 ## Summary
-- 1412 nodes · 1758 edges · 36 communities detected
-- Extraction: 78% EXTRACTED · 22% INFERRED · 0% AMBIGUOUS · INFERRED: 384 edges (avg confidence: 0.8)
+- 1416 nodes · 1764 edges · 42 communities detected
+- Extraction: 78% EXTRACTED · 22% INFERRED · 0% AMBIGUOUS · INFERRED: 386 edges (avg confidence: 0.8)
 - Token cost: 0 input · 0 output
 
 ## Community Hubs (Navigation)
@@ -34,20 +34,26 @@
 - [[_COMMUNITY_Community 22|Community 22]]
 - [[_COMMUNITY_Community 23|Community 23]]
 - [[_COMMUNITY_Community 24|Community 24]]
-- [[_COMMUNITY_Community 25|Community 25]]
+- [[_COMMUNITY_Community 26|Community 26]]
 - [[_COMMUNITY_Community 27|Community 27]]
-- [[_COMMUNITY_Community 29|Community 29]]
+- [[_COMMUNITY_Community 28|Community 28]]
 - [[_COMMUNITY_Community 30|Community 30]]
 - [[_COMMUNITY_Community 31|Community 31]]
 - [[_COMMUNITY_Community 32|Community 32]]
+- [[_COMMUNITY_Community 33|Community 33]]
 - [[_COMMUNITY_Community 34|Community 34]]
 - [[_COMMUNITY_Community 35|Community 35]]
 - [[_COMMUNITY_Community 36|Community 36]]
 - [[_COMMUNITY_Community 37|Community 37]]
 - [[_COMMUNITY_Community 39|Community 39]]
+- [[_COMMUNITY_Community 40|Community 40]]
+- [[_COMMUNITY_Community 41|Community 41]]
+- [[_COMMUNITY_Community 42|Community 42]]
+- [[_COMMUNITY_Community 44|Community 44]]
+- [[_COMMUNITY_Community 52|Community 52]]
 
 ## God Nodes (most connected - your core abstractions)
-1. `get()` - 153 edges
+1. `get()` - 155 edges
 2. `WebSocketClient` - 26 edges
 3. `push()` - 18 edges
 4. `withFullscreenLoading()` - 16 edges
@@ -65,45 +71,45 @@
   views/basic-info/customer/index.vue → utils/fullscreen-loading.ts
 - `handleUnauthorizedResponse()` --calls--> `fetchRefreshToken()`  [INFERRED]
   utils/http/index.ts → api/auth.ts
-- `fetchConsumableList()` --calls--> `get()`  [INFERRED]
-  api/production.ts → views/production/process-route/index.vue
-- `fetchAllConsumables()` --calls--> `get()`  [INFERRED]
-  api/production.ts → views/production/process-route/index.vue
+- `fetchLeaveList()` --calls--> `get()`  [INFERRED]
+  api/leave.ts → views/production/process-route/index.vue
+- `fetchLeaveDetail()` --calls--> `get()`  [INFERRED]
+  api/leave.ts → views/production/process-route/index.vue
 
 ## Communities
 
 ### Community 0 - "Community 0"
 Cohesion: 0.01
-Nodes (110): fetchActiveRegions(), fetchCategoryList(), fetchCategoryParameters(), fetchCellProductPriceList(), fetchCellProductSpecList(), fetchCellProductTypeList(), fetchCellSampleTypeList(), fetchChannelAgreementPrices() (+102 more)
+Nodes (80): fetchActiveRegions(), fetchCategoryList(), fetchCategoryParameters(), fetchCellProductPriceList(), fetchCellProductSpecList(), fetchCellProductTypeList(), fetchCellSampleTypeList(), fetchChannelAgreementPrices() (+72 more)
 
 ### Community 1 - "Community 1"
-Cohesion: 0.02
-Nodes (6): fetchDeleteProjectSystemItem(), fetchGetRoleList(), fetchNotDeletedRegions(), fetchRegionList(), loadRoleList(), if()
-
-### Community 2 - "Community 2"
 Cohesion: 0.03
-Nodes (56): fetchBatchGenerateProtocolNos(), fetchBatchGenerateSampleCodes(), fetchBatchIssueProtocolNos(), fetchBatchIssueSampleCodes(), fetchCollectionProtocolNoList(), fetchCreateCollectionProtocolNo(), fetchCreateSampleCode(), fetchDeleteCollectionProtocolNo() (+48 more)
+Nodes (57): fetchBatchGenerateProtocolNos(), fetchBatchGenerateSampleCodes(), fetchBatchIssueProtocolNos(), fetchBatchIssueSampleCodes(), fetchCollectionProtocolNoList(), fetchCreateCollectionProtocolNo(), fetchCreateSampleCode(), fetchDeleteCollectionProtocolNo() (+49 more)
 
-### Community 3 - "Community 3"
+### Community 2 - "Community 2"
 Cohesion: 0.04
 Nodes (31): fetchGetUserInfo(), clickTab(), handleTouchEnd(), setTransition(), IframeRouteManager, RouteRegistry, useCommon(), setupAfterEachGuard() (+23 more)
 
-### Community 4 - "Community 4"
+### Community 3 - "Community 3"
 Cohesion: 0.05
 Nodes (32): fetchDeptTree(), fetchPostBoundUserIds(), FireworkSystem, reload(), formatUserLabel(), loadDeptData(), onPersonSelectChange(), subscribeTokenRefresh() (+24 more)
 
-### Community 5 - "Community 5"
+### Community 4 - "Community 4"
 Cohesion: 0.04
 Nodes (18): fetchAllConsumablePacks(), fetchAllConsumables(), fetchAllOutputTypes(), fetchAllProcessCategories(), fetchAllProcesses(), fetchAllReagents(), fetchAllSopDocuments(), fetchConsumableList() (+10 more)
 
-### Community 6 - "Community 6"
-Cohesion: 0.06
-Nodes (10): useSettingsHandlers(), useSettingsPanel(), useSettingsState(), RoutePermissionValidator, useCeremony(), useTheme(), TableCache, themeAnimation() (+2 more)
+### Community 5 - "Community 5"
+Cohesion: 0.05
+Nodes (40): fetchAccessLogList(), fetchCleanRoomList(), fetchCreateAccessLog(), fetchCreateCleanRoom(), fetchCreateSite(), fetchDeleteAccessLog(), fetchDeleteCleanRoom(), fetchDeleteSite() (+32 more)
 
-### Community 7 - "Community 7"
+### Community 6 - "Community 6"
 Cohesion: 0.09
 Nodes (31): toLogin(), base64ToBytes(), bytesToBase64(), decryptJson(), deriveKey(), encryptJson(), fromBase64Url(), hexToBytes() (+23 more)
 
+### Community 7 - "Community 7"
+Cohesion: 0.06
+Nodes (10): useSettingsHandlers(), useSettingsPanel(), useSettingsState(), RoutePermissionValidator, useCeremony(), useTheme(), TableCache, themeAnimation() (+2 more)
+
 ### Community 8 - "Community 8"
 Cohesion: 0.09
 Nodes (8): getDefaultLanguage(), checkStorageCompatibility(), StorageConfig, getSystemStorage(), getSysVersion(), StorageKeyManager, StorageCompatibilityManager, validateStorageData()
@@ -113,12 +119,12 @@ Cohesion: 0.13
 Nodes (4): handleReset(), handleSearch(), WebSocketClient, emit()
 
 ### Community 10 - "Community 10"
-Cohesion: 0.08
-Nodes (17): fetchCreateMenu(), fetchDeleteMenu(), fetchMenuTree(), fetchUpdateMenu(), collectIds(), postorder(), preorder(), applyTreeExpand() (+9 more)
+Cohesion: 0.07
+Nodes (13): fetchAttachmentDownloadUrl(), fetchOrderAdjusts(), fetchOrderDetail(), fetchOrderItems(), fetchOrderList(), fetchPriceVersionDetail(), fetchSystemConfig(), handleReceptionAttachmentDownload() (+5 more)
 
 ### Community 11 - "Community 11"
-Cohesion: 0.1
-Nodes (23): fetchAccessLogList(), fetchCleanRoomList(), fetchCreateAccessLog(), fetchCreateCleanRoom(), fetchDeleteAccessLog(), fetchDeleteCleanRoom(), fetchUpdateAccessLog(), fetchUpdateCleanRoom() (+15 more)
+Cohesion: 0.09
+Nodes (9): fetchGetAllUpgradeLogs(), fetchGetChinaMapData(), fetchGetComments(), fetchGetCurrentVersion(), fetchGetLatestUpgradeLog(), fetchGetSystemConfig(), fetchGetUpgradeLogs(), systemUpgrade() (+1 more)
 
 ### Community 12 - "Community 12"
 Cohesion: 0.1
@@ -129,124 +135,158 @@ Cohesion: 0.1
 Nodes (8): fetchGetMenuList(), fetchGetMenuCodes(), fetchLogin(), fetchRefreshToken(), MenuProcessor, useAppMode(), useAuth(), buildWebEncryptedLoginBody()
 
 ### Community 14 - "Community 14"
-Cohesion: 0.1
-Nodes (18): fetchCollectionRegionList(), fetchCreateCollectionRegion(), fetchCreateHospital(), fetchDeleteCollectionRegion(), fetchDeleteHospital(), fetchHospitalList(), fetchUpdateCollectionRegion(), fetchUpdateHospital() (+10 more)
+Cohesion: 0.08
+Nodes (14): fetchLeaveDetail(), fetchLeaveList(), fetchApproveTask(), fetchBusinessAllTasks(), fetchCompletedTasks(), fetchDesignerData(), fetchInstanceDetail(), fetchMyInstances() (+6 more)
 
 ### Community 15 - "Community 15"
 Cohesion: 0.1
-Nodes (19): fetchCreateSite(), fetchDeleteSite(), fetchDictDataOptions(), fetchSiteList(), fetchUpdateSite(), fetchUserList(), remoteSearchUsers(), handleAdd() (+11 more)
+Nodes (18): fetchCollectionRegionList(), fetchCreateCollectionRegion(), fetchCreateHospital(), fetchDeleteCollectionRegion(), fetchDeleteHospital(), fetchHospitalList(), fetchUpdateCollectionRegion(), fetchUpdateHospital() (+10 more)
 
 ### Community 16 - "Community 16"
 Cohesion: 0.12
 Nodes (15): addCopyButton(), highlightCode(), insertLineNumbers(), isBlockProcessed(), markBlockAsProcessed(), processBlock(), setupHighlightDirective(), setupRippleDirective() (+7 more)
 
 ### Community 17 - "Community 17"
-Cohesion: 0.12
-Nodes (19): fetchCreateUser(), fetchDeleteUser(), fetchResetPassword(), fetchRoleOptions(), fetchUpdateUser(), fetchUserDetail(), formatUserDeptDisplay(), handleAdd() (+11 more)
+Cohesion: 0.13
+Nodes (16): fetchCreateMenu(), fetchDeleteMenu(), fetchMenuTree(), fetchUpdateMenu(), postorder(), preorder(), applyTreeExpand(), handleAdd() (+8 more)
 
 ### Community 18 - "Community 18"
-Cohesion: 0.12
-Nodes (7): fetchAttachmentDownloadUrl(), handleReceptionAttachmentDownload(), attachmentFullUrl(), handleAttachmentDownload(), handleAttachmentPreview(), downloadAttachmentFile(), triggerAnchorDownload()
+Cohesion: 0.14
+Nodes (17): fetchCreateUser(), fetchDeleteUser(), fetchResetPassword(), fetchUpdateUser(), fetchUserDetail(), formatUserDeptDisplay(), handleAdd(), handleDelete() (+9 more)
 
 ### Community 19 - "Community 19"
-Cohesion: 0.19
-Nodes (3): fetchGetLatestUpgradeLog(), systemUpgrade(), VersionManager
-
-### Community 20 - "Community 20"
 Cohesion: 0.16
 Nodes (9): auditorDisplay(), dash(), expressName(), formatYm(), formatYmd(), sampleTypeName(), siteName(), unitName() (+1 more)
 
-### Community 21 - "Community 21"
+### Community 20 - "Community 20"
 Cohesion: 0.16
 Nodes (10): useTable(), useTableImpl(), getColumnChecks(), useTableColumns(), createErrorHandler(), createSmartDebounce(), defaultResponseAdapter(), extractPagination() (+2 more)
 
-### Community 22 - "Community 22"
+### Community 21 - "Community 21"
 Cohesion: 0.12
 Nodes (2): fetchProductionTaskDetail(), fetchProductionTaskList()
 
-### Community 23 - "Community 23"
+### Community 22 - "Community 22"
 Cohesion: 0.2
 Nodes (2): ComponentLoader, RouteTransformer
 
-### Community 24 - "Community 24"
+### Community 23 - "Community 23"
 Cohesion: 0.27
 Nodes (14): useChart(), useChartComponent(), useChartOps(), colourBlend(), getCssVar(), getDarkColor(), getLightColor(), handleElementThemeColor() (+6 more)
 
-### Community 25 - "Community 25"
+### Community 24 - "Community 24"
 Cohesion: 0.17
 Nodes (12): fetchCreateCustomer(), fetchCustomerList(), fetchDeleteCustomer(), fetchUpdateCustomer(), handleAdd(), handleBatchDelete(), handleDelete(), handleReset() (+4 more)
 
+### Community 26 - "Community 26"
+Cohesion: 0.17
+Nodes (3): fetchResearchDonationAuditList(), fetchResearchDonationDetail(), fetchResearchDonationList()
+
 ### Community 27 - "Community 27"
+Cohesion: 0.18
+Nodes (3): fetchMaterialList(), fetchMaterialOptions(), fetchMaterialStockLogList()
+
+### Community 28 - "Community 28"
 Cohesion: 0.2
 Nodes (2): fetchQcMaterialList(), fetchQcMaterialOptions()
 
-### Community 29 - "Community 29"
+### Community 30 - "Community 30"
 Cohesion: 0.39
 Nodes (6): isIgnorableScriptError(), normalizeErrorMessage(), registerPromiseErrorHandler(), registerResourceErrorHandler(), scriptErrorHandler(), setupErrorHandle()
 
-### Community 30 - "Community 30"
+### Community 31 - "Community 31"
 Cohesion: 0.36
 Nodes (1): TableHeightCalculator
 
-### Community 31 - "Community 31"
+### Community 32 - "Community 32"
 Cohesion: 0.33
 Nodes (2): logApiCryptoBootInfo(), maskSecret()
 
-### Community 32 - "Community 32"
+### Community 33 - "Community 33"
 Cohesion: 0.47
 Nodes (3): isPaymentDueWithinWarnWindow(), parsePaymentDueYmd(), startOfLocalDay()
 
 ### Community 34 - "Community 34"
+Cohesion: 0.33
+Nodes (2): fetchSampleReceptionCheckItemList(), fetchSampleReceptionCheckItemsActive()
+
+### Community 35 - "Community 35"
+Cohesion: 0.33
+Nodes (2): fetchQcThirdPartyItemList(), fetchQcThirdPartyItemsActive()
+
+### Community 36 - "Community 36"
+Cohesion: 0.33
+Nodes (2): fetchQcLabTestItemList(), fetchQcLabTestItemsActive()
+
+### Community 37 - "Community 37"
+Cohesion: 0.33
+Nodes (2): fetchQcTaskDetail(), fetchQcTaskList()
+
+### Community 39 - "Community 39"
 Cohesion: 0.53
 Nodes (4): isEtaTodayOrEarlier(), parseEtaLocalDate(), sampleReceptionRowClassName(), startOfLocalDay()
 
-### Community 35 - "Community 35"
+### Community 40 - "Community 40"
 Cohesion: 0.5
 Nodes (2): focusInput(), handleKeydown()
 
-### Community 36 - "Community 36"
+### Community 41 - "Community 41"
 Cohesion: 0.67
 Nodes (2): if(), logSidebarMenu()
 
-### Community 37 - "Community 37"
+### Community 42 - "Community 42"
 Cohesion: 1.0
 Nodes (2): getSettingDefaults(), resetToDefaults()
 
-### Community 39 - "Community 39"
+### Community 44 - "Community 44"
 Cohesion: 1.0
 Nodes (2): defaultAvatarPathByRole(), resolveUserAvatarUrl()
 
+### Community 52 - "Community 52"
+Cohesion: 0.67
+Nodes (2): fetchDeleteProjectSystemItem(), if()
+
 ## Knowledge Gaps
-- **Thin community `Community 22`** (17 nodes): `fetchAddTaskStep()`, `fetchCopyTaskStep()`, `fetchCreateProductionTask()`, `fetchDeleteTaskStep()`, `fetchProductionTaskDetail()`, `fetchProductionTaskList()`, `fetchReorderTaskSteps()`, `fetchReplaceStepConsumables()`, `fetchReplaceStepDataCollection()`, `fetchReplaceStepOutputs()`, `fetchReplaceStepReagents()`, `fetchSetTaskInputs()`, `fetchSetTaskOutputs()`, `fetchUpdateProductionTask()`, `fetchUpdateProductionTaskStatus()`, `fetchUpdateTaskStep()`, `production-task.ts`
+- **Thin community `Community 21`** (17 nodes): `fetchAddTaskStep()`, `fetchCopyTaskStep()`, `fetchCreateProductionTask()`, `fetchDeleteTaskStep()`, `fetchProductionTaskDetail()`, `fetchProductionTaskList()`, `fetchReorderTaskSteps()`, `fetchReplaceStepConsumables()`, `fetchReplaceStepDataCollection()`, `fetchReplaceStepOutputs()`, `fetchReplaceStepReagents()`, `fetchSetTaskInputs()`, `fetchSetTaskOutputs()`, `fetchUpdateProductionTask()`, `fetchUpdateProductionTaskStatus()`, `fetchUpdateTaskStep()`, `production-task.ts`
+  Too small to be a meaningful cluster - may be noise or needs more connections extracted.
+- **Thin community `Community 22`** (17 nodes): `ComponentLoader`, `.constructor()`, `.createEmptyComponent()`, `.createErrorComponent()`, `.load()`, `.loadIframe()`, `.loadLayout()`, `.load()`, `RouteTransformer`, `.extractFirstSegment()`, `.handleFirstLevelRoute()`, `.handleIframeRoute()`, `.handleNormalRoute()`, `.isFirstLevelRoute()`, `.transform()`, `ComponentLoader.ts`, `RouteTransformer.ts`
+  Too small to be a meaningful cluster - may be noise or needs more connections extracted.
+- **Thin community `Community 28`** (10 nodes): `fetchBatchStockInQcMaterial()`, `fetchBatchStockOutQcMaterial()`, `fetchCreateQcMaterial()`, `fetchDeleteQcMaterial()`, `fetchQcMaterialList()`, `fetchQcMaterialOptions()`, `fetchStockInQcMaterial()`, `fetchStockOutQcMaterial()`, `fetchUpdateQcMaterial()`, `qc-material.ts`
+  Too small to be a meaningful cluster - may be noise or needs more connections extracted.
+- **Thin community `Community 31`** (8 nodes): `TableHeightCalculator`, `.calculate()`, `.calculateOffset()`, `.calculatePaginationOffset()`, `.constructor()`, `.getHeaderHeight()`, `useTableHeight()`, `useTableHeight.ts`
+  Too small to be a meaningful cluster - may be noise or needs more connections extracted.
+- **Thin community `Community 32`** (7 nodes): `App.vue`, `main.ts`, `refreshButtonAutoIcons()`, `refreshPanelTitleIcons()`, `logApiCryptoBootInfo()`, `maskSecret()`, `console.ts`
+  Too small to be a meaningful cluster - may be noise or needs more connections extracted.
+- **Thin community `Community 34`** (6 nodes): `fetchCreateSampleReceptionCheckItem()`, `fetchDeleteSampleReceptionCheckItem()`, `fetchSampleReceptionCheckItemList()`, `fetchSampleReceptionCheckItemsActive()`, `fetchUpdateSampleReceptionCheckItem()`, `sample-reception-check-item.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 23`** (17 nodes): `ComponentLoader`, `.constructor()`, `.createEmptyComponent()`, `.createErrorComponent()`, `.load()`, `.loadIframe()`, `.loadLayout()`, `.load()`, `RouteTransformer`, `.extractFirstSegment()`, `.handleFirstLevelRoute()`, `.handleIframeRoute()`, `.handleNormalRoute()`, `.isFirstLevelRoute()`, `.transform()`, `ComponentLoader.ts`, `RouteTransformer.ts`
+- **Thin community `Community 35`** (6 nodes): `fetchCreateQcThirdPartyItem()`, `fetchDeleteQcThirdPartyItem()`, `fetchQcThirdPartyItemList()`, `fetchQcThirdPartyItemsActive()`, `fetchUpdateQcThirdPartyItem()`, `qc-third-party-item.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 27`** (10 nodes): `fetchBatchStockInQcMaterial()`, `fetchBatchStockOutQcMaterial()`, `fetchCreateQcMaterial()`, `fetchDeleteQcMaterial()`, `fetchQcMaterialList()`, `fetchQcMaterialOptions()`, `fetchStockInQcMaterial()`, `fetchStockOutQcMaterial()`, `fetchUpdateQcMaterial()`, `qc-material.ts`
+- **Thin community `Community 36`** (6 nodes): `fetchCreateQcLabTestItem()`, `fetchDeleteQcLabTestItem()`, `fetchQcLabTestItemList()`, `fetchQcLabTestItemsActive()`, `fetchUpdateQcLabTestItem()`, `qc-lab-test-item.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 30`** (8 nodes): `TableHeightCalculator`, `.calculate()`, `.calculateOffset()`, `.calculatePaginationOffset()`, `.constructor()`, `.getHeaderHeight()`, `useTableHeight()`, `useTableHeight.ts`
+- **Thin community `Community 37`** (6 nodes): `fetchCompleteQcTask()`, `fetchQcTaskDetail()`, `fetchQcTaskList()`, `fetchStartQcTask()`, `fetchUpdateQcTaskAttachments()`, `qc-task.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 31`** (7 nodes): `App.vue`, `main.ts`, `refreshButtonAutoIcons()`, `refreshPanelTitleIcons()`, `logApiCryptoBootInfo()`, `maskSecret()`, `console.ts`
+- **Thin community `Community 40`** (5 nodes): `focusInput()`, `handleKeydown()`, `if()`, `search()`, `index.vue`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 35`** (5 nodes): `focusInput()`, `handleKeydown()`, `if()`, `search()`, `index.vue`
+- **Thin community `Community 41`** (4 nodes): `if()`, `logSidebarMenu()`, `index.vue`, `SidebarSubmenu.vue`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 36`** (4 nodes): `if()`, `logSidebarMenu()`, `index.vue`, `SidebarSubmenu.vue`
+- **Thin community `Community 42`** (3 nodes): `getSettingDefaults()`, `resetToDefaults()`, `setting.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 37`** (3 nodes): `getSettingDefaults()`, `resetToDefaults()`, `setting.ts`
+- **Thin community `Community 44`** (3 nodes): `defaultAvatarPathByRole()`, `resolveUserAvatarUrl()`, `user-avatar.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 39`** (3 nodes): `defaultAvatarPathByRole()`, `resolveUserAvatarUrl()`, `user-avatar.ts`
+- **Thin community `Community 52`** (3 nodes): `fetchDeleteProjectSystemItem()`, `if()`, `index.vue`
   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 `get()` connect `Community 0` to `Community 1`, `Community 2`, `Community 3`, `Community 4`, `Community 5`, `Community 10`, `Community 11`, `Community 12`, `Community 13`, `Community 14`, `Community 15`, `Community 17`, `Community 19`, `Community 22`, `Community 25`, `Community 27`?**
-  _High betweenness centrality (0.264) - this node is a cross-community bridge._
-- **Why does `push()` connect `Community 4` to `Community 1`, `Community 3`, `Community 6`, `Community 9`, `Community 12`, `Community 19`?**
-  _High betweenness centrality (0.078) - this node is a cross-community bridge._
-- **Why does `fetchGetLatestUpgradeLog()` connect `Community 19` to `Community 0`?**
+- **Why does `get()` connect `Community 0` to `Community 1`, `Community 2`, `Community 3`, `Community 4`, `Community 5`, `Community 10`, `Community 11`, `Community 12`, `Community 13`, `Community 14`, `Community 15`, `Community 17`, `Community 18`, `Community 21`, `Community 24`, `Community 26`, `Community 27`, `Community 28`, `Community 34`, `Community 35`, `Community 36`, `Community 37`?**
+  _High betweenness centrality (0.267) - this node is a cross-community bridge._
+- **Why does `push()` connect `Community 3` to `Community 0`, `Community 2`, `Community 7`, `Community 9`, `Community 11`, `Community 12`?**
+  _High betweenness centrality (0.075) - this node is a cross-community bridge._
+- **Why does `buildTreePathLabelFromFlat()` connect `Community 3` to `Community 0`, `Community 7`?**
   _High betweenness centrality (0.042) - this node is a cross-community bridge._
-- **Are the 152 inferred relationships involving `get()` (e.g. with `buildTreePathLabelFromFlat()` and `alignFqPaymentDetailsForDisplay()`) actually correct?**
-  _`get()` has 152 INFERRED edges - model-reasoned connections that need verification._
+- **Are the 154 inferred relationships involving `get()` (e.g. with `buildTreePathLabelFromFlat()` and `alignFqPaymentDetailsForDisplay()`) actually correct?**
+  _`get()` has 154 INFERRED edges - model-reasoned connections that need verification._
 - **Are the 17 inferred relationships involving `push()` (e.g. with `fetchAllPagedRecords()` and `calcInstallmentDueAmounts()`) actually correct?**
   _`push()` has 17 INFERRED edges - model-reasoned connections that need verification._
 - **Are the 15 inferred relationships involving `withFullscreenLoading()` (e.g. with `.close()` and `handleSubmit()`) actually correct?**

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1 - 1
web-admin/graphify-out/graph.html


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

@@ -17,8 +17,8 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/common/index.ts": 1778582068.0254738,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/directive/directive.d.ts": 1778582068.026978,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/api/api.d.ts": 1778582068.0246184,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts": 1779405328.0912213,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/auto-imports.d.ts": 1779405314.3910556,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts": 1779420283.3944924,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/auto-imports.d.ts": 1779420178.0237312,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/store/index.ts": 1778582068.0293276,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/router/index.ts": 1778775532.895603,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/locales/index.ts": 1778582068.004279,
@@ -201,14 +201,14 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/collection-protocol.ts": 1779244706.1724706,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/chinaRegion.ts": 1778582067.8620915,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/business.ts": 1779159587.6033742,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts": 1779310973.2465198,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts": 1779418057.065808,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/ethnic.ts": 1778929297.3919344,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-lab-test-item.ts": 1779017838.8231559,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts": 1779339624.2590876,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/research-donation.ts": 1779160004.1431847,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts": 1779182848.881475,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-item-param.ts": 1779022632.5658252,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-task.ts": 1779362887.1562896,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-task.ts": 1779416180.8780618,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-material-usage.ts": 1779284726.00147,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-material.ts": 1779283280.0705447,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/system.ts": 1778582067.862994,
@@ -234,7 +234,7 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/outside/Iframe.vue": 1778582068.071536,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/operations/demo/index.vue": 1779227504.0518396,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/operations/clean-room/index.vue": 1779271253.4280891,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/operations/storage-facility/index.vue": 1779270994.1626155,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/operations/storage-facility/index.vue": 1779419674.3381562,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/operations/exhibition-appointment/index.vue": 1779269551.8199837,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/audit/index.vue": 1779227504.1761265,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/index.vue": 1779227504.1804457,
@@ -269,15 +269,15 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/leave/index.vue": 1779257191.2768743,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/output-type/index.vue": 1779307917.5932443,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/index.vue": 1779341967.5417912,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue": 1779370831.7062883,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue": 1779408337.9346838,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/sop/index.vue": 1779309609.6630545,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-task/index.vue": 1779404266.0171812,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-task/index.vue": 1779414898.4036016,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/reagent/index.vue": 1779307562.93293,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue": 1779361308.0826523,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts": 1779403989.7509887,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/StepDetailPanel.vue": 1779404000.53773,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskExecutionDialog.vue": 1779405231.9717994,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/DispatchTaskDialog.vue": 1779370890.9720688,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts": 1779418710.3300052,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/StepDetailPanel.vue": 1779408746.3297637,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskExecutionDialog.vue": 1779415778.1769066,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/DispatchTaskDialog.vue": 1779414418.4825,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/consumable-pack/index.vue": 1779307488.018346,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/consumable/index.vue": 1779307921.9798808,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process/index.vue": 1779309524.0991938,

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
web-admin/src/graphify-out/cache/ast/06e8cbafff75ab1b3038690ecf398fcfd47e125ac19af34353c8f86f000e6319.json


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

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_dispatchtaskdialog_vue", "label": "DispatchTaskDialog.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/DispatchTaskDialog.vue", "source_location": "L1"}, {"id": "production_work_order_dispatchtaskdialog_if", "label": "if()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/DispatchTaskDialog.vue", "source_location": "L203"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_dispatchtaskdialog_vue", "target": "production_work_order_dispatchtaskdialog_if", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/DispatchTaskDialog.vue", "source_location": "L203", "weight": 1.0}], "raw_calls": [{"caller_nid": "production_work_order_dispatchtaskdialog_if", "callee": "initEditor", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/DispatchTaskDialog.vue", "source_location": "L207"}]}

+ 1 - 0
web-admin/src/graphify-out/cache/ast/4d5ec2b6baeee6160e0a47abe2c705d8021e4a3b23079b90bd5da8713b20c9a2.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_operations_storage_facility_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/operations/storage-facility/index.vue", "source_location": "L1"}, {"id": "storage_facility_index_if", "label": "if()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/operations/storage-facility/index.vue", "source_location": "L720"}, {"id": "storage_facility_index_push", "label": "push()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/operations/storage-facility/index.vue", "source_location": "L723"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_operations_storage_facility_index_vue", "target": "storage_facility_index_if", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/operations/storage-facility/index.vue", "source_location": "L720", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_operations_storage_facility_index_vue", "target": "storage_facility_index_push", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/operations/storage-facility/index.vue", "source_location": "L723", "weight": 1.0}], "raw_calls": [{"caller_nid": "storage_facility_index_if", "callee": "set", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/operations/storage-facility/index.vue", "source_location": "L721"}]}

+ 1 - 0
web-admin/src/graphify-out/cache/ast/7310426cab33026e9fc1a9a939228914f2f9a0f388503f3f9a96ec4b8f5bec3d.json

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

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
web-admin/src/graphify-out/cache/ast/736d57d953c189f69e6909383ad11720f05f7ea892f4aed6be1a140c6ce97bee.json


+ 1 - 0
web-admin/src/graphify-out/cache/ast/8c799aa9e77ed13d10ce8cb47f7bfbcf342bf461cdde928692d4fee5c83190da.json

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

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
web-admin/src/graphify-out/cache/ast/91e603def28fab255ff52f5194293aff57fa13e7bc2d3df892d96a66584afb17.json


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
web-admin/src/graphify-out/cache/ast/9bcd2e9117669a13a7f9c3de6a2daac127dc85c37c3c88802b0196b4e0644bd8.json


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

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

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

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_types_import_components_d_ts", "label": "components.d.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts", "source_location": "L1"}], "edges": [], "raw_calls": []}

+ 108 - 80
web-admin/src/views/cell-storage/protocol/ProtocolFormDialog.vue

@@ -39,7 +39,7 @@
                 <div class="section-title">
                   <div class="section-title-left"><h3>基本信息</h3></div>
                 </div>
-                <div class="form-grid">
+                <div class="form-grid form-grid--col4">
                   <el-form-item label="协议号" prop="protocolNo">
                     <el-select
                       v-model="formData.protocolNoId"
@@ -110,89 +110,89 @@
                   </el-form-item>
                 </div>
 
-                <!-- 协议附件 -->
-                <el-form-item label="协议附件" style="margin-top: 18px">
-                  <div class="attachment-upload">
-                    <div class="attachment-list">
-                      <el-upload
-                        ref="attachmentUploadRef"
-                        :auto-upload="false"
-                        :show-file-list="false"
-                        :multiple="true"
-                        accept="image/*,video/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.zip,.rar"
-                        @change="handleAttachmentChange"
-                      >
-                        <div class="upload-card">
-                          <el-icon :size="28" color="#10b981"><Plus /></el-icon>
-                          <span class="upload-card-text">上传附件</span>
-                        </div>
-                      </el-upload>
-                      <div
-                        v-for="(file, index) in formData.attachments"
-                        :key="file.id || index"
-                        class="attachment-item"
-                        :class="{ 'is-uploading': file._uploading }"
-                      >
+                <div class="form-grid form-grid--col2 basic-remark-attach-row">
+                  <el-form-item label="备注">
+                    <el-input
+                      v-model="formData.remark"
+                      type="textarea"
+                      :rows="3"
+                      placeholder="可选"
+                    />
+                  </el-form-item>
+                  <el-form-item label="协议附件">
+                    <div class="attachment-upload">
+                      <div class="attachment-list">
+                        <el-upload
+                          ref="attachmentUploadRef"
+                          :auto-upload="false"
+                          :show-file-list="false"
+                          :multiple="true"
+                          accept="image/*,video/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.zip,.rar"
+                          @change="handleAttachmentChange"
+                        >
+                          <div class="upload-card">
+                            <el-icon :size="28" color="#10b981"><Plus /></el-icon>
+                            <span class="upload-card-text">上传附件</span>
+                          </div>
+                        </el-upload>
                         <div
-                          class="attachment-preview"
-                          @click="
-                            isImageFile(file) && file.fileUrl
-                              ? handlePreviewAttachment(file)
-                              : undefined
-                          "
+                          v-for="(file, index) in formData.attachments"
+                          :key="file.id || index"
+                          class="attachment-item"
+                          :class="{ 'is-uploading': file._uploading }"
                         >
-                          <template v-if="isImageFile(file) && file.fileUrl">
-                            <img :src="fullFileUrl(file.fileUrl)" :alt="file.fileName" />
-                          </template>
-                          <template v-else-if="isImageFile(file) && !file.fileUrl">
-                            <el-icon :size="24" color="#94a3b8"><Loading /></el-icon>
-                          </template>
-                          <template v-else-if="isVideoFile(file)">
-                            <div class="attachment-preview__icon attachment-preview__icon--video">
-                              <el-icon :size="28"><VideoPlay /></el-icon>
-                            </div>
-                          </template>
-                          <template v-else>
-                            <div class="attachment-preview__icon attachment-preview__icon--doc">
-                              <el-icon :size="28"><Document /></el-icon>
+                          <div
+                            class="attachment-preview"
+                            @click="
+                              isImageFile(file) && file.fileUrl
+                                ? handlePreviewAttachment(file)
+                                : undefined
+                            "
+                          >
+                            <template v-if="isImageFile(file) && file.fileUrl">
+                              <img :src="fullFileUrl(file.fileUrl)" :alt="file.fileName" />
+                            </template>
+                            <template v-else-if="isImageFile(file) && !file.fileUrl">
+                              <el-icon :size="24" color="#94a3b8"><Loading /></el-icon>
+                            </template>
+                            <template v-else-if="isVideoFile(file)">
+                              <div class="attachment-preview__icon attachment-preview__icon--video">
+                                <el-icon :size="28"><VideoPlay /></el-icon>
+                              </div>
+                            </template>
+                            <template v-else>
+                              <div class="attachment-preview__icon attachment-preview__icon--doc">
+                                <el-icon :size="28"><Document /></el-icon>
+                              </div>
+                            </template>
+                          </div>
+                          <div class="attachment-footer">
+                            <span class="attachment-name" :title="file.fileName">{{
+                              file.fileName
+                            }}</span>
+                            <div class="attachment-actions">
+                              <el-icon
+                                class="action-btn"
+                                title="下载"
+                                @click="handleDownloadAttachment(file)"
+                                ><Download
+                              /></el-icon>
+                              <el-icon
+                                class="action-btn action-btn--danger"
+                                title="删除"
+                                @click="removeAttachment(index)"
+                                ><Delete
+                              /></el-icon>
                             </div>
-                          </template>
-                        </div>
-                        <div class="attachment-footer">
-                          <span class="attachment-name" :title="file.fileName">{{
-                            file.fileName
-                          }}</span>
-                          <div class="attachment-actions">
-                            <el-icon
-                              class="action-btn"
-                              title="下载"
-                              @click="handleDownloadAttachment(file)"
-                              ><Download
-                            /></el-icon>
-                            <el-icon
-                              class="action-btn action-btn--danger"
-                              title="删除"
-                              @click="removeAttachment(index)"
-                              ><Delete
-                            /></el-icon>
                           </div>
                         </div>
                       </div>
+                      <span class="upload-hint"
+                        >支持图片、视频、PDF、Word、Excel等文件,单文件不超过 20MB</span
+                      >
                     </div>
-                    <span class="upload-hint"
-                      >支持图片、视频、PDF、Word、Excel等文件,单文件不超过 20MB</span
-                    >
-                  </div>
-                </el-form-item>
-
-                <el-form-item label="备注">
-                  <el-input
-                    v-model="formData.remark"
-                    type="textarea"
-                    :rows="3"
-                    placeholder="可选"
-                  />
-                </el-form-item>
+                  </el-form-item>
+                </div>
               </div>
             </div>
 
@@ -527,7 +527,7 @@
                       </el-button>
                     </div>
                   </div>
-                  <div class="form-grid">
+                  <div class="form-grid form-grid--col4">
                     <el-form-item label="样本类型">
                       <el-select
                         v-model="row.sampleTypeId"
@@ -749,7 +749,7 @@
                 <!-- 缴费主表 -->
                 <div class="payment-main-card">
                   <div class="section-subtitle">缴费主表</div>
-                  <div class="form-grid">
+                  <div class="form-grid form-grid--col5">
                     <el-form-item label="缴费模式">
                       <el-radio-group v-model="formData.payMode" @change="onPayModeChange">
                         <el-radio label="dj">趸交</el-radio>
@@ -872,7 +872,7 @@
                     </div>
                     <div v-show="detail._expanded" class="detail-body">
                       <div class="detail-body-inner">
-                        <div class="form-grid">
+                        <div class="form-grid form-grid--col4">
                           <el-form-item label="应缴费金额(元)">
                             <el-input
                               v-model="detail.dueAmount"
@@ -3008,6 +3008,22 @@
     &--col2 {
       grid-template-columns: repeat(2, 1fr);
     }
+
+    &--col4 {
+      grid-template-columns: repeat(4, minmax(0, 1fr));
+    }
+
+    &--col5 {
+      grid-template-columns: repeat(5, minmax(0, 1fr));
+    }
+
+    :deep(.el-form-item) {
+      margin-bottom: 0;
+    }
+  }
+
+  .basic-remark-attach-row {
+    margin-top: 16px;
   }
 
   .form-col-span-2 {
@@ -3516,6 +3532,18 @@
       &--col2 {
         grid-template-columns: 1fr;
       }
+
+      &--col4 {
+        grid-template-columns: repeat(2, minmax(0, 1fr));
+      }
+
+      &--col5 {
+        grid-template-columns: repeat(2, minmax(0, 1fr));
+      }
+
+      &--col2.basic-remark-attach-row {
+        grid-template-columns: 1fr;
+      }
     }
 
     .client-sub-cards {

+ 2 - 0
web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue

@@ -1877,6 +1877,8 @@
   /* 储存证:储存证附件 / 制作状态 / 制作日期同一行(在协议附件行下方) */
   .storage-cert-info-row {
     margin-top: 14px;
+    padding-top: 14px;
+    border-top: 1px dashed #e2e8f0;
   }
 
   .storage-cert-attachments-item {

+ 2 - 2
web-admin/src/views/production/cell-product/index.vue

@@ -249,7 +249,7 @@
       <el-dialog
         v-model="dialogVisible"
         :title="dialogTitle"
-        width="960px"
+        width="1080px"
         class="dialog-form-layout"
         align-center
         destroy-on-close
@@ -264,7 +264,7 @@
           scroll-to-error
           :scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
         >
-          <div class="form-grid-3">
+          <div class="form-grid-4">
             <el-form-item label="产品编码" prop="productCode">
               <el-input
                 v-model="formData.productCode"

+ 133 - 10
web-admin/src/views/production/process-route/index.vue

@@ -334,7 +334,7 @@
               </div>
               <div class="route-config-right-body">
                 <el-form label-position="top" class="route-config-step-form">
-                  <div class="form-grid-2">
+                  <div class="form-grid-4">
                     <el-form-item label="工序名称">
                       <el-input v-model="selectedStep.processName" @change="saveStepBasic" />
                     </el-form-item>
@@ -353,7 +353,13 @@
                         />
                       </el-select>
                     </el-form-item>
-                    <el-form-item label="描述" class="form-col-span-2">
+                    <el-form-item label="设备名称">
+                      <el-input v-model="selectedStep.equipmentName" @change="saveStepBasic" />
+                    </el-form-item>
+                    <el-form-item label="预计时长(min)">
+                      <el-input v-model="selectedStep.estimatedDuration" placeholder="如 2h" @change="saveStepBasic" />
+                    </el-form-item>
+                    <el-form-item label="描述" class="step-form-col-half">
                       <el-input
                         v-model="selectedStep.description"
                         type="textarea"
@@ -362,13 +368,7 @@
                         @change="saveStepBasic"
                       />
                     </el-form-item>
-                    <el-form-item label="设备名称">
-                      <el-input v-model="selectedStep.equipmentName" @change="saveStepBasic" />
-                    </el-form-item>
-                    <el-form-item label="预计时长(min)">
-                      <el-input v-model="selectedStep.estimatedDuration" placeholder="如 2h" @change="saveStepBasic" />
-                    </el-form-item>
-                    <el-form-item label="关联SOP" class="form-col-span-2">
+                    <el-form-item label="关联SOP" class="step-form-col-half">
                       <el-select
                         v-model="sopIdsArr"
                         multiple
@@ -384,6 +384,16 @@
                         />
                       </el-select>
                     </el-form-item>
+                    <div
+                      v-if="sopIdsArr.length"
+                      class="step-sop-attachment-row step-form-col-full"
+                    >
+                      <ProtocolAttachmentReadonly
+                        :files="linkedSopAttachmentFiles"
+                        @preview="handleSopAttachmentPreview"
+                        @download="handleSopAttachmentDownload"
+                      />
+                    </div>
                   </div>
                 </el-form>
 
@@ -715,7 +725,10 @@
 </template>
 
 <script setup lang="ts">
+  import { computed } from 'vue'
   import { Plus, Delete, Rank, Close, Check, CopyDocument, RefreshLeft, Search } from '@element-plus/icons-vue'
+  import ProtocolAttachmentReadonly from '@/views/cell-storage/protocol/ProtocolAttachmentReadonly.vue'
+  import { downloadAttachmentFile } from '@/utils/download-attachment'
   import { VueDraggable as draggable } from 'vue-draggable-plus'
   import {
     fetchProcessRouteList,
@@ -1275,6 +1288,63 @@
     saveStepBasic()
   }
 
+  const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
+
+  function sopAttachmentFullUrl(url: string) {
+    const u = String(url || '').trim()
+    if (!u) return ''
+    if (/^https?:\/\//i.test(u)) return u
+    return minioBaseUrl ? `${minioBaseUrl}/${u.replace(/^\//, '')}` : u
+  }
+
+  function attachmentsFromUrls(urls: string, sopId: string): Array<Record<string, unknown>> {
+    return urls
+      .split(',')
+      .map((u) => u.trim())
+      .filter(Boolean)
+      .map((url, i) => ({
+        id: `${sopId}_url_${i}`,
+        fileName: decodeURIComponent(url.split('/').pop() || `附件${i + 1}`),
+        fileUrl: url,
+        fileType: ''
+      }))
+  }
+
+  const linkedSopAttachmentFiles = computed(() => {
+    const ids = sopIdsArr.value
+    if (!ids?.length) return []
+    const idSet = new Set(ids)
+    const files: Array<Record<string, unknown>> = []
+    for (const sop of sopOptions.value) {
+      if (!idSet.has(sop.id)) continue
+      if (sop.attachmentFiles?.length) {
+        for (const f of sop.attachmentFiles) {
+          if (!f?.fileUrl) continue
+          files.push({
+            id: f.id || `${sop.id}_${f.fileUrl}`,
+            fileName:
+              f.fileName ||
+              decodeURIComponent(String(f.fileUrl).split('/').pop() || '文档'),
+            fileUrl: f.fileUrl,
+            fileType: f.fileType || ''
+          })
+        }
+      } else if (sop.sopAttachments) {
+        files.push(...attachmentsFromUrls(sop.sopAttachments, sop.id))
+      }
+    }
+    return files
+  })
+
+  function handleSopAttachmentPreview(file: Record<string, unknown>) {
+    const url = sopAttachmentFullUrl(String(file.fileUrl || ''))
+    if (url) window.open(url, '_blank')
+  }
+
+  function handleSopAttachmentDownload(file: Record<string, unknown>) {
+    downloadAttachmentFile(file, { fullFileUrl: sopAttachmentFullUrl })
+  }
+
   // 按分类分组的工艺库(支持搜索过滤)
   const filteredProcessLibrary = computed(() => {
     const keyword = processSearchKeyword.value?.trim().toLowerCase() || ''
@@ -1835,7 +1905,7 @@
 
   /* 中间面板:步骤列表 */
   .route-config-center {
-    width: 640px;
+    width: 480px;
     flex-shrink: 0;
     display: flex;
     flex-direction: column;
@@ -2054,4 +2124,57 @@
       padding: 16px;
     }
   }
+
+  .form-grid-4 .step-form-col-half {
+    grid-column: span 2;
+  }
+
+  .step-form-col-full {
+    grid-column: 1 / -1;
+  }
+
+  .step-sop-attachment-row {
+    margin-top: -4px;
+
+    :deep(.attachment-list) {
+      gap: 8px;
+    }
+
+    :deep(.attachment-item) {
+      width: 100px;
+    }
+
+    :deep(.attachment-preview) {
+      height: 68px;
+    }
+
+    :deep(.attachment-preview__icon) {
+      width: 36px;
+      height: 36px;
+
+      .el-icon {
+        font-size: 22px !important;
+      }
+    }
+
+    :deep(.attachment-footer) {
+      padding: 4px 6px;
+    }
+
+    :deep(.attachment-name) {
+      font-size: 10px;
+      min-height: calc(10px * 1.4 * 2);
+    }
+
+    :deep(.attachment-actions) {
+      margin-top: 2px;
+      gap: 6px;
+    }
+
+    :deep(.action-btn) {
+      width: 20px;
+      height: 20px;
+      font-size: 11px;
+    }
+  }
 </style>

+ 3 - 3
web-admin/src/views/production/process/index.vue

@@ -228,7 +228,7 @@
                     />
                   </el-select>
                 </el-form-item>
-                <el-form-item label="描述" class="form-col-span-2">
+                <el-form-item label="描述">
                   <el-input
                     v-model="formData.description"
                     type="textarea"
@@ -236,8 +236,8 @@
                     placeholder="请输入描述"
                   />
                 </el-form-item>
-                <el-form-item label="备注" class="form-col-span-2">
-                  <el-input v-model="formData.remark" placeholder="可选" />
+                <el-form-item label="备注">
+                  <el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="可选" />
                 </el-form-item>
                 </div>
               </div>

+ 4 - 0
web-admin/src/views/production/production-work-order/DispatchTaskDialog.vue

@@ -240,5 +240,9 @@
       overflow: hidden;
       padding: 16px;
     }
+
+    .route-config-center {
+      width: 480px;
+    }
   }
 </style>

+ 131 - 7
web-admin/src/views/production/production-work-order/StepDetailPanel.vue

@@ -40,7 +40,7 @@
       </div>
       <div class="route-config-right-body">
         <el-form label-position="top" class="route-config-step-form">
-          <div class="form-grid-2">
+          <div class="form-grid-4">
             <el-form-item label="工序名称">
               <el-input v-model="editor.selectedStep.value.processName" />
             </el-form-item>
@@ -50,23 +50,33 @@
                   :label="cat.categoryName || cat.name" :value="cat.id" />
               </el-select>
             </el-form-item>
-            <el-form-item label="描述" class="form-col-span-2">
-              <el-input v-model="editor.selectedStep.value.description" type="textarea" :rows="2"
-                placeholder="工序描述" />
-            </el-form-item>
             <el-form-item label="设备名称">
               <el-input v-model="editor.selectedStep.value.equipmentName" />
             </el-form-item>
             <el-form-item label="预计时长(min)">
               <el-input v-model="editor.selectedStep.value.estimatedDuration" placeholder="如 2h" />
             </el-form-item>
-            <el-form-item label="关联SOP" class="form-col-span-2">
+            <el-form-item label="描述" class="step-form-col-half">
+              <el-input v-model="editor.selectedStep.value.description" type="textarea" :rows="2"
+                placeholder="工序描述" />
+            </el-form-item>
+            <el-form-item label="关联SOP" class="step-form-col-half">
               <el-select v-model="editor.sopIdsArr.value" multiple placeholder="选择SOP文档" filterable
                 @change="editor.onSopIdsChange">
                 <el-option v-for="sop in editor.sopOptions.value" :key="sop.id"
                   :label="sop.sopName || sop.name" :value="sop.id" />
               </el-select>
             </el-form-item>
+            <div
+              v-if="editor.sopIdsArr.value.length"
+              class="step-sop-attachment-row step-form-col-full"
+            >
+              <ProtocolAttachmentReadonly
+                :files="linkedSopAttachmentFiles"
+                @preview="handleSopAttachmentPreview"
+                @download="handleSopAttachmentDownload"
+              />
+            </div>
           </div>
         </el-form>
 
@@ -308,13 +318,73 @@
 </template>
 
 <script setup lang="ts">
+  import { computed } from 'vue'
   import { Check, Plus, Rank, VideoPlay } from '@element-plus/icons-vue'
+  import ProtocolAttachmentReadonly from '@/views/cell-storage/protocol/ProtocolAttachmentReadonly.vue'
+  import { downloadAttachmentFile } from '@/utils/download-attachment'
   import { useTaskStepEditor } from './useTaskStepEditor'
 
-  defineProps<{
+  const props = defineProps<{
     editor: ReturnType<typeof useTaskStepEditor>
     readonly?: boolean
   }>()
+
+  const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
+
+  function sopAttachmentFullUrl(url: string) {
+    const u = String(url || '').trim()
+    if (!u) return ''
+    if (/^https?:\/\//i.test(u)) return u
+    return minioBaseUrl ? `${minioBaseUrl}/${u.replace(/^\//, '')}` : u
+  }
+
+  function attachmentsFromUrls(urls: string, sopId: string): Array<Record<string, unknown>> {
+    return urls
+      .split(',')
+      .map((u) => u.trim())
+      .filter(Boolean)
+      .map((url, i) => ({
+        id: `${sopId}_url_${i}`,
+        fileName: decodeURIComponent(url.split('/').pop() || `附件${i + 1}`),
+        fileUrl: url,
+        fileType: ''
+      }))
+  }
+
+  const linkedSopAttachmentFiles = computed(() => {
+    const ids = props.editor.sopIdsArr.value
+    if (!ids?.length) return []
+    const idSet = new Set(ids)
+    const files: Array<Record<string, unknown>> = []
+    for (const sop of props.editor.sopOptions.value) {
+      if (!idSet.has(sop.id)) continue
+      if (sop.attachmentFiles?.length) {
+        for (const f of sop.attachmentFiles) {
+          if (!f?.fileUrl) continue
+          files.push({
+            id: f.id || `${sop.id}_${f.fileUrl}`,
+            fileName:
+              f.fileName ||
+              decodeURIComponent(String(f.fileUrl).split('/').pop() || '文档'),
+            fileUrl: f.fileUrl,
+            fileType: f.fileType || ''
+          })
+        }
+      } else if (sop.sopAttachments) {
+        files.push(...attachmentsFromUrls(sop.sopAttachments, sop.id))
+      }
+    }
+    return files
+  })
+
+  function handleSopAttachmentPreview(file: Record<string, unknown>) {
+    const url = sopAttachmentFullUrl(String(file.fileUrl || ''))
+    if (url) window.open(url, '_blank')
+  }
+
+  function handleSopAttachmentDownload(file: Record<string, unknown>) {
+    downloadAttachmentFile(file, { fullFileUrl: sopAttachmentFullUrl })
+  }
 </script>
 
 <style scoped lang="scss">
@@ -326,4 +396,58 @@
     gap: 8px;
     margin-left: 12px;
   }
+
+  /* 4 列栅格下各占半行(2/4 列) */
+  .form-grid-4 .step-form-col-half {
+    grid-column: span 2;
+  }
+
+  .step-form-col-full {
+    grid-column: 1 / -1;
+  }
+
+  .step-sop-attachment-row {
+    margin-top: -4px;
+
+    :deep(.attachment-list) {
+      gap: 8px;
+    }
+
+    :deep(.attachment-item) {
+      width: 100px;
+    }
+
+    :deep(.attachment-preview) {
+      height: 68px;
+    }
+
+    :deep(.attachment-preview__icon) {
+      width: 36px;
+      height: 36px;
+
+      .el-icon {
+        font-size: 22px !important;
+      }
+    }
+
+    :deep(.attachment-footer) {
+      padding: 4px 6px;
+    }
+
+    :deep(.attachment-name) {
+      font-size: 10px;
+      min-height: calc(10px * 1.4 * 2);
+    }
+
+    :deep(.attachment-actions) {
+      margin-top: 2px;
+      gap: 6px;
+    }
+
+    :deep(.action-btn) {
+      width: 20px;
+      height: 20px;
+      font-size: 11px;
+    }
+  }
 </style>

+ 9 - 3
web-admin/src/views/quality-inspection/lab-test-item/index.vue

@@ -84,7 +84,7 @@
 
       <el-dialog v-model="dialogVisible" :title="dialogTitle" width="840px" class="dialog-form-layout" align-center destroy-on-close :close-on-click-modal="false">
         <el-form ref="formRef" :model="formData" :rules="formRules" label-position="top" class="biz-dialog-form">
-          <div class="form-grid-2">
+          <div class="form-grid-3">
             <el-form-item label="项目编码" prop="itemCode">
               <el-input v-model="formData.itemCode" :disabled="isEdit" />
             </el-form-item>
@@ -97,10 +97,12 @@
                 <el-radio value="0">停用</el-radio>
               </el-radio-group>
             </el-form-item>
-            <el-form-item label="描述" class="form-col-span-2">
+          </div>
+          <div class="form-grid-2">
+            <el-form-item label="描述">
               <el-input v-model="formData.description" type="textarea" :rows="2" />
             </el-form-item>
-            <el-form-item label="备注" class="form-col-span-2">
+            <el-form-item label="备注">
               <el-input v-model="formData.remark" type="textarea" :rows="2" />
             </el-form-item>
           </div>
@@ -371,6 +373,10 @@
 </script>
 
 <style scoped lang="scss">
+  .biz-dialog-form .form-grid-3 + .form-grid-2 {
+    margin-top: 12px;
+  }
+
   .form-section {
     background: #fafafa;
     border-radius: 8px;

+ 9 - 5
web-admin/src/views/quality-inspection/sample-reception/index.vue

@@ -220,19 +220,19 @@
       >
         <div v-if="currentRow" class="sample-reception-readonly">
           <h4 class="detail-section-title">样本信息</h4>
-          <div class="readonly-grid form-grid-2">
+          <div class="readonly-grid form-grid-3">
             <div class="readonly-item"><span class="label">协议号</span>{{ currentRow.protocolNo || '-' }}</div>
             <div class="readonly-item"><span class="label">样本编码</span>{{ currentRow.sampleCode || '-' }}</div>
             <div class="readonly-item"><span class="label">样本类型</span>{{ currentRow.sampleTypeName || '-' }}</div>
             <div class="readonly-item"><span class="label">预计到达</span>{{ currentRow.etaTime || '-' }}</div>
-            <div class="readonly-item form-col-span-2">
+            <div class="readonly-item readonly-col-span-2">
               <span class="label">客户</span>{{ currentRow.motherName || '-' }} / {{ currentRow.newbornName || '-' }}
             </div>
           </div>
         </div>
         <div v-if="currentRow" class="form-section-divider" />
         <h4 class="detail-section-title">运输信息</h4>
-        <div class="readonly-grid form-grid-2">
+        <div class="readonly-grid form-grid-3">
           <div class="readonly-item">
             <span class="label">运输方式</span>{{ transportTypeLabel(String(currentRow?.transportType || '')) }}
           </div>
@@ -255,7 +255,7 @@
           label-position="top"
           class="biz-dialog-form"
         >
-          <div class="form-grid-2">
+          <div class="form-grid-3">
             <el-form-item label="样本接收时间" prop="receptionTime">
               <el-date-picker
                 v-model="receiveForm.receptionTime"
@@ -453,7 +453,7 @@
               <div class="readonly-item"><span class="label">运输方式</span>{{ entry.transportType === 'kdys' ? '快递运输' : entry.transportType === 'ryxd' ? '人员携带' : '-' }}</div>
             </div>
             <el-form label-position="top" class="biz-dialog-form">
-              <div class="form-grid-2">
+              <div class="form-grid-3">
                 <el-form-item label="样本接收时间" required>
                   <el-date-picker
                     v-model="entry.receptionTime"
@@ -1224,6 +1224,10 @@
       }
     }
 
+    .readonly-grid.form-grid-3 .readonly-item.readonly-col-span-2 {
+      grid-column: span 2;
+    }
+
     .check-list {
       display: flex;
       flex-wrap: wrap;

+ 47 - 43
web-admin/src/views/quality-inspection/third-party-company/index.vue

@@ -101,60 +101,60 @@
       <el-dialog
         v-model="companyDialogVisible"
         :title="companyDialogTitle"
-        width="720px"
+        width="920px"
         class="dialog-form-layout"
         align-center
         destroy-on-close
         :close-on-click-modal="false"
       >
         <el-form ref="companyFormRef" :model="companyForm" :rules="companyFormRules" label-position="top" class="biz-dialog-form">
-          <div class="form-grid-2">
-              <el-form-item label="公司编码" prop="companyCode">
-                <el-input v-model="companyForm.companyCode" :disabled="companyIsEdit" placeholder="租户内唯一" />
-              </el-form-item>
-              <el-form-item label="公司名称" prop="companyName">
-                <el-input v-model="companyForm.companyName" />
-              </el-form-item>
-              <el-form-item label="联系人">
-                <el-input v-model="companyForm.contactName" />
-              </el-form-item>
-              <el-form-item label="联系电话">
-                <el-input v-model="companyForm.contactPhone" />
+          <div class="form-grid-3">
+            <el-form-item label="公司编码" prop="companyCode">
+              <el-input v-model="companyForm.companyCode" :disabled="companyIsEdit" placeholder="租户内唯一" />
+            </el-form-item>
+            <el-form-item label="公司名称" prop="companyName">
+              <el-input v-model="companyForm.companyName" />
+            </el-form-item>
+            <el-form-item label="联系人">
+              <el-input v-model="companyForm.contactName" />
+            </el-form-item>
+            <el-form-item label="联系电话">
+              <el-input v-model="companyForm.contactPhone" />
+            </el-form-item>
+            <el-form-item label="地址" class="form-col-span-2-in-grid3">
+              <el-input v-model="companyForm.address" />
+            </el-form-item>
+          </div>
+          <div class="form-section">
+            <div class="section-title section-title--static">
+              <div class="section-title-left"><h3>开票信息</h3></div>
+            </div>
+            <div class="form-grid-3">
+              <el-form-item label="发票抬头">
+                <el-input v-model="companyForm.invoiceTitle" placeholder="与营业执照一致" />
               </el-form-item>
-              <el-form-item label="地址" class="form-col-span-2">
-                <el-input v-model="companyForm.address" />
+              <el-form-item label="纳税人识别号">
+                <el-input v-model="companyForm.taxId" placeholder="统一社会信用代码" />
               </el-form-item>
-            </div>
-            <div class="form-section">
-              <div class="section-title section-title--static">
-                <div class="section-title-left"><h3>开票信息</h3></div>
-              </div>
-              <div class="form-grid-2">
-                <el-form-item label="发票抬头">
-                  <el-input v-model="companyForm.invoiceTitle" placeholder="与营业执照一致" />
-                </el-form-item>
-                <el-form-item label="纳税人识别号">
-                  <el-input v-model="companyForm.taxId" placeholder="统一社会信用代码" />
-                </el-form-item>
-                <el-form-item label="开户银行">
-                  <el-input v-model="companyForm.bankName" placeholder="收款银行全称" />
-                </el-form-item>
-                <el-form-item label="银行账号">
-                  <el-input v-model="companyForm.bankAccount" placeholder="收款账号" />
-                </el-form-item>
-              </div>
-            </div>
-            <div class="form-grid-2">
-              <el-form-item label="状态" prop="status">
-                <el-radio-group v-model="companyForm.status">
-                  <el-radio value="1">启用</el-radio>
-                  <el-radio value="0">停用</el-radio>
-                </el-radio-group>
+              <el-form-item label="开户银行">
+                <el-input v-model="companyForm.bankName" placeholder="收款银行全称" />
               </el-form-item>
-              <el-form-item label="备注" class="form-col-span-2">
-                <el-input v-model="companyForm.remark" type="textarea" :rows="2" />
+              <el-form-item label="银行账号">
+                <el-input v-model="companyForm.bankAccount" placeholder="收款账号" />
               </el-form-item>
             </div>
+          </div>
+          <div class="form-grid-3">
+            <el-form-item label="状态" prop="status">
+              <el-radio-group v-model="companyForm.status">
+                <el-radio value="1">启用</el-radio>
+                <el-radio value="0">停用</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="备注" class="form-col-span-2">
+              <el-input v-model="companyForm.remark" type="textarea" :rows="2" />
+            </el-form-item>
+          </div>
         </el-form>
         <template #footer>
           <el-button class="btn-ghost" @click="companyDialogVisible = false">
@@ -876,6 +876,10 @@
       width: 100%;
     }
   }
+
+  .form-grid-3 .form-col-span-2-in-grid3 {
+    grid-column: span 2;
+  }
 </style>
 
 <!-- 检测项目配置弹窗 teleport 到 body,尺寸类名需非 scoped -->

+ 14 - 2
web-admin/src/views/research-donation/list/DonationFormDialog.vue

@@ -39,7 +39,7 @@
             <div class="section-title">
               <div class="section-title-left"><h3>基本信息</h3></div>
             </div>
-            <div class="form-grid">
+            <div class="form-grid form-grid--col4">
               <el-form-item label="捐赠协议号" prop="donationNoId">
                 <el-select
                   v-model="formData.donationNoId"
@@ -444,7 +444,7 @@
                   </el-button>
                 </div>
               </div>
-              <div class="form-grid">
+              <div class="form-grid form-grid--col4">
                 <el-form-item label="样本类型">
                   <el-select
                     v-model="row.sampleTypeId"
@@ -2413,6 +2413,14 @@
     &--col2 {
       grid-template-columns: repeat(2, 1fr);
     }
+
+    &--col4 {
+      grid-template-columns: repeat(4, minmax(0, 1fr));
+    }
+
+    :deep(.el-form-item) {
+      margin-bottom: 0;
+    }
   }
 
   .form-col-span-2 {
@@ -2804,6 +2812,10 @@
       &--col2 {
         grid-template-columns: 1fr;
       }
+
+      &--col4 {
+        grid-template-columns: repeat(2, minmax(0, 1fr));
+      }
     }
 
     .client-sub-cards {

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio