zlwu 5 месяцев назад
Родитель
Сommit
ad0e849f6d
50 измененных файлов с 1437 добавлено и 538 удалено
  1. 0 0
      backend/app/graphify-out/cache/ast/56fb414d0ff79ad16d1459096dc3c2266bb5f4b50b0a33e1bd9df1a385c99b74.json
  2. 1 1
      backend/graphify-out/GRAPH_REPORT.md
  3. 1 1
      backend/graphify-out/manifest.json
  4. 316 311
      web-admin/graphify-out/.graphify_analysis.json
  5. 228 103
      web-admin/graphify-out/.graphify_extract.json
  6. 76 67
      web-admin/graphify-out/GRAPH_REPORT.md
  7. 1 1
      web-admin/graphify-out/graph.html
  8. 20 18
      web-admin/graphify-out/manifest.json
  9. 9 0
      web-admin/src/assets/styles/core/app.scss
  10. 510 0
      web-admin/src/components/business/dialog-detail-skeleton/index.vue
  11. 51 0
      web-admin/src/components/business/dialog-form-body/index.vue
  12. 15 1
      web-admin/src/components/business/product-view-dialog/index.vue
  13. 3 0
      web-admin/src/components/business/qc-task-view-dialog/index.vue
  14. 26 2
      web-admin/src/components/business/system-view-dialog/index.vue
  15. 1 0
      web-admin/src/graphify-out/cache/ast/0919ab69176db00458cf11536e9f8f25b2d54aa04f359f946ce636de60092601.json
  16. 1 0
      web-admin/src/graphify-out/cache/ast/17d8a1f2402be017c5961cb14a65047fa5fda5d96c5a09bb5b3f504fafa080d2.json
  17. 0 0
      web-admin/src/graphify-out/cache/ast/1f74926c9e404a89d65f46551cac7b6dcd616f7fd9f2e5ac7eb01b7ba804e379.json
  18. 0 0
      web-admin/src/graphify-out/cache/ast/2953db43f80227c3f747c6dad2bb29c4976e5f0df6612463157c0d48ac55babf.json
  19. 1 0
      web-admin/src/graphify-out/cache/ast/339d0710ab237338786084a3e8483576eaea779abb55b2054a5ff8a60d1d762f.json
  20. 0 0
      web-admin/src/graphify-out/cache/ast/354985acb33d1e79536ed4fb76e7c5455da04f2d88c4938699e66f5b26190067.json
  21. 0 0
      web-admin/src/graphify-out/cache/ast/3583b874d55d884f105055592985af56864249973048d03fecc341c9a0540b8b.json
  22. 1 0
      web-admin/src/graphify-out/cache/ast/3c9248b54b07a7801b64b40937010518cfd6231cbb2cce35c1d59c4be40f4191.json
  23. 1 0
      web-admin/src/graphify-out/cache/ast/40c0e7c04a69c4955fab4dc9df0e02b97ac35cb6f5b1c8b186cd35e22a36ddc8.json
  24. 0 0
      web-admin/src/graphify-out/cache/ast/52f8cbd7cca8c5542f13970ba488e99bd75e1a7d9114ed5e5749e45be3219db1.json
  25. 1 0
      web-admin/src/graphify-out/cache/ast/565374128500ab8c49352ac99f35803e7d08203bcff6251f0ccbcb168e8c3c71.json
  26. 0 0
      web-admin/src/graphify-out/cache/ast/645a103eecc5442a1ada78f0965864ba910c200f667a2631169488dfe100e4f4.json
  27. 1 0
      web-admin/src/graphify-out/cache/ast/7111ff0b9b4da9c5fbd20b998a47c487396195f713066cd426a3069d98ae1804.json
  28. 1 0
      web-admin/src/graphify-out/cache/ast/79925f27f5933010fea441da38186aadbeaad7ca729bdc4d90f2dfadaf85b3ba.json
  29. 0 0
      web-admin/src/graphify-out/cache/ast/ad7f20a4d2055d6b668b4a69dd6bb35c872c781200d875d633f4e3b04ee1fd56.json
  30. 1 0
      web-admin/src/graphify-out/cache/ast/b0eeff970a7af24343cf94818e329ce3c95565c947add7c74e53ef47ea1cd5d5.json
  31. 0 0
      web-admin/src/graphify-out/cache/ast/baae1d55be47abbdefb02d02e678ce0b24abb5d9bdfdda40e4aff1c0a2e06b9a.json
  32. 1 0
      web-admin/src/graphify-out/cache/ast/c3c79edf307610be976047b829cb40a3d0db48f93f7319e5ba9f67d110bb8931.json
  33. 0 0
      web-admin/src/graphify-out/cache/ast/cc8cc6104481bad9e09af21635ae0a9567654e167f8e9cdfd9a7f4200c71d8d1.json
  34. 1 0
      web-admin/src/graphify-out/cache/ast/da6ef240dfb6fe3b55e3237a3779644ceb884765c2db09c74697447d419c971f.json
  35. 0 0
      web-admin/src/graphify-out/cache/ast/e430e4f0e5d4ca4152b681bd350e2203569138d9240ad0659ffe2e17d93607fe.json
  36. 9 2
      web-admin/src/views/cell-storage/protocol/ProtocolFormDialog.vue
  37. 21 3
      web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue
  38. 10 1
      web-admin/src/views/pilot-service/channel/ChannelViewDialog.vue
  39. 3 2
      web-admin/src/views/production/process-route/index.vue
  40. 10 1
      web-admin/src/views/quality-inspection/qc-task/index.vue
  41. 14 0
      web-admin/src/views/quality-inspection/sample-reception/index.vue
  42. 9 2
      web-admin/src/views/research-donation/list/DonationFormDialog.vue
  43. 15 3
      web-admin/src/views/research-donation/list/DonationViewDialog.vue
  44. 29 4
      web-admin/src/views/system/order/index.vue
  45. 9 2
      web-admin/src/views/system/post/index.vue
  46. 9 6
      web-admin/src/views/system/role/index.vue
  47. 4 1
      web-admin/src/views/system/workflow/components/ApprovalDialog.vue
  48. 9 2
      web-admin/src/views/system/workflow/my-applications/index.vue
  49. 9 2
      web-admin/src/views/system/workflow/task-completed/index.vue
  50. 9 2
      web-admin/src/views/system/workflow/task-pending/index.vue

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


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

@@ -1,7 +1,7 @@
 # Graph Report - app  (2026-05-21)
 
 ## Corpus Check
-- 135 files · ~117,792 words
+- 135 files · ~117,795 words
 - Verdict: corpus is large enough that graph structure adds value.
 
 ## Summary

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

@@ -59,7 +59,7 @@
   "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/product_review.py": 1778578258.098316,
   "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/category.py": 1778578258.0941947,
   "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/leave.py": 1778666088.7680702,
-  "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py": 1779331765.6469367,
+  "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py": 1779332373.211639,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/process_route.py": 1779265696.2771301,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/sample_code.py": 1779279460.690872,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task_result.py": 1779331731.6720955,

+ 316 - 311
web-admin/graphify-out/.graphify_analysis.json

@@ -168,8 +168,33 @@
       "api_admin_fetchuploaduseravatar",
       "api_admin_fetchvoidchannelagreement",
       "api_admin_ts",
+      "api_cell_product_fetchcellproductdetail",
+      "api_cell_product_fetchcellproductlist",
+      "api_cell_product_fetchcreatecellproduct",
+      "api_cell_product_fetchdeletecellproduct",
+      "api_cell_product_fetchupdatecellproduct",
+      "api_cell_product_ts",
       "api_chinaregion_fetchchinaregiontree",
       "api_chinaregion_ts",
+      "api_collection_protocol_fetchcollectionprotocolauditlist",
+      "api_collection_protocol_fetchcollectionprotocoldetail",
+      "api_collection_protocol_fetchcollectionprotocollist",
+      "api_collection_protocol_fetchcreatecollectionprotocol",
+      "api_collection_protocol_fetchdeletecollectionprotocol",
+      "api_collection_protocol_fetchpaymentdetaillist",
+      "api_collection_protocol_fetchreceiptconfirmedlist",
+      "api_collection_protocol_fetchreceiptconfirmlist",
+      "api_collection_protocol_fetchreceiptconfirmsubmit",
+      "api_collection_protocol_fetchrefundconfirmedlist",
+      "api_collection_protocol_fetchrefundconfirmlist",
+      "api_collection_protocol_fetchrefundconfirmsubmit",
+      "api_collection_protocol_fetchsubmitprotocolaudit",
+      "api_collection_protocol_fetchsubmitreceiptfinance",
+      "api_collection_protocol_fetchsubmitreceiptfinancebatch",
+      "api_collection_protocol_fetchsubmitrefundfinance",
+      "api_collection_protocol_fetchsubmitrefundfinancebatch",
+      "api_collection_protocol_fetchupdatecollectionprotocol",
+      "api_collection_protocol_ts",
       "api_production_work_order_fetchcompleteproductionworkorder",
       "api_production_work_order_fetchproductionworkorderdetail",
       "api_production_work_order_fetchproductionworkorderlist",
@@ -183,12 +208,6 @@
       "api_qc_task_result_fetchqctaskmaterialusages",
       "api_qc_task_result_fetchqctaskresults",
       "api_qc_task_result_ts",
-      "api_qc_third_party_company_fetchcreateqcthirdpartycompany",
-      "api_qc_third_party_company_fetchdeleteqcthirdpartycompany",
-      "api_qc_third_party_company_fetchqcthirdpartycompanylist",
-      "api_qc_third_party_company_fetchqcthirdpartycompanyoptions",
-      "api_qc_third_party_company_fetchupdateqcthirdpartycompany",
-      "api_qc_third_party_company_ts",
       "api_qc_work_order_fetchconfigureqcworkorder",
       "api_qc_work_order_fetchqcworkorderdetail",
       "api_qc_work_order_fetchqcworkorderlist",
@@ -199,18 +218,6 @@
       "api_sample_reception_fetchsamplereceptionsubmit",
       "api_sample_reception_fetchsamplereceptionupdate",
       "api_sample_reception_ts",
-      "api_system_fetchcreatecomment",
-      "api_system_fetchcreateupgradelog",
-      "api_system_fetchdeletecomment",
-      "api_system_fetchdeleteupgradelog",
-      "api_system_fetchgetallupgradelogs",
-      "api_system_fetchgetchinamapdata",
-      "api_system_fetchgetcomments",
-      "api_system_fetchgetcurrentversion",
-      "api_system_fetchgetsystemconfig",
-      "api_system_fetchgetupgradelogs",
-      "api_system_fetchupdateupgradelog",
-      "api_system_ts",
       "modules_user_dialog_initformdata",
       "modules_user_dialog_loadrolelist",
       "process_route_index_get",
@@ -381,63 +388,6 @@
       "utils_router_ts"
     ],
     "3": [
-      "api_system_fetchgetlatestupgradelog",
-      "locales_index_getdefaultlanguage",
-      "locales_index_ts",
-      "storage_storage_checkstoragecompatibility",
-      "storage_storage_config_storageconfig",
-      "storage_storage_config_storageconfig_createcurrentversionpattern",
-      "storage_storage_config_storageconfig_createkeypattern",
-      "storage_storage_config_storageconfig_createversionpattern",
-      "storage_storage_config_storageconfig_extractstoreidfromkey",
-      "storage_storage_config_storageconfig_extractversionfromkey",
-      "storage_storage_config_storageconfig_generatelegacykey",
-      "storage_storage_config_storageconfig_generatestoragekey",
-      "storage_storage_config_storageconfig_iscurrentversionkey",
-      "storage_storage_config_storageconfig_isversionedkey",
-      "storage_storage_getsystemstorage",
-      "storage_storage_getsysversion",
-      "storage_storage_key_manager_storagekeymanager",
-      "storage_storage_key_manager_storagekeymanager_findexistingkey",
-      "storage_storage_key_manager_storagekeymanager_getcurrentversionkey",
-      "storage_storage_key_manager_storagekeymanager_getstoragekey",
-      "storage_storage_key_manager_storagekeymanager_hascurrentversiondata",
-      "storage_storage_key_manager_storagekeymanager_migratedata",
-      "storage_storage_storagecompatibilitymanager",
-      "storage_storage_storagecompatibilitymanager_checkcompatibility",
-      "storage_storage_storagecompatibilitymanager_getlegacystoragedata",
-      "storage_storage_storagecompatibilitymanager_getsystemstorage",
-      "storage_storage_storagecompatibilitymanager_getsystemversion",
-      "storage_storage_storagecompatibilitymanager_handlestorageerror",
-      "storage_storage_storagecompatibilitymanager_hasanyversionstorage",
-      "storage_storage_storagecompatibilitymanager_hascurrentversionstorage",
-      "storage_storage_storagecompatibilitymanager_isstorageempty",
-      "storage_storage_storagecompatibilitymanager_performsystemlogout",
-      "storage_storage_storagecompatibilitymanager_showstorageerror",
-      "storage_storage_storagecompatibilitymanager_validatestoragedata",
-      "storage_storage_validatestoragedata",
-      "sys_upgrade_systemupgrade",
-      "sys_upgrade_versionmanager",
-      "sys_upgrade_versionmanager_buildupgrademessage",
-      "sys_upgrade_versionmanager_cleanuplegacydata",
-      "sys_upgrade_versionmanager_executeupgrade",
-      "sys_upgrade_versionmanager_findlegacystorage",
-      "sys_upgrade_versionmanager_getstoredversion",
-      "sys_upgrade_versionmanager_isfirstvisit",
-      "sys_upgrade_versionmanager_issameversion",
-      "sys_upgrade_versionmanager_normalizeversion",
-      "sys_upgrade_versionmanager_performlogout",
-      "sys_upgrade_versionmanager_processupgrade",
-      "sys_upgrade_versionmanager_setstoredversion",
-      "sys_upgrade_versionmanager_shouldrequirerelogin",
-      "sys_upgrade_versionmanager_shouldskipupgrade",
-      "sys_upgrade_versionmanager_showupgradenotification",
-      "utils_storage_storage_config_ts",
-      "utils_storage_storage_key_manager_ts",
-      "utils_storage_storage_ts",
-      "utils_sys_upgrade_ts"
-    ],
-    "4": [
       "api_production_fetchaddroutestep",
       "api_production_fetchallconsumablepacks",
       "api_production_fetchallconsumables",
@@ -492,7 +442,7 @@
       "api_production_fetchupdatestepreagents",
       "api_production_ts"
     ],
-    "5": [
+    "4": [
       "api_admin_fetchdepttree",
       "api_admin_fetchpostbounduserids",
       "art_fireworks_effect_index_fireworksystem",
@@ -507,6 +457,7 @@
       "clean_room_index_formatuserlabel",
       "clean_room_index_loaddeptdata",
       "clean_room_index_onpersonselectchange",
+      "components_business_dialog_form_body_index_vue",
       "components_core_layouts_art_fireworks_effect_index_vue",
       "components_core_layouts_art_page_content_index_vue",
       "http_index_subscribetokenrefresh",
@@ -545,7 +496,7 @@
       "views_system_post_index_vue",
       "views_system_role_index_vue"
     ],
-    "6": [
+    "5": [
       "api_admin_fetchaccessloglist",
       "api_admin_fetchcleanroomlist",
       "api_admin_fetchcreateaccesslog",
@@ -598,7 +549,7 @@
       "views_operations_clean_room_index_vue",
       "views_system_site_index_vue"
     ],
-    "7": [
+    "6": [
       "components_core_layouts_art_settings_panel_widget_settingitem_vue",
       "modules_user_search_handlereset",
       "modules_user_search_handlesearch",
@@ -648,6 +599,55 @@
       "widget_settingitem_emit",
       "widget_settingitem_if"
     ],
+    "7": [
+      "category_index_catch",
+      "category_index_collectids",
+      "category_index_handlecategorytableselectionchange",
+      "category_index_handleview",
+      "components_core_layouts_art_settings_panel_composables_usesettingshandlers_ts",
+      "components_core_layouts_art_settings_panel_composables_usesettingspanel_ts",
+      "components_core_layouts_art_settings_panel_composables_usesettingsstate_ts",
+      "composables_usesettingshandlers_usesettingshandlers",
+      "composables_usesettingspanel_usesettingspanel",
+      "composables_usesettingsstate_usesettingsstate",
+      "core_componentloader_componentloader",
+      "core_componentloader_componentloader_constructor",
+      "core_componentloader_componentloader_createemptycomponent",
+      "core_componentloader_componentloader_createerrorcomponent",
+      "core_componentloader_componentloader_load",
+      "core_componentloader_componentloader_loadiframe",
+      "core_componentloader_componentloader_loadlayout",
+      "core_iframeroutemanager_iframeroutemanager_add",
+      "core_iframeroutemanager_iframeroutemanager_load",
+      "core_routepermissionvalidator_routepermissionvalidator",
+      "core_routepermissionvalidator_routepermissionvalidator_buildmenupathset",
+      "core_routepermissionvalidator_routepermissionvalidator_checkpathprefix",
+      "core_routepermissionvalidator_routepermissionvalidator_haspermission",
+      "core_routepermissionvalidator_routepermissionvalidator_isdynamicroutematch",
+      "core_routepermissionvalidator_routepermissionvalidator_matchroute",
+      "core_routepermissionvalidator_routepermissionvalidator_validatepath",
+      "core_routetransformer_routetransformer",
+      "core_routetransformer_routetransformer_extractfirstsegment",
+      "core_routetransformer_routetransformer_handlefirstlevelroute",
+      "core_routetransformer_routetransformer_handleiframeroute",
+      "core_routetransformer_routetransformer_handlenormalroute",
+      "core_routetransformer_routetransformer_isfirstlevelroute",
+      "core_routetransformer_routetransformer_transform",
+      "core_routevalidator_routevalidator_loglayouterror",
+      "core_useceremony_useceremony",
+      "core_usetheme_initializetheme",
+      "core_usetheme_usetheme",
+      "hooks_core_useceremony_ts",
+      "hooks_core_usetheme_ts",
+      "router_core_componentloader_ts",
+      "router_core_routepermissionvalidator_ts",
+      "router_core_routetransformer_ts",
+      "ui_animation_themeanimation",
+      "ui_animation_toggletheme",
+      "ui_animation_toggletransition",
+      "utils_ui_animation_ts",
+      "views_system_category_index_vue"
+    ],
     "8": [
       "art_screen_lock_index_tologin",
       "http_crypto_base64tobytes",
@@ -690,41 +690,42 @@
       "utils_http_index_ts"
     ],
     "9": [
-      "api_cell_product_fetchcellproductdetail",
-      "api_cell_product_fetchcellproductlist",
-      "api_cell_product_fetchcreatecellproduct",
-      "api_cell_product_fetchdeletecellproduct",
-      "api_cell_product_fetchupdatecellproduct",
-      "api_cell_product_ts",
-      "api_leave_fetchcreateleave",
-      "api_leave_fetchdeleteleave",
-      "api_leave_fetchleavedetail",
-      "api_leave_fetchleavelist",
-      "api_leave_fetchsubmitleave",
-      "api_leave_fetchupdateleave",
-      "api_leave_ts",
-      "api_qc_material_fetchbatchstockinqcmaterial",
-      "api_qc_material_fetchbatchstockoutqcmaterial",
-      "api_qc_material_fetchcreateqcmaterial",
-      "api_qc_material_fetchdeleteqcmaterial",
-      "api_qc_material_fetchqcmateriallist",
-      "api_qc_material_fetchqcmaterialoptions",
-      "api_qc_material_fetchstockinqcmaterial",
-      "api_qc_material_fetchstockoutqcmaterial",
-      "api_qc_material_fetchupdateqcmaterial",
-      "api_qc_material_ts",
-      "api_research_donation_fetchbatchdeleteresearchdonation",
-      "api_research_donation_fetchconvertfromstorage",
-      "api_research_donation_fetchconverttostorage",
-      "api_research_donation_fetchcreateresearchdonation",
-      "api_research_donation_fetchdeleteresearchdonation",
-      "api_research_donation_fetchdonationstatuschange",
-      "api_research_donation_fetchresearchdonationauditlist",
-      "api_research_donation_fetchresearchdonationdetail",
-      "api_research_donation_fetchresearchdonationlist",
-      "api_research_donation_fetchsubmitdonationaudit",
-      "api_research_donation_fetchupdateresearchdonation",
-      "api_research_donation_ts"
+      "locales_index_getdefaultlanguage",
+      "locales_index_ts",
+      "storage_storage_checkstoragecompatibility",
+      "storage_storage_config_storageconfig",
+      "storage_storage_config_storageconfig_createcurrentversionpattern",
+      "storage_storage_config_storageconfig_createkeypattern",
+      "storage_storage_config_storageconfig_createversionpattern",
+      "storage_storage_config_storageconfig_extractstoreidfromkey",
+      "storage_storage_config_storageconfig_extractversionfromkey",
+      "storage_storage_config_storageconfig_generatelegacykey",
+      "storage_storage_config_storageconfig_iscurrentversionkey",
+      "storage_storage_config_storageconfig_isversionedkey",
+      "storage_storage_getsystemstorage",
+      "storage_storage_getsysversion",
+      "storage_storage_key_manager_storagekeymanager",
+      "storage_storage_key_manager_storagekeymanager_findexistingkey",
+      "storage_storage_key_manager_storagekeymanager_getcurrentversionkey",
+      "storage_storage_key_manager_storagekeymanager_getstoragekey",
+      "storage_storage_key_manager_storagekeymanager_hascurrentversiondata",
+      "storage_storage_key_manager_storagekeymanager_migratedata",
+      "storage_storage_storagecompatibilitymanager",
+      "storage_storage_storagecompatibilitymanager_checkcompatibility",
+      "storage_storage_storagecompatibilitymanager_getlegacystoragedata",
+      "storage_storage_storagecompatibilitymanager_getsystemstorage",
+      "storage_storage_storagecompatibilitymanager_getsystemversion",
+      "storage_storage_storagecompatibilitymanager_handlestorageerror",
+      "storage_storage_storagecompatibilitymanager_hasanyversionstorage",
+      "storage_storage_storagecompatibilitymanager_hascurrentversionstorage",
+      "storage_storage_storagecompatibilitymanager_isstorageempty",
+      "storage_storage_storagecompatibilitymanager_performsystemlogout",
+      "storage_storage_storagecompatibilitymanager_showstorageerror",
+      "storage_storage_storagecompatibilitymanager_validatestoragedata",
+      "storage_storage_validatestoragedata",
+      "utils_storage_storage_config_ts",
+      "utils_storage_storage_key_manager_ts",
+      "utils_storage_storage_ts"
     ],
     "10": [
       "api_business_fetchadjustorder",
@@ -761,44 +762,78 @@
       "views_quality_inspection_qc_task_index_vue"
     ],
     "11": [
-      "category_index_catch",
-      "category_index_collectids",
-      "category_index_handlecategorytableselectionchange",
-      "category_index_handleview",
-      "components_core_layouts_art_settings_panel_composables_usesettingshandlers_ts",
-      "components_core_layouts_art_settings_panel_composables_usesettingspanel_ts",
-      "components_core_layouts_art_settings_panel_composables_usesettingsstate_ts",
-      "composables_usesettingshandlers_usesettingshandlers",
-      "composables_usesettingspanel_usesettingspanel",
-      "composables_usesettingsstate_usesettingsstate",
-      "core_iframeroutemanager_iframeroutemanager_add",
-      "core_routepermissionvalidator_routepermissionvalidator",
-      "core_routepermissionvalidator_routepermissionvalidator_buildmenupathset",
-      "core_routepermissionvalidator_routepermissionvalidator_checkpathprefix",
-      "core_routepermissionvalidator_routepermissionvalidator_haspermission",
-      "core_routepermissionvalidator_routepermissionvalidator_isdynamicroutematch",
-      "core_routepermissionvalidator_routepermissionvalidator_matchroute",
-      "core_routepermissionvalidator_routepermissionvalidator_validatepath",
-      "core_routevalidator_routevalidator_loglayouterror",
-      "core_useceremony_useceremony",
-      "core_usetheme_initializetheme",
-      "core_usetheme_usetheme",
-      "hooks_core_useceremony_ts",
-      "hooks_core_usetheme_ts",
-      "router_core_routepermissionvalidator_ts",
-      "ui_animation_themeanimation",
-      "ui_animation_toggletheme",
-      "ui_animation_toggletransition",
-      "utils_ui_animation_ts",
-      "views_system_category_index_vue"
-    ],
-    "12": [
-      "api_admin_fetchcreatemenu",
-      "api_admin_fetchdeletemenu",
-      "api_admin_fetchmenutree",
-      "api_admin_fetchupdatemenu",
-      "components_business_system_view_dialog_index_vue",
-      "dept_index_get",
+      "api_system_fetchcreatecomment",
+      "api_system_fetchcreateupgradelog",
+      "api_system_fetchdeletecomment",
+      "api_system_fetchdeleteupgradelog",
+      "api_system_fetchgetallupgradelogs",
+      "api_system_fetchgetchinamapdata",
+      "api_system_fetchgetcomments",
+      "api_system_fetchgetcurrentversion",
+      "api_system_fetchgetlatestupgradelog",
+      "api_system_fetchgetsystemconfig",
+      "api_system_fetchgetupgradelogs",
+      "api_system_fetchupdateupgradelog",
+      "api_system_ts",
+      "storage_storage_config_storageconfig_generatestoragekey",
+      "sys_upgrade_systemupgrade",
+      "sys_upgrade_versionmanager",
+      "sys_upgrade_versionmanager_buildupgrademessage",
+      "sys_upgrade_versionmanager_cleanuplegacydata",
+      "sys_upgrade_versionmanager_executeupgrade",
+      "sys_upgrade_versionmanager_findlegacystorage",
+      "sys_upgrade_versionmanager_getstoredversion",
+      "sys_upgrade_versionmanager_isfirstvisit",
+      "sys_upgrade_versionmanager_issameversion",
+      "sys_upgrade_versionmanager_normalizeversion",
+      "sys_upgrade_versionmanager_performlogout",
+      "sys_upgrade_versionmanager_processupgrade",
+      "sys_upgrade_versionmanager_setstoredversion",
+      "sys_upgrade_versionmanager_shouldrequirerelogin",
+      "sys_upgrade_versionmanager_shouldskipupgrade",
+      "sys_upgrade_versionmanager_showupgradenotification",
+      "utils_sys_upgrade_ts"
+    ],
+    "12": [
+      "api_research_donation_fetchbatchdeleteresearchdonation",
+      "api_research_donation_fetchconvertfromstorage",
+      "api_research_donation_fetchconverttostorage",
+      "api_research_donation_fetchcreateresearchdonation",
+      "api_research_donation_fetchdeleteresearchdonation",
+      "api_research_donation_fetchdonationstatuschange",
+      "api_research_donation_fetchresearchdonationauditlist",
+      "api_research_donation_fetchresearchdonationdetail",
+      "api_research_donation_fetchresearchdonationlist",
+      "api_research_donation_fetchsubmitdonationaudit",
+      "api_research_donation_fetchupdateresearchdonation",
+      "api_research_donation_ts",
+      "api_workflow_fetchapprovetask",
+      "api_workflow_fetchbusinessalltasks",
+      "api_workflow_fetchcompletedtasks",
+      "api_workflow_fetchcreateprocessdef",
+      "api_workflow_fetchdeleteprocessdef",
+      "api_workflow_fetchdesignerdata",
+      "api_workflow_fetchinstancedetail",
+      "api_workflow_fetchmyinstances",
+      "api_workflow_fetchpendingtasks",
+      "api_workflow_fetchprocessdeflist",
+      "api_workflow_fetchpublishversion",
+      "api_workflow_fetchrejecttask",
+      "api_workflow_fetchstartinstance",
+      "api_workflow_fetchupdateprocessdef",
+      "api_workflow_fetchversionlist",
+      "api_workflow_ts",
+      "components_approvaldialog_handlesubmit",
+      "components_approvaldialog_loadformdata",
+      "views_system_workflow_components_approvaldialog_vue"
+    ],
+    "13": [
+      "api_admin_fetchcreatemenu",
+      "api_admin_fetchdeletemenu",
+      "api_admin_fetchmenutree",
+      "api_admin_fetchupdatemenu",
+      "components_business_system_view_dialog_index_vue",
+      "dept_index_get",
       "dept_index_postorder",
       "dept_index_preorder",
       "material_index_pad",
@@ -822,7 +857,7 @@
       "views_system_dept_index_vue",
       "views_system_menu_index_vue"
     ],
-    "13": [
+    "14": [
       "api_admin_fetchgetmenulist",
       "api_auth_fetchgetmenucodes",
       "api_auth_fetchlogin",
@@ -851,7 +886,7 @@
       "utils_loginrsa_buildwebencryptedloginbody",
       "utils_loginrsa_ts"
     ],
-    "14": [
+    "15": [
       "api_ethnic_fetchethniclist",
       "api_ethnic_ts",
       "list_donationformdialog_addsample",
@@ -880,7 +915,7 @@
       "views_cell_storage_protocol_protocolformdialog_vue",
       "views_research_donation_list_donationformdialog_vue"
     ],
-    "15": [
+    "16": [
       "api_admin_fetchcollectionregionlist",
       "api_admin_fetchcreatecollectionregion",
       "api_admin_fetchcreatehospital",
@@ -908,7 +943,33 @@
       "collection_region_index_submitregion",
       "views_basic_info_collection_region_index_vue"
     ],
-    "16": [
+    "17": [
+      "components_business_dialog_detail_skeleton_index_vue",
+      "components_business_product_view_dialog_index_vue",
+      "components_business_sample_qc_info_cards_index_vue",
+      "list_donationviewdialog_auditordisplay",
+      "list_donationviewdialog_dash",
+      "list_donationviewdialog_expressname",
+      "list_donationviewdialog_formatmetric",
+      "list_donationviewdialog_formatym",
+      "list_donationviewdialog_formatymd",
+      "list_donationviewdialog_if",
+      "list_donationviewdialog_samplereceptionchecklist",
+      "list_donationviewdialog_sampletypename",
+      "list_donationviewdialog_sitename",
+      "list_donationviewdialog_unitname",
+      "product_view_dialog_index_formatmeasureview",
+      "product_view_dialog_index_iscategoryenabled",
+      "product_view_dialog_index_newbannerclientkey",
+      "product_view_dialog_index_normalizeid",
+      "protocol_protocolattachmentreadonly_resolvefileurl",
+      "protocol_protocolviewdialog_dash",
+      "protocol_protocolviewdialog_handleclosed",
+      "views_cell_storage_protocol_protocolattachmentreadonly_vue",
+      "views_cell_storage_protocol_protocolviewdialog_vue",
+      "views_research_donation_list_donationviewdialog_vue"
+    ],
+    "18": [
       "business_highlight_addcopybutton",
       "business_highlight_highlightcode",
       "business_highlight_insertlinenumbers",
@@ -932,7 +993,7 @@
       "directives_index_setupglobdirectives",
       "directives_index_ts"
     ],
-    "17": [
+    "19": [
       "api_admin_fetchcreateuser",
       "api_admin_fetchdeleteuser",
       "api_admin_fetchresetpassword",
@@ -954,69 +1015,7 @@
       "user_index_resetform",
       "views_system_user_index_vue"
     ],
-    "18": [
-      "api_collection_protocol_fetchcollectionprotocolauditlist",
-      "api_collection_protocol_fetchcollectionprotocoldetail",
-      "api_collection_protocol_fetchcollectionprotocollist",
-      "api_collection_protocol_fetchcreatecollectionprotocol",
-      "api_collection_protocol_fetchdeletecollectionprotocol",
-      "api_collection_protocol_fetchpaymentdetaillist",
-      "api_collection_protocol_fetchreceiptconfirmedlist",
-      "api_collection_protocol_fetchreceiptconfirmlist",
-      "api_collection_protocol_fetchreceiptconfirmsubmit",
-      "api_collection_protocol_fetchrefundconfirmedlist",
-      "api_collection_protocol_fetchrefundconfirmlist",
-      "api_collection_protocol_fetchrefundconfirmsubmit",
-      "api_collection_protocol_fetchsubmitprotocolaudit",
-      "api_collection_protocol_fetchsubmitreceiptfinance",
-      "api_collection_protocol_fetchsubmitreceiptfinancebatch",
-      "api_collection_protocol_fetchsubmitrefundfinance",
-      "api_collection_protocol_fetchsubmitrefundfinancebatch",
-      "api_collection_protocol_fetchupdatecollectionprotocol",
-      "api_collection_protocol_ts"
-    ],
-    "19": [
-      "api_workflow_fetchapprovetask",
-      "api_workflow_fetchbusinessalltasks",
-      "api_workflow_fetchcompletedtasks",
-      "api_workflow_fetchcreateprocessdef",
-      "api_workflow_fetchdeleteprocessdef",
-      "api_workflow_fetchdesignerdata",
-      "api_workflow_fetchinstancedetail",
-      "api_workflow_fetchmyinstances",
-      "api_workflow_fetchpendingtasks",
-      "api_workflow_fetchprocessdeflist",
-      "api_workflow_fetchpublishversion",
-      "api_workflow_fetchrejecttask",
-      "api_workflow_fetchstartinstance",
-      "api_workflow_fetchupdateprocessdef",
-      "api_workflow_fetchversionlist",
-      "api_workflow_ts",
-      "components_approvaldialog_handlesubmit",
-      "components_approvaldialog_loadformdata",
-      "views_system_workflow_components_approvaldialog_vue"
-    ],
     "20": [
-      "components_business_sample_qc_info_cards_index_vue",
-      "list_donationviewdialog_auditordisplay",
-      "list_donationviewdialog_dash",
-      "list_donationviewdialog_expressname",
-      "list_donationviewdialog_formatmetric",
-      "list_donationviewdialog_formatym",
-      "list_donationviewdialog_formatymd",
-      "list_donationviewdialog_if",
-      "list_donationviewdialog_samplereceptionchecklist",
-      "list_donationviewdialog_sampletypename",
-      "list_donationviewdialog_sitename",
-      "list_donationviewdialog_unitname",
-      "protocol_protocolattachmentreadonly_resolvefileurl",
-      "protocol_protocolviewdialog_dash",
-      "protocol_protocolviewdialog_handleclosed",
-      "views_cell_storage_protocol_protocolattachmentreadonly_vue",
-      "views_cell_storage_protocol_protocolviewdialog_vue",
-      "views_research_donation_list_donationviewdialog_vue"
-    ],
-    "21": [
       "core_usetable_usetable",
       "core_usetable_usetableimpl",
       "core_usetablecolumns_getcolumnchecks",
@@ -1035,26 +1034,7 @@
       "table_tableutils_updatepaginationfromresponse",
       "utils_table_tableutils_ts"
     ],
-    "22": [
-      "core_componentloader_componentloader",
-      "core_componentloader_componentloader_constructor",
-      "core_componentloader_componentloader_createemptycomponent",
-      "core_componentloader_componentloader_createerrorcomponent",
-      "core_componentloader_componentloader_load",
-      "core_componentloader_componentloader_loadiframe",
-      "core_componentloader_componentloader_loadlayout",
-      "core_iframeroutemanager_iframeroutemanager_load",
-      "core_routetransformer_routetransformer",
-      "core_routetransformer_routetransformer_extractfirstsegment",
-      "core_routetransformer_routetransformer_handlefirstlevelroute",
-      "core_routetransformer_routetransformer_handleiframeroute",
-      "core_routetransformer_routetransformer_handlenormalroute",
-      "core_routetransformer_routetransformer_isfirstlevelroute",
-      "core_routetransformer_routetransformer_transform",
-      "router_core_componentloader_ts",
-      "router_core_routetransformer_ts"
-    ],
-    "23": [
+    "21": [
       "core_usechart_usechart",
       "core_usechart_usechartcomponent",
       "core_usechart_usechartops",
@@ -1072,7 +1052,7 @@
       "ui_colors_setelementthemecolor",
       "utils_ui_colors_ts"
     ],
-    "24": [
+    "22": [
       "api_admin_fetchcreatecustomer",
       "api_admin_fetchcustomerlist",
       "api_admin_fetchdeletecustomer",
@@ -1090,7 +1070,7 @@
       "customer_index_resetform",
       "views_basic_info_customer_index_vue"
     ],
-    "25": [
+    "23": [
       "api_production_task_fetchaddtaskstep",
       "api_production_task_fetchcreateproductiontask",
       "api_production_task_fetchdeletetaskstep",
@@ -1106,7 +1086,7 @@
       "api_production_task_fetchupdatetaskstep",
       "api_production_task_ts"
     ],
-    "26": [
+    "24": [
       "form_validator_getpasswordstrength",
       "form_validator_trimspaces",
       "form_validator_validateaccount",
@@ -1121,7 +1101,7 @@
       "form_validator_validateurl",
       "utils_form_validator_ts"
     ],
-    "27": [
+    "25": [
       "api_material_fetchbatchstockinmaterial",
       "api_material_fetchbatchstockoutmaterial",
       "api_material_fetchcreatematerial",
@@ -1134,7 +1114,19 @@
       "api_material_fetchupdatematerial",
       "api_material_ts"
     ],
-    "28": [
+    "26": [
+      "api_qc_material_fetchbatchstockinqcmaterial",
+      "api_qc_material_fetchbatchstockoutqcmaterial",
+      "api_qc_material_fetchcreateqcmaterial",
+      "api_qc_material_fetchdeleteqcmaterial",
+      "api_qc_material_fetchqcmateriallist",
+      "api_qc_material_fetchqcmaterialoptions",
+      "api_qc_material_fetchstockinqcmaterial",
+      "api_qc_material_fetchstockoutqcmaterial",
+      "api_qc_material_fetchupdateqcmaterial",
+      "api_qc_material_ts"
+    ],
+    "27": [
       "art_count_to_index_clamp",
       "art_count_to_index_currentvalue",
       "art_count_to_index_formatnumber",
@@ -1145,7 +1137,7 @@
       "art_count_to_index_targetvalue",
       "components_core_text_effect_art_count_to_index_vue"
     ],
-    "29": [
+    "28": [
       "sys_error_handle_isignorablescripterror",
       "sys_error_handle_normalizeerrormessage",
       "sys_error_handle_registerpromiseerrorhandler",
@@ -1155,7 +1147,7 @@
       "sys_error_handle_vueerrorhandler",
       "utils_sys_error_handle_ts"
     ],
-    "30": [
+    "29": [
       "core_usetableheight_tableheightcalculator",
       "core_usetableheight_tableheightcalculator_calculate",
       "core_usetableheight_tableheightcalculator_calculateoffset",
@@ -1165,7 +1157,7 @@
       "core_usetableheight_usetableheight",
       "hooks_core_usetableheight_ts"
     ],
-    "31": [
+    "30": [
       "app_vue",
       "main_ts",
       "src_app_refreshbuttonautoicons",
@@ -1174,6 +1166,15 @@
       "src_main_masksecret",
       "utils_sys_console_ts"
     ],
+    "31": [
+      "api_leave_fetchcreateleave",
+      "api_leave_fetchdeleteleave",
+      "api_leave_fetchleavedetail",
+      "api_leave_fetchleavelist",
+      "api_leave_fetchsubmitleave",
+      "api_leave_fetchupdateleave",
+      "api_leave_ts"
+    ],
     "32": [
       "utils_protocol_payment_labels_formatpaymentamount",
       "utils_protocol_payment_labels_formatpaymentduedate",
@@ -1199,6 +1200,14 @@
       "api_qc_third_party_item_ts"
     ],
     "35": [
+      "api_qc_third_party_company_fetchcreateqcthirdpartycompany",
+      "api_qc_third_party_company_fetchdeleteqcthirdpartycompany",
+      "api_qc_third_party_company_fetchqcthirdpartycompanylist",
+      "api_qc_third_party_company_fetchqcthirdpartycompanyoptions",
+      "api_qc_third_party_company_fetchupdateqcthirdpartycompany",
+      "api_qc_third_party_company_ts"
+    ],
+    "36": [
       "api_qc_lab_test_item_fetchcreateqclabtestitem",
       "api_qc_lab_test_item_fetchdeleteqclabtestitem",
       "api_qc_lab_test_item_fetchqclabtestitemlist",
@@ -1206,7 +1215,7 @@
       "api_qc_lab_test_item_fetchupdateqclabtestitem",
       "api_qc_lab_test_item_ts"
     ],
-    "36": [
+    "37": [
       "api_qc_task_fetchcompleteqctask",
       "api_qc_task_fetchqctaskdetail",
       "api_qc_task_fetchqctasklist",
@@ -1214,7 +1223,7 @@
       "api_qc_task_fetchupdateqctaskattachments",
       "api_qc_task_ts"
     ],
-    "37": [
+    "38": [
       "sample_reception_index_isetatodayorearlier",
       "sample_reception_index_pad",
       "sample_reception_index_parseetalocaldate",
@@ -1222,141 +1231,137 @@
       "sample_reception_index_startoflocalday",
       "views_quality_inspection_sample_reception_index_vue"
     ],
-    "38": [
+    "39": [
       "art_global_search_index_focusinput",
       "art_global_search_index_handlekeydown",
       "art_global_search_index_if",
       "art_global_search_index_search",
       "components_core_layouts_art_global_search_index_vue"
     ],
-    "39": [
+    "40": [
       "art_sidebar_menu_index_if",
       "art_sidebar_menu_index_logsidebarmenu",
       "components_core_layouts_art_menus_art_sidebar_menu_index_vue",
       "components_core_layouts_art_menus_art_sidebar_menu_widget_sidebarsubmenu_vue"
     ],
-    "40": [
+    "41": [
       "config_setting_getsettingdefaults",
       "config_setting_resettodefaults",
       "config_setting_ts"
     ],
-    "41": [
+    "42": [
       "config_modules_component_ts",
       "modules_component_getenabledglobalcomponents",
       "modules_component_getglobalcomponentbykey"
     ],
-    "42": [
+    "43": [
       "utils_user_avatar_defaultavatarpathbyrole",
       "utils_user_avatar_resolveuseravatarurl",
       "utils_user_avatar_ts"
     ],
-    "43": [
+    "44": [
       "utils_status_tag_registry_createlabelgetter",
       "utils_status_tag_registry_createtagtypegetter",
       "utils_status_tag_registry_ts"
     ],
-    "44": [
+    "45": [
       "form_responsive_calculateresponsivespan",
       "form_responsive_createresponsivespancalculator",
       "utils_form_responsive_ts"
     ],
-    "45": [
+    "46": [
       "art_line_chart_index_clearanimationtimers",
       "art_line_chart_index_if",
       "components_core_charts_art_line_chart_index_vue"
     ],
-    "46": [
+    "47": [
       "components_core_layouts_art_header_bar_index_vue",
       "components_core_layouts_art_header_bar_widget_artusermenu_vue",
       "widget_artusermenu_getrolename"
     ],
-    "47": [
+    "48": [
       "art_text_scroll_index_handlecontentclick",
       "art_text_scroll_index_measuresizes",
       "components_core_text_effect_art_text_scroll_index_vue"
     ],
-    "48": [
+    "49": [
       "components_business_order_operate_dialog_bridge_index_vue",
       "order_operate_dialog_bridge_index_handlemessage",
       "order_operate_dialog_bridge_index_postopen"
     ],
-    "49": [
+    "50": [
       "core_uselayoutheight_useautolayoutheight",
       "core_uselayoutheight_uselayoutheight",
       "hooks_core_uselayoutheight_ts"
     ],
-    "50": [
+    "51": [
       "utils_sort_order_fetchnextsortorder",
       "utils_sort_order_ts"
     ],
-    "51": [
+    "52": [
       "utils_mask_sensitive_maskidcard",
       "utils_mask_sensitive_ts"
     ],
-    "52": [
+    "53": [
       "ui_ep_icon_map_resolveepicon",
       "utils_ui_ep_icon_map_ts"
     ],
-    "53": [
+    "54": [
       "ui_tabs_gettabconfig",
       "utils_ui_tabs_ts"
     ],
-    "54": [
+    "55": [
       "ui_loading_getloadingbackground",
       "utils_ui_loading_ts"
     ],
-    "55": [
+    "56": [
       "art_search_bar_index_if",
       "components_core_forms_art_search_bar_index_vue"
     ],
-    "56": [
+    "57": [
       "art_form_index_if",
       "components_core_forms_art_form_index_vue"
     ],
-    "57": [
+    "58": [
       "art_wang_editor_index_onsuccess",
       "components_core_forms_art_wang_editor_index_vue"
     ],
-    "58": [
+    "59": [
       "art_donut_chart_card_index_formatnumber",
       "components_core_cards_art_donut_chart_card_index_vue"
     ],
-    "59": [
+    "60": [
       "art_k_line_chart_index_getactualcolors",
       "components_core_charts_art_k_line_chart_index_vue"
     ],
-    "60": [
+    "61": [
       "art_bar_chart_index_creategradientcolor",
       "components_core_charts_art_bar_chart_index_vue"
     ],
-    "61": [
+    "62": [
       "art_h_bar_chart_index_creategradientcolor",
       "components_core_charts_art_h_bar_chart_index_vue"
     ],
-    "62": [
+    "63": [
       "art_basic_banner_index_generatemeteors",
       "components_core_banners_art_basic_banner_index_vue"
     ],
-    "63": [
+    "64": [
       "components_core_layouts_art_settings_panel_composables_usesettingsconfig_ts",
       "composables_usesettingsconfig_usesettingsconfig"
     ],
-    "64": [
+    "65": [
       "components_core_layouts_art_menus_art_horizontal_menu_index_vue",
       "components_core_layouts_art_menus_art_horizontal_menu_widget_horizontalsubmenu_vue"
     ],
-    "65": [
+    "66": [
       "components_business_protocol_finance_confirm_fields_finance_attachments_ts",
       "protocol_finance_confirm_fields_finance_attachments_financeattachmentstourlstring"
     ],
-    "66": [
+    "67": [
       "components_business_order_operate_local_dialog_index_vue",
       "order_operate_local_dialog_index_handlemessage"
     ],
-    "67": [
-      "components_business_product_view_dialog_index_vue",
-      "product_view_dialog_index_formatmeasureview"
-    ],
     "68": [
       "core_usefastenter_usefastenter",
       "hooks_core_usefastenter_ts"
@@ -1875,54 +1880,54 @@
     "0": 0.01,
     "1": 0.03,
     "2": 0.04,
-    "3": 0.06,
-    "4": 0.04,
+    "3": 0.04,
+    "4": 0.05,
     "5": 0.05,
-    "6": 0.05,
-    "7": 0.09,
+    "6": 0.09,
+    "7": 0.06,
     "8": 0.09,
-    "9": 0.05,
+    "9": 0.09,
     "10": 0.07,
-    "11": 0.08,
-    "12": 0.1,
+    "11": 0.09,
+    "12": 0.07,
     "13": 0.1,
     "14": 0.1,
     "15": 0.1,
-    "16": 0.12,
-    "17": 0.14,
-    "18": 0.11,
-    "19": 0.12,
+    "16": 0.1,
+    "17": 0.11,
+    "18": 0.12,
+    "19": 0.14,
     "20": 0.16,
-    "21": 0.16,
-    "22": 0.2,
-    "23": 0.27,
-    "24": 0.17,
-    "25": 0.14,
-    "26": 0.15,
-    "27": 0.18,
-    "28": 0.22,
-    "29": 0.39,
-    "30": 0.36,
-    "31": 0.33,
+    "21": 0.27,
+    "22": 0.17,
+    "23": 0.14,
+    "24": 0.15,
+    "25": 0.18,
+    "26": 0.2,
+    "27": 0.22,
+    "28": 0.39,
+    "29": 0.36,
+    "30": 0.33,
+    "31": 0.29,
     "32": 0.47,
     "33": 0.33,
     "34": 0.33,
     "35": 0.33,
     "36": 0.33,
-    "37": 0.53,
-    "38": 0.5,
-    "39": 0.67,
-    "40": 1.0,
-    "41": 0.67,
-    "42": 1.0,
-    "43": 0.67,
+    "37": 0.33,
+    "38": 0.53,
+    "39": 0.5,
+    "40": 0.67,
+    "41": 1.0,
+    "42": 0.67,
+    "43": 1.0,
     "44": 0.67,
     "45": 0.67,
     "46": 0.67,
     "47": 0.67,
     "48": 0.67,
     "49": 0.67,
-    "50": 1.0,
+    "50": 0.67,
     "51": 1.0,
     "52": 1.0,
     "53": 1.0,

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


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

@@ -1,10 +1,10 @@
 # Graph Report - src  (2026-05-21)
 
 ## Corpus Check
-- Large corpus: 575 files · ~293,920 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
+- Large corpus: 577 files · ~295,435 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
 
 ## Summary
-- 1396 nodes · 1745 edges · 40 communities detected
+- 1401 nodes · 1751 edges · 41 communities detected
 - Extraction: 78% EXTRACTED · 22% INFERRED · 0% AMBIGUOUS · INFERRED: 383 edges (avg confidence: 0.8)
 - Token cost: 0 input · 0 output
 
@@ -33,9 +33,9 @@
 - [[_COMMUNITY_Community 21|Community 21]]
 - [[_COMMUNITY_Community 22|Community 22]]
 - [[_COMMUNITY_Community 23|Community 23]]
-- [[_COMMUNITY_Community 24|Community 24]]
 - [[_COMMUNITY_Community 25|Community 25]]
-- [[_COMMUNITY_Community 27|Community 27]]
+- [[_COMMUNITY_Community 26|Community 26]]
+- [[_COMMUNITY_Community 28|Community 28]]
 - [[_COMMUNITY_Community 29|Community 29]]
 - [[_COMMUNITY_Community 30|Community 30]]
 - [[_COMMUNITY_Community 31|Community 31]]
@@ -48,7 +48,8 @@
 - [[_COMMUNITY_Community 38|Community 38]]
 - [[_COMMUNITY_Community 39|Community 39]]
 - [[_COMMUNITY_Community 40|Community 40]]
-- [[_COMMUNITY_Community 42|Community 42]]
+- [[_COMMUNITY_Community 41|Community 41]]
+- [[_COMMUNITY_Community 43|Community 43]]
 
 ## God Nodes (most connected - your core abstractions)
 1. `get()` - 152 edges
@@ -78,7 +79,7 @@
 
 ### Community 0 - "Community 0"
 Cohesion: 0.01
-Nodes (75): fetchActiveRegions(), fetchCategoryList(), fetchCategoryParameters(), fetchCellProductPriceList(), fetchCellProductSpecList(), fetchCellProductTypeList(), fetchCellSampleTypeList(), fetchChannelAgreementPrices() (+67 more)
+Nodes (77): fetchActiveRegions(), fetchCategoryList(), fetchCategoryParameters(), fetchCellProductPriceList(), fetchCellProductSpecList(), fetchCellProductTypeList(), fetchCellSampleTypeList(), fetchChannelAgreementPrices() (+69 more)
 
 ### Community 1 - "Community 1"
 Cohesion: 0.03
@@ -89,113 +90,113 @@ Cohesion: 0.04
 Nodes (31): fetchGetUserInfo(), clickTab(), handleTouchEnd(), setTransition(), IframeRouteManager, RouteRegistry, useCommon(), setupAfterEachGuard() (+23 more)
 
 ### Community 3 - "Community 3"
-Cohesion: 0.06
-Nodes (11): fetchGetLatestUpgradeLog(), getDefaultLanguage(), checkStorageCompatibility(), StorageConfig, getSystemStorage(), getSysVersion(), StorageKeyManager, StorageCompatibilityManager (+3 more)
-
-### Community 4 - "Community 4"
 Cohesion: 0.04
 Nodes (18): fetchAllConsumablePacks(), fetchAllConsumables(), fetchAllOutputTypes(), fetchAllProcessCategories(), fetchAllProcesses(), fetchAllReagents(), fetchAllSopDocuments(), fetchConsumableList() (+10 more)
 
-### Community 5 - "Community 5"
+### Community 4 - "Community 4"
 Cohesion: 0.05
 Nodes (32): fetchDeptTree(), fetchPostBoundUserIds(), FireworkSystem, reload(), formatUserLabel(), loadDeptData(), onPersonSelectChange(), subscribeTokenRefresh() (+24 more)
 
-### Community 6 - "Community 6"
+### 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 (5): handleReset(), handleSearch(), WebSocketClient, TableCache, emit()
 
+### Community 7 - "Community 7"
+Cohesion: 0.06
+Nodes (12): collectIds(), useSettingsHandlers(), useSettingsPanel(), useSettingsState(), ComponentLoader, RoutePermissionValidator, RouteTransformer, useCeremony() (+4 more)
+
 ### Community 8 - "Community 8"
 Cohesion: 0.09
 Nodes (31): toLogin(), base64ToBytes(), bytesToBase64(), decryptJson(), deriveKey(), encryptJson(), fromBase64Url(), hexToBytes() (+23 more)
 
 ### Community 9 - "Community 9"
-Cohesion: 0.05
-Nodes (9): fetchCellProductDetail(), fetchCellProductList(), fetchLeaveDetail(), fetchLeaveList(), fetchQcMaterialList(), fetchQcMaterialOptions(), fetchResearchDonationAuditList(), fetchResearchDonationDetail() (+1 more)
+Cohesion: 0.09
+Nodes (8): getDefaultLanguage(), checkStorageCompatibility(), StorageConfig, getSystemStorage(), getSysVersion(), StorageKeyManager, StorageCompatibilityManager, validateStorageData()
 
 ### Community 10 - "Community 10"
 Cohesion: 0.07
 Nodes (13): fetchAttachmentDownloadUrl(), fetchOrderAdjusts(), fetchOrderDetail(), fetchOrderItems(), fetchOrderList(), fetchPriceVersionDetail(), fetchSystemConfig(), handleReceptionAttachmentDownload() (+5 more)
 
 ### Community 11 - "Community 11"
-Cohesion: 0.08
-Nodes (10): collectIds(), useSettingsHandlers(), useSettingsPanel(), useSettingsState(), RoutePermissionValidator, useCeremony(), useTheme(), themeAnimation() (+2 more)
+Cohesion: 0.09
+Nodes (9): fetchGetAllUpgradeLogs(), fetchGetChinaMapData(), fetchGetComments(), fetchGetCurrentVersion(), fetchGetLatestUpgradeLog(), fetchGetSystemConfig(), fetchGetUpgradeLogs(), systemUpgrade() (+1 more)
 
 ### Community 12 - "Community 12"
-Cohesion: 0.1
-Nodes (16): fetchCreateMenu(), fetchDeleteMenu(), fetchMenuTree(), fetchUpdateMenu(), postorder(), preorder(), applyTreeExpand(), handleAdd() (+8 more)
+Cohesion: 0.07
+Nodes (15): fetchResearchDonationAuditList(), fetchResearchDonationDetail(), fetchResearchDonationList(), fetchApproveTask(), fetchBusinessAllTasks(), fetchCompletedTasks(), fetchDesignerData(), fetchInstanceDetail() (+7 more)
 
 ### Community 13 - "Community 13"
 Cohesion: 0.1
-Nodes (8): fetchGetMenuList(), fetchGetMenuCodes(), fetchLogin(), fetchRefreshToken(), MenuProcessor, useAppMode(), useAuth(), buildWebEncryptedLoginBody()
+Nodes (16): fetchCreateMenu(), fetchDeleteMenu(), fetchMenuTree(), fetchUpdateMenu(), postorder(), preorder(), applyTreeExpand(), handleAdd() (+8 more)
 
 ### Community 14 - "Community 14"
 Cohesion: 0.1
-Nodes (13): fetchEthnicList(), addSample(), applyDonationBundleToForm(), confirmBatchTransportApply(), createEmptyTransport(), ensureDonationProtocolNoInOptions(), ensureEthnicOptionInList(), handleOpenForAdd() (+5 more)
+Nodes (8): fetchGetMenuList(), fetchGetMenuCodes(), fetchLogin(), fetchRefreshToken(), MenuProcessor, useAppMode(), useAuth(), buildWebEncryptedLoginBody()
 
 ### Community 15 - "Community 15"
 Cohesion: 0.1
-Nodes (18): fetchCollectionRegionList(), fetchCreateCollectionRegion(), fetchCreateHospital(), fetchDeleteCollectionRegion(), fetchDeleteHospital(), fetchHospitalList(), fetchUpdateCollectionRegion(), fetchUpdateHospital() (+10 more)
+Nodes (13): fetchEthnicList(), addSample(), applyDonationBundleToForm(), confirmBatchTransportApply(), createEmptyTransport(), ensureDonationProtocolNoInOptions(), ensureEthnicOptionInList(), handleOpenForAdd() (+5 more)
 
 ### Community 16 - "Community 16"
-Cohesion: 0.12
-Nodes (15): addCopyButton(), highlightCode(), insertLineNumbers(), isBlockProcessed(), markBlockAsProcessed(), processBlock(), setupHighlightDirective(), setupRippleDirective() (+7 more)
+Cohesion: 0.1
+Nodes (18): fetchCollectionRegionList(), fetchCreateCollectionRegion(), fetchCreateHospital(), fetchDeleteCollectionRegion(), fetchDeleteHospital(), fetchHospitalList(), fetchUpdateCollectionRegion(), fetchUpdateHospital() (+10 more)
 
 ### Community 17 - "Community 17"
-Cohesion: 0.14
-Nodes (17): fetchCreateUser(), fetchDeleteUser(), fetchResetPassword(), fetchUpdateUser(), fetchUserDetail(), formatUserDeptDisplay(), handleAdd(), handleDelete() (+9 more)
+Cohesion: 0.11
+Nodes (9): auditorDisplay(), dash(), expressName(), formatYm(), formatYmd(), sampleTypeName(), siteName(), unitName() (+1 more)
 
 ### Community 18 - "Community 18"
-Cohesion: 0.11
-Nodes (8): fetchCollectionProtocolAuditList(), fetchCollectionProtocolDetail(), fetchCollectionProtocolList(), fetchPaymentDetailList(), fetchReceiptConfirmedList(), fetchReceiptConfirmList(), fetchRefundConfirmedList(), fetchRefundConfirmList()
+Cohesion: 0.12
+Nodes (15): addCopyButton(), highlightCode(), insertLineNumbers(), isBlockProcessed(), markBlockAsProcessed(), processBlock(), setupHighlightDirective(), setupRippleDirective() (+7 more)
 
 ### Community 19 - "Community 19"
-Cohesion: 0.12
-Nodes (12): fetchApproveTask(), fetchBusinessAllTasks(), fetchCompletedTasks(), fetchDesignerData(), fetchInstanceDetail(), fetchMyInstances(), fetchPendingTasks(), fetchProcessDefList() (+4 more)
+Cohesion: 0.14
+Nodes (17): fetchCreateUser(), fetchDeleteUser(), fetchResetPassword(), fetchUpdateUser(), fetchUserDetail(), formatUserDeptDisplay(), handleAdd(), handleDelete() (+9 more)
 
 ### Community 20 - "Community 20"
 Cohesion: 0.16
-Nodes (9): auditorDisplay(), dash(), expressName(), formatYm(), formatYmd(), sampleTypeName(), siteName(), unitName() (+1 more)
-
-### Community 21 - "Community 21"
-Cohesion: 0.16
 Nodes (10): useTable(), useTableImpl(), getColumnChecks(), useTableColumns(), createErrorHandler(), createSmartDebounce(), defaultResponseAdapter(), extractPagination() (+2 more)
 
-### Community 22 - "Community 22"
-Cohesion: 0.2
-Nodes (2): ComponentLoader, RouteTransformer
-
-### Community 23 - "Community 23"
+### Community 21 - "Community 21"
 Cohesion: 0.27
 Nodes (14): useChart(), useChartComponent(), useChartOps(), colourBlend(), getCssVar(), getDarkColor(), getLightColor(), handleElementThemeColor() (+6 more)
 
-### Community 24 - "Community 24"
+### Community 22 - "Community 22"
 Cohesion: 0.17
 Nodes (12): fetchCreateCustomer(), fetchCustomerList(), fetchDeleteCustomer(), fetchUpdateCustomer(), handleAdd(), handleBatchDelete(), handleDelete(), handleReset() (+4 more)
 
-### Community 25 - "Community 25"
+### Community 23 - "Community 23"
 Cohesion: 0.14
 Nodes (2): fetchProductionTaskDetail(), fetchProductionTaskList()
 
-### Community 27 - "Community 27"
+### Community 25 - "Community 25"
 Cohesion: 0.18
 Nodes (3): fetchMaterialList(), fetchMaterialOptions(), fetchMaterialStockLogList()
 
-### Community 29 - "Community 29"
+### Community 26 - "Community 26"
+Cohesion: 0.2
+Nodes (2): fetchQcMaterialList(), fetchQcMaterialOptions()
+
+### Community 28 - "Community 28"
 Cohesion: 0.39
 Nodes (6): isIgnorableScriptError(), normalizeErrorMessage(), registerPromiseErrorHandler(), registerResourceErrorHandler(), scriptErrorHandler(), setupErrorHandle()
 
-### Community 30 - "Community 30"
+### Community 29 - "Community 29"
 Cohesion: 0.36
 Nodes (1): TableHeightCalculator
 
-### Community 31 - "Community 31"
+### Community 30 - "Community 30"
 Cohesion: 0.33
 Nodes (2): logApiCryptoBootInfo(), maskSecret()
 
+### Community 31 - "Community 31"
+Cohesion: 0.29
+Nodes (2): fetchLeaveDetail(), fetchLeaveList()
+
 ### Community 32 - "Community 32"
 Cohesion: 0.47
 Nodes (3): isPaymentDueWithinWarnWindow(), parsePaymentDueYmd(), startOfLocalDay()
@@ -210,67 +211,75 @@ Nodes (2): fetchQcThirdPartyItemList(), fetchQcThirdPartyItemsActive()
 
 ### Community 35 - "Community 35"
 Cohesion: 0.33
-Nodes (2): fetchQcLabTestItemList(), fetchQcLabTestItemsActive()
+Nodes (2): fetchQcThirdPartyCompanyList(), fetchQcThirdPartyCompanyOptions()
 
 ### Community 36 - "Community 36"
 Cohesion: 0.33
-Nodes (2): fetchQcTaskDetail(), fetchQcTaskList()
+Nodes (2): fetchQcLabTestItemList(), fetchQcLabTestItemsActive()
 
 ### Community 37 - "Community 37"
+Cohesion: 0.33
+Nodes (2): fetchQcTaskDetail(), fetchQcTaskList()
+
+### Community 38 - "Community 38"
 Cohesion: 0.53
 Nodes (4): isEtaTodayOrEarlier(), parseEtaLocalDate(), sampleReceptionRowClassName(), startOfLocalDay()
 
-### Community 38 - "Community 38"
+### Community 39 - "Community 39"
 Cohesion: 0.5
 Nodes (2): focusInput(), handleKeydown()
 
-### Community 39 - "Community 39"
+### Community 40 - "Community 40"
 Cohesion: 0.67
 Nodes (2): if(), logSidebarMenu()
 
-### Community 40 - "Community 40"
+### Community 41 - "Community 41"
 Cohesion: 1.0
 Nodes (2): getSettingDefaults(), resetToDefaults()
 
-### Community 42 - "Community 42"
+### Community 43 - "Community 43"
 Cohesion: 1.0
 Nodes (2): defaultAvatarPathByRole(), resolveUserAvatarUrl()
 
 ## Knowledge Gaps
-- **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`
+- **Thin community `Community 23`** (14 nodes): `fetchAddTaskStep()`, `fetchCreateProductionTask()`, `fetchDeleteTaskStep()`, `fetchProductionTaskDetail()`, `fetchProductionTaskList()`, `fetchReplaceStepConsumables()`, `fetchReplaceStepDataCollection()`, `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 25`** (14 nodes): `fetchAddTaskStep()`, `fetchCreateProductionTask()`, `fetchDeleteTaskStep()`, `fetchProductionTaskDetail()`, `fetchProductionTaskList()`, `fetchReplaceStepConsumables()`, `fetchReplaceStepDataCollection()`, `fetchReplaceStepReagents()`, `fetchSetTaskInputs()`, `fetchSetTaskOutputs()`, `fetchUpdateProductionTask()`, `fetchUpdateProductionTaskStatus()`, `fetchUpdateTaskStep()`, `production-task.ts`
+- **Thin community `Community 26`** (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 30`** (8 nodes): `TableHeightCalculator`, `.calculate()`, `.calculateOffset()`, `.calculatePaginationOffset()`, `.constructor()`, `.getHeaderHeight()`, `useTableHeight()`, `useTableHeight.ts`
+- **Thin community `Community 29`** (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 31`** (7 nodes): `App.vue`, `main.ts`, `refreshButtonAutoIcons()`, `refreshPanelTitleIcons()`, `logApiCryptoBootInfo()`, `maskSecret()`, `console.ts`
+- **Thin community `Community 30`** (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 31`** (7 nodes): `fetchCreateLeave()`, `fetchDeleteLeave()`, `fetchLeaveDetail()`, `fetchLeaveList()`, `fetchSubmitLeave()`, `fetchUpdateLeave()`, `leave.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
 - **Thin community `Community 33`** (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 34`** (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 35`** (6 nodes): `fetchCreateQcLabTestItem()`, `fetchDeleteQcLabTestItem()`, `fetchQcLabTestItemList()`, `fetchQcLabTestItemsActive()`, `fetchUpdateQcLabTestItem()`, `qc-lab-test-item.ts`
+- **Thin community `Community 35`** (6 nodes): `fetchCreateQcThirdPartyCompany()`, `fetchDeleteQcThirdPartyCompany()`, `fetchQcThirdPartyCompanyList()`, `fetchQcThirdPartyCompanyOptions()`, `fetchUpdateQcThirdPartyCompany()`, `qc-third-party-company.ts`
+  Too small to be a meaningful cluster - may be noise or needs more connections extracted.
+- **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 36`** (6 nodes): `fetchCompleteQcTask()`, `fetchQcTaskDetail()`, `fetchQcTaskList()`, `fetchStartQcTask()`, `fetchUpdateQcTaskAttachments()`, `qc-task.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 38`** (5 nodes): `focusInput()`, `handleKeydown()`, `if()`, `search()`, `index.vue`
+- **Thin community `Community 39`** (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 39`** (4 nodes): `if()`, `logSidebarMenu()`, `index.vue`, `SidebarSubmenu.vue`
+- **Thin community `Community 40`** (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 40`** (3 nodes): `getSettingDefaults()`, `resetToDefaults()`, `setting.ts`
+- **Thin community `Community 41`** (3 nodes): `getSettingDefaults()`, `resetToDefaults()`, `setting.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 42`** (3 nodes): `defaultAvatarPathByRole()`, `resolveUserAvatarUrl()`, `user-avatar.ts`
+- **Thin community `Community 43`** (3 nodes): `defaultAvatarPathByRole()`, `resolveUserAvatarUrl()`, `user-avatar.ts`
   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 6`, `Community 9`, `Community 10`, `Community 12`, `Community 13`, `Community 14`, `Community 15`, `Community 17`, `Community 18`, `Community 19`, `Community 24`, `Community 25`, `Community 27`, `Community 33`, `Community 34`, `Community 35`, `Community 36`?**
-  _High betweenness centrality (0.256) - this node is a cross-community bridge._
-- **Why does `push()` connect `Community 5` to `Community 0`, `Community 2`, `Community 3`, `Community 7`, `Community 11`, `Community 14`?**
-  _High betweenness centrality (0.086) - this node is a cross-community bridge._
-- **Why does `fetchGetLatestUpgradeLog()` connect `Community 3` to `Community 0`?**
-  _High betweenness centrality (0.043) - this node is a cross-community bridge._
+- **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 16`, `Community 19`, `Community 22`, `Community 23`, `Community 25`, `Community 26`, `Community 31`, `Community 33`, `Community 34`, `Community 35`, `Community 36`, `Community 37`?**
+  _High betweenness centrality (0.276) - this node is a cross-community bridge._
+- **Why does `push()` connect `Community 4` to `Community 0`, `Community 2`, `Community 6`, `Community 7`, `Community 11`, `Community 15`?**
+  _High betweenness centrality (0.085) - this node is a cross-community bridge._
+- **Why does `fetchGetLatestUpgradeLog()` connect `Community 11` to `Community 0`?**
+  _High betweenness centrality (0.042) - this node is a cross-community bridge._
 - **Are the 151 inferred relationships involving `get()` (e.g. with `buildTreePathLabelFromFlat()` and `alignFqPaymentDetailsForDisplay()`) actually correct?**
   _`get()` has 151 INFERRED edges - model-reasoned connections that need verification._
 - **Are the 17 inferred relationships involving `push()` (e.g. with `fetchAllPagedRecords()` and `calcInstallmentDueAmounts()`) actually correct?**

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


+ 20 - 18
web-admin/graphify-out/manifest.json

@@ -17,7 +17,7 @@
   "/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": 1779328937.243264,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts": 1779333640.1373396,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/auto-imports.d.ts": 1779313245.9581928,
   "/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,
@@ -169,12 +169,14 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/core/media/art-cutter-img/index.vue": 1778582067.9817953,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/business/protocol-finance-confirm-fields/index.vue": 1779184108.8553443,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/business/protocol-finance-confirm-fields/finance-attachments.ts": 1778987672.644368,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/business/qc-task-view-dialog/index.vue": 1779332634.772821,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/business/qc-task-view-dialog/index.vue": 1779333579.4876578,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/business/dialog-form-body/index.vue": 1779333473.8613615,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/business/sample-qc-info-cards/index.vue": 1779254253.4663467,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/business/order-operate-local-dialog/index.vue": 1778582866.2188683,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/business/product-view-dialog/index.vue": 1778582067.9494708,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/business/product-view-dialog/index.vue": 1779333717.557603,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/business/dialog-detail-skeleton/index.vue": 1779333469.3668177,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/business/protocol-payment-detail-readonly/index.vue": 1779006281.4220655,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/business/system-view-dialog/index.vue": 1779307486.6016765,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/business/system-view-dialog/index.vue": 1779333483.3425906,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/business/order-operate-dialog-bridge/index.vue": 1778582865.387537,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/hooks/index.ts": 1778582068.0037498,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/hooks/core/useFastEnter.ts": 1778582068.0014625,
@@ -219,9 +221,9 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/refund-confirm/index.vue": 1779269573.509064,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/index.vue": 1779329214.96505,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolStatusChangeDialog.vue": 1779161483.857836,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue": 1779328834.3196502,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue": 1779333532.6375403,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolAttachmentReadonly.vue": 1778938314.528914,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolFormDialog.vue": 1779327539.5577006,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolFormDialog.vue": 1779333591.365972,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolAuditDialog.vue": 1778978838.5026703,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/payment-info/index.vue": 1779313518.430975,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/storage-cert/index.vue": 1779313530.5053055,
@@ -238,15 +240,15 @@
   "/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,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationAuditDialog.vue": 1779112125.8042517,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue": 1779255542.4968102,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationFormDialog.vue": 1779327540.322163,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue": 1779333577.1438212,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationFormDialog.vue": 1779333609.8141866,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationStatusChangeDialog.vue": 1779161484.445245,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/dashboard/console/index.vue": 1779227504.1947322,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/tenant/index.vue": 1779257191.2809517,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/order/index.vue": 1779227504.2074113,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/order/index.vue": 1779333726.5420778,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/unit/index.vue": 1779269675.2464228,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/role/index.vue": 1779262465.7066724,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/post/index.vue": 1779269637.686323,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/role/index.vue": 1779333690.3537648,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/post/index.vue": 1779333705.6902134,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/category/index.vue": 1779269601.5291371,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/express/index.vue": 1779269623.124673,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/user/index.vue": 1779269558.2320182,
@@ -254,11 +256,11 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/user/modules/user-dialog.vue": 1778775532.9028454,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/user/modules/user-search.vue": 1778582068.0978386,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/product/index.vue": 1779269580.3943145,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/workflow/my-applications/index.vue": 1779269608.691914,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/workflow/task-pending/index.vue": 1779269663.4965441,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/workflow/task-completed/index.vue": 1779269563.1046119,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/workflow/my-applications/index.vue": 1779333693.858461,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/workflow/task-pending/index.vue": 1779333690.3537714,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/workflow/task-completed/index.vue": 1779333690.353951,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/workflow/components/ProcessStatusTag.vue": 1778635996.7440987,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/workflow/components/ApprovalDialog.vue": 1778726550.7429476,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/workflow/components/ApprovalDialog.vue": 1779333694.8993936,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/workflow/designer/FlowDesigner.vue": 1778758509.6141794,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/workflow/process/index.vue": 1779269669.156024,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/menu/index.vue": 1779265260.150818,
@@ -268,7 +270,7 @@
   "/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": 1779227504.3581011,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue": 1779269597.1131647,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue": 1779333730.1243677,
   "/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": 1779269660.0717294,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/reagent/index.vue": 1779307562.93293,
@@ -283,7 +285,7 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/lab-test-item/index.vue": 1779331328.5864854,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-material/index.vue": 1779284232.1223023,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue": 1779328630.8453777,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue": 1779332615.9118567,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue": 1779333720.422681,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-material-usage/index.vue": 1779285902.4727366,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/third-party-company/index.vue": 1779330250.9402645,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/exception/500/index.vue": 1778582068.0705533,
@@ -293,7 +295,7 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/pilot-service/checkup-institution/index.vue": 1779311045.8670757,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/pilot-service/checkup-institution/CheckupPackagePanel.vue": 1779311419.51521,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/pilot-service/channel/index.vue": 1779269591.7394044,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/pilot-service/channel/ChannelViewDialog.vue": 1779310694.8957999,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/pilot-service/channel/ChannelViewDialog.vue": 1779333576.2032166,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/pilot-service/cell-product-price/index.vue": 1779309928.4076538,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/pilot-service/project-price-system/index.vue": 1779310370.9475677,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/pilot-service/cell-product-spec/index.vue": 1779309818.5525613,

+ 9 - 0
web-admin/src/assets/styles/core/app.scss

@@ -780,6 +780,15 @@ body {
   flex-shrink: 0;
 }
 
+/* 弹窗异步加载:骨架占位,减轻先小后大 */
+.dialog-scroll.is-view-loading,
+.dialog-form-body.is-dialog-loading,
+.protocol-view-body.is-detail-loading,
+.donation-view-body.is-detail-loading,
+.product-form-container.is-view-loading {
+  min-height: min(360px, calc(100vh - 220px));
+}
+
 #app-header > div {
   background: var(--mint-surface) !important;
 }

+ 510 - 0
web-admin/src/components/business/dialog-detail-skeleton/index.vue

@@ -0,0 +1,510 @@
+<!-- 弹窗详情/表单加载骨架(Fresh Mint,对齐 SystemViewDialog / dialog-form-layout) -->
+<template>
+  <div
+    class="dialog-detail-skeleton"
+    :class="[
+      `dialog-detail-skeleton--${variant}`,
+      { 'dialog-detail-skeleton--compact': compact }
+    ]"
+    aria-busy="true"
+    aria-label="内容加载中"
+  >
+    <!-- SystemViewDialog 布局 -->
+    <template v-if="variant === 'system-view' || variant === 'system-view-image'">
+      <el-skeleton animated class="dds-hero">
+        <template #template>
+          <div class="dds-hero__inner">
+            <el-skeleton-item
+              :variant="variant === 'system-view-image' ? 'image' : 'rect'"
+              class="dds-hero__icon"
+            />
+            <div class="dds-hero__main">
+              <el-skeleton-item variant="h1" class="dds-hero__title" />
+              <el-skeleton-item variant="text" class="dds-hero__meta" />
+            </div>
+          </div>
+        </template>
+      </el-skeleton>
+      <div
+        v-for="i in resolvedSectionCount"
+        :key="`sv-${i}`"
+        class="dds-section dds-section--view"
+      >
+        <el-skeleton animated>
+          <template #template>
+            <el-skeleton-item variant="h3" class="dds-section__title" />
+            <div class="dds-kv-grid" :class="`dds-kv-grid--cols-${gridColumns}`">
+              <div v-for="f in resolvedFieldCount" :key="f" class="dds-kv-field">
+                <el-skeleton-item variant="text" class="dds-kv-field__label" />
+                <el-skeleton-item variant="p" class="dds-kv-field__value" />
+              </div>
+            </div>
+          </template>
+        </el-skeleton>
+      </div>
+    </template>
+
+    <!-- CRUD 表单弹窗(无 Tab) -->
+    <template v-else-if="variant === 'form'">
+      <div
+        v-for="i in resolvedSectionCount"
+        :key="`form-${i}`"
+        class="dds-section dds-section--form"
+      >
+        <el-skeleton animated>
+          <template #template>
+            <el-skeleton-item variant="h3" class="dds-section__title dds-section__title--form" />
+            <div class="dds-form-grid" :class="`dds-form-grid--cols-${gridColumns}`">
+              <div v-for="f in resolvedFieldCount" :key="f" class="dds-form-field">
+                <el-skeleton-item variant="text" class="dds-form-field__label" />
+                <el-skeleton-item variant="rect" class="dds-form-field__control" />
+              </div>
+            </div>
+          </template>
+        </el-skeleton>
+      </div>
+    </template>
+
+    <!-- 带 Sticky Tab 的表单弹窗(协议单/捐赠单等) -->
+    <template v-else-if="variant === 'tabs-form'">
+      <div class="dds-tabs">
+        <el-skeleton-item
+          v-for="t in resolvedTabCount"
+          :key="t"
+          variant="rect"
+          class="dds-tabs__item"
+        />
+      </div>
+      <div
+        v-for="i in resolvedSectionCount"
+        :key="`tf-${i}`"
+        class="dds-section dds-section--form"
+      >
+        <el-skeleton animated>
+          <template #template>
+            <el-skeleton-item variant="h3" class="dds-section__title dds-section__title--form" />
+            <div class="dds-form-grid dds-form-grid--cols-2">
+              <div v-for="f in resolvedFieldCount" :key="f" class="dds-form-field">
+                <el-skeleton-item variant="text" class="dds-form-field__label" />
+                <el-skeleton-item variant="rect" class="dds-form-field__control" />
+              </div>
+            </div>
+          </template>
+        </el-skeleton>
+      </div>
+    </template>
+
+    <!-- 大宽查看弹窗(采集协议单等:KPI + 多区块) -->
+    <template v-else-if="variant === 'tabs-detail'">
+      <div class="dds-tabs">
+        <el-skeleton-item
+          v-for="t in resolvedTabCount"
+          :key="t"
+          variant="rect"
+          class="dds-tabs__item"
+        />
+      </div>
+      <el-skeleton animated class="dds-kpi">
+        <template #template>
+          <div class="dds-kpi__grid">
+            <div v-for="i in 4" :key="i" class="dds-kpi__card">
+              <el-skeleton-item variant="circle" class="dds-kpi__icon" />
+              <div class="dds-kpi__text">
+                <el-skeleton-item variant="text" class="dds-kpi__label" />
+                <el-skeleton-item variant="h3" class="dds-kpi__value" />
+              </div>
+            </div>
+          </div>
+        </template>
+      </el-skeleton>
+      <div
+        v-for="(sec, sIdx) in tabsDetailSections"
+        :key="sec.key"
+        class="dds-section"
+        :class="{ 'dds-section--green': sec.tone === 'green' }"
+      >
+        <el-skeleton animated>
+          <template #template>
+            <el-skeleton-item variant="h3" class="dds-section__title" />
+            <div
+              v-if="sec.fieldCount > 0"
+              class="dds-data-grid"
+              :class="`dds-data-grid--${sec.cols}`"
+            >
+              <div v-for="f in sec.fieldCount" :key="f" class="dds-data-field">
+                <el-skeleton-item variant="text" class="dds-data-field__label" />
+                <el-skeleton-item variant="p" class="dds-data-field__value" />
+              </div>
+            </div>
+            <div v-if="sIdx === 1" class="dds-subcards">
+              <div v-for="j in 3" :key="j" class="dds-subcard">
+                <el-skeleton-item variant="text" style="width: 28%; margin-bottom: 12px" />
+                <div class="dds-data-grid dds-data-grid--4">
+                  <div v-for="k in 4" :key="k" class="dds-data-field">
+                    <el-skeleton-item variant="text" class="dds-data-field__label" />
+                    <el-skeleton-item variant="p" class="dds-data-field__value" />
+                  </div>
+                </div>
+              </div>
+            </div>
+          </template>
+        </el-skeleton>
+      </div>
+    </template>
+
+    <!-- 简易块(工作流/订单等自定义弹窗) -->
+    <template v-else>
+      <div
+        v-for="i in resolvedSectionCount"
+        :key="`simple-${i}`"
+        class="dds-section dds-section--simple"
+      >
+        <el-skeleton animated :rows="resolvedFieldCount" />
+      </div>
+    </template>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { computed } from 'vue'
+
+  const props = withDefaults(
+    defineProps<{
+      /** system-view | system-view-image | form | tabs-form | tabs-detail | simple */
+      variant?: 'system-view' | 'system-view-image' | 'form' | 'tabs-form' | 'tabs-detail' | 'simple'
+      sectionCount?: number
+      fieldCount?: number
+      tabCount?: number
+      gridColumns?: 2 | 3 | 4
+      compact?: boolean
+    }>(),
+    {
+      variant: 'system-view',
+      sectionCount: 2,
+      fieldCount: 6,
+      tabCount: 5,
+      gridColumns: 2,
+      compact: false
+    }
+  )
+
+  const resolvedSectionCount = computed(() => Math.max(1, props.sectionCount))
+  const resolvedFieldCount = computed(() => Math.max(2, props.fieldCount))
+  const resolvedTabCount = computed(() => Math.max(3, props.tabCount))
+
+  const tabsDetailSections = [
+    { key: 'basic', tone: 'white' as const, cols: 4, fieldCount: 8 },
+    { key: 'party', tone: 'green' as const, cols: 4, fieldCount: 0 },
+    { key: 'collection', tone: 'white' as const, cols: 4, fieldCount: 8 },
+    { key: 'sample', tone: 'white' as const, cols: 4, fieldCount: 6 },
+    { key: 'payment', tone: 'white' as const, cols: 4, fieldCount: 8 }
+  ]
+</script>
+
+<style scoped lang="scss">
+  .dialog-detail-skeleton {
+    display: flex;
+    flex-direction: column;
+    gap: 0;
+    min-height: min(360px, calc(100vh - 240px));
+
+    &--form,
+    &--tabs-form {
+      gap: 16px;
+      padding: 2px 0 8px;
+      min-height: min(320px, calc(100vh - 240px));
+    }
+
+    &--tabs-detail {
+      gap: 20px;
+      min-height: min(640px, calc(100vh - 240px));
+    }
+
+    &--compact {
+      min-height: min(280px, calc(100vh - 200px));
+    }
+  }
+
+  .dds-hero {
+    :deep(.el-skeleton) {
+      width: 100%;
+    }
+  }
+
+  .dds-hero__inner {
+    display: flex;
+    gap: 16px;
+    padding: 20px 22px;
+    background: linear-gradient(135deg, #ecfdf5 0%, #ffffff 55%);
+    border-bottom: 1px solid #d9e8e2;
+    align-items: center;
+  }
+
+  .dds-hero__icon {
+    width: 72px;
+    height: 72px;
+    border-radius: 12px;
+    flex-shrink: 0;
+  }
+
+  .dds-hero__main {
+    flex: 1;
+    min-width: 0;
+  }
+
+  .dds-hero__title {
+    width: 42%;
+    height: 22px;
+    margin-bottom: 10px;
+  }
+
+  .dds-hero__meta {
+    width: 58%;
+    height: 14px;
+  }
+
+  .dialog-detail-skeleton--compact .dds-hero__inner {
+    padding: 12px 18px;
+    gap: 12px;
+  }
+
+  .dialog-detail-skeleton--compact .dds-hero__icon {
+    width: 48px;
+    height: 48px;
+  }
+
+  .dds-section--view {
+    padding: 18px 22px;
+    border-bottom: 1px solid #ebeef5;
+
+    &:last-child {
+      border-bottom: none;
+    }
+
+    :deep(.el-skeleton) {
+      width: 100%;
+    }
+  }
+
+  .dds-section--form,
+  .dds-section--simple {
+    padding: 4px 0;
+
+    :deep(.el-skeleton) {
+      width: 100%;
+    }
+  }
+
+  .dds-section--green {
+    padding: 22px 24px;
+    background: #f0fdf4;
+    border-radius: 12px;
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
+  }
+
+  .dds-section:not(.dds-section--view):not(.dds-section--green) {
+    &.dds-section--form {
+      padding: 8px 0;
+    }
+  }
+
+  .dds-section__title {
+    width: 96px;
+    height: 18px;
+    margin-bottom: 14px;
+
+    &--form {
+      width: 80px;
+      margin-bottom: 16px;
+    }
+  }
+
+  .dds-kv-grid {
+    display: grid;
+    gap: 12px 20px;
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+
+    &--cols-3 {
+      grid-template-columns: repeat(3, minmax(0, 1fr));
+    }
+  }
+
+  .dds-kv-field {
+    display: grid;
+    grid-template-columns: 96px 1fr;
+    gap: 8px 12px;
+  }
+
+  .dds-kv-field__label {
+    width: 80%;
+    height: 13px;
+  }
+
+  .dds-kv-field__value {
+    width: 92%;
+    height: 14px;
+  }
+
+  .dds-form-grid {
+    display: grid;
+    gap: 14px 20px;
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+
+    &--cols-3 {
+      grid-template-columns: repeat(3, minmax(0, 1fr));
+    }
+  }
+
+  .dds-form-field__label {
+    width: 40%;
+    height: 12px;
+    margin-bottom: 8px;
+  }
+
+  .dds-form-field__control {
+    width: 100%;
+    height: 36px;
+    border-radius: 8px;
+  }
+
+  .dds-tabs {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 4px;
+    padding: 4px;
+    background: #f0fdf4;
+    border-radius: 8px;
+    width: fit-content;
+    max-width: 100%;
+    margin-bottom: 4px;
+  }
+
+  .dds-tabs__item {
+    width: 88px;
+    height: 36px;
+    border-radius: 6px;
+  }
+
+  .dds-kpi {
+    margin-bottom: 4px;
+
+    :deep(.el-skeleton) {
+      width: 100%;
+    }
+  }
+
+  .dds-kpi__grid {
+    display: grid;
+    grid-template-columns: repeat(4, 1fr);
+    padding: 6px;
+    background: linear-gradient(135deg, #ecfdf5 0%, #f8fafc 52%, #ecfdf5 100%);
+    border: 1px solid #a7f3d0;
+    border-radius: 12px;
+  }
+
+  .dds-kpi__card {
+    display: flex;
+    gap: 12px;
+    padding: 14px 18px;
+
+    &:not(:last-child) {
+      border-right: 1px solid rgba(167, 243, 208, 0.75);
+    }
+  }
+
+  .dds-kpi__icon {
+    width: 40px;
+    height: 40px;
+    flex-shrink: 0;
+  }
+
+  .dds-kpi__text {
+    flex: 1;
+    min-width: 0;
+  }
+
+  .dds-kpi__label {
+    width: 48%;
+    height: 11px;
+  }
+
+  .dds-kpi__value {
+    width: 72%;
+    height: 16px;
+    margin-top: 8px;
+  }
+
+  .dds-section:not(.dds-section--view) {
+    border-radius: 12px;
+    padding: 22px 24px;
+    background: #fff;
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
+
+    :deep(.el-skeleton) {
+      width: 100%;
+    }
+  }
+
+  .dds-data-grid {
+    display: grid;
+    gap: 14px 20px;
+
+    &--4 {
+      grid-template-columns: repeat(4, minmax(0, 1fr));
+    }
+  }
+
+  .dds-data-field__label {
+    width: 56%;
+    height: 12px;
+    margin-bottom: 6px;
+  }
+
+  .dds-data-field__value {
+    width: 88%;
+    height: 14px;
+  }
+
+  .dds-subcards {
+    display: grid;
+    grid-template-columns: repeat(3, 1fr);
+    gap: 16px;
+    margin-top: 4px;
+  }
+
+  .dds-subcard {
+    padding: 16px;
+    background: #fff;
+    border-radius: 10px;
+    border: 1px solid #e2e8f0;
+  }
+
+  @media (max-width: 1200px) {
+    .dds-kpi__grid {
+      grid-template-columns: repeat(2, 1fr);
+    }
+
+    .dds-kpi__card {
+      border-right: none !important;
+      border-bottom: 1px solid rgba(167, 243, 208, 0.75);
+
+      &:nth-last-child(-n + 2) {
+        border-bottom: none;
+      }
+    }
+
+    .dds-subcards {
+      grid-template-columns: 1fr;
+    }
+
+    .dds-data-grid--4 {
+      grid-template-columns: repeat(2, minmax(0, 1fr));
+    }
+  }
+
+  @media (max-width: 900px) {
+    .dds-kv-grid--cols-3 {
+      grid-template-columns: repeat(2, minmax(0, 1fr));
+    }
+  }
+</style>

+ 51 - 0
web-admin/src/components/business/dialog-form-body/index.vue

@@ -0,0 +1,51 @@
+<!-- 弹窗正文:加载中显示骨架,避免 dialog-form-layout 先小后大 -->
+<template>
+  <div
+    class="dialog-form-body"
+    :class="[bodyClass, { 'is-dialog-loading': loading }]"
+  >
+    <DialogDetailSkeleton
+      v-if="loading"
+      :variant="skeletonVariant"
+      :section-count="skeletonSectionCount"
+      :field-count="skeletonFieldCount"
+      :tab-count="skeletonTabCount"
+      :grid-columns="skeletonGridColumns"
+      :compact="skeletonCompact"
+    />
+    <slot v-else />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import DialogDetailSkeleton from '@/components/business/dialog-detail-skeleton/index.vue'
+
+  withDefaults(
+    defineProps<{
+      loading?: boolean
+      bodyClass?: string
+      skeletonVariant?: 'system-view' | 'system-view-image' | 'form' | 'tabs-form' | 'tabs-detail' | 'simple'
+      skeletonSectionCount?: number
+      skeletonFieldCount?: number
+      skeletonTabCount?: number
+      skeletonGridColumns?: 2 | 3 | 4
+      skeletonCompact?: boolean
+    }>(),
+    {
+      loading: false,
+      bodyClass: '',
+      skeletonVariant: 'form',
+      skeletonSectionCount: 2,
+      skeletonFieldCount: 6,
+      skeletonTabCount: 5,
+      skeletonGridColumns: 2,
+      skeletonCompact: false
+    }
+  )
+</script>
+
+<style scoped lang="scss">
+  .dialog-form-body.is-dialog-loading {
+    min-height: min(400px, calc(100vh - 220px));
+  }
+</style>

+ 15 - 1
web-admin/src/components/business/product-view-dialog/index.vue

@@ -9,7 +9,14 @@
     @update:model-value="emit('update:modelValue', $event)"
     @closed="handleClosed"
   >
-    <div v-loading="loading" class="product-form-container">
+    <div class="product-form-container" :class="{ 'is-view-loading': loading }">
+      <DialogDetailSkeleton
+        v-if="loading"
+        variant="system-view-image"
+        :section-count="3"
+        :grid-columns="2"
+      />
+      <template v-else>
       <div class="view-hero">
         <el-image
           v-if="formData.imageUrl"
@@ -229,6 +236,7 @@
           </div>
         </div>
       </div>
+      </template>
     </div>
 
     <template #footer>
@@ -261,6 +269,7 @@
 <script setup lang="ts">
 import { computed, reactive, ref, watch } from 'vue'
 import { Close, Document, Money, Picture, Setting, VideoPlay } from '@element-plus/icons-vue'
+import DialogDetailSkeleton from '@/components/business/dialog-detail-skeleton/index.vue'
 import { buildTreePathLabelFromFlat } from '@/utils/tree-path'
 import {
   fetchProductDetail,
@@ -616,6 +625,11 @@ watch(
 .product-form-container {
   max-height: 62vh;
   overflow-y: auto;
+
+  &.is-view-loading {
+    min-height: min(420px, calc(100vh - 220px));
+    overflow: hidden;
+  }
   padding-right: 6px;
   &::-webkit-scrollbar {
     width: 6px;

+ 3 - 0
web-admin/src/components/business/qc-task-view-dialog/index.vue

@@ -9,6 +9,9 @@
     :icon="Tickets"
     :meta="viewDetail?.taskNo || ''"
     :sections="viewSections"
+    :loading="loading"
+    :skeleton-section-count="3"
+    :skeleton-field-count="8"
     append-to-body
     compact
     modal-class="qc-task-view-dialog-mask"

+ 26 - 2
web-admin/src/components/business/system-view-dialog/index.vue

@@ -13,7 +13,16 @@
     :modal-class="modalClass"
     style="backdrop-filter: none"
   >
-    <div class="dialog-scroll">
+    <div class="dialog-scroll" :class="{ 'is-view-loading': loading }">
+      <DialogDetailSkeleton
+        v-if="loading"
+        variant="system-view"
+        :section-count="skeletonSectionCount"
+        :field-count="skeletonFieldCount"
+        :grid-columns="gridColumns"
+        :compact="compact"
+      />
+      <template v-else>
       <!-- hero -->
       <div class="view-hero">
         <div class="view-hero-icon">
@@ -64,6 +73,7 @@
         </div>
       </div>
       <slot name="append" />
+      </template>
     </div>
 
     <template #footer>
@@ -77,6 +87,7 @@
 <script setup lang="ts">
   import { computed, type Component } from 'vue'
   import { Close, Document } from '@element-plus/icons-vue'
+  import DialogDetailSkeleton from '@/components/business/dialog-detail-skeleton/index.vue'
 
   function isIconComponent(val: unknown): val is Component {
     return val != null && (typeof val === 'object' || typeof val === 'function')
@@ -129,6 +140,12 @@
       dialogClass?: string
       /** 紧凑布局:缩小头部/区块间距与内容区最大高度 */
       compact?: boolean
+      /** 详情异步加载中:展示骨架屏 */
+      loading?: boolean
+      /** 骨架区块数量(默认 2) */
+      skeletonSectionCount?: number
+      /** 每区块骨架字段行数(默认 6) */
+      skeletonFieldCount?: number
     }>(),
     {
       width: '720px',
@@ -136,7 +153,10 @@
       appendToBody: false,
       modalClass: '',
       dialogClass: '',
-      compact: false
+      compact: false,
+      loading: false,
+      skeletonSectionCount: 2,
+      skeletonFieldCount: 6
     }
   )
 
@@ -166,6 +186,10 @@
   .dialog-scroll {
     overflow-y: auto;
     max-height: calc(100vh - 120px);
+
+    &.is-view-loading {
+      overflow: hidden;
+    }
   }
 
   .view-hero {

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

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_components_business_dialog_form_body_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/business/dialog-form-body/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

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

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

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


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


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

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

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


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


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

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_components_business_system_view_dialog_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/business/system-view-dialog/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

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

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

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


+ 1 - 0
web-admin/src/graphify-out/cache/ast/565374128500ab8c49352ac99f35803e7d08203bcff6251f0ccbcb168e8c3c71.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": []}

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


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

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

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

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_system_role_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/role/index.vue", "source_location": "L1"}, {"id": "role_index_walk", "label": "walk()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/role/index.vue", "source_location": "L519"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_system_role_index_vue", "target": "role_index_walk", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/role/index.vue", "source_location": "L519", "weight": 1.0}], "raw_calls": [{"caller_nid": "role_index_walk", "callee": "push", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/role/index.vue", "source_location": "L521"}]}

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


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

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_pilot_service_channel_channelviewdialog_vue", "label": "ChannelViewDialog.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/pilot-service/channel/ChannelViewDialog.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

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


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

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_protocol_protocolviewdialog_vue", "label": "ProtocolViewDialog.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue", "source_location": "L1"}, {"id": "protocol_protocolviewdialog_dash", "label": "dash()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue", "source_location": "L316"}, {"id": "protocol_protocolviewdialog_handleclosed", "label": "handleClosed()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue", "source_location": "L1635"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_protocol_protocolviewdialog_vue", "target": "protocol_protocolviewdialog_dash", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue", "source_location": "L316", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_protocol_protocolviewdialog_vue", "target": "protocol_protocolviewdialog_handleclosed", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue", "source_location": "L1635", "weight": 1.0}], "raw_calls": [{"caller_nid": "protocol_protocolviewdialog_dash", "callee": "unitName", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue", "source_location": "L316"}]}

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


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

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_components_business_dialog_detail_skeleton_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/business/dialog-detail-skeleton/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

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


+ 9 - 2
web-admin/src/views/cell-storage/protocol/ProtocolFormDialog.vue

@@ -9,7 +9,13 @@
         destroy-on-close
         :close-on-click-modal="false"
       >
-        <div v-loading="dialogFormLoading" class="protocol-dialog-body min-h-[200px]">
+        <DialogFormBody
+          :loading="dialogFormLoading"
+          body-class="protocol-dialog-body min-h-[200px]"
+          skeleton-variant="tabs-form"
+          :skeleton-tab-count="formTabs.length"
+          :skeleton-section-count="4"
+        >
           <div class="detail-tabs">
             <div
               v-for="tab in formTabs"
@@ -1024,7 +1030,7 @@
               </div>
             </div>
           </el-form>
-        </div>
+        </DialogFormBody>
 
         <template #footer>
           <el-button class="btn-ghost" @click="emit('update:modelValue', false)">
@@ -1174,6 +1180,7 @@
       </el-dialog>
 </template>
 <script setup lang="ts">
+  import DialogFormBody from '@/components/business/dialog-form-body/index.vue'
   import { ElMessage } from 'element-plus'
   import {
     Plus,

+ 21 - 3
web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue

@@ -11,20 +11,26 @@
     @update:model-value="emit('update:modelValue', $event)"
     @closed="handleClosed"
   >
-    <div v-loading="detailLoading" class="protocol-view-body">
+    <div class="protocol-view-body" :class="{ 'is-detail-loading': detailLoading && !bundle }">
       <div class="detail-tabs">
         <div
           v-for="tab in formTabs"
           :key="tab.key"
           class="detail-tab"
-          :class="{ active: activeTab === tab.key }"
+          :class="{ active: activeTab === tab.key, 'is-disabled': detailLoading && !bundle }"
           @click="scrollToSection(tab.key)"
         >
           {{ tab.label }}
         </div>
       </div>
 
-      <template v-if="bundle">
+      <DialogDetailSkeleton
+        v-if="detailLoading && !bundle"
+        variant="tabs-detail"
+        :tab-count="formTabs.length"
+      />
+
+      <template v-else-if="bundle">
         <!-- KPI 摘要条 -->
         <div class="kpi-strip">
           <div class="kpi-card">
@@ -781,6 +787,7 @@
 <script setup lang="ts">
   import { computed, nextTick, ref, watch } from 'vue'
   import { Calendar, CircleCheck, Close, Document, User } from '@element-plus/icons-vue'
+  import DialogDetailSkeleton from '@/components/business/dialog-detail-skeleton/index.vue'
   import ProtocolAttachmentReadonly from './ProtocolAttachmentReadonly.vue'
   import SampleQcInfoCards from '@/components/business/sample-qc-info-cards/index.vue'
   import {
@@ -1616,6 +1623,7 @@
   }
 
   function scrollToSection(key: string) {
+    if (detailLoading.value && !bundle.value) return
     activeTab.value = key
     nextTick(() => {
       document
@@ -1644,6 +1652,10 @@
 <style scoped lang="scss">
   .protocol-view-body {
     overflow: visible;
+
+    &.is-detail-loading {
+      min-height: min(680px, calc(100vh - 220px));
+    }
   }
 
   /* ---- Sticky Tab 导航 ---- */
@@ -1680,6 +1692,12 @@
     &:hover:not(.active) {
       color: #059669;
     }
+
+    &.is-disabled {
+      cursor: default;
+      opacity: 0.65;
+      pointer-events: none;
+    }
   }
 
   /* ---- KPI 摘要条(统一薄荷绿通栏 + 图标徽章) ---- */

+ 10 - 1
web-admin/src/views/pilot-service/channel/ChannelViewDialog.vue

@@ -12,7 +12,7 @@
     @update:model-value="emit('update:modelValue', $event)"
     @closed="handleClosed"
   >
-    <div v-loading="loading" class="channel-view-body">
+    <div class="channel-view-body" :class="{ 'is-sub-loading': loading && channel }">
       <template v-if="channel">
         <div class="detail-tabs">
           <div
@@ -303,7 +303,15 @@
           </div>
           <el-empty v-else description="暂无协议信息" :image-size="48" />
         </div>
+
+        <DialogDetailSkeleton
+          v-if="loading"
+          variant="simple"
+          :section-count="2"
+          class="channel-view-sub-skeleton"
+        />
       </template>
+      <DialogDetailSkeleton v-else variant="tabs-detail" :tab-count="viewTabs.length" />
     </div>
 
     <template #footer>
@@ -336,6 +344,7 @@
 <script setup lang="ts">
   import { computed, nextTick, ref, watch } from 'vue'
   import { Calendar, Close, CollectionTag, OfficeBuilding, User } from '@element-plus/icons-vue'
+  import DialogDetailSkeleton from '@/components/business/dialog-detail-skeleton/index.vue'
   import ProtocolAttachmentReadonly from '@/views/cell-storage/protocol/ProtocolAttachmentReadonly.vue'
   import { downloadAttachmentFile } from '@/utils/download-attachment'
   import {

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

@@ -263,9 +263,9 @@
             class="route-config-center"
             @dragover.prevent
             @drop="onDropToCenter"
-            v-loading="stepListLoading"
-            element-loading-text="加载中..."
           >
+            <el-skeleton v-if="stepListLoading" animated :rows="6" class="route-config-step-skeleton" />
+            <template v-else>
             <div class="route-config-center-header">
               <h3>工序步骤</h3>
               <span class="route-config-step-count">共 {{ stepList.length }} 步</span>
@@ -316,6 +316,7 @@
                 <p>从左侧拖拽工序到此处,添加路线步骤</p>
               </div>
             </div>
+            </template>
           </div>
 
           <!-- 右侧:步骤详情 -->

+ 10 - 1
web-admin/src/views/quality-inspection/qc-task/index.vue

@@ -151,7 +151,14 @@
         destroy-on-close
         :close-on-click-modal="false"
       >
-        <el-form v-loading="resultLoading" label-position="top" class="biz-dialog-form">
+        <DialogFormBody
+          :loading="resultLoading"
+          skeleton-variant="form"
+          :skeleton-section-count="2"
+          :skeleton-field-count="6"
+          :skeleton-grid-columns="3"
+        >
+        <el-form label-position="top" class="biz-dialog-form">
           <div class="form-section">
             <div class="section-title">检测参数</div>
             <div
@@ -280,6 +287,7 @@
             </div>
           </div>
         </el-form>
+        </DialogFormBody>
         <template #footer>
           <el-button class="btn-ghost" @click="resultVisible = false"><el-icon><Close /></el-icon>取消</el-button>
           <el-button class="btn-main" type="primary" :loading="resultLoading" @click="submitResult"><el-icon><Check /></el-icon>{{ resultSubmitLabel }}</el-button>
@@ -376,6 +384,7 @@
   import { Close, Check, Search, RefreshLeft, VideoPlay, View, Tickets, EditPen, Plus } from '@element-plus/icons-vue'
   import ProtocolFinanceConfirmFields from '@/components/business/protocol-finance-confirm-fields/index.vue'
   import ProtocolAttachmentReadonly from '@/views/cell-storage/protocol/ProtocolAttachmentReadonly.vue'
+  import DialogFormBody from '@/components/business/dialog-form-body/index.vue'
   import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
   import type { ViewSection } from '@/components/business/system-view-dialog/index.vue'
   import {

+ 14 - 0
web-admin/src/views/quality-inspection/sample-reception/index.vue

@@ -1186,6 +1186,20 @@
       font-size: 14px;
       font-weight: 600;
       color: var(--mint-text, #0f172a);
+      padding-left: 10px;
+      position: relative;
+
+      &::before {
+        content: '';
+        position: absolute;
+        left: 0;
+        top: 50%;
+        transform: translateY(-50%);
+        width: 3px;
+        height: 14px;
+        background-color: var(--el-color-primary);
+        border-radius: 2px;
+      }
     }
 
     .form-section-divider {

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

@@ -9,7 +9,13 @@
     destroy-on-close
     :close-on-click-modal="false"
   >
-    <div v-loading="dialogFormLoading" class="protocol-dialog-body min-h-[200px]">
+    <DialogFormBody
+      :loading="dialogFormLoading"
+      body-class="protocol-dialog-body min-h-[200px]"
+      skeleton-variant="tabs-form"
+      :skeleton-tab-count="formTabs.length"
+      :skeleton-section-count="4"
+    >
       <div class="detail-tabs">
         <div
           v-for="tab in formTabs"
@@ -785,7 +791,7 @@
           </div>
         </div>
       </el-form>
-    </div>
+    </DialogFormBody>
 
     <template #footer>
       <el-button class="btn-ghost" @click="emit('update:modelValue', false)">
@@ -928,6 +934,7 @@
   </el-dialog>
 </template>
 <script setup lang="ts">
+  import DialogFormBody from '@/components/business/dialog-form-body/index.vue'
   import { ElMessage } from 'element-plus'
   import { Plus, Close, Check, Delete, Document, VideoPlay, Loading, Download } from '@element-plus/icons-vue'
   import {

+ 15 - 3
web-admin/src/views/research-donation/list/DonationViewDialog.vue

@@ -11,20 +11,26 @@
     @update:model-value="emit('update:modelValue', $event)"
     @closed="handleClosed"
   >
-    <div v-loading="loading" class="donation-view-body">
+    <div class="donation-view-body" :class="{ 'is-detail-loading': loading && !bundle }">
       <div class="detail-tabs">
         <div
           v-for="tab in formTabs"
           :key="tab.key"
           class="detail-tab"
-          :class="{ active: activeTab === tab.key }"
+          :class="{ active: activeTab === tab.key, 'is-disabled': loading && !bundle }"
           @click="scrollToSection(tab.key)"
         >
           {{ tab.label }}
         </div>
       </div>
 
-      <template v-if="bundle">
+      <DialogDetailSkeleton
+        v-if="loading && !bundle"
+        variant="tabs-detail"
+        :tab-count="formTabs.length"
+      />
+
+      <template v-else-if="bundle">
         <!-- KPI 摘要条 -->
         <div class="kpi-strip">
           <div class="kpi-card">
@@ -573,6 +579,7 @@
 <script setup lang="ts">
   import { computed, nextTick, ref, watch } from 'vue'
   import { Calendar, CircleCheck, Close, Document, User } from '@element-plus/icons-vue'
+  import DialogDetailSkeleton from '@/components/business/dialog-detail-skeleton/index.vue'
   import ProtocolAttachmentReadonly from '@/views/cell-storage/protocol/ProtocolAttachmentReadonly.vue'
   import SampleQcInfoCards from '@/components/business/sample-qc-info-cards/index.vue'
   import {
@@ -949,6 +956,7 @@
   }
 
   function scrollToSection(key: string) {
+    if (loading.value && !bundle.value) return
     activeTab.value = key
     nextTick(() => {
       document
@@ -974,6 +982,10 @@
 <style scoped lang="scss">
   .donation-view-body {
     overflow: visible;
+
+    &.is-detail-loading {
+      min-height: min(680px, calc(100vh - 220px));
+    }
   }
 
   /* ---- Sticky Tab 导航 ---- */

+ 29 - 4
web-admin/src/views/system/order/index.vue

@@ -295,7 +295,15 @@
       class="order-module-dialog order-detail-dialog"
       modal-class="order-module-dialog-mask"
     >
-      <div v-loading="detailLoading" class="dialog-scroll order-detail-dialog-scroll">
+      <div class="dialog-scroll order-detail-dialog-scroll" :class="{ 'is-view-loading': detailLoading }">
+      <DialogDetailSkeleton
+        v-if="detailLoading"
+        variant="system-view"
+        :section-count="4"
+        :field-count="8"
+        :grid-columns="3"
+      />
+      <template v-else>
       <div class="view-hero order-view-hero">
         <div class="order-view-hero-icon">
           <el-icon><ShoppingBag /></el-icon>
@@ -810,6 +818,7 @@
         </div>
       </div>
 
+      </template>
       </div>
       <template #footer>
         <el-button class="btn-ghost" @click="viewVisible = false"><el-icon><Close /></el-icon>关闭</el-button>
@@ -830,7 +839,15 @@
     </el-dialog>
 
     <div v-else v-show="viewVisible" class="order-embedded-detail">
-      <div v-loading="detailLoading" class="dialog-scroll order-detail-dialog-scroll">
+      <div class="dialog-scroll order-detail-dialog-scroll" :class="{ 'is-view-loading': detailLoading }">
+      <DialogDetailSkeleton
+        v-if="detailLoading"
+        variant="system-view"
+        :section-count="4"
+        :field-count="8"
+        :grid-columns="3"
+      />
+      <template v-else>
       <div class="view-hero order-view-hero">
         <div class="order-view-hero-icon">
           <el-icon><ShoppingBag /></el-icon>
@@ -1135,6 +1152,7 @@
         </div>
       </div>
 
+      </template>
       </div>
     </div>
 
@@ -1382,8 +1400,14 @@
       modal-class="order-module-dialog-mask"
       @opened="loadOrderPriceVersionDetail"
     >
-      <div v-loading="orderPriceVersionLoading" class="order-pv-body">
-        <template v-if="orderPriceVersionDetail">
+      <div class="order-pv-body" :class="{ 'is-view-loading': orderPriceVersionLoading }">
+        <DialogDetailSkeleton
+          v-if="orderPriceVersionLoading"
+          variant="system-view"
+          :section-count="2"
+          :field-count="6"
+        />
+        <template v-else-if="orderPriceVersionDetail">
           <div class="view-hero order-pv-hero">
             <div class="order-view-hero-icon">
               <el-icon><Setting /></el-icon>
@@ -1516,6 +1540,7 @@
   } from 'vue'
   import { useRoute, useRouter } from 'vue-router'
   import { ElMessage, ElMessageBox } from 'element-plus'
+  import DialogDetailSkeleton from '@/components/business/dialog-detail-skeleton/index.vue'
   import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
   import {
     ArrowDown,

+ 9 - 2
web-admin/src/views/system/post/index.vue

@@ -165,7 +165,13 @@
         destroy-on-close
         :close-on-click-modal="false"
       >
-        <div v-loading="postBindLoading" class="post-bind-dialog-body">
+        <DialogFormBody
+          :loading="postBindLoading"
+          body-class="post-bind-dialog-body"
+          skeleton-variant="form"
+          :skeleton-section-count="1"
+          :skeleton-field-count="4"
+        >
           <p v-if="postBindPostName" class="post-bind-subtitle">
             职位:<strong>{{ postBindPostName }}</strong>
           </p>
@@ -240,7 +246,7 @@
               </div>
             </el-form-item>
           </el-form>
-        </div>
+        </DialogFormBody>
         <template #footer>
           <el-button class="btn-ghost" @click="postBindDialogVisible = false">
             <el-icon><Close /></el-icon>
@@ -273,6 +279,7 @@
   import { ref, reactive, computed, onMounted, nextTick } from 'vue'
   import { ElMessage, ElMessageBox } from 'element-plus'
   import { Plus, Close, Check, User, View, Briefcase, RefreshLeft, Search } from '@element-plus/icons-vue'
+  import DialogFormBody from '@/components/business/dialog-form-body/index.vue'
   import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
   import { MAX_ADMIN_LIST_PAGE_SIZE } from '@/utils/fetch-paged-list'
   import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'

+ 9 - 6
web-admin/src/views/system/role/index.vue

@@ -197,7 +197,7 @@
         destroy-on-close
         :close-on-click-modal="false"
       >
-        <div v-loading="menuDialogLoading" style="min-height: 200px">
+        <DialogFormBody :loading="menuDialogLoading" skeleton-variant="simple" :skeleton-section-count="1">
           <!-- 父子关联勾选:选父级全选子级,选子级自动带上父级(与 check-strictly 互斥,故不加) -->
           <el-tree
             ref="menuTreeRef"
@@ -208,7 +208,7 @@
             :default-checked-keys="menuCheckedKeys"
             style="max-height: 500px; overflow-y: auto"
           />
-        </div>
+        </DialogFormBody>
         <template #footer>
           <el-button class="btn-ghost" @click="menuDialogVisible = false">
             <el-icon><Close /></el-icon>
@@ -363,8 +363,9 @@
         :sections="viewSections"
       >
         <template #menuTree>
-          <div v-loading="viewDetailLoading" class="role-view-tree-block">
-            <div v-if="viewMenuTreeData.length" class="role-view-tree-scroll">
+          <div class="role-view-tree-block">
+            <el-skeleton v-if="viewDetailLoading" animated :rows="5" class="role-view-tree-skeleton" />
+            <div v-else-if="viewMenuTreeData.length" class="role-view-tree-scroll">
               <el-tree
                 :key="'menu-' + viewTreeRenderKey"
                 ref="viewMenuTreeRef"
@@ -381,8 +382,9 @@
           </div>
         </template>
         <template #customDeptTree>
-          <div v-loading="viewDetailLoading" class="role-view-tree-block">
-            <div v-if="viewDeptTreeData.length" class="role-view-tree-scroll">
+          <div class="role-view-tree-block">
+            <el-skeleton v-if="viewDetailLoading" animated :rows="5" class="role-view-tree-skeleton" />
+            <div v-else-if="viewDeptTreeData.length" class="role-view-tree-scroll">
               <el-tree
                 :key="'dept-' + viewTreeRenderKey"
                 ref="viewDeptTreeRef"
@@ -408,6 +410,7 @@
   import { ref, reactive, computed, onMounted, nextTick, watch } from 'vue'
   import { ElMessage, ElMessageBox } from 'element-plus'
   import { Plus, Close, Check, DataAnalysis, View, Stamp, RefreshLeft, Search } from '@element-plus/icons-vue'
+  import DialogFormBody from '@/components/business/dialog-form-body/index.vue'
   import SystemViewDialog, {
     type ViewSection
   } from '@/components/business/system-view-dialog/index.vue'

+ 4 - 1
web-admin/src/views/system/workflow/components/ApprovalDialog.vue

@@ -10,7 +10,9 @@
     :show-close="false"
     style="backdrop-filter: none"
   >
-    <div v-if="bizLoading" v-loading="true" class="dialog-scroll" style="min-height: 120px" />
+    <div v-if="bizLoading" class="dialog-scroll is-view-loading" style="min-height: 120px">
+      <DialogDetailSkeleton variant="simple" :section-count="2" :field-count="4" />
+    </div>
     <div v-else class="dialog-scroll">
       <!-- Hero header -->
       <div class="view-hero">
@@ -75,6 +77,7 @@
 </template>
 
 <script setup lang="ts">
+  import DialogDetailSkeleton from '@/components/business/dialog-detail-skeleton/index.vue'
   import { fetchApproveTask, fetchRejectTask, fetchInstanceDetail } from '@/api/workflow'
   import { Checked, Close, Check } from '@element-plus/icons-vue'
 

+ 9 - 2
web-admin/src/views/system/workflow/my-applications/index.vue

@@ -126,8 +126,14 @@
         :show-close="false"
         style="backdrop-filter: none"
       >
-        <div v-loading="detailLoading" class="dialog-scroll">
-          <template v-if="detailData">
+        <div class="dialog-scroll" :class="{ 'is-view-loading': detailLoading }">
+          <DialogDetailSkeleton
+            v-if="detailLoading"
+            variant="system-view"
+            :section-count="2"
+            :field-count="6"
+          />
+          <template v-else-if="detailData">
             <div class="view-hero">
               <div class="view-hero-icon">
                 <el-icon :size="28"><Document /></el-icon>
@@ -194,6 +200,7 @@
 </template>
 
 <script setup lang="ts">
+  import DialogDetailSkeleton from '@/components/business/dialog-detail-skeleton/index.vue'
   import { fetchMyInstances, fetchBusinessAllTasks } from '@/api/workflow'
   import ProcessStatusTag from '../components/ProcessStatusTag.vue'
   import { Document, View, Search, RefreshLeft, Close } from '@element-plus/icons-vue'

+ 9 - 2
web-admin/src/views/system/workflow/task-completed/index.vue

@@ -127,8 +127,14 @@
         :show-close="false"
         style="backdrop-filter: none"
       >
-        <div v-loading="detailLoading" class="dialog-scroll">
-          <template v-if="detailData">
+        <div class="dialog-scroll" :class="{ 'is-view-loading': detailLoading }">
+          <DialogDetailSkeleton
+            v-if="detailLoading"
+            variant="system-view"
+            :section-count="2"
+            :field-count="6"
+          />
+          <template v-else-if="detailData">
             <div class="view-hero">
               <div class="view-hero-icon">
                 <el-icon :size="28"><Document /></el-icon>
@@ -197,6 +203,7 @@
 </template>
 
 <script setup lang="ts">
+  import DialogDetailSkeleton from '@/components/business/dialog-detail-skeleton/index.vue'
   import { fetchCompletedTasks, fetchInstanceDetail } from '@/api/workflow'
   import ProcessStatusTag from '../components/ProcessStatusTag.vue'
   import { Document, View, Search, RefreshLeft, Close } from '@element-plus/icons-vue'

+ 9 - 2
web-admin/src/views/system/workflow/task-pending/index.vue

@@ -124,8 +124,14 @@
         :show-close="false"
         style="backdrop-filter: none"
       >
-        <div v-loading="detailLoading" class="dialog-scroll">
-          <template v-if="detailData">
+        <div class="dialog-scroll" :class="{ 'is-view-loading': detailLoading }">
+          <DialogDetailSkeleton
+            v-if="detailLoading"
+            variant="system-view"
+            :section-count="2"
+            :field-count="6"
+          />
+          <template v-else-if="detailData">
             <div class="view-hero">
               <div class="view-hero-icon">
                 <el-icon :size="28"><Document /></el-icon>
@@ -194,6 +200,7 @@
 </template>
 
 <script setup lang="ts">
+  import DialogDetailSkeleton from '@/components/business/dialog-detail-skeleton/index.vue'
   import { fetchPendingTasks, fetchInstanceDetail } from '@/api/workflow'
   import ApprovalDialog from '../components/ApprovalDialog.vue'
   import ProcessStatusTag from '../components/ProcessStatusTag.vue'

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