zlwu il y a 6 mois
Parent
commit
70cf3a1409
36 fichiers modifiés avec 715 ajouts et 352 suppressions
  1. 31 3
      backend/app/api/admin.py
  2. 123 118
      graphify-out/GRAPH_REPORT.md
  3. 1 0
      graphify-out/cache/ast/1201fbeeb5101eb90b7a878648e329adab59bfcfd584b4aa5bfe3a8f545a8cad.json
  4. 1 0
      graphify-out/cache/ast/19bdc910191a019358fa77ce588a02f1a3864f2a731247a3624b1074fa79b7ac.json
  5. 0 0
      graphify-out/cache/ast/200d962daea4bd4a19ce6a072217ba2d627e9f24b20774776fec1338acc370f3.json
  6. 1 0
      graphify-out/cache/ast/21205ae389b5db6d74694d37cf50e5fb102b9062ba7eca1c094a2c05602452c8.json
  7. 1 0
      graphify-out/cache/ast/25874112b4e9cb0bf1a3767d9b1b6a11223051b5870dcce236569b12877b5ec5.json
  8. 0 0
      graphify-out/cache/ast/30511557ea2de5b7033d26d11aec23aa226bce4b2c3e1cbb9a428c21d97968af.json
  9. 0 0
      graphify-out/cache/ast/332bafcc5ccd7df4eac7b18ab2b337d57d35a151bf60863c5fc5a21a1a17265f.json
  10. 1 0
      graphify-out/cache/ast/629d79232c2b3b06d36e074d44d0ca60e19b4a268ace391f814c825dd843aa9b.json
  11. 0 0
      graphify-out/cache/ast/683214eaca1fe5391a61bf37b0556a8e898e50b449d1a263a036d4d9c9f187c9.json
  12. 1 0
      graphify-out/cache/ast/6e37a9c73b5b64c19cf7006922dbc10a4323de5fd220afccfab632fb9615e5ac.json
  13. 1 0
      graphify-out/cache/ast/723405b1285c572c9e099d59f3c8bcef8b9860f308547903b23dec5d88d85276.json
  14. 1 0
      graphify-out/cache/ast/7f5eacf6615c79fcdf75f5a9c93924a3bb1cd4a353de4d2f8972cb67524f8e89.json
  15. 1 0
      graphify-out/cache/ast/842ee7a2074b944332d6c892c78dcd8b6b6d76c3a6b5ef44b2a6455de046ca34.json
  16. 0 0
      graphify-out/cache/ast/a5d653cf15823c5a02c6585745e03cff8b7606a27d74cdfef1afe329186ac04e.json
  17. 1 0
      graphify-out/cache/ast/a681077d1a86dccf965b79bd5bd36c24d7b5618940e2bb1f91fc4046557771ab.json
  18. 0 0
      graphify-out/cache/ast/ac9106c1a0efda46ea1f0051e2ba86f41e4797f834d384e1280b8d25d10c32bb.json
  19. 1 0
      graphify-out/cache/ast/b9d4dfb713968281b123074afc73c5aaef6878d868701ef7588d221e29abea2d.json
  20. 1 0
      graphify-out/cache/ast/bdfdb7e8468c8c89cd6ecb4170a1951359a2e822aa6ab7b9d996b79e04fc193c.json
  21. 0 0
      graphify-out/cache/ast/cb0bcefa81564cfa06e2dba328ca6c79a61a1d89cdbbcd178b795acf7ea29b2a.json
  22. 1 0
      graphify-out/cache/ast/ce77b593e67bcd13edc15df6471ccb52589a5ef3da4ae24b303cf3843bf3c2fa.json
  23. 1 0
      graphify-out/cache/ast/da2b72a74710c8016b218e6d4cd9ee0db68668d1e2bbc8ca9b178f443e1707ed.json
  24. 1 0
      graphify-out/cache/ast/e41c1dcac2beec7f572250c5b0107b15d030511fb414ef6a26b2550061491bcb.json
  25. 1 0
      graphify-out/cache/ast/e84c8b1227dd040625b4590d4a7b823a5efdf27d7c3ce80bab451b36d688d188.json
  26. 1 0
      graphify-out/cache/ast/ea746c5476c6fe533f0269d3540f106e00da35215b20675d22bada2b5c1d4aea.json
  27. 1 0
      graphify-out/cache/ast/f16c31d3b2502e584db1f548788fa6c8ae8190444469628125c5e1bbef91cbdb.json
  28. 1 0
      graphify-out/cache/ast/f31952118aea1747509ef4d95cbe33bea7d92875077ae36e03b8bc458814d6f3.json
  29. 1 0
      graphify-out/cache/ast/ff73c2ea753322368a74530c3afda48c13b939cd3fbdeff6402e61944c563587.json
  30. 1 1
      graphify-out/graph.html
  31. 112 112
      graphify-out/graph.json
  32. 23 3
      sqxp-wx/pages/client/order/create/create.vue
  33. 38 31
      sqxp-wx/pages/supplier/inquiry/inquiry.vue
  34. 97 40
      web-admin/src/views/system/client-settlement/index.vue
  35. 91 10
      web-admin/src/views/system/inquiry-quotation/index.vue
  36. 179 34
      web-admin/src/views/system/supplier-settlement/index.vue

+ 31 - 3
backend/app/api/admin.py

@@ -5322,6 +5322,8 @@ def _build_order_line_children_for_settlement_list(
     pli_map: dict,
     product_map: dict,
     unit_map: dict,
+    supplier_id: Optional[str] = None,
+    supplier_name: Optional[str] = None,
 ) -> List[dict]:
     children: List[dict] = []
     for ln in lines:
@@ -5359,6 +5361,8 @@ def _build_order_line_children_for_settlement_list(
             "sellingAmount": sell_amt,
             "supplierUnitPrice": sup_u,
             "supplierAmount": sup_amt,
+            "supplierId": supplier_id,
+            "supplierName": supplier_name,
         })
     return children
 
@@ -5411,8 +5415,10 @@ def _build_client_pending_settlement_tree_rows(
 
     out: List[dict] = []
     for o in orders:
+        sid = supplier_id_by_price_list.get(o.price_list_id)
+        sname = supplier_name_map.get(sid) if sid else None
         ch = _build_order_line_children_for_settlement_list(
-            db, o, by_oid.get(o.id, []), pli_map, product_map, unit_map
+            db, o, by_oid.get(o.id, []), pli_map, product_map, unit_map, sid, sname
         )
         sup_ref = sum(c["supplierAmount"] for c in ch)
         normalized_payment_method = order_crud.normalize_payment_method(o.payment_method)
@@ -5435,6 +5441,7 @@ def _build_client_pending_settlement_tree_rows(
             "settlementAmount": float(o.total_amount or 0),
             "supplierReferenceTotal": round(sup_ref, 2),
             "orderStatus": o.order_status,
+            "deliveryStatus": getattr(o, "delivery_status", None) or "pending_dispatch",
             "settlementSelectable": sel,
             "paymentMethod": normalized_payment_method,
             "clientSettlementStatus": o.client_settlement_status,
@@ -5491,8 +5498,10 @@ def _build_supplier_pending_settlement_tree_rows(
 
     out: List[dict] = []
     for o in orders:
+        sid = sup_by_oid.get(o.id)
+        sname = supplier_map.get(sid) if sid else None
         ch = _build_order_line_children_for_settlement_list(
-            db, o, by_oid.get(o.id, []), pli_map, product_map, unit_map
+            db, o, by_oid.get(o.id, []), pli_map, product_map, unit_map, sid, sname
         )
         sup_order_total = sum(c["supplierAmount"] for c in ch)
         normalized_payment_method = order_crud.normalize_payment_method(o.payment_method)
@@ -5501,7 +5510,6 @@ def _build_supplier_pending_settlement_tree_rows(
             and normalized_payment_method == "offline"
             and getattr(o, "client_settlement_status", None) == "pending"
         )
-        sid = sup_by_oid.get(o.id)
         out.append({
             "rowType": "order",
             "id": o.id,
@@ -5516,6 +5524,7 @@ def _build_supplier_pending_settlement_tree_rows(
             "totalAmount": float(o.total_amount or 0),
             "clientSellingTotal": float(o.total_amount or 0),
             "orderStatus": o.order_status,
+            "deliveryStatus": getattr(o, "delivery_status", None) or "pending_dispatch",
             "settlementSelectable": sel,
             "supplierSettlementStatus": o.supplier_settlement_status,
             "clientSettlementStatus": o.client_settlement_status,
@@ -6012,6 +6021,15 @@ def _build_settlement_detail_item_tree(db: Session, settlement_items: List[Any])
 
     pl_ids = list({o.price_list_id for o in order_map.values() if o and o.price_list_id})
     pli_map = _price_list_item_map(db, pl_ids)
+    price_lists = (
+        db.query(BizPriceList).filter(BizPriceList.id.in_(pl_ids), BizPriceList.is_del == 0).all()
+    ) if pl_ids else []
+    supplier_id_by_price_list = {p.id: p.supplier_id for p in price_lists}
+    supplier_ids = list({sid for sid in supplier_id_by_price_list.values() if sid})
+    suppliers = (
+        db.query(SysUser).filter(SysUser.id.in_(supplier_ids), SysUser.is_del == 0).all()
+    ) if supplier_ids else []
+    supplier_name_map = {u.id: (u.real_name or u.username) for u in suppliers}
 
     out: List[dict] = []
     for it in settlement_items:
@@ -6038,6 +6056,7 @@ def _build_settlement_detail_item_tree(db: Session, settlement_items: List[Any])
             qty = float(ln.quantity or 0)
             sell_amt = float(ln.amount or 0) if ln.amount is not None else round(qty * sell_u, 2)
             sup_amt = round(qty * sup_u, 2)
+            supplier_id = supplier_id_by_price_list.get(order.price_list_id) if order else None
             children.append({
                 "rowType": "line",
                 "id": f"line-{ln.id}",
@@ -6056,10 +6075,13 @@ def _build_settlement_detail_item_tree(db: Session, settlement_items: List[Any])
                 "sellingAmount": sell_amt,
                 "supplierUnitPrice": sup_u,
                 "supplierAmount": sup_amt,
+                "supplierId": supplier_id,
+                "supplierName": supplier_name_map.get(supplier_id),
                 "deliveredQuantity": float(ln.delivered_quantity or 0),
                 "note": ln.note,
             })
 
+        supplier_id = supplier_id_by_price_list.get(order.price_list_id) if order else None
         out.append({
             "rowType": "order",
             "id": f"order-{it.id}",
@@ -6070,6 +6092,8 @@ def _build_settlement_detail_item_tree(db: Session, settlement_items: List[Any])
             "totalAmount": float(it.order_amount or 0),
             "clientId": order.client_id if order else None,
             "clientName": client_map.get(order.client_id) if order else None,
+            "supplierId": supplier_id,
+            "supplierName": supplier_name_map.get(supplier_id),
             "regionId": rid,
             "regionName": region_map.get(rid),
             "orderCreateAt": str(order.create_at) if order and order.create_at else None,
@@ -6077,6 +6101,7 @@ def _build_settlement_detail_item_tree(db: Session, settlement_items: List[Any])
             "contactPhone": order.contact_phone if order else None,
             "deliveryAddress": order.delivery_address if order else None,
             "orderStatus": order.order_status if order else None,
+            "deliveryStatus": (getattr(order, "delivery_status", None) or "pending_dispatch") if order else None,
             "paymentMethod": order_crud.normalize_payment_method(order.payment_method) if order else None,
             "createAt": str(it.create_at) if it.create_at else None,
             "children": children,
@@ -7551,6 +7576,9 @@ def cancel_order_admin(
     current_user: dict = Depends(require_admin)
 ):
     """运营端取消订单"""
+    request, req_err = _resolve_admin_payload_with_crypto_fallback(request, current_user)
+    if req_err is not None:
+        return req_err
     from app.crud.order import order_crud
 
     order = order_crud.get(db, order_id)

+ 123 - 118
graphify-out/GRAPH_REPORT.md

@@ -1,12 +1,12 @@
 # Graph Report - sqxp_副本2  (2026-04-30)
 
 ## Corpus Check
-- 701 files · ~636,516 words
+- 701 files · ~637,582 words
 - Verdict: corpus is large enough that graph structure adds value.
 
 ## Summary
-- 4826 nodes · 23142 edges · 59 communities detected
-- Extraction: 22% EXTRACTED · 78% INFERRED · 0% AMBIGUOUS · INFERRED: 17994 edges (avg confidence: 0.55)
+- 4853 nodes · 23872 edges · 60 communities detected
+- Extraction: 22% EXTRACTED · 78% INFERRED · 0% AMBIGUOUS · INFERRED: 18723 edges (avg confidence: 0.55)
 - Token cost: 0 input · 0 output
 
 ## Community Hubs (Navigation)
@@ -37,84 +37,85 @@
 - [[_COMMUNITY_Community 26|Community 26]]
 - [[_COMMUNITY_Community 27|Community 27]]
 - [[_COMMUNITY_Community 28|Community 28]]
-- [[_COMMUNITY_Community 30|Community 30]]
-- [[_COMMUNITY_Community 32|Community 32]]
+- [[_COMMUNITY_Community 29|Community 29]]
+- [[_COMMUNITY_Community 31|Community 31]]
 - [[_COMMUNITY_Community 33|Community 33]]
 - [[_COMMUNITY_Community 34|Community 34]]
 - [[_COMMUNITY_Community 35|Community 35]]
-- [[_COMMUNITY_Community 37|Community 37]]
+- [[_COMMUNITY_Community 36|Community 36]]
 - [[_COMMUNITY_Community 38|Community 38]]
 - [[_COMMUNITY_Community 39|Community 39]]
 - [[_COMMUNITY_Community 40|Community 40]]
 - [[_COMMUNITY_Community 41|Community 41]]
-- [[_COMMUNITY_Community 46|Community 46]]
-- [[_COMMUNITY_Community 48|Community 48]]
+- [[_COMMUNITY_Community 42|Community 42]]
+- [[_COMMUNITY_Community 47|Community 47]]
 - [[_COMMUNITY_Community 49|Community 49]]
 - [[_COMMUNITY_Community 50|Community 50]]
 - [[_COMMUNITY_Community 51|Community 51]]
 - [[_COMMUNITY_Community 52|Community 52]]
 - [[_COMMUNITY_Community 53|Community 53]]
-- [[_COMMUNITY_Community 55|Community 55]]
+- [[_COMMUNITY_Community 54|Community 54]]
 - [[_COMMUNITY_Community 56|Community 56]]
-- [[_COMMUNITY_Community 60|Community 60]]
-- [[_COMMUNITY_Community 63|Community 63]]
+- [[_COMMUNITY_Community 57|Community 57]]
+- [[_COMMUNITY_Community 61|Community 61]]
 - [[_COMMUNITY_Community 64|Community 64]]
-- [[_COMMUNITY_Community 66|Community 66]]
-- [[_COMMUNITY_Community 79|Community 79]]
-- [[_COMMUNITY_Community 89|Community 89]]
-- [[_COMMUNITY_Community 91|Community 91]]
-- [[_COMMUNITY_Community 144|Community 144]]
+- [[_COMMUNITY_Community 65|Community 65]]
+- [[_COMMUNITY_Community 67|Community 67]]
+- [[_COMMUNITY_Community 80|Community 80]]
+- [[_COMMUNITY_Community 90|Community 90]]
+- [[_COMMUNITY_Community 92|Community 92]]
 - [[_COMMUNITY_Community 145|Community 145]]
 - [[_COMMUNITY_Community 146|Community 146]]
 - [[_COMMUNITY_Community 147|Community 147]]
 - [[_COMMUNITY_Community 148|Community 148]]
 - [[_COMMUNITY_Community 149|Community 149]]
+- [[_COMMUNITY_Community 150|Community 150]]
 
 ## God Nodes (most connected - your core abstractions)
-1. `BizPriceListItem` - 672 edges
-2. `BizInquiry` - 601 edges
-3. `BizQuotation` - 599 edges
-4. `BizAttachment` - 589 edges
-5. `BasProduct` - 588 edges
-6. `SysUser` - 579 edges
-7. `BasProductCategory` - 578 edges
-8. `BasProductUnit` - 578 edges
-9. `BizDeliveryTask` - 571 edges
-10. `BizOrder` - 568 edges
+1. `BizPriceListItem` - 699 edges
+2. `BizInquiry` - 628 edges
+3. `BizQuotation` - 626 edges
+4. `BizAttachment` - 616 edges
+5. `BasProduct` - 615 edges
+6. `SysUser` - 606 edges
+7. `BasProductCategory` - 605 edges
+8. `BasProductUnit` - 605 edges
+9. `BizDeliveryTask` - 598 edges
+10. `BizOrder` - 595 edges
 
 ## Surprising Connections (you probably didn't know these)
 - `get_active()` --calls--> `query()`  [INFERRED]
   backend/app/models/base.py → sqxp-wx/uni_modules/uni-data-select/components/uni-data-select/uni-data-select.vue
 - `register()` --calls--> `validate()`  [INFERRED]
   web-admin/src/views/auth/register/index.vue → sqxp-wx/pages/client/address/edit.vue
-- `toTime()` --calls--> `getTime()`  [INFERRED]
-  web-admin/src/views/dashboard/console/index.vue → sqxp-wx/uni_modules/uni-datetime-picker/components/uni-datetime-picker/util.js
 - `number()` --calls--> `inputMaxlength()`  [INFERRED]
   web-admin/src/views/system/inquiry-quotation/index.vue → sqxp-wx/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue
 - `number()` --calls--> `mounted()`  [INFERRED]
   web-admin/src/views/system/inquiry-quotation/index.vue → sqxp-wx/uni_modules/uni-table/components/uni-tr/uni-tr.vue
+- `showMenu()` --calls--> `nextTick()`  [INFERRED]
+  web-admin/src/views/widgets/context-menu/index.vue → sqxp-wx/wxcomponents/vant/common/utils.js
 
 ## Communities
 
 ### Community 0 - "Community 0"
 Cohesion: 0.01
-Nodes (624): adjust_order(), _aggregate_client_settlement_history(), _aggregate_supplier_settlement_history(), _apply_region_minutes_to_data(), approve_quotation(), assign_deliverer_to_order(), assign_delivery_alias(), batch_create_settlements() (+616 more)
+Nodes (647): adjust_order(), _aggregate_client_settlement_history(), _aggregate_supplier_settlement_history(), _apply_region_minutes_to_data(), approve_quotation(), assign_deliverer_to_order(), assign_delivery_alias(), batch_create_settlements() (+639 more)
 
 ### Community 1 - "Community 1"
 Cohesion: 0.08
-Nodes (596): 获取管理员的区域过滤条件      对于区域管理员,返回其所属区域ID(用于过滤数据)     对于超级管理员(无区域绑定),返回None(不过滤), 更新用户菜单授权(按 code 全量覆盖;兼容明文/密文载荷)。, 更新用户菜单授权(按 code 全量覆盖;兼容明文/密文载荷)。, 与 PUT 同逻辑:前端按 POST 模式调用时复用。, 兼容旧逻辑:返回单区域过滤ID。多区域场景返回排序后首个区域。, 更新用户菜单授权(按 code 全量覆盖;兼容明文/密文载荷)。, 兼容旧逻辑:返回单区域过滤ID。多区域场景返回排序后首个区域。, 上传用户头像图片,返回可写入 avatarUrl 的地址 (+588 more)
+Nodes (618): 获取管理员的区域过滤条件      对于区域管理员,返回其所属区域ID(用于过滤数据)     对于超级管理员(无区域绑定),返回None(不过滤), 更新用户菜单授权(按 code 全量覆盖;兼容明文/密文载荷)。, 更新用户菜单授权(按 code 全量覆盖;兼容明文/密文载荷)。, 与 PUT 同逻辑:前端按 POST 模式调用时复用。, 兼容旧逻辑:返回单区域过滤ID。多区域场景返回排序后首个区域。, 更新用户菜单授权(按 code 全量覆盖;兼容明文/密文载荷)。, 兼容旧逻辑:返回单区域过滤ID。多区域场景返回排序后首个区域。, 上传用户头像图片,返回可写入 avatarUrl 的地址 (+610 more)
 
 ### Community 2 - "Community 2"
 Cohesion: 0.01
-Nodes (187): handleSubmit(), loadDetail(), onRegionMultiConfirm(), regionLabel(), syncMultiFromForm(), validate(), fetchActiveRegions(), fetchAssignableMenuCodes() (+179 more)
+Nodes (206): fullRegion(), if(), if(), FireworkSystem, mapKeys(), VantComponent(), HTMLParser(), parseAttrs() (+198 more)
 
 ### Community 3 - "Community 3"
 Cohesion: 0.01
-Nodes (146): calendar(), memo(), record(), handleSelect(), onMenuShow(), showMenu(), onClear(), onInput() (+138 more)
+Nodes (186): handleSubmit(), loadDetail(), onRegionMultiConfirm(), regionLabel(), syncMultiFromForm(), validate(), fetchActiveRegions(), fetchAssignableMenuCodes() (+178 more)
 
 ### Community 4 - "Community 4"
 Cohesion: 0.01
-Nodes (121): fullRegion(), if(), if(), FireworkSystem, mapKeys(), VantComponent(), HTMLParser(), parseAttrs() (+113 more)
+Nodes (146): calendar(), memo(), record(), handleSelect(), onMenuShow(), showMenu(), onClear(), onInput() (+138 more)
 
 ### Community 5 - "Community 5"
 Cohesion: 0.01
@@ -122,39 +123,39 @@ Nodes (249): InvoiceCreateRequest, MessageCreateRequest, 获取商品的评价
 
 ### Community 6 - "Community 6"
 Cohesion: 0.02
-Nodes (186): # TODO: 发送微信模板消息提醒配送员, # TODO: 发送微信模板消息提醒配送员, 记录处理过程(不结案,结案由配送完成/取消自动触发), 获取我抢到的任务列表      返回当前配送员已抢但尚未开始配送的任务, 获取我抢到的任务列表      返回当前配送员已抢但尚未开始配送的任务, 取消抢单      配送员可以取消自己抢到但尚未开始配送的任务, 取消抢单      配送员可以取消自己抢到但尚未开始配送的任务, 取消抢单      配送员可以取消自己抢到但尚未开始配送的任务 (+178 more)
+Nodes (172): # TODO: 发送微信模板消息提醒配送员, # TODO: 发送微信模板消息提醒配送员, 记录处理过程(不结案,结案由配送完成/取消自动触发), 获取我抢到的任务列表      返回当前配送员已抢但尚未开始配送的任务, 获取我抢到的任务列表      返回当前配送员已抢但尚未开始配送的任务, 取消抢单      配送员可以取消自己抢到但尚未开始配送的任务, 取消抢单      配送员可以取消自己抢到但尚未开始配送的任务, 取消抢单      配送员可以取消自己抢到但尚未开始配送的任务 (+164 more)
 
 ### Community 7 - "Community 7"
 Cohesion: 0.02
-Nodes (111): endDate(), selected(), startDate(), Calendar, changeFlag(), countDown(), day(), handler() (+103 more)
+Nodes (69): fetchGetMenuCodes(), fetchGetUserInfo(), fetchLogin(), fetchGetMenuList(), fetchGetRoleList(), fetchGetUserList(), reload(), addUnit() (+61 more)
 
 ### Community 8 - "Community 8"
-Cohesion: 0.01
-Nodes (91): fetchGetMenuCodes(), fetchGetUserInfo(), fetchLogin(), fetchGetMenuList(), fetchGetRoleList(), fetchGetUserList(), reload(), addUnit() (+83 more)
+Cohesion: 0.03
+Nodes (92): 小程序客户端扩展接口:购物车、商品分类、仪表盘、发票、下单、支付, 从叶子分类 id 向上解析(含缓存):返回 (根分类, 叶子分类)。, 可申请开票的已完成订单(简化:已完成且尚无发票), 可申请开票的已完成订单(简化:已完成且尚无发票), 可申请开票的已完成订单(简化:已完成且尚无发票), 商品对应的一级(根)分类,供购物车行展示与序列化。, 从叶子分类 id 向上解析(含缓存):返回 (根分类, 叶子分类)。, 购物车:先一级分类 sort_order,再商品名、商品 id(稳定)。 (+84 more)
 
 ### Community 9 - "Community 9"
 Cohesion: 0.03
-Nodes (102): 小程序客户端扩展接口:购物车、商品分类、仪表盘、发票、下单、支付, 从叶子分类 id 向上解析(含缓存):返回 (根分类, 叶子分类)。, 可申请开票的已完成订单(简化:已完成且尚无发票), 可申请开票的已完成订单(简化:已完成且尚无发票), 可申请开票的已完成订单(简化:已完成且尚无发票), 商品对应的一级(根)分类,供购物车行展示与序列化。, 从叶子分类 id 向上解析(含缓存):返回 (根分类, 叶子分类)。, 购物车:先一级分类 sort_order,再商品名、商品 id(稳定)。 (+94 more)
+Nodes (52): handleFormClose(), _Blur(), created(), init(), inputContentStyle(), inputMaxlength(), inputStyle(), obj2strStyle() (+44 more)
 
 ### Community 10 - "Community 10"
-Cohesion: 0.04
-Nodes (40): BaseHTTPMiddleware, ApiCryptoError, _b64url_decode(), _b64url_encode(), encrypt_json(), gen_nonce_hex(), is_ts_in_window(), ApiCryptoMiddleware (+32 more)
+Cohesion: 0.03
+Nodes (17): toLogin(), if(), confirmLogout(), getDefaultLanguage(), emitUrls(), removeItem(), revokeLocalPreview(), checkStorageCompatibility() (+9 more)
 
 ### Community 11 - "Community 11"
-Cohesion: 0.03
-Nodes (43): askAbortDownload(), checkLocalStoragePackage(), closeUpdate(), compare(), deleteSavedFile(), downLoadComplete(), installPackage(), onLoad() (+35 more)
+Cohesion: 0.04
+Nodes (40): BaseHTTPMiddleware, ApiCryptoError, _b64url_decode(), _b64url_encode(), encrypt_json(), gen_nonce_hex(), is_ts_in_window(), ApiCryptoMiddleware (+32 more)
 
 ### Community 12 - "Community 12"
 Cohesion: 0.05
-Nodes (72): 获取供应商配送任务详情(订单仅返回客户订单号;商品行含主图与规格;单价由价格清单行 price(客户最终单价)与 pricing_coefficient 反推供, 获取供应商配送任务详情(订单仅返回客户订单号;商品行含主图与规格;单价由价格清单行 price(客户最终单价)与 pricing_coefficient 反推供, 获取供应商配送任务详情(订单仅返回客户订单号;商品行含主图与规格;单价由价格清单行 price(客户最终单价)与 pricing_coefficient 反推供, 获取供应商配送任务详情(订单仅返回客户订单号;商品行含主图与规格;单价由价格清单行 price(客户最终单价)与 pricing_coefficient 反推供, 备货完成后通知配送员(微信订阅消息,失败不阻断主流程), 备货完成后通知配送员(微信订阅消息,失败不阻断主流程), 备货完成后通知配送员(微信订阅消息,失败不阻断主流程), 供应商开始备货:assigned -> supplier_stocking,并写入订单状态日志(配送进度) (+64 more)
+Nodes (77): 批量获取供应商取货地址,避免列表接口逐条查询。, 一级/二级分类用 / 连接(与订单详情一致)。, 价格清单行 price 为面向客户的最终单价;pricing_coefficient 为调价系数(%),供货单价 = price / (1 + 系数/100)。, (price_list_id, product_id, unit_id) -> BizPriceListItem, 供应商默认取货地址;无默认时取第一条 pickup 类型地址。, 获取供应商配送任务详情(订单仅返回客户订单号;商品行含主图与规格;单价由价格清单行 price(客户最终单价)与 pricing_coefficient 反推供, 获取供应商配送任务详情(订单仅返回客户订单号;商品行含主图与规格;单价由价格清单行 price(客户最终单价)与 pricing_coefficient 反推供, 获取供应商配送任务详情(订单仅返回客户订单号;商品行含主图与规格;单价由价格清单行 price(客户最终单价)与 pricing_coefficient 反推供 (+69 more)
 
 ### Community 13 - "Community 13"
-Cohesion: 0.04
-Nodes (35): handleFormClose(), _Blur(), created(), init(), inputContentStyle(), inputMaxlength(), inputStyle(), obj2strStyle() (+27 more)
+Cohesion: 0.03
+Nodes (43): askAbortDownload(), checkLocalStoragePackage(), closeUpdate(), compare(), deleteSavedFile(), downLoadComplete(), installPackage(), onLoad() (+35 more)
 
 ### Community 14 - "Community 14"
 Cohesion: 0.05
-Nodes (15): fetchGetLatestUpgradeLog(), if(), getDefaultLanguage(), emitUrls(), removeItem(), revokeLocalPreview(), checkStorageCompatibility(), StorageConfig (+7 more)
+Nodes (22): change(), created(), getDataList(), getSelectedValue(), handler(), loadData(), mixinDatacomResData(), modelValue() (+14 more)
 
 ### Community 15 - "Community 15"
 Cohesion: 0.11
@@ -173,17 +174,17 @@ Cohesion: 0.12
 Nodes (15): addCopyButton(), highlightCode(), insertLineNumbers(), isBlockProcessed(), markBlockAsProcessed(), processBlock(), setupHighlightDirective(), setupRippleDirective() (+7 more)
 
 ### Community 19 - "Community 19"
-Cohesion: 0.17
-Nodes (16): animation(), bindTiming(), close(), getDom(), getEl(), getSelectorQuery(), init(), leftOptions() (+8 more)
-
-### Community 20 - "Community 20"
 Cohesion: 0.18
 Nodes (16): defaultAvatarPathByRole(), normalizeAvatarUrlForMini(), resolveUserAvatarUrl(), appendQueryParam(), fallbackVersionFromManifest(), getStaticAssetsVersion(), hasSignedQuery(), hasVersionParam() (+8 more)
 
-### Community 21 - "Community 21"
+### Community 20 - "Community 20"
 Cohesion: 0.16
 Nodes (10): useTable(), useTableImpl(), getColumnChecks(), useTableColumns(), createErrorHandler(), createSmartDebounce(), defaultResponseAdapter(), extractPagination() (+2 more)
 
+### Community 21 - "Community 21"
+Cohesion: 0.27
+Nodes (14): useChart(), useChartComponent(), useChartOps(), colourBlend(), getCssVar(), getDarkColor(), getLightColor(), handleElementThemeColor() (+6 more)
+
 ### Community 22 - "Community 22"
 Cohesion: 0.28
 Nodes (14): getDirection(), getDisabledType(), getDom(), move(), moveDirection(), openState(), range(), resetTouchStatus() (+6 more)
@@ -193,142 +194,146 @@ Cohesion: 0.17
 Nodes (2): clean(), onInput()
 
 ### Community 26 - "Community 26"
+Cohesion: 0.33
+Nodes (11): changeFlag(), countDown(), day(), handler(), hour(), minute(), second(), startData() (+3 more)
+
+### Community 27 - "Community 27"
 Cohesion: 0.24
 Nodes (4): getVal(), leftIconWidth(), navbarHeight(), rightIconWidth()
 
-### Community 27 - "Community 27"
+### Community 28 - "Community 28"
 Cohesion: 0.4
 Nodes (9): backup_table(), count_affected(), get_text_columns(), main(), make_backup_table_name(), parse_args(), quote_ident(), 生成备份表名,控制在 MySQL 64 字符限制内。 (+1 more)
 
-### Community 28 - "Community 28"
+### Community 29 - "Community 29"
 Cohesion: 0.24
 Nodes (3): _calcValue(), _getDecimalScale(), _onBlur()
 
-### Community 30 - "Community 30"
+### Community 31 - "Community 31"
 Cohesion: 0.27
 Nodes (4): dataValue(), emit(), onChange(), setOpen()
 
-### Community 32 - "Community 32"
+### Community 33 - "Community 33"
 Cohesion: 0.25
 Nodes (2): borderStyle(), value2px()
 
-### Community 33 - "Community 33"
+### Community 34 - "Community 34"
 Cohesion: 0.39
 Nodes (6): isIgnorableScriptError(), normalizeErrorMessage(), registerPromiseErrorHandler(), registerResourceErrorHandler(), scriptErrorHandler(), setupErrorHandle()
 
-### Community 34 - "Community 34"
+### Community 35 - "Community 35"
 Cohesion: 0.29
 Nodes (2): handleTouchEnd(), setTransition()
 
-### Community 35 - "Community 35"
+### Community 36 - "Community 36"
 Cohesion: 0.36
 Nodes (1): TableHeightCalculator
 
-### Community 37 - "Community 37"
+### Community 38 - "Community 38"
 Cohesion: 0.36
 Nodes (1): RouteValidator
 
-### Community 38 - "Community 38"
+### Community 39 - "Community 39"
 Cohesion: 0.39
 Nodes (5): chooseAll(), chooseAndUploadFile(), chooseImage(), chooseVideo(), uploadFiles()
 
-### Community 39 - "Community 39"
+### Community 40 - "Community 40"
 Cohesion: 0.33
 Nodes (2): logApiCryptoBootInfo(), maskSecret()
 
-### Community 40 - "Community 40"
+### Community 41 - "Community 41"
 Cohesion: 0.38
 Nodes (4): fetchApiRequestLogList(), handleReset(), handleSearch(), loadData()
 
-### Community 41 - "Community 41"
+### Community 42 - "Community 42"
 Cohesion: 0.38
 Nodes (3): _change(), close(), open()
 
-### Community 46 - "Community 46"
+### Community 47 - "Community 47"
 Cohesion: 0.33
 Nodes (1): BridgeError
 
-### Community 48 - "Community 48"
+### Community 49 - "Community 49"
 Cohesion: 0.4
 Nodes (2): created(), getTable()
 
-### Community 49 - "Community 49"
+### Community 50 - "Community 50"
 Cohesion: 0.47
 Nodes (3): get_file_data(), get_file_ext(), get_file_info()
 
-### Community 50 - "Community 50"
+### Community 51 - "Community 51"
 Cohesion: 0.4
 Nodes (2): created(), getForm()
 
-### Community 51 - "Community 51"
+### Community 52 - "Community 52"
 Cohesion: 0.47
 Nodes (4): onDial(), onTextClick(), dialPhone(), normalizeDialNumber()
 
-### Community 52 - "Community 52"
+### Community 53 - "Community 53"
 Cohesion: 0.4
 Nodes (4): 运营端角色守卫:admin / super_admin 可访问。, 接口菜单权限守卫。super_admin 直接放行。, require_admin_or_super_admin(), require_menu_code()
 
-### Community 53 - "Community 53"
+### Community 54 - "Community 54"
 Cohesion: 0.5
 Nodes (2): focusInput(), handleKeydown()
 
-### Community 55 - "Community 55"
+### Community 56 - "Community 56"
 Cohesion: 0.5
 Nodes (3): BaseSettings, Config, Settings
 
-### Community 56 - "Community 56"
+### Community 57 - "Community 57"
 Cohesion: 0.5
 Nodes (1): collectIds()
 
-### Community 60 - "Community 60"
+### Community 61 - "Community 61"
 Cohesion: 0.83
 Nodes (3): classes(), isTrue(), onClick()
 
-### Community 63 - "Community 63"
+### Community 64 - "Community 64"
 Cohesion: 0.67
 Nodes (2): created(), getParent()
 
-### Community 64 - "Community 64"
+### Community 65 - "Community 65"
 Cohesion: 1.0
 Nodes (2): getSettingDefaults(), resetToDefaults()
 
-### Community 66 - "Community 66"
+### Community 67 - "Community 67"
 Cohesion: 1.0
 Nodes (2): defaultAvatarPathByRole(), resolveUserAvatarUrl()
 
-### Community 79 - "Community 79"
+### Community 80 - "Community 80"
 Cohesion: 0.67
 Nodes (1): updateUseModal()
 
-### Community 89 - "Community 89"
+### Community 90 - "Community 90"
 Cohesion: 1.0
 Nodes (2): buildMiniEncryptedLoginBody(), fetchLoginPublicKey()
 
-### Community 91 - "Community 91"
+### Community 92 - "Community 92"
 Cohesion: 0.67
 Nodes (1): return()
 
-### Community 144 - "Community 144"
+### Community 145 - "Community 145"
 Cohesion: 1.0
 Nodes (1): 发送订单状态通知                  Args:             openid: 用户openid             ord
 
-### Community 145 - "Community 145"
+### Community 146 - "Community 146"
 Cohesion: 1.0
 Nodes (1): 发送支付成功通知                  Args:             openid: 用户openid             ord
 
-### Community 146 - "Community 146"
+### Community 147 - "Community 147"
 Cohesion: 1.0
 Nodes (1): 发送配送通知                  Args:             openid: 配送员openid             task
 
-### Community 147 - "Community 147"
+### Community 148 - "Community 148"
 Cohesion: 1.0
 Nodes (1): 发送配送时效预警                  Args:             openid: 配送员openid             ta
 
-### Community 148 - "Community 148"
+### Community 149 - "Community 149"
 Cohesion: 1.0
 Nodes (1): 发送退款结果通知                  Args:             openid: 用户openid             ord
 
-### Community 149 - "Community 149"
+### Community 150 - "Community 150"
 Cohesion: 1.0
 Nodes (1): 发送催单通知给配送员                  Args:             deliver_openid: 配送员openid
 
@@ -337,65 +342,65 @@ Nodes (1): 发送催单通知给配送员                  Args:             del
   These have ≤1 connection - possible missing edges or undocumented components.
 - **Thin community `Community 25`** (13 nodes): `uni-combox.vue`, `clean()`, `data()`, `default()`, `filterCandidates()`, `filterCandidatesLength()`, `handler()`, `labelStyle()`, `onBlur()`, `onFocus()`, `onInput()`, `onSelectorClick()`, `toggleSelector()`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 32`** (9 nodes): `upload-file.vue`, `borderLineStyle()`, `borderStyle()`, `choose()`, `default()`, `delFile()`, `styles()`, `uploadFiles()`, `value2px()`
+- **Thin community `Community 33`** (9 nodes): `upload-file.vue`, `borderLineStyle()`, `borderStyle()`, `choose()`, `default()`, `delFile()`, `styles()`, `uploadFiles()`, `value2px()`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 34`** (8 nodes): `cleanupEventListeners()`, `handleTouchEnd()`, `handleTouchMove()`, `handleTouchStart()`, `handleWheelScroll()`, `setTransition()`, `setupEventListeners()`, `index.vue`
+- **Thin community `Community 35`** (8 nodes): `cleanupEventListeners()`, `handleTouchEnd()`, `handleTouchMove()`, `handleTouchStart()`, `handleWheelScroll()`, `setTransition()`, `setupEventListeners()`, `index.vue`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 35`** (8 nodes): `TableHeightCalculator`, `.calculate()`, `.calculateOffset()`, `.calculatePaginationOffset()`, `.constructor()`, `.getHeaderHeight()`, `useTableHeight()`, `useTableHeight.ts`
+- **Thin community `Community 36`** (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 37`** (8 nodes): `RouteValidator`, `.checkComponents()`, `.checkDuplicates()`, `.checkNestedIndexComponent()`, `.logLayoutError()`, `.resolvePath()`, `.validate()`, `RouteValidator.ts`
+- **Thin community `Community 38`** (8 nodes): `RouteValidator`, `.checkComponents()`, `.checkDuplicates()`, `.checkNestedIndexComponent()`, `.logLayoutError()`, `.resolvePath()`, `.validate()`, `RouteValidator.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 39`** (7 nodes): `refreshButtonAutoIcons()`, `refreshPanelTitleIcons()`, `logApiCryptoBootInfo()`, `maskSecret()`, `App.vue`, `main.ts`, `console.ts`
+- **Thin community `Community 40`** (7 nodes): `refreshButtonAutoIcons()`, `refreshPanelTitleIcons()`, `logApiCryptoBootInfo()`, `maskSecret()`, `App.vue`, `main.ts`, `console.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 46`** (6 nodes): `bridge-error.js`, `BridgeError`, `.code()`, `.constructor()`, `.errCode()`, `.errMsg()`
+- **Thin community `Community 47`** (6 nodes): `bridge-error.js`, `BridgeError`, `.code()`, `.constructor()`, `.errCode()`, `.errMsg()`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 48`** (6 nodes): `uni-thead.vue`, `checkboxSelected()`, `created()`, `data()`, `getTable()`, `init()`
+- **Thin community `Community 49`** (6 nodes): `uni-thead.vue`, `checkboxSelected()`, `created()`, `data()`, `getTable()`, `init()`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 50`** (6 nodes): `uni-group.vue`, `created()`, `data()`, `getForm()`, `onClick()`, `title()`
+- **Thin community `Community 51`** (6 nodes): `uni-group.vue`, `created()`, `data()`, `getForm()`, `onClick()`, `title()`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 53`** (5 nodes): `focusInput()`, `handleKeydown()`, `if()`, `search()`, `index.vue`
+- **Thin community `Community 54`** (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 56`** (4 nodes): `catch()`, `collectIds()`, `handleCategoryTableSelectionChange()`, `index.vue`
+- **Thin community `Community 57`** (4 nodes): `catch()`, `collectIds()`, `handleCategoryTableSelectionChange()`, `index.vue`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 63`** (4 nodes): `popup.js`, `created()`, `data()`, `getParent()`
+- **Thin community `Community 64`** (4 nodes): `popup.js`, `created()`, `data()`, `getParent()`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 64`** (3 nodes): `getSettingDefaults()`, `resetToDefaults()`, `setting.ts`
+- **Thin community `Community 65`** (3 nodes): `getSettingDefaults()`, `resetToDefaults()`, `setting.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 66`** (3 nodes): `defaultAvatarPathByRole()`, `resolveUserAvatarUrl()`, `user-avatar.ts`
+- **Thin community `Community 67`** (3 nodes): `defaultAvatarPathByRole()`, `resolveUserAvatarUrl()`, `user-avatar.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 79`** (3 nodes): `check-update.js`, `check-update.ts`, `updateUseModal()`
+- **Thin community `Community 80`** (3 nodes): `check-update.js`, `check-update.ts`, `updateUseModal()`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 89`** (3 nodes): `loginRsa.js`, `buildMiniEncryptedLoginBody()`, `fetchLoginPublicKey()`
+- **Thin community `Community 90`** (3 nodes): `loginRsa.js`, `buildMiniEncryptedLoginBody()`, `fetchLoginPublicKey()`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 91`** (3 nodes): `return()`, `detail.vue`, `detail.vue`
+- **Thin community `Community 92`** (3 nodes): `return()`, `detail.vue`, `detail.vue`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 144`** (1 nodes): `发送订单状态通知                  Args:             openid: 用户openid             ord`
+- **Thin community `Community 145`** (1 nodes): `发送订单状态通知                  Args:             openid: 用户openid             ord`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 145`** (1 nodes): `发送支付成功通知                  Args:             openid: 用户openid             ord`
+- **Thin community `Community 146`** (1 nodes): `发送支付成功通知                  Args:             openid: 用户openid             ord`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 146`** (1 nodes): `发送配送通知                  Args:             openid: 配送员openid             task`
+- **Thin community `Community 147`** (1 nodes): `发送配送通知                  Args:             openid: 配送员openid             task`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 147`** (1 nodes): `发送配送时效预警                  Args:             openid: 配送员openid             ta`
+- **Thin community `Community 148`** (1 nodes): `发送配送时效预警                  Args:             openid: 配送员openid             ta`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 148`** (1 nodes): `发送退款结果通知                  Args:             openid: 用户openid             ord`
+- **Thin community `Community 149`** (1 nodes): `发送退款结果通知                  Args:             openid: 用户openid             ord`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 149`** (1 nodes): `发送催单通知给配送员                  Args:             deliver_openid: 配送员openid`
+- **Thin community `Community 150`** (1 nodes): `发送催单通知给配送员                  Args:             deliver_openid: 配送员openid`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
 
 ## Suggested Questions
 _Questions this graph is uniquely positioned to answer:_
 
-- **Why does `push()` connect `Community 4` to `Community 0`, `Community 2`, `Community 3`, `Community 7`, `Community 9`, `Community 11`, `Community 13`, `Community 14`?**
+- **Why does `push()` connect `Community 2` to `Community 0`, `Community 3`, `Community 4`, `Community 9`, `Community 10`, `Community 13`, `Community 14`?**
   _High betweenness centrality (0.133) - this node is a cross-community bridge._
-- **Why does `number()` connect `Community 2` to `Community 0`, `Community 4`, `Community 7`, `Community 8`, `Community 11`, `Community 13`?**
-  _High betweenness centrality (0.052) - this node is a cross-community bridge._
 - **Why does `query()` connect `Community 0` to `Community 17`, `Community 6`?**
   _High betweenness centrality (0.049) - this node is a cross-community bridge._
-- **Are the 670 inferred relationships involving `BizPriceListItem` (e.g. with `PriceListCRUD` and `PriceListItemCRUD`) actually correct?**
-  _`BizPriceListItem` has 670 INFERRED edges - model-reasoned connections that need verification._
-- **Are the 599 inferred relationships involving `BizInquiry` (e.g. with `InquiryCRUD` and `InquiryItemCRUD`) actually correct?**
-  _`BizInquiry` has 599 INFERRED edges - model-reasoned connections that need verification._
-- **Are the 597 inferred relationships involving `BizQuotation` (e.g. with `QuotationCRUD` and `QuotationItemCRUD`) actually correct?**
-  _`BizQuotation` has 597 INFERRED edges - model-reasoned connections that need verification._
-- **Are the 587 inferred relationships involving `BizAttachment` (e.g. with `AttachmentCRUD` and `价格清单行 price 为面向客户的最终单价;pricing_coefficient 为调价系数(%),供货单价 = price / (1 + 系数/100)。`) actually correct?**
-  _`BizAttachment` has 587 INFERRED edges - model-reasoned connections that need verification._
+- **Why does `number()` connect `Community 3` to `Community 0`, `Community 2`, `Community 9`, `Community 13`, `Community 14`, `Community 21`?**
+  _High betweenness centrality (0.044) - this node is a cross-community bridge._
+- **Are the 697 inferred relationships involving `BizPriceListItem` (e.g. with `PriceListCRUD` and `PriceListItemCRUD`) actually correct?**
+  _`BizPriceListItem` has 697 INFERRED edges - model-reasoned connections that need verification._
+- **Are the 626 inferred relationships involving `BizInquiry` (e.g. with `InquiryCRUD` and `InquiryItemCRUD`) actually correct?**
+  _`BizInquiry` has 626 INFERRED edges - model-reasoned connections that need verification._
+- **Are the 624 inferred relationships involving `BizQuotation` (e.g. with `QuotationCRUD` and `QuotationItemCRUD`) actually correct?**
+  _`BizQuotation` has 624 INFERRED edges - model-reasoned connections that need verification._
+- **Are the 614 inferred relationships involving `BizAttachment` (e.g. with `AttachmentCRUD` and `价格清单行 price 为面向客户的最终单价;pricing_coefficient 为调价系数(%),供货单价 = price / (1 + 系数/100)。`) actually correct?**
+  _`BizAttachment` has 614 INFERRED edges - model-reasoned connections that need verification._

+ 1 - 0
graphify-out/cache/ast/1201fbeeb5101eb90b7a878648e329adab59bfcfd584b4aa5bfe3a8f545a8cad.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_sqxp_2_sqxp_wx_pages_supplier_inquiry_inquiry_vue", "label": "inquiry.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/sqxp-wx/pages/supplier/inquiry/inquiry.vue", "source_location": "L1"}, {"id": "inquiry_inquiry_if", "label": "if()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/sqxp-wx/pages/supplier/inquiry/inquiry.vue", "source_location": "L125"}], "edges": [{"source": "users_wuzilong_work_00code_sqxp_2_sqxp_wx_pages_supplier_inquiry_inquiry_vue", "target": "inquiry_inquiry_if", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/sqxp-wx/pages/supplier/inquiry/inquiry.vue", "source_location": "L125", "weight": 1.0}, {"source": "users_wuzilong_work_00code_sqxp_2_sqxp_wx_pages_supplier_inquiry_inquiry_vue", "target": "inquiry_inquiry_if", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/sqxp-wx/pages/supplier/inquiry/inquiry.vue", "source_location": "L205", "weight": 1.0}], "raw_calls": [{"caller_nid": "inquiry_inquiry_if", "callee": "filter", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/sqxp-wx/pages/supplier/inquiry/inquiry.vue", "source_location": "L126"}, {"caller_nid": "inquiry_inquiry_if", "callee": "setStorageSync", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/sqxp-wx/pages/supplier/inquiry/inquiry.vue", "source_location": "L208"}]}

+ 1 - 0
graphify-out/cache/ast/19bdc910191a019358fa77ce588a02f1a3864f2a731247a3624b1074fa79b7ac.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_sqxp_2_web_admin_src_views_system_supplier_settlement_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/web-admin/src/views/system/supplier-settlement/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
graphify-out/cache/ast/200d962daea4bd4a19ce6a072217ba2d627e9f24b20774776fec1338acc370f3.json


+ 1 - 0
graphify-out/cache/ast/21205ae389b5db6d74694d37cf50e5fb102b9062ba7eca1c094a2c05602452c8.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_sqxp_2_web_admin_src_views_system_client_settlement_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/web-admin/src/views/system/client-settlement/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

+ 1 - 0
graphify-out/cache/ast/25874112b4e9cb0bf1a3767d9b1b6a11223051b5870dcce236569b12877b5ec5.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_sqxp_2_web_admin_src_views_system_supplier_settlement_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/web-admin/src/views/system/supplier-settlement/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
graphify-out/cache/ast/30511557ea2de5b7033d26d11aec23aa226bce4b2c3e1cbb9a428c21d97968af.json


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
graphify-out/cache/ast/332bafcc5ccd7df4eac7b18ab2b337d57d35a151bf60863c5fc5a21a1a17265f.json


+ 1 - 0
graphify-out/cache/ast/629d79232c2b3b06d36e074d44d0ca60e19b4a268ace391f814c825dd843aa9b.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_sqxp_2_web_admin_src_views_system_supplier_settlement_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/web-admin/src/views/system/supplier-settlement/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
graphify-out/cache/ast/683214eaca1fe5391a61bf37b0556a8e898e50b449d1a263a036d4d9c9f187c9.json


+ 1 - 0
graphify-out/cache/ast/6e37a9c73b5b64c19cf7006922dbc10a4323de5fd220afccfab632fb9615e5ac.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_sqxp_2_web_admin_src_views_system_supplier_settlement_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/web-admin/src/views/system/supplier-settlement/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

+ 1 - 0
graphify-out/cache/ast/723405b1285c572c9e099d59f3c8bcef8b9860f308547903b23dec5d88d85276.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_sqxp_2_sqxp_wx_pages_client_order_create_create_vue", "label": "create.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/sqxp-wx/pages/client/order/create/create.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

+ 1 - 0
graphify-out/cache/ast/7f5eacf6615c79fcdf75f5a9c93924a3bb1cd4a353de4d2f8972cb67524f8e89.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_sqxp_2_web_admin_src_views_system_client_settlement_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/web-admin/src/views/system/client-settlement/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

+ 1 - 0
graphify-out/cache/ast/842ee7a2074b944332d6c892c78dcd8b6b6d76c3a6b5ef44b2a6455de046ca34.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_sqxp_2_web_admin_src_views_system_supplier_settlement_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/web-admin/src/views/system/supplier-settlement/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
graphify-out/cache/ast/a5d653cf15823c5a02c6585745e03cff8b7606a27d74cdfef1afe329186ac04e.json


+ 1 - 0
graphify-out/cache/ast/a681077d1a86dccf965b79bd5bd36c24d7b5618940e2bb1f91fc4046557771ab.json

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

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
graphify-out/cache/ast/ac9106c1a0efda46ea1f0051e2ba86f41e4797f834d384e1280b8d25d10c32bb.json


+ 1 - 0
graphify-out/cache/ast/b9d4dfb713968281b123074afc73c5aaef6878d868701ef7588d221e29abea2d.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_sqxp_2_sqxp_wx_pages_client_order_create_create_vue", "label": "create.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/sqxp-wx/pages/client/order/create/create.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

+ 1 - 0
graphify-out/cache/ast/bdfdb7e8468c8c89cd6ecb4170a1951359a2e822aa6ab7b9d996b79e04fc193c.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_sqxp_2_web_admin_src_views_system_client_settlement_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/web-admin/src/views/system/client-settlement/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
graphify-out/cache/ast/cb0bcefa81564cfa06e2dba328ca6c79a61a1d89cdbbcd178b795acf7ea29b2a.json


+ 1 - 0
graphify-out/cache/ast/ce77b593e67bcd13edc15df6471ccb52589a5ef3da4ae24b303cf3843bf3c2fa.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_sqxp_2_web_admin_src_views_system_supplier_settlement_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/web-admin/src/views/system/supplier-settlement/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

+ 1 - 0
graphify-out/cache/ast/da2b72a74710c8016b218e6d4cd9ee0db68668d1e2bbc8ca9b178f443e1707ed.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_sqxp_2_web_admin_src_views_system_client_settlement_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/web-admin/src/views/system/client-settlement/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

+ 1 - 0
graphify-out/cache/ast/e41c1dcac2beec7f572250c5b0107b15d030511fb414ef6a26b2550061491bcb.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_sqxp_2_web_admin_src_views_system_client_settlement_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/web-admin/src/views/system/client-settlement/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

+ 1 - 0
graphify-out/cache/ast/e84c8b1227dd040625b4590d4a7b823a5efdf27d7c3ce80bab451b36d688d188.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_sqxp_2_sqxp_wx_pages_supplier_inquiry_inquiry_vue", "label": "inquiry.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/sqxp-wx/pages/supplier/inquiry/inquiry.vue", "source_location": "L1"}, {"id": "inquiry_inquiry_if", "label": "if()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/sqxp-wx/pages/supplier/inquiry/inquiry.vue", "source_location": "L125"}], "edges": [{"source": "users_wuzilong_work_00code_sqxp_2_sqxp_wx_pages_supplier_inquiry_inquiry_vue", "target": "inquiry_inquiry_if", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/sqxp-wx/pages/supplier/inquiry/inquiry.vue", "source_location": "L125", "weight": 1.0}, {"source": "users_wuzilong_work_00code_sqxp_2_sqxp_wx_pages_supplier_inquiry_inquiry_vue", "target": "inquiry_inquiry_if", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/sqxp-wx/pages/supplier/inquiry/inquiry.vue", "source_location": "L205", "weight": 1.0}], "raw_calls": [{"caller_nid": "inquiry_inquiry_if", "callee": "filter", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/sqxp-wx/pages/supplier/inquiry/inquiry.vue", "source_location": "L126"}, {"caller_nid": "inquiry_inquiry_if", "callee": "setStorageSync", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/sqxp-wx/pages/supplier/inquiry/inquiry.vue", "source_location": "L208"}]}

+ 1 - 0
graphify-out/cache/ast/ea746c5476c6fe533f0269d3540f106e00da35215b20675d22bada2b5c1d4aea.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_sqxp_2_web_admin_src_views_system_supplier_settlement_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/web-admin/src/views/system/supplier-settlement/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

+ 1 - 0
graphify-out/cache/ast/f16c31d3b2502e584db1f548788fa6c8ae8190444469628125c5e1bbef91cbdb.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_sqxp_2_web_admin_src_views_system_supplier_settlement_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/web-admin/src/views/system/supplier-settlement/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

+ 1 - 0
graphify-out/cache/ast/f31952118aea1747509ef4d95cbe33bea7d92875077ae36e03b8bc458814d6f3.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_sqxp_2_web_admin_src_views_system_supplier_settlement_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/web-admin/src/views/system/supplier-settlement/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

+ 1 - 0
graphify-out/cache/ast/ff73c2ea753322368a74530c3afda48c13b939cd3fbdeff6402e61944c563587.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_sqxp_2_web_admin_src_views_system_client_settlement_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/sqxp_\u526f\u672c2/web-admin/src/views/system/client-settlement/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
graphify-out/graph.html


Fichier diff supprimé car celui-ci est trop grand
+ 112 - 112
graphify-out/graph.json


+ 23 - 3
sqxp-wx/pages/client/order/create/create.vue

@@ -38,11 +38,11 @@
         <radio-group class="pay-group" @change="onPaymentMethodChange">
           <label class="pay-row">
             <text class="pay-text">线上支付({{ autoCancelMinutesText }}分钟未支付,自动取消)</text>
-            <radio value="online" :checked="paymentMethod === 'online'" />
+            <radio value="online" :checked="paymentMethod === 'online'" color="#07c160" />
           </label>
           <label class="pay-row">
             <text class="pay-text">线下支付</text>
-            <radio value="offline" :checked="paymentMethod === 'offline'" />
+            <radio value="offline" :checked="paymentMethod === 'offline'" color="#07c160" />
           </label>
         </radio-group>
       </view>
@@ -127,7 +127,7 @@
 </template>
 
 <script setup>
-import { ref, computed, onMounted } from 'vue'
+import { ref, computed, onMounted, onUnmounted } from 'vue'
 import { onLoad, onShow } from '@dcloudio/uni-app'
 import { useUserStore } from '@/stores/user'
 import { useCartStore } from '@/stores/cart'
@@ -137,6 +137,8 @@ import ProductDetailOverlay from '@/components/client/product-detail-overlay.vue
 import ClientSkeleton from '@/components/client/client-skeleton.vue'
 import ClientImageLoader from '@/components/client/client-image-loader.vue'
 import { IMG_DEFAULT_PRODUCT, staticAssetUrl } from '@/utils/staticAsset'
+import { on as busOn, off as busOff } from '@/utils/bus'
+import { ensurePriceRealtimeConnected, checkActivePriceVersionOnce } from '@/utils/price-realtime'
 
 const userStore = useUserStore()
 const cartStore = useCartStore()
@@ -390,6 +392,9 @@ onShow(() => {
   // 返回收货地址选择页后,刷新当前选中地址显示
   if (!userStore.isLoggedIn) return
   if (!userStore.isClient) return
+  void ensurePriceRealtimeConnected()
+  void checkActivePriceVersionOnce()
+  void cartStore.fetchCart()
   void loadAddresses()
 })
 
@@ -407,6 +412,21 @@ onLoad((options) => {
   }
   excludedCartIds.value = new Set()
 })
+
+const onPriceListUpdated = () => {
+  // 与首页/购物车一致:价格清单更新后,确认订单明细与金额应立即重算
+  void cartStore.fetchCart()
+}
+
+onMounted(() => {
+  busOn('price_list_updated', onPriceListUpdated)
+  uni.$on && uni.$on('price_list_updated', onPriceListUpdated)
+})
+
+onUnmounted(() => {
+  busOff('price_list_updated', onPriceListUpdated)
+  uni.$off && uni.$off('price_list_updated', onPriceListUpdated)
+})
 </script>
 
 

+ 38 - 31
sqxp-wx/pages/supplier/inquiry/inquiry.vue

@@ -34,6 +34,16 @@ const TEMP_PRICE_STORAGE_KEY = 'supplier_inquiry_temp_prices';
 const isH5Env = typeof window !== 'undefined';
 const priceInputType = isH5Env ? 'text' : 'digit';
 const priceInputMode = isH5Env ? 'decimal' : undefined;
+function parsePriceGreaterThanZero(raw) {
+    if (raw === '' || raw == null)
+        return { ok: false, value: null };
+    const num = Number(raw);
+    if (Number.isNaN(num))
+        return { ok: false, value: null };
+    if (num <= 0)
+        return { ok: false, value: null };
+    return { ok: true, value: Number(num.toFixed(2)) };
+}
 /** 顶栏选中某一级时,左侧「二级」筛选用:有 second 用 second;否则仅当叶子类目与一级不同时用 categoryId */
 function getItemSecondCategoryId(item) {
     const fid = item.firstCategoryId || item.categoryId || '';
@@ -78,10 +88,7 @@ function hasPositivePrice(productId) {
     const raw = priceMap.value[productId];
     if (raw === '' || raw == null)
         return false;
-    const num = Number(raw);
-    if (Number.isNaN(num))
-        return false;
-    return num > 0;
+    return parsePriceGreaterThanZero(raw).ok;
 }
 const firstCategorySwiperIndex = ref(0);
 const leftCategoryOptions = computed(() => {
@@ -354,11 +361,14 @@ async function doSubmitQuotation({ emptyCount, inquiryId }) {
         uni.showToast({ title: '询价单ID不能为空', icon: 'none' });
         return;
     }
-    const payloadItems = items.value.map((item) => ({
-        productId: item.productId,
-        unitId: item.unitId,
-        price: Number(Number(priceMap.value[item.productId] || 0).toFixed(2))
-    }));
+    const payloadItems = items.value.map((item) => {
+        const parsed = parsePriceGreaterThanZero(priceMap.value[item.productId]);
+        return {
+            productId: item.productId,
+            unitId: item.unitId,
+            price: parsed.ok ? parsed.value : 0
+        };
+    });
     isSaving.value = true;
     try {
         const res = await request({
@@ -384,29 +394,19 @@ async function doSubmitQuotation({ emptyCount, inquiryId }) {
         isSaving.value = false;
     }
 }
+const canSubmit = computed(() => {
+    if (isSaving.value)
+        return false;
+    if (!currentInquiryId.value || items.value.length === 0)
+        return false;
+    return items.value.every((item) => parsePriceGreaterThanZero(priceMap.value[item.productId]).ok);
+});
 async function handleSave() {
     if (!currentInquiryId.value || items.value.length === 0)
         return;
-    const invalidItem = items.value.find((item) => {
-        const value = priceMap.value[item.productId];
-        if (!value)
-            return false;
-        const num = Number(value);
-        return Number.isNaN(num) || num < 0;
-    });
-    if (invalidItem) {
-        uni.showToast({ title: '请填写完整且有效的价格', icon: 'none' });
-        return;
-    }
-    const emptyCount = items.value.filter((item) => !priceMap.value[item.productId]).length;
-    if (emptyCount > 0) {
-        const inquiryIdSnapshot = currentInquiryId.value;
-        openSubmitConfirmDialog({
-            title: '提示',
-            desc: `有 ${emptyCount} 个商品未填写价格,将按 0.00 元保存,是否继续?`,
-            confirmText: '继续提交',
-            onConfirm: () => doSubmitQuotation({ emptyCount, inquiryId: inquiryIdSnapshot })
-        });
+    const invalidItems = items.value.filter((item) => !parsePriceGreaterThanZero(priceMap.value[item.productId]).ok);
+    if (invalidItems.length > 0) {
+        uni.showToast({ title: `有 ${invalidItems.length} 个商品未填写或报价为0,请填写大于0的价格后再提交`, icon: 'none' });
         return;
     }
     await doSubmitQuotation({ emptyCount: 0, inquiryId: currentInquiryId.value });
@@ -588,8 +588,8 @@ onShow(() => {
     </view>
 
     <view v-if="currentInquiryId" class="footer-bar">
-      <button class="save-btn" :disabled="isSaving" :loading="isSaving" @click="handleSave">
-        <VanIcon name="completed-o" size="24" color="#ffffff" />
+      <button class="save-btn" :disabled="!canSubmit" :loading="isSaving" @click="handleSave">
+        <VanIcon name="completed-o" size="24" :color="canSubmit ? '#ffffff' : '#e5e7eb'" />
       </button>
     </view>
 
@@ -1093,6 +1093,13 @@ page {
   pointer-events: auto;
 }
 
+.save-btn[disabled] {
+  background: linear-gradient(145deg, #94a3b8, #cbd5e1);
+  box-shadow: none;
+  opacity: 0.55;
+  filter: grayscale(0.9) saturate(0.2);
+}
+
 .save-icon-line {
   width: 42rpx;
   height: 42rpx;

+ 97 - 40
web-admin/src/views/system/client-settlement/index.vue

@@ -330,15 +330,17 @@
             <el-table-column type="expand" width="48">
               <template #default="{ row }">
                 <div class="sheet-row-expand-wrap" v-loading="isSheetRowLoading(row.id)">
-                  <el-table
-                    v-if="getSheetOrders(row.id).length"
-                    :data="getSheetOrders(row.id)"
-                    row-key="id"
-                    stripe
-                    size="small"
-                    class="sheet-orders-table"
-                    @expand-change="handleSheetOrderExpand(row.id)"
-                  >
+                  <div v-if="getSheetOrders(row.id).length" class="sheet-orders-scroll">
+                    <el-table
+                      :data="getSheetOrders(row.id)"
+                      row-key="id"
+                      stripe
+                      size="small"
+                      :fit="false"
+                      scrollbar-always-on
+                      class="sheet-orders-table"
+                      @expand-change="handleSheetOrderExpand(row.id)"
+                    >
                     <el-table-column type="expand" width="48">
                       <template #default="{ row: ord }">
                         <div class="order-row-expand-wrap">
@@ -346,13 +348,16 @@
                             v-if="Array.isArray(ord.children) && ord.children.length"
                             class="settlement-row-expand-table-wrap sqxp-subtable-wrap"
                           >
-                            <el-table
-                              :data="ord.children"
-                              stripe
-                              size="small"
-                              class="order-row-expand-table sqxp-subtable"
-                              @row-dblclick="openOrderProductView"
-                            >
+                            <div class="order-row-expand-scroll">
+                              <el-table
+                                :data="ord.children"
+                                stripe
+                                size="small"
+                                :fit="true"
+                                scrollbar-always-on
+                                class="order-row-expand-table sqxp-subtable"
+                                @row-dblclick="openOrderProductView"
+                              >
                             <el-table-column prop="productName" label="商品" min-width="220">
                               <template #default="{ row: line }">
                                 <div class="expand-product-cell">
@@ -399,7 +404,8 @@
                                 >¥{{ formatAmount(line.sellingAmount ?? line.amount) }}</template
                               >
                             </el-table-column>
-                            </el-table>
+                              </el-table>
+                            </div>
                             <div class="order-row-expand-summary sqxp-subtable-summary">
                               <span
                                 >供货总金额(元):<span class="sqxp-subtable-summary__amount"
@@ -423,6 +429,11 @@
                       </template>
                     </el-table-column>
                     <el-table-column prop="clientName" label="客户名称" min-width="150" show-overflow-tooltip />
+                    <el-table-column label="供应商名称" min-width="150" show-overflow-tooltip>
+                      <template #default="{ row: ord }">
+                        {{ resolveSupplierName(ord) }}
+                      </template>
+                    </el-table-column>
                     <el-table-column prop="regionName" label="区域" min-width="110" show-overflow-tooltip />
                     <el-table-column label="订单金额(元)" min-width="130" align="right">
                       <template #default="{ row: ord }">
@@ -476,7 +487,8 @@
                       </template>
                     </el-table-column>
                     <el-table-column prop="orderCreateAt" label="下单时间" min-width="170" />
-                  </el-table>
+                    </el-table>
+                  </div>
                   <el-empty v-else description="暂无明细" :image-size="72" />
                 </div>
               </template>
@@ -805,12 +817,10 @@
 
   const ORDER_STATUS_MAP: Record<string, string> = {
     pending_payment: '待支付',
+    pending_confirm: '待收货',
+    pending: '待收货',
     pending_review_cancel: '待审核取消',
-    pending_delivery: '待分配配送',
-    delivering: '配送中',
-    partial_delivered: '部分送达',
-    pending_confirm: '待确认收货',
-    completed: '已完结',
+    completed: '已完成',
     cancelled: '已取消'
   }
   const orderStatusLabel = (s: string) => (s ? ORDER_STATUS_MAP[s] || s : '—')
@@ -819,10 +829,11 @@
     return row?.orderStatus || row?.status || ''
   }
   const orderStatusTagType = (s: string) => {
+    if (s === 'pending_payment' || s === 'pending_review_cancel') return 'warning'
+    if (s === 'pending_confirm' || s === 'pending') return 'primary'
     if (s === 'completed') return 'success'
     if (s === 'cancelled') return 'info'
-    if (s === 'pending_payment' || s === 'partial_delivered') return 'warning'
-    return 'primary'
+    return 'info'
   }
   const DELIVERY_STATUS_MAP: Record<string, string> = {
     pending_dispatch: '待分配配送',
@@ -834,19 +845,22 @@
     picking: '取货中',
     completed: '已完成'
   }
-  const resolveDeliveryStatus = (row: any) =>
-    row?.deliveryStatus ||
-    (['pending_payment', 'pending_dispatch', 'pending_delivery', 'pending_review_cancel'].includes(
-      resolveOrderStatus(row)
+  const resolveDeliveryStatus = (row: any) => {
+    const direct = row?.deliveryStatus || row?.delivery_status
+    if (direct) return direct
+    const baseStatus = row?.status || row?.orderStatus || row?.order_status
+    return ['pending_payment', 'pending_dispatch', 'pending_delivery', 'pending_review_cancel'].includes(
+      baseStatus
     )
       ? 'pending_dispatch'
-      : resolveOrderStatus(row) === 'delivering'
+      : baseStatus === 'delivering'
         ? 'delivering'
-        : resolveOrderStatus(row) === 'partial_delivered'
+        : baseStatus === 'partial_delivered'
           ? 'partial_delivered'
-          : ['pending_confirm', 'completed'].includes(resolveOrderStatus(row))
+          : ['pending_confirm', 'completed'].includes(baseStatus)
             ? 'delivered'
-            : 'pending_dispatch')
+            : 'pending_dispatch'
+  }
   const deliveryStatusLabel = (s: string) => (s ? DELIVERY_STATUS_MAP[s] || s : '—')
   const deliveryStatusTagType = (s: string) => {
     if (s === 'delivered' || s === 'completed') return 'success'
@@ -865,6 +879,12 @@
     if (m === 'offline') return '线下支付'
     return m || '-'
   }
+  const resolveSupplierName = (row: any) => {
+    if (row?.supplierName) return row.supplierName
+    if (row?.supplier?.name) return row.supplier.name
+    const firstLine = Array.isArray(row?.children) ? row.children.find((x: any) => x?.supplierName) : null
+    return firstLine?.supplierName || '-'
+  }
   const orderTotalAmount = (row: any) =>
     Number(row?.totalAmount ?? row?.sellingTotalAmount ?? row?.settlementAmount ?? 0)
   const orderReceivableAmount = (row: any) => Number(row?.receivableAmount ?? orderTotalAmount(row))
@@ -1517,9 +1537,10 @@
     pointer-events: none;
   }
 
-  /* 结算单记录:底部分页右边界与二级列表右侧对齐 */
+  /* 结算单记录分页与其他页面保持一致:右对齐,不做人为右侧缩进 */
   .sheet-main-card .pagination {
-    padding-right: 400px;
+    justify-content: flex-end;
+    padding-right: 16px;
   }
 
   .order-row-expand-wrap {
@@ -1556,8 +1577,8 @@
     border-radius: 10px;
     max-width: 100%;
     box-sizing: border-box;
-    overflow: hidden;
     overflow-x: auto;
+    overflow-y: hidden;
     position: relative;
     padding: 12px 12px 14px 18px;
     background: #f8fafc;
@@ -1578,7 +1599,6 @@
   .sheet-row-expand-wrap :deep(.sheet-orders-table) {
     border: 1px solid var(--mint-border, #d9e8e2);
     border-radius: 8px;
-    overflow: hidden;
     background: #fff;
     position: relative;
     z-index: 1;
@@ -1598,8 +1618,9 @@
   .sheet-row-expand-wrap :deep(.sheet-orders-table .el-table__inner-wrapper),
   .sheet-row-expand-wrap :deep(.sheet-orders-table .el-table__header-wrapper),
   .sheet-row-expand-wrap :deep(.sheet-orders-table .el-table__body-wrapper) {
-    width: max-content !important;
+    width: 100% !important;
     min-width: 100% !important;
+    max-width: none !important;
   }
 
   .sheet-row-expand-wrap :deep(.sheet-orders-table .el-table__body-wrapper),
@@ -1609,7 +1630,7 @@
 
   .sheet-row-expand-wrap :deep(.sheet-orders-table.el-table),
   .sheet-row-expand-wrap :deep(.sheet-orders-table .el-table) {
-    width: max-content !important;
+    width: 100% !important;
     min-width: 100% !important;
   }
 
@@ -1617,7 +1638,8 @@
     display: block;
     width: 100%;
     max-width: 100%;
-    overflow: hidden;
+    overflow-x: auto !important;
+    overflow-y: hidden;
     border-radius: 0;
     text-align: left;
     position: relative;
@@ -1802,6 +1824,41 @@
     white-space: nowrap;
   }
 
+  /* ---------- 客户结算:结算单记录展开层滚动兜底 ---------- */
+  .sheet-orders-scroll,
+  .order-row-expand-scroll {
+    width: 100%;
+    max-width: 100%;
+    overflow-x: auto !important;
+    overflow-y: hidden;
+    scrollbar-gutter: stable;
+  }
+
+  .sheet-orders-scroll :deep(.sheet-orders-table) {
+    width: max(1560px, 100%) !important;
+    min-width: 1560px !important;
+  }
+
+  .order-row-expand-scroll :deep(.order-row-expand-table) {
+    width: 100% !important;
+    min-width: 100% !important;
+    max-width: 100% !important;
+  }
+
+  .sheet-orders-scroll :deep(.sheet-orders-table .el-table__body-wrapper),
+  .sheet-orders-scroll :deep(.sheet-orders-table .el-scrollbar__wrap),
+  .order-row-expand-scroll :deep(.order-row-expand-table .el-table__body-wrapper),
+  .order-row-expand-scroll :deep(.order-row-expand-table .el-scrollbar__wrap) {
+    overflow-x: auto !important;
+  }
+
+  .sheet-orders-scroll :deep(.el-scrollbar__bar.is-horizontal),
+  .order-row-expand-scroll :deep(.el-scrollbar__bar.is-horizontal) {
+    opacity: 1 !important;
+    display: block !important;
+    height: 10px !important;
+  }
+
 </style>
 
 <!-- Teleport 到 body:弹窗内容需非 scoped 才能稳定命中 -->

+ 91 - 10
web-admin/src/views/system/inquiry-quotation/index.vue

@@ -1383,6 +1383,7 @@
               height="420"
               row-key="productId"
               :row-class-name="confirmPricingRowClassName"
+              :cell-style="confirmPricingCellStyle"
               @row-dblclick="openConfirmPricingProductView"
             >
               <template #empty>
@@ -2264,8 +2265,12 @@
   const confirmPricingContext = reactive({
     supplierName: '',
     quotationId: '',
-    quotationTime: ''
+    quotationTime: '',
+    /** 当前弹窗选择的供应商是否为“当前生效供应商” */
+    isCurrentEffectiveSupplier: false
   })
+  /** 当前生效供应商:用于标红“本次报价 vs 上次生效报价”不一致的商品行 */
+  const confirmPricingLastSupplierPriceMap = ref<Map<string, number>>(new Map())
   const confirmPricingItems = ref<
     Array<{
       productId: string
@@ -2395,7 +2400,7 @@
     if (getSupplierHeaderLoading(supplierId)) return
     supplierHeaderLoadingMap[supplierId] = true
     try {
-      await openConfirmPricingDialog(row.supplierName, row.quotation)
+      await openConfirmPricingDialog(row.supplierName, row.quotation, supplierId)
     } finally {
       supplierHeaderLoadingMap[supplierId] = false
     }
@@ -2666,9 +2671,27 @@
     return list
   })
 
+  const isConfirmPricingDiffRow = (row: any) => {
+    if (!row?.inSale) return false
+    if (!confirmPricingContext.isCurrentEffectiveSupplier) return false
+    if (!row?.productId) return false
+    const last = confirmPricingLastSupplierPriceMap.value.get(String(row.productId))
+    if (typeof last !== 'number') return false
+    const cur = Number(row.supplierPrice || 0)
+    if (!Number.isFinite(cur) || !Number.isFinite(last)) return false
+    return Number(cur.toFixed(2)) !== Number(last.toFixed(2))
+  }
+
   const confirmPricingRowClassName = ({ row }: any) => {
     if (!row?.inSale) return 'confirm-pricing-row--not-sale'
-    return ''
+    return isConfirmPricingDiffRow(row) ? 'confirm-pricing-row--diff' : ''
+  }
+
+  const confirmPricingCellStyle = ({ row }: any) => {
+    if (isConfirmPricingDiffRow(row)) {
+      return { backgroundColor: '#fef2f2' }
+    }
+    return {}
   }
 
   const loadConfirmPricingCategoryProducts = async (categoryId: string, forceReload = false) => {
@@ -2700,7 +2723,11 @@
     await Promise.all(ids.map((id) => loadConfirmPricingCategoryProducts(id, true)))
   }
 
-  const openConfirmPricingDialog = async (supplierName: string, quotation: any) => {
+  const openConfirmPricingDialog = async (
+    supplierName: string,
+    quotation: any,
+    supplierId?: string
+  ) => {
     const quotationItems = Array.isArray(quotation?.items) ? quotation.items : []
     if (!quotation?.id || quotationItems.length === 0) {
       ElMessage.warning('该报价单没有可用商品,无法选中')
@@ -2715,6 +2742,11 @@
     confirmPricingContext.supplierName = supplierName || ''
     confirmPricingContext.quotationId = quotation.id
     confirmPricingContext.quotationTime = quotation?.submitTime || quotation?.createAt || ''
+    confirmPricingContext.isCurrentEffectiveSupplier =
+      !!supplierId &&
+      !!selectedQuotationDetail.value?.supplierId &&
+      String(selectedQuotationDetail.value.supplierId) === String(supplierId)
+    confirmPricingLastSupplierPriceMap.value = new Map()
 
     confirmPricingSaleTab.value = 'inSale'
     confirmPricingSelectedCategoryId.value = PICKER_ALL_CATEGORY
@@ -2752,8 +2784,35 @@
       quotationMap.set(String(it.productId), it)
     }
 
-    const selectedPriceMap = new Map<string, { price: number; pricingCoefficient: number }>()
+    // 回显优先级:
+    // - 当前生效供应商:最终售价/系数以“当前生效清单”回显(保持当前最终售价不被覆盖)
+    // - 非当前生效供应商:若报价单ID与当前生效清单一致(重复打开/二次调整),回显历史数据;否则首次按系数计算
+    const effectiveMap = new Map<
+      string,
+      { finalPrice: number; pricingCoefficient: number; lastSupplierPrice: number }
+    >()
+    if (confirmPricingContext.isCurrentEffectiveSupplier) {
+      const items = Array.isArray(selectedQuotationDetail.value?.items)
+        ? selectedQuotationDetail.value.items
+        : []
+      for (const it of items) {
+        const pid = String(it?.productId || '')
+        if (!pid) continue
+        const finalPrice = Number(it?.price ?? it?.finalPrice ?? it?.final_price ?? 0) || 0
+        const pricingCoefficient =
+          Number(it?.pricingCoefficient ?? it?.pricing_coefficient ?? 0) || 0
+        const lastSupplierPrice = Number(it?.supplierPrice ?? it?.supplier_price ?? 0) || 0
+        effectiveMap.set(pid, {
+          finalPrice: Number(finalPrice.toFixed(2)),
+          pricingCoefficient: Number(pricingCoefficient.toFixed(2)),
+          lastSupplierPrice: Number(lastSupplierPrice.toFixed(2))
+        })
+        confirmPricingLastSupplierPriceMap.value.set(pid, Number(lastSupplierPrice.toFixed(2)))
+      }
+    }
+    const selectedPriceMap = new Map<string, { finalPrice: number; pricingCoefficient: number }>()
     if (
+      !confirmPricingContext.isCurrentEffectiveSupplier &&
       selectedQuotationDetail.value?.quotationId &&
       String(selectedQuotationDetail.value.quotationId) === String(quotation.id)
     ) {
@@ -2764,8 +2823,8 @@
         const pid = String(it?.productId || '')
         if (!pid) continue
         selectedPriceMap.set(pid, {
-          price: Number(it?.price || 0),
-          pricingCoefficient: Number(it?.pricingCoefficient ?? 0)
+          finalPrice: Number(it?.price ?? it?.finalPrice ?? it?.final_price ?? 0) || 0,
+          pricingCoefficient: Number(it?.pricingCoefficient ?? it?.pricing_coefficient ?? 0) || 0
         })
       }
     }
@@ -2802,6 +2861,7 @@
         const rawCoeff = base.priceCoefficient ?? p.priceCoefficient ?? 0
         const numCoeff = Number(rawCoeff ?? 0)
         const safeCoeff = Number.isFinite(numCoeff) ? numCoeff : 0
+        const eff = effectiveMap.get(pid)
 
         return {
           productId: pid,
@@ -2816,6 +2876,16 @@
           supplierPrice,
           productPriceCoefficient: safeCoeff,
           pricingCoefficient: (() => {
+            if (inSale && eff) {
+              // 当前生效供应商:最终售价固定取当前生效清单;若本次供应商报价有变化,则反推系数以匹配最终售价
+              const fp = Number(eff.finalPrice || 0)
+              const sp = Number(supplierPrice || 0)
+              if (Number.isFinite(fp) && fp > 0 && Number.isFinite(sp) && sp > 0) {
+                const coeff = (fp / sp - 1) * 100
+                return Number(coeff.toFixed(2))
+              }
+              return Number.isFinite(Number(eff.pricingCoefficient)) ? eff.pricingCoefficient : safeCoeff
+            }
             const s = selectedPriceMap.get(pid)
             if (inSale && s)
               return Number.isFinite(Number(s.pricingCoefficient))
@@ -2824,9 +2894,11 @@
             return safeCoeff
           })(),
           finalPrice: (() => {
+            if (inSale && eff && Number.isFinite(Number(eff.finalPrice)) && Number(eff.finalPrice) > 0)
+              return Number(Number(eff.finalPrice).toFixed(2))
             const s = selectedPriceMap.get(pid)
-            if (inSale && s && Number.isFinite(Number(s.price)) && Number(s.price) > 0)
-              return Number(Number(s.price).toFixed(2))
+            if (inSale && s && Number.isFinite(Number(s.finalPrice)) && Number(s.finalPrice) > 0)
+              return Number(Number(s.finalPrice).toFixed(2))
             return inSale ? Number(Number(supplierPrice || 0).toFixed(2)) : 0
           })(),
           inSale
@@ -2845,7 +2917,11 @@
     // 应用“本次临时调价系数”,预填“在售”商品最终售价(若后端已回显 price,则保持回显值不覆盖)
     for (const item of confirmPricingItems.value) {
       if (!item?.inSale) continue
-      if (Number(item.finalPrice || 0) > 0 && selectedPriceMap.has(item.productId)) continue
+      if (
+        Number(item.finalPrice || 0) > 0 &&
+        (selectedPriceMap.has(item.productId) || effectiveMap.has(item.productId))
+      )
+        continue
       const percent = Number(item.pricingCoefficient ?? 0)
       const safePercent = Number.isFinite(percent) ? percent : 0
       const factor = 1 + safePercent / 100
@@ -3958,6 +4034,11 @@
     background: #fafafa !important;
     color: #909399 !important;
   }
+  /* 变更行红底:主要由 cell-style 内联样式保证,这里保留 class 兜底 */
+  .confirm-pricing-dialog
+    :deep(.el-table__body tr.el-table__row.confirm-pricing-row--diff td.el-table__cell) {
+    background: #fef2f2 !important;
+  }
   .matrix-cell {
     padding: 6px 8px;
     border-radius: 6px;

+ 179 - 34
web-admin/src/views/system/supplier-settlement/index.vue

@@ -327,15 +327,17 @@
             <el-table-column type="expand" width="48">
               <template #default="{ row }">
                 <div class="sheet-row-expand-wrap" v-loading="isSheetRowLoading(row.id)">
-                  <el-table
-                    v-if="getSheetOrders(row.id).length"
-                    :data="getSheetOrders(row.id)"
-                    row-key="id"
-                    stripe
-                    size="small"
-                    class="sheet-orders-table"
-                    @expand-change="handleSheetOrderExpand(row.id)"
-                  >
+                  <div v-if="getSheetOrders(row.id).length" class="sheet-orders-scroll">
+                    <el-table
+                      :data="getSheetOrders(row.id)"
+                      row-key="id"
+                      stripe
+                      size="small"
+                      :fit="false"
+                      scrollbar-always-on
+                      class="sheet-orders-table"
+                      @expand-change="handleSheetOrderExpand(row.id)"
+                    >
                     <el-table-column type="expand" width="48">
                       <template #default="{ row: ord }">
                         <div class="order-row-expand-wrap">
@@ -343,13 +345,16 @@
                             v-if="Array.isArray(ord.children) && ord.children.length"
                             class="settlement-row-expand-table-wrap sqxp-subtable-wrap"
                           >
-                            <el-table
-                              :data="ord.children"
-                              stripe
-                              size="small"
-                              class="order-row-expand-table sqxp-subtable"
-                              @row-dblclick="openOrderProductView"
-                            >
+                            <div class="order-row-expand-scroll">
+                              <el-table
+                                :data="ord.children"
+                                stripe
+                                size="small"
+                                :fit="true"
+                                scrollbar-always-on
+                                class="order-row-expand-table sqxp-subtable"
+                                @row-dblclick="openOrderProductView"
+                              >
                             <el-table-column prop="productName" label="商品" min-width="220">
                               <template #default="{ row: line }">
                                 <div class="expand-product-cell">
@@ -396,7 +401,8 @@
                                 >¥{{ formatAmount(line.sellingAmount ?? line.amount) }}</template
                               >
                             </el-table-column>
-                            </el-table>
+                              </el-table>
+                            </div>
                             <div class="order-row-expand-summary sqxp-subtable-summary">
                               <span
                                 >供货总金额(元):<span class="sqxp-subtable-summary__amount"
@@ -420,6 +426,11 @@
                       </template>
                     </el-table-column>
                     <el-table-column prop="clientName" label="客户名称" min-width="150" show-overflow-tooltip />
+                    <el-table-column label="供应商名称" min-width="150" show-overflow-tooltip>
+                      <template #default="{ row: ord }">
+                        {{ resolveSupplierName(ord) }}
+                      </template>
+                    </el-table-column>
                     <el-table-column prop="regionName" label="区域" min-width="110" show-overflow-tooltip />
                     <el-table-column label="供货总金额(元)" min-width="140" align="right">
                       <template #default="{ row: ord }">
@@ -463,7 +474,8 @@
                       </template>
                     </el-table-column>
                     <el-table-column prop="orderCreateAt" label="下单时间" min-width="170" />
-                  </el-table>
+                    </el-table>
+                  </div>
                   <el-empty v-else description="暂无明细" :image-size="72" />
                 </div>
               </template>
@@ -793,12 +805,10 @@
 
   const ORDER_STATUS_MAP: Record<string, string> = {
     pending_payment: '待支付',
+    pending_confirm: '待收货',
+    pending: '待收货',
     pending_review_cancel: '待审核取消',
-    pending_delivery: '待分配配送',
-    delivering: '配送中',
-    partial_delivered: '部分送达',
-    pending_confirm: '待确认收货',
-    completed: '已完结',
+    completed: '已完成',
     cancelled: '已取消'
   }
   const orderStatusLabel = (s: string) => (s ? ORDER_STATUS_MAP[s] || s : '—')
@@ -807,10 +817,11 @@
     return row?.orderStatus || row?.status || ''
   }
   const orderStatusTagType = (s: string) => {
+    if (s === 'pending_payment' || s === 'pending_review_cancel') return 'warning'
+    if (s === 'pending_confirm' || s === 'pending') return 'primary'
     if (s === 'completed') return 'success'
     if (s === 'cancelled') return 'info'
-    if (s === 'pending_payment' || s === 'partial_delivered') return 'warning'
-    return 'primary'
+    return 'info'
   }
   const DELIVERY_STATUS_MAP: Record<string, string> = {
     pending_dispatch: '待分配配送',
@@ -822,19 +833,22 @@
     picking: '取货中',
     completed: '已完成'
   }
-  const resolveDeliveryStatus = (row: any) =>
-    row?.deliveryStatus ||
-    (['pending_payment', 'pending_dispatch', 'pending_delivery', 'pending_review_cancel'].includes(
-      resolveOrderStatus(row)
+  const resolveDeliveryStatus = (row: any) => {
+    const direct = row?.deliveryStatus || row?.delivery_status
+    if (direct) return direct
+    const baseStatus = row?.status || row?.orderStatus || row?.order_status
+    return ['pending_payment', 'pending_dispatch', 'pending_delivery', 'pending_review_cancel'].includes(
+      baseStatus
     )
       ? 'pending_dispatch'
-      : resolveOrderStatus(row) === 'delivering'
+      : baseStatus === 'delivering'
         ? 'delivering'
-        : resolveOrderStatus(row) === 'partial_delivered'
+        : baseStatus === 'partial_delivered'
           ? 'partial_delivered'
-          : ['pending_confirm', 'completed'].includes(resolveOrderStatus(row))
+          : ['pending_confirm', 'completed'].includes(baseStatus)
             ? 'delivered'
-            : 'pending_dispatch')
+            : 'pending_dispatch'
+  }
   const deliveryStatusLabel = (s: string) => (s ? DELIVERY_STATUS_MAP[s] || s : '—')
   const deliveryStatusTagType = (s: string) => {
     if (s === 'delivered' || s === 'completed') return 'success'
@@ -853,6 +867,12 @@
     if (m === 'offline') return '线下支付'
     return m || '-'
   }
+  const resolveSupplierName = (row: any) => {
+    if (row?.supplierName) return row.supplierName
+    if (row?.supplier?.name) return row.supplier.name
+    const firstLine = Array.isArray(row?.children) ? row.children.find((x: any) => x?.supplierName) : null
+    return firstLine?.supplierName || '-'
+  }
   const orderTotalAmount = (row: any) =>
     Number(row?.totalAmount ?? row?.sellingTotalAmount ?? row?.clientSellingTotal ?? row?.settlementAmount ?? 0)
   const orderReceivableAmount = (row: any) => Number(row?.receivableAmount ?? orderTotalAmount(row))
@@ -1348,6 +1368,8 @@
   .order-row-expand-wrap {
     padding: 0;
     background: #f8fafc;
+    overflow-x: auto !important;
+    overflow-y: hidden;
   }
 
   /* 第二层:结算单展开后的“订单列表”容器 */
@@ -1863,7 +1885,8 @@
     display: block;
     width: 100%;
     max-width: 100%;
-    overflow: hidden;
+    overflow-x: auto !important;
+    overflow-y: hidden;
     border-radius: 0;
     text-align: left;
     position: relative;
@@ -2002,6 +2025,81 @@
     }
   }
 
+  /* 结算单记录主表:占满可用宽度,避免右侧出现大块空白 */
+  .supplier-settlement-sheet-table {
+    width: 100%;
+
+    :deep(.el-table__inner-wrapper),
+    :deep(.el-table__header-wrapper),
+    :deep(.el-table__body-wrapper) {
+      width: 100% !important;
+      min-width: 100% !important;
+    }
+  }
+
+  /* 子列表(结算单展开后的订单表):内容超宽时横向滚动 */
+  .sheet-row-expand-wrap {
+    overflow-x: auto;
+  }
+
+  .sheet-row-expand-wrap :deep(.sheet-orders-table) {
+    width: max-content !important;
+    min-width: 100% !important;
+  }
+
+  .sheet-row-expand-wrap :deep(.sheet-orders-table .el-table__inner-wrapper),
+  .sheet-row-expand-wrap :deep(.sheet-orders-table .el-table__header-wrapper),
+  .sheet-row-expand-wrap :deep(.sheet-orders-table .el-table__body-wrapper),
+  .sheet-row-expand-wrap :deep(.sheet-orders-table .el-scrollbar__wrap) {
+    overflow-x: auto !important;
+  }
+
+  /* 孙列表(订单展开后的商品明细表):内容超宽时横向滚动 */
+  .supplier-settlement-sheet-table :deep(.sheet-orders-table .settlement-row-expand-table-wrap),
+  .supplier-settlement-sheet-table :deep(.sheet-orders-table .order-row-expand-wrap) {
+    overflow-x: auto !important;
+  }
+
+  .supplier-settlement-sheet-table :deep(.sheet-orders-table .order-row-expand-table),
+  .supplier-settlement-sheet-table :deep(.sheet-orders-table .order-row-expand-table.el-table) {
+    width: max-content !important;
+    min-width: 100% !important;
+  }
+
+  .supplier-settlement-sheet-table
+    :deep(.sheet-orders-table .order-row-expand-table .el-table__inner-wrapper),
+  .supplier-settlement-sheet-table
+    :deep(.sheet-orders-table .order-row-expand-table .el-table__header-wrapper),
+  .supplier-settlement-sheet-table
+    :deep(.sheet-orders-table .order-row-expand-table .el-table__body-wrapper),
+  .supplier-settlement-sheet-table
+    :deep(.sheet-orders-table .order-row-expand-table .el-scrollbar__wrap) {
+    overflow-x: auto !important;
+  }
+
+  /* 强制显示子表/孙表横向滚动条(避免被主题或父容器裁剪) */
+  .sheet-row-expand-wrap :deep(.sheet-orders-table .el-scrollbar__bar.is-horizontal),
+  .supplier-settlement-sheet-table
+    :deep(.sheet-orders-table .order-row-expand-table .el-scrollbar__bar.is-horizontal) {
+    opacity: 1 !important;
+    display: block !important;
+    height: 10px !important;
+    bottom: 2px !important;
+  }
+
+  .sheet-row-expand-wrap :deep(.sheet-orders-table .el-table__body-wrapper::-webkit-scrollbar),
+  .supplier-settlement-sheet-table
+    :deep(.sheet-orders-table .order-row-expand-table .el-table__body-wrapper::-webkit-scrollbar) {
+    height: 10px;
+  }
+
+  .sheet-row-expand-wrap :deep(.sheet-orders-table .el-table__body-wrapper::-webkit-scrollbar-thumb),
+  .supplier-settlement-sheet-table
+    :deep(.sheet-orders-table .order-row-expand-table .el-table__body-wrapper::-webkit-scrollbar-thumb) {
+    background: #94a3b8;
+    border-radius: 999px;
+  }
+
   .order-row-head {
     display: flex;
     align-items: center;
@@ -2059,6 +2157,53 @@
     color: #c0c4cc;
   }
 
+  /* ---------- 结算单记录展开层滚动兜底(修复子表/孙表横向滚动条不可见 + 孙表右侧大空白) ---------- */
+  .sheet-orders-scroll,
+  .order-row-expand-scroll {
+    width: 100%;
+    max-width: 100%;
+    overflow-x: auto !important;
+    overflow-y: hidden;
+    scrollbar-gutter: stable;
+  }
+
+  .sheet-orders-scroll :deep(.sheet-orders-table) {
+    width: max(1560px, 100%) !important;
+    min-width: 1560px !important;
+  }
+
+  /* 孙列表:避免被 max-content / 过大最小宽度撑出右侧大空白,默认占满容器 */
+  .order-row-expand-scroll :deep(.order-row-expand-table) {
+    width: 100% !important;
+    min-width: 100% !important;
+    max-width: 100% !important;
+  }
+
+  .sheet-orders-scroll :deep(.sheet-orders-table .el-table__inner-wrapper),
+  .sheet-orders-scroll :deep(.sheet-orders-table .el-table__header-wrapper),
+  .sheet-orders-scroll :deep(.sheet-orders-table .el-table__body-wrapper),
+  .order-row-expand-scroll :deep(.order-row-expand-table .el-table__inner-wrapper),
+  .order-row-expand-scroll :deep(.order-row-expand-table .el-table__header-wrapper),
+  .order-row-expand-scroll :deep(.order-row-expand-table .el-table__body-wrapper) {
+    width: 100% !important;
+    min-width: 100% !important;
+    max-width: none !important;
+  }
+
+  .sheet-orders-scroll :deep(.sheet-orders-table .el-table__body-wrapper),
+  .sheet-orders-scroll :deep(.sheet-orders-table .el-scrollbar__wrap),
+  .order-row-expand-scroll :deep(.order-row-expand-table .el-table__body-wrapper),
+  .order-row-expand-scroll :deep(.order-row-expand-table .el-scrollbar__wrap) {
+    overflow-x: auto !important;
+  }
+
+  .sheet-orders-scroll :deep(.el-scrollbar__bar.is-horizontal),
+  .order-row-expand-scroll :deep(.el-scrollbar__bar.is-horizontal) {
+    opacity: 1 !important;
+    display: block !important;
+    height: 10px !important;
+  }
+
 </style>
 
 <!-- Teleport 到 body:弹窗内容需非 scoped 才能稳定命中 -->

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff