Просмотр исходного кода

统一结算与系统页面样式,并修正配送明细供货价计算。

对结算、接口日志、系统设置、时效预警与配送相关页面进行 Fresh Mint 标准化改造,同时让配送详情按订单价格版本计算供货单价/金额,避免与售价错误一致。

Made-with: Cursor
zlwu 6 месяцев назад
Родитель
Сommit
a6193c5acc

+ 57 - 5
backend/app/api/delivery.py

@@ -1,6 +1,7 @@
 """配送管理接口"""
 from typing import Optional, List
 from datetime import datetime
+from decimal import Decimal
 from fastapi import APIRouter, Depends, Query
 from sqlalchemy.orm import Session
 
@@ -14,6 +15,7 @@ from app.crud.product import product_crud
 from app.crud.unit import unit_crud
 from app.crud.address import address_crud
 from app.core.order_status_compat import derive_legacy_status_from_three
+from app.models.models import BizPriceListItem
 
 router = APIRouter(prefix="/web", tags=["运营端-配送管理"])
 
@@ -46,6 +48,38 @@ def _category_path_display(db: Session, category_id: Optional[str]) -> str:
     return "/".join([n for n in names if n])
 
 
+def _supplier_unit_yuan_from_pli(pli: Optional[BizPriceListItem]) -> Optional[float]:
+    """价格清单行 price 为面向客户的最终单价;pricing_coefficient 为调价系数(%),供货单价 = price / (1 + 系数/100)。"""
+    if pli is None or getattr(pli, "price", None) is None:
+        return None
+    final_price = Decimal(str(pli.price))
+    raw_coeff = getattr(pli, "pricing_coefficient", None)
+    if raw_coeff is None:
+        coeff_pct = Decimal("0")
+    else:
+        try:
+            coeff_pct = Decimal(str(raw_coeff))
+        except Exception:
+            coeff_pct = Decimal("0")
+    factor = Decimal("1") + coeff_pct / Decimal("100")
+    if factor <= Decimal("0"):
+        factor = Decimal("1")
+    supplier_unit = (final_price / factor).quantize(Decimal("0.01"))
+    return float(supplier_unit)
+
+
+def _price_list_item_map(db: Session, price_list_ids: List[str]) -> dict:
+    """(price_list_id, product_id, unit_id) -> BizPriceListItem"""
+    if not price_list_ids:
+        return {}
+    rows = (
+        db.query(BizPriceListItem)
+        .filter(BizPriceListItem.price_list_id.in_(price_list_ids), BizPriceListItem.is_del == 0)
+        .all()
+    )
+    return {(r.price_list_id, r.product_id, r.unit_id): r for r in rows}
+
+
 def _supplier_pickup_address_block(db: Session, supplier_id: Optional[str]) -> dict:
     """供应商默认取货地址;无默认时取第一条 pickup 类型地址。"""
     empty = {"fullAddress": None, "contactPerson": None, "contactPhone": None}
@@ -243,6 +277,7 @@ def get_delivery_task(
 
     # 获取订单信息
     order_info = {}
+    order_price_list_id: Optional[str] = None
     if task.order_id:
         order = order_crud.get(db, task.order_id)
         if order:
@@ -253,11 +288,13 @@ def get_delivery_task(
                 "contactPhone": order.contact_phone,
                 "deliveryAddress": order.delivery_address
             }
+            order_price_list_id = getattr(order, "price_list_id", None)
 
     pickup_addr = _supplier_pickup_address_block(db, task.supplier_id)
 
     # 处理任务明细(商品展示字段对齐运营端订单明细)
     item_details = []
+    pli_map = _price_list_item_map(db, [order_price_list_id]) if order_price_list_id else {}
     for item in items:
         product = product_crud.get(db, item.product_id)
         order_line = order_item_crud.get(db, item.order_item_id) if item.order_item_id else None
@@ -274,17 +311,32 @@ def get_delivery_task(
             category_path = _category_path_display(db, str(product.category_id))
         measure_value = _numeric_to_float_optional(getattr(product, "measure_value", None)) if product else None
 
+        qty = float(item.quantity) if item.quantity else 0.0
         unit_price = float(order_line.unit_price) if order_line and order_line.unit_price is not None else None
         amount = float(order_line.amount) if order_line and order_line.amount is not None else None
+        if amount is None and unit_price is not None:
+            amount = round(qty * float(unit_price), 2)
 
         client = user_crud.get(db, item.client_id) if item.client_id else None
 
-        # 供货/售价字段:配送任务明细接口目前不计算价格清单的供货价,先以订单行售价兜底,
-        # 确保运营端「配送列表」展开明细对齐「订单列表」列结构时有数据可展示。
+        # 供货/售价字段:对齐「订单详情」逻辑:优先使用价格清单计算供货单价;无价格清单时兜底订单行售价
         selling_unit_price = unit_price
         selling_amount = amount
-        supplier_unit_price = unit_price
-        supplier_amount = amount
+        unit_id_for_price = None
+        if order_line and getattr(order_line, "unit_id", None):
+            unit_id_for_price = order_line.unit_id
+        elif product and getattr(product, "unit_id", None):
+            unit_id_for_price = product.unit_id
+
+        supplier_unit_price = None
+        if order_price_list_id and item.product_id and unit_id_for_price:
+            pli = pli_map.get((order_price_list_id, item.product_id, unit_id_for_price))
+            supplier_unit_price = _supplier_unit_yuan_from_pli(pli)
+        if supplier_unit_price is None:
+            supplier_unit_price = unit_price
+        supplier_amount = None
+        if supplier_unit_price is not None:
+            supplier_amount = round(qty * float(supplier_unit_price), 2)
 
         item_details.append({
             "id": item.id,
@@ -303,7 +355,7 @@ def get_delivery_task(
             "supplierName": supplier_info.get("name"),
             "supplierUnitPrice": supplier_unit_price,
             "supplierAmount": supplier_amount,
-            "quantity": float(item.quantity) if item.quantity else 0,
+            "quantity": qty,
             "deliveredQuantity": float(item.delivered_quantity) if item.delivered_quantity else 0,
             "clientName": client.real_name if client else None,
             "clientPhone": client.phone if client else None,

+ 1 - 1
web-admin/src/router/modules/sqxp.ts

@@ -202,7 +202,7 @@ export const sqxpAdminRoutes: AppRouteRecord[] = [
         name: 'DeliveryDemo',
         path: 'delivery-demo',
         component: '/system/delivery-demo/index',
-        meta: { title: '配送调度演示', keepAlive: true }
+        meta: { title: '配送调度演示', isHide: true, keepAlive: true }
       }
     ]
   },

+ 56 - 27
web-admin/src/views/system/api-request-log/index.vue

@@ -16,7 +16,7 @@
         </h2>
       </div>
       <div class="filter-block">
-      <el-form :model="searchForm" class="filter-form" @submit.prevent>
+      <el-form :model="searchForm" class="filter-form" label-position="top" @submit.prevent>
         <div class="filter-grid">
         <el-form-item label="方法">
           <el-select v-model="searchForm.httpMethod" placeholder="全部" clearable>
@@ -29,13 +29,13 @@
           </el-select>
         </el-form-item>
         <el-form-item label="状态码">
-          <el-input v-model.number="searchForm.statusCode" placeholder="如 200" clearable />
+          <el-input v-model.number="searchForm.statusCode" placeholder="如 200" clearable @keyup.enter="handleSearch" />
         </el-form-item>
         <el-form-item label="路径包含">
-          <el-input v-model="searchForm.pathPrefix" placeholder="/api/..." clearable />
+          <el-input v-model="searchForm.pathPrefix" placeholder="/api/..." clearable @keyup.enter="handleSearch" />
         </el-form-item>
         <el-form-item label="关键词">
-          <el-input v-model="searchForm.keyword" placeholder="路径/用户/内容" clearable />
+          <el-input v-model="searchForm.keyword" placeholder="路径/用户/内容" clearable @keyup.enter="handleSearch" />
         </el-form-item>
         </div>
         <div class="filter-actions-row">
@@ -68,47 +68,72 @@
       <el-table-column prop="createAt" label="时间" min-width="170" />
       <el-table-column label="操作" width="100" fixed="right">
         <template #default="{ row }">
-          <el-button class="btn-op-item primary" @click="openDetail(row)">详情</el-button>
+          <el-button class="btn-op-item primary auto-icon-view" @click="openDetail(row)">查看</el-button>
         </template>
       </el-table-column>
     </el-table>
     <div class="pagination list-pager-bar">
       <el-pagination
-        v-model:current-page="pagination.page"
-        v-model:page-size="pagination.pageSize"
+        :current-page="pagination.page"
+        :page-size="pagination.pageSize"
         :total="pagination.total"
         :page-sizes="[10, 20, 50]"
         layout="total, sizes, prev, pager, next"
-        @size-change="loadData"
-        @current-change="loadData"
+        @update:page-size="
+          (s) => {
+            pagination.pageSize = s
+            pagination.page = 1
+            loadData()
+          }
+        "
+        @update:current-page="
+          (p) => {
+            pagination.page = p
+            loadData()
+          }
+        "
       />
     </div>
     </div>
 
-    <el-dialog v-model="detailVisible" title="请求详情" width="720px" destroy-on-close>
-      <el-descriptions v-if="detailRow" :column="1" border size="small">
-        <el-descriptions-item label="方法">{{ detailRow.httpMethod }}</el-descriptions-item>
-        <el-descriptions-item label="路径">{{ detailRow.path }}</el-descriptions-item>
-        <el-descriptions-item label="Query">{{ detailRow.queryString || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="状态 / 耗时">{{ detailRow.statusCode }} / {{ detailRow.durationMs }} ms</el-descriptions-item>
-        <el-descriptions-item label="用户">{{ detailRow.username || '-' }} ({{ detailRow.userId || '-' }})</el-descriptions-item>
-        <el-descriptions-item label="角色">{{ roleText(detailRow.role) }}</el-descriptions-item>
-        <el-descriptions-item label="IP">{{ detailRow.ipAddress || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="UA">{{ detailRow.userAgent || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="请求体">
-          <pre class="body-pre">{{ detailRow.requestBody || '-' }}</pre>
-        </el-descriptions-item>
-        <el-descriptions-item label="响应体">
-          <pre class="body-pre">{{ detailRow.responseBody || '-' }}</pre>
-        </el-descriptions-item>
-      </el-descriptions>
+    <el-dialog
+      v-model="detailVisible"
+      title="请求详情"
+      width="920px"
+      destroy-on-close
+      class="order-module-dialog api-request-log-dialog"
+      modal-class="order-module-dialog-mask"
+    >
+      <div class="dialog-scroll api-request-log-dialog-scroll">
+        <el-descriptions v-if="detailRow" :column="1" border size="small">
+          <el-descriptions-item label="方法">{{ detailRow.httpMethod }}</el-descriptions-item>
+          <el-descriptions-item label="路径">{{ detailRow.path }}</el-descriptions-item>
+          <el-descriptions-item label="Query">{{ detailRow.queryString || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="状态 / 耗时"
+            >{{ detailRow.statusCode }} / {{ detailRow.durationMs }} ms</el-descriptions-item
+          >
+          <el-descriptions-item label="用户">{{ detailRow.username || '-' }} ({{ detailRow.userId || '-' }})</el-descriptions-item>
+          <el-descriptions-item label="角色">{{ roleText(detailRow.role) }}</el-descriptions-item>
+          <el-descriptions-item label="IP">{{ detailRow.ipAddress || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="UA">{{ detailRow.userAgent || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="请求体">
+            <pre class="body-pre">{{ detailRow.requestBody || '-' }}</pre>
+          </el-descriptions-item>
+          <el-descriptions-item label="响应体">
+            <pre class="body-pre">{{ detailRow.responseBody || '-' }}</pre>
+          </el-descriptions-item>
+        </el-descriptions>
+      </div>
+      <template #footer>
+        <el-button class="btn-ghost" @click="detailVisible = false"><el-icon><Close /></el-icon>关闭</el-button>
+      </template>
     </el-dialog>
     </section>
   </div>
 </template>
 <script setup lang="ts">
   import { ref, reactive, onMounted } from 'vue'
-  import { Operation } from '@element-plus/icons-vue'
+  import { Close, Operation } from '@element-plus/icons-vue'
   import { fetchApiRequestLogList } from '@/api/business'
   defineOptions({ name: 'ApiRequestLog' })
   const searchForm = reactive({
@@ -167,4 +192,8 @@
     max-height: 280px;
     overflow: auto;
   }
+
+  .api-request-log-dialog-scroll :deep(.el-descriptions__body) {
+    background: #fff;
+  }
 </style>

+ 17 - 2
web-admin/src/views/system/client-settlement/index.vue

@@ -466,7 +466,7 @@
             </el-table-column>
             <el-table-column prop="settlementAt" label="结算时间" min-width="170" />
             <el-table-column prop="createName" label="操作人" width="100" />
-            <el-table-column label="操作" min-width="260" fixed="right" align="center" header-align="center">
+            <el-table-column label="操作" min-width="400" fixed="right" align="center" header-align="center">
               <template #default="{ row }">
                 <el-button class="btn-op-item primary" @click="openSheetDetail(row.id)">详情</el-button>
                 <el-button class="btn-op-item" @click="openSheetEdit(row)">编辑</el-button>
@@ -1263,6 +1263,11 @@
     background: #fff;
   }
 
+  /* 结算单记录:底部分页右边界与二级列表右侧对齐 */
+  .sheet-main-card .pagination {
+    padding-right: 400px;
+  }
+
   .order-row-expand-wrap {
     padding: 0;
     background: #f8fafc;
@@ -1387,12 +1392,22 @@
     margin-top: 4px;
     padding-left: 14px;
     padding-top: 10px;
-    padding-right: 0;
+    padding-right: 12px;
     padding-bottom: 0;
     border-left: 3px solid #f59e0b;
     background: linear-gradient(90deg, rgba(254, 243, 199, 0.35) 0%, transparent 12px);
   }
 
+  /* 第三级底部汇总(含“商品行数”):跟随子表“内容宽度”右对齐(避免对齐到一级列表最右侧) */
+  .client-settlement-sheet-table :deep(.sheet-orders-table .settlement-row-expand-table-wrap .order-row-expand-summary),
+  .client-settlement-table :deep(.settlement-row-expand-table-wrap .order-row-expand-summary) {
+    width: max-content;
+    min-width: 100%;
+    box-sizing: border-box;
+    padding-right: 12px;
+    justify-content: flex-end;
+  }
+
   .client-settlement-sheet-table :deep(.sheet-orders-table .settlement-row-expand-table-wrap::before) {
     background: #f59e0b;
   }

+ 253 - 164
web-admin/src/views/system/delivery-warning/index.vue

@@ -1,170 +1,216 @@
 <!-- 配送时效预警页面 -->
 <template>
   <div class="page-container">
-    <div class="search-bar">
-      <el-form :inline="true" :model="searchForm">
-        <el-form-item label="预警类型">
-          <el-select v-model="searchForm.warningType" placeholder="请选择类型" clearable>
-            <el-option label="全部" value="" />
-            <el-option label="超时" value="timeout" />
-            <el-option label="时效预警" value="advance_deadline" />
-            <el-option label="紧急" value="urgent" />
-            <el-option label="危急" value="critical" />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="处理状态">
-          <el-select v-model="searchForm.isResolved" placeholder="请选择状态" clearable>
-            <el-option label="全部" value="" />
-            <el-option label="未解决" :value="false" />
-            <el-option label="已解决" :value="true" />
-          </el-select>
-        </el-form-item>
-        <el-form-item>
-          <el-button type="primary" @click="handleSearch">搜索</el-button>
-          <el-button @click="handleReset">重置</el-button>
-        </el-form-item>
-      </el-form>
-    </div>
-    <el-table
-      ref="warningTableRef"
-      class="delivery-warning-table"
-      :data="tableData"
-      v-loading="loading"
-      stripe
-      @expand-change="handleRowExpand"
-    >
-      <el-table-column type="expand" width="48">
-        <template #default="{ row }">
-          <div class="delivery-row-expand-wrap" v-loading="!!expandedRowLoadingMap[row.id]">
-            <div v-if="getExpandedItems(row.id).length" class="delivery-row-expand-block">
-              <div class="sqxp-subtable-wrap">
-                <el-table :data="getExpandedItems(row.id)" stripe size="small" class="delivery-row-expand-table sqxp-subtable">
-                  <el-table-column prop="productName" label="商品" min-width="220">
-                    <template #default="{ row: line }">
-                      <div class="expand-product-cell">
-                        <div class="expand-product-thumb-wrap">
-                          <el-image v-if="line.imageUrl" :src="line.imageUrl" fit="cover" class="expand-product-thumb" />
-                          <span v-else class="expand-product-thumb placeholder">无图</span>
-                        </div>
-                        <span class="expand-product-name">{{ line.productName || '-' }}</span>
-                      </div>
-                    </template>
-                  </el-table-column>
-                  <el-table-column prop="categoryName" label="分类" min-width="120" show-overflow-tooltip>
-                    <template #default="{ row: line }">
-                      {{ formatCategoryPathLabel(line.categoryId ?? line.category_id, line.categoryName) }}
-                    </template>
-                  </el-table-column>
-                  <el-table-column label="计量" width="88" align="center">
-                    <template #default="{ row: line }">{{ formatMeasureCell(line.measureValue) }}</template>
-                  </el-table-column>
-                  <el-table-column prop="unitName" label="单位" width="70" align="center" />
-                  <el-table-column prop="quantity" label="数量" width="80" align="right" />
-                  <el-table-column label="单价(元)" min-width="110" align="right">
-                    <template #default="{ row: line }">
-                      <span v-if="line.unitPrice == null || line.unitPrice === ''">-</span>
-                      <span v-else>¥{{ formatMoney(line.unitPrice) }}</span>
-                    </template>
-                  </el-table-column>
-                  <el-table-column label="金额(元)" min-width="110" align="right">
-                    <template #default="{ row: line }">
-                      <span v-if="line.amount == null || line.amount === ''">-</span>
-                      <span v-else>¥{{ formatMoney(line.amount) }}</span>
-                    </template>
-                  </el-table-column>
-                  <el-table-column prop="status" label="状态" min-width="100" align="center">
-                    <template #default="{ row: line }">
-                      <el-tag :type="getDeliveryStatusType(line.status)" size="small">{{ getDeliveryStatusText(line.status) }}</el-tag>
-                    </template>
-                  </el-table-column>
-                </el-table>
-              </div>
-              <div class="delivery-row-expand-summary sqxp-subtable-summary">
-                <span>商品行数:{{ getExpandedItems(row.id).length }}</span>
-              </div>
+    <header class="page-head">
+      <h1>时效预警</h1>
+      <p class="page-desc">集中查看即将超时与已超时任务,支持快速处理与查看配送明细。</p>
+    </header>
+
+    <section class="panel">
+      <div class="panel-top">
+        <h2>
+          <span class="title-ic" aria-hidden="true">
+            <el-icon><Bell /></el-icon>
+          </span>
+          预警列表
+        </h2>
+      </div>
+
+      <div class="filter-block">
+        <el-form :model="searchForm" class="filter-form" label-position="top" @submit.prevent>
+          <div class="filter-grid">
+            <el-form-item label="预警类型">
+              <el-select v-model="searchForm.warningType" placeholder="全部" clearable @keyup.enter="handleSearch">
+                <el-option label="全部" value="" />
+                <el-option label="超时" value="timeout" />
+                <el-option label="时效预警" value="advance_deadline" />
+                <el-option label="紧急" value="urgent" />
+                <el-option label="危急" value="critical" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="处理状态">
+              <el-select v-model="searchForm.isResolved" placeholder="全部" clearable @keyup.enter="handleSearch">
+                <el-option label="全部" value="" />
+                <el-option label="未解决" :value="false" />
+                <el-option label="已解决" :value="true" />
+              </el-select>
+            </el-form-item>
+          </div>
+          <div class="filter-actions-row">
+            <div class="filter-actions-end">
+              <el-button class="btn-ghost auto-icon-refresh" @click="handleReset">重置</el-button>
+              <el-button class="btn-main auto-icon-search" type="primary" @click="handleSearch">搜索</el-button>
             </div>
-            <el-empty v-else-if="!expandedRowLoadingMap[row.id]" description="暂无商品明细" :image-size="72" />
           </div>
+        </el-form>
+      </div>
+
+      <div class="list-body list-wrap">
+        <el-table
+          ref="warningTableRef"
+          class="delivery-warning-table list-table"
+          :data="tableData"
+          v-loading="loading"
+          stripe
+          @expand-change="handleRowExpand"
+        >
+          <el-table-column type="expand" width="48">
+            <template #default="{ row }">
+              <div class="delivery-row-expand-wrap" v-loading="!!expandedRowLoadingMap[row.id]">
+                <div v-if="getExpandedItems(row.id).length" class="delivery-row-expand-block">
+                  <div class="sqxp-subtable-wrap">
+                    <el-table
+                      :data="getExpandedItems(row.id)"
+                      stripe
+                      size="small"
+                      class="delivery-row-expand-table sqxp-subtable"
+                    >
+                      <el-table-column prop="productName" label="商品" min-width="220">
+                        <template #default="{ row: line }">
+                          <div class="expand-product-cell">
+                            <div class="expand-product-thumb-wrap">
+                              <el-image v-if="line.imageUrl" :src="line.imageUrl" fit="cover" class="expand-product-thumb" />
+                              <span v-else class="expand-product-thumb placeholder">无图</span>
+                            </div>
+                            <span class="expand-product-name">{{ line.productName || '-' }}</span>
+                          </div>
+                        </template>
+                      </el-table-column>
+                      <el-table-column prop="categoryName" label="分类" min-width="120" show-overflow-tooltip>
+                        <template #default="{ row: line }">
+                          {{ formatCategoryPathLabel(line.categoryId ?? line.category_id, line.categoryName) }}
+                        </template>
+                      </el-table-column>
+                      <el-table-column label="计量" width="88" align="center">
+                        <template #default="{ row: line }">{{ formatMeasureCell(line.measureValue) }}</template>
+                      </el-table-column>
+                      <el-table-column prop="unitName" label="单位" width="70" align="center" />
+                      <el-table-column prop="quantity" label="数量" width="80" align="right" />
+                      <el-table-column label="单价(元)" min-width="110" align="right">
+                        <template #default="{ row: line }">
+                          <span v-if="line.unitPrice == null || line.unitPrice === ''">-</span>
+                          <span v-else>¥{{ formatMoney(line.unitPrice) }}</span>
+                        </template>
+                      </el-table-column>
+                      <el-table-column label="金额(元)" min-width="110" align="right">
+                        <template #default="{ row: line }">
+                          <span v-if="line.amount == null || line.amount === ''">-</span>
+                          <span v-else>¥{{ formatMoney(line.amount) }}</span>
+                        </template>
+                      </el-table-column>
+                      <el-table-column prop="status" label="状态" min-width="100" align="center">
+                        <template #default="{ row: line }">
+                          <el-tag :type="getDeliveryStatusType(line.status)" size="small">{{ getDeliveryStatusText(line.status) }}</el-tag>
+                        </template>
+                      </el-table-column>
+                    </el-table>
+                  </div>
+                  <div class="delivery-row-expand-summary sqxp-subtable-summary"></div>
+                </div>
+                <el-empty v-else-if="!expandedRowLoadingMap[row.id]" description="暂无商品明细" :image-size="72" />
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column prop="taskNo" label="配送单号" min-width="180">
+            <template #default="{ row }">
+              <el-button link type="primary" @click="toggleRowExpand(row)">{{ row.taskNo }}</el-button>
+            </template>
+          </el-table-column>
+          <el-table-column prop="orderNo" label="订单编号" min-width="180" />
+          <el-table-column prop="deliverName" label="配送员" min-width="110" />
+          <el-table-column prop="deliverPhone" label="电话" min-width="120" />
+          <el-table-column prop="clientName" label="客户" min-width="140" show-overflow-tooltip />
+          <el-table-column prop="contactPerson" label="联系人" min-width="110" />
+          <el-table-column prop="contactPhone" label="联系电话" min-width="120" />
+          <el-table-column prop="deliveryAddress" label="收货地址" min-width="220" show-overflow-tooltip />
+          <el-table-column prop="deliveryDeadlineAt" label="时效截止" min-width="180">
+            <template #default="{ row }">{{ formatDetailTime(row.deliveryDeadlineAt) || '-' }}</template>
+          </el-table-column>
+          <el-table-column prop="urgencyLevel" label="紧急程度" min-width="100">
+            <template #default="{ row }">
+              <el-tag :type="getUrgencyType(row.urgencyLevel)" size="small">{{ getUrgencyText(row.urgencyLevel) }}</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column prop="remainingMinutes" label="剩余时间" min-width="100">
+            <template #default="{ row }">
+              <span :class="getUrgencyClass(row.urgencyLevel)">{{ row.remainingMinutes }}分钟</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="message" label="预警消息" min-width="200" show-overflow-tooltip />
+          <el-table-column prop="isResolved" label="状态" min-width="80">
+            <template #default="{ row }">
+              <el-tag :type="row.isResolved ? 'success' : 'danger'" size="small">{{ row.isResolved ? '已解决' : '未解决' }}</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column prop="dispatchRemark" label="分配备注" min-width="180" show-overflow-tooltip />
+          <el-table-column prop="createAt" label="预警时间" min-width="180">
+            <template #default="{ row }">{{ formatDetailTime(row.createAt) || '-' }}</template>
+          </el-table-column>
+          <el-table-column label="操作" min-width="200" fixed="right" align="center" header-align="center">
+            <template #default="{ row }">
+              <el-button v-if="!row.isResolved" class="btn-op-item op-warn auto-icon-check" @click="handleResolve(row)"
+                >处理</el-button
+              >
+              <el-button class="btn-op-item primary auto-icon-view" @click="handleView(row)">查看</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <div class="pagination list-pager-bar">
+          <el-pagination
+            :current-page="pagination.page"
+            :page-size="pagination.pageSize"
+            :total="pagination.total"
+            :page-sizes="[10, 20, 50]"
+            layout="total, sizes, prev, pager, next"
+            @update:current-page="
+              (p) => {
+                pagination.page = p
+                loadData()
+              }
+            "
+            @update:page-size="
+              (s) => {
+                pagination.pageSize = s
+                pagination.page = 1
+                loadData()
+              }
+            "
+          />
+        </div>
+      </div>
+
+      <el-dialog
+        v-model="resolveVisible"
+        title="处理预警"
+        width="500px"
+        class="order-module-dialog delivery-warning-dialog"
+        modal-class="order-module-dialog-mask"
+      >
+        <div class="dialog-scroll">
+          <el-form :model="resolveForm" label-position="top">
+            <el-form-item label="处理备注">
+              <el-input v-model="resolveForm.note" type="textarea" placeholder="请输入处理备注" :rows="3" />
+            </el-form-item>
+          </el-form>
+        </div>
+        <template #footer>
+          <el-button class="btn-ghost" @click="resolveVisible = false"><el-icon><Close /></el-icon>取消</el-button>
+          <el-button class="btn-main" type="primary" @click="confirmResolve" :loading="submitLoading"
+            ><el-icon><Check /></el-icon>确定</el-button
+          >
         </template>
-      </el-table-column>
-      <el-table-column prop="taskNo" label="配送单号" min-width="180">
-        <template #default="{ row }">
-          <el-button link type="primary" @click="toggleRowExpand(row)">{{ row.taskNo }}</el-button>
-        </template>
-      </el-table-column>
-      <el-table-column prop="orderNo" label="订单编号" min-width="180" />
-      <el-table-column prop="deliverName" label="配送员" min-width="110" />
-      <el-table-column prop="deliverPhone" label="电话" min-width="120" />
-      <el-table-column prop="clientName" label="客户" min-width="140" show-overflow-tooltip />
-      <el-table-column prop="contactPerson" label="联系人" min-width="110" />
-      <el-table-column prop="contactPhone" label="联系电话" min-width="120" />
-      <el-table-column prop="deliveryAddress" label="收货地址" min-width="220" show-overflow-tooltip />
-      <el-table-column prop="deliveryDeadlineAt" label="时效截止" min-width="180">
-        <template #default="{ row }">{{ formatDetailTime(row.deliveryDeadlineAt) || '-' }}</template>
-      </el-table-column>
-      <el-table-column prop="urgencyLevel" label="紧急程度" min-width="100">
-        <template #default="{ row }">
-          <el-tag :type="getUrgencyType(row.urgencyLevel)" size="small">{{ getUrgencyText(row.urgencyLevel) }}</el-tag>
-        </template>
-      </el-table-column>
-      <el-table-column prop="remainingMinutes" label="剩余时间" min-width="100">
-        <template #default="{ row }">
-          <span :class="getUrgencyClass(row.urgencyLevel)">{{ row.remainingMinutes }}分钟</span>
-        </template>
-      </el-table-column>
-      <el-table-column prop="message" label="预警消息" min-width="200" show-overflow-tooltip />
-      <el-table-column prop="isResolved" label="状态" min-width="80">
-        <template #default="{ row }">
-          <el-tag :type="row.isResolved ? 'success' : 'danger'" size="small">{{ row.isResolved ? '已解决' : '未解决' }}</el-tag>
-        </template>
-      </el-table-column>
-      <el-table-column prop="dispatchRemark" label="分配备注" min-width="180" show-overflow-tooltip />
-      <el-table-column prop="createAt" label="预警时间" min-width="180">
-        <template #default="{ row }">{{ formatDetailTime(row.createAt) || '-' }}</template>
-      </el-table-column>
-      <el-table-column label="操作" min-width="120" fixed="right">
-        <template #default="{ row }">
-          <el-button v-if="!row.isResolved" type="primary" link @click="handleResolve(row)">处理</el-button>
-          <el-button type="primary" link @click="handleView(row)">查看</el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-    <div class="pagination">
-      <el-pagination
-        :current-page="pagination.page"
-        :page-size="pagination.pageSize"
-        :total="pagination.total"
-        :page-sizes="[10, 20, 50]"
-        layout="total, sizes, prev, pager, next"
-        @update:current-page="
-          (p) => {
-            pagination.page = p
-            loadData()
-          }
-        "
-        @update:page-size="
-          (s) => {
-            pagination.pageSize = s
-            pagination.page = 1
-            loadData()
-          }
-        "
-      />
-    </div>
-    <el-dialog v-model="resolveVisible" title="处理预警" width="500px">
-      <el-form :model="resolveForm" label-width="100px">
-        <el-form-item label="处理备注">
-          <el-input v-model="resolveForm.note" type="textarea" placeholder="请输入处理备注" :rows="3" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <el-button @click="resolveVisible = false">取消</el-button>
-        <el-button type="primary" @click="confirmResolve" :loading="submitLoading">确定</el-button>
-      </template>
-    </el-dialog>
+      </el-dialog>
 
     <!-- 预警详情弹窗 -->
-    <el-dialog v-model="detailVisible" title="预警详情" width="1000px">
+    <el-dialog
+      v-model="detailVisible"
+      title="预警详情"
+      width="1100px"
+      class="order-module-dialog delivery-warning-dialog"
+      modal-class="order-module-dialog-mask"
+    >
+      <div class="dialog-scroll">
       <el-descriptions :column="2" border class="mb-4">
         <el-descriptions-item label="配送单号">{{ detailData.taskNo || detailData.deliveryNo }}</el-descriptions-item>
         <el-descriptions-item label="订单编号">{{ detailData.orderNo }}</el-descriptions-item>
@@ -297,16 +343,26 @@
         </div>
       </div>
 
+      </div>
       <template #footer>
-        <el-button @click="detailVisible = false">关闭</el-button>
-        <el-button v-if="!detailData.isResolved" type="primary" @click="handleResolve(detailData)">处理预警</el-button>
+        <el-button class="btn-ghost" @click="detailVisible = false"><el-icon><Close /></el-icon>关闭</el-button>
+        <el-button
+          v-if="!detailData.isResolved"
+          class="btn-main"
+          type="primary"
+          @click="handleResolve(detailData)"
+        >
+          <el-icon><Check /></el-icon>处理预警
+        </el-button>
       </template>
     </el-dialog>
+    </section>
   </div>
 </template>
 <script setup lang="ts">
   import { ref, reactive, onMounted } from 'vue'
   import { ElMessage } from 'element-plus'
+  import { Bell, Check, Close } from '@element-plus/icons-vue'
   import { fetchDeliveryWarningList, fetchResolveWarning, fetchDeliveryWarningDetail, fetchDeliveryDetail } from '@/api/business'
   import { ensureCategoryPathDataLoaded, formatCategoryPathLabel } from '@/utils/category-path-display'
   defineOptions({ name: 'DeliveryWarning' })
@@ -480,15 +536,48 @@
   })
 </script>
 <style lang="scss" scoped>
-.page-container { padding: 20px; }
+.filter-block {
+  padding: 12px 16px 10px;
+  border-bottom: 1px solid #eef4f0;
+  background: #fff;
+}
+
+.filter-form :deep(.el-form-item) {
+  margin-bottom: 0;
+}
+
+.filter-form :deep(.el-form-item__label) {
+  font-size: 13px;
+  font-weight: 500;
+  color: var(--mint-text-muted, #64748b);
+  padding-bottom: 4px;
+  line-height: 1.2;
+}
+
+.filter-grid {
+  display: grid;
+  grid-template-columns: repeat(3, minmax(0, 1fr));
+  gap: 10px 14px;
+}
+
+.filter-actions-row {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 10px;
+}
+
+.filter-actions-end {
+  display: inline-flex;
+  align-items: center;
+  gap: 8px;
+}
+
 .delivery-warning-table :deep(th.el-table__cell > .cell),
 .delivery-warning-table :deep(td.el-table__cell > .cell) {
   display: flex;
   align-items: center;
   justify-content: center;
 }
-.search-bar { background: #fff; padding: 20px; border-radius: 8px; margin-bottom: 16px; }
-.pagination { display: flex; justify-content: flex-end; margin-top: 20px; }
 .text-danger { color: #dc2626; font-weight: 600; }
 .text-warning { color: #d97706; font-weight: 600; }
 .text-success { color: #059669; }

+ 383 - 110
web-admin/src/views/system/delivery/index.vue

@@ -1,52 +1,67 @@
 <!-- 配送管理页面 -->
 <template>
   <div class="page-container">
-    <!-- 搜索栏 -->
-    <div class="search-bar">
-      <el-form :inline="true" :model="searchForm">
-        <el-form-item label="关键词">
-          <el-input v-model="searchForm.keyword" placeholder="配送单号/订单编号" clearable />
-        </el-form-item>
-        <el-form-item label="状态">
-          <el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
-            <el-option label="全部" value="" />
-            <el-option label="待取货" value="pending" />
-            <el-option label="已分配" value="assigned" />
-            <el-option label="供应商备货中" value="supplier_stocking" />
-            <el-option label="备货完成(待取货)" value="supplier_ready" />
-            <el-option label="取货中" value="picking" />
-            <el-option label="配送中" value="delivering" />
-            <el-option label="已完成" value="completed" />
-            <el-option label="已取消" value="cancelled" />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="区域">
-          <el-select
-            v-model="searchForm.regionId"
-            placeholder="请选择区域"
-            clearable
-            @visible-change="(open) => open && loadRegions()"
-          >
-            <el-option label="全部" value="" />
-            <el-option v-for="r in regionList" :key="r.id" :label="r.name" :value="r.id" />
-          </el-select>
-        </el-form-item>
-        <el-form-item>
-          <el-button type="primary" @click="handleSearch">搜索</el-button>
-          <el-button @click="handleReset">重置</el-button>
-        </el-form-item>
-      </el-form>
+    <div class="page-head">
+      <h1>配送列表</h1>
+      <p class="page-desc">按区域查看配送任务状态,支持筛选、展开商品明细与详情查看。</p>
     </div>
-
-    <!-- 数据表格 -->
-    <el-table
-      ref="deliveryListTableRef"
-      class="delivery-list-table"
-      :data="tableData"
-      v-loading="loading"
-      stripe
-      @expand-change="handleDeliveryRowExpand"
-    >
+    <section class="panel">
+      <div class="panel-top">
+        <h2>
+          <el-icon class="title-ic"><Setting /></el-icon>
+          <span>配送任务列表</span>
+        </h2>
+      </div>
+
+      <div class="filter-block">
+        <el-form :model="searchForm" class="filter-form" label-position="top">
+          <div class="filter-grid">
+            <el-form-item label="关键词">
+              <el-input v-model="searchForm.keyword" placeholder="配送单号/订单编号" clearable />
+            </el-form-item>
+            <el-form-item label="状态">
+              <el-select v-model="searchForm.status" placeholder="全部" clearable>
+                <el-option label="全部" value="" />
+                <el-option label="待取货" value="pending" />
+                <el-option label="已分配" value="assigned" />
+                <el-option label="供应商备货中" value="supplier_stocking" />
+                <el-option label="备货完成(待取货)" value="supplier_ready" />
+                <el-option label="取货中" value="picking" />
+                <el-option label="配送中" value="delivering" />
+                <el-option label="已完成" value="completed" />
+                <el-option label="已取消" value="cancelled" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="区域">
+              <el-select
+                v-model="searchForm.regionId"
+                placeholder="全部"
+                clearable
+                @visible-change="(open) => open && loadRegions()"
+              >
+                <el-option label="全部" value="" />
+                <el-option v-for="r in regionList" :key="r.id" :label="r.name" :value="r.id" />
+              </el-select>
+            </el-form-item>
+          </div>
+          <div class="filter-actions-row">
+            <div class="filter-actions-end">
+              <el-button class="btn-ghost" @click="handleReset">重置</el-button>
+              <el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
+            </div>
+          </div>
+        </el-form>
+      </div>
+
+      <div class="list-body list-wrap">
+        <el-table
+          ref="deliveryListTableRef"
+          class="delivery-list-table list-table"
+          :data="tableData"
+          v-loading="loading"
+          stripe
+          @expand-change="handleDeliveryRowExpand"
+        >
       <el-table-column type="expand" width="48">
         <template #default="{ row }">
           <div class="delivery-row-expand-wrap" v-loading="!!expandedRowLoadingMap[row.id]">
@@ -111,9 +126,7 @@
                   </el-table-column>
                 </el-table>
               </div>
-              <div class="delivery-row-expand-summary sqxp-subtable-summary">
-                <span>商品行数:{{ getExpandedItems(row.id).length }}</span>
-              </div>
+              <div class="delivery-row-expand-summary sqxp-subtable-summary"></div>
             </div>
             <el-empty v-else-if="!expandedRowLoadingMap[row.id]" description="暂无商品明细" :image-size="72" />
           </div>
@@ -157,9 +170,6 @@
           <span v-else>¥{{ formatMoney(row.totalAmount) }}</span>
         </template>
       </el-table-column>
-      <el-table-column prop="itemCount" label="商品行数" min-width="90" align="center">
-        <template #default="{ row }">{{ row.itemCount ?? '-' }}</template>
-      </el-table-column>
       <el-table-column prop="expireTime" label="时效截止" min-width="180">
         <template #default="{ row }">{{ formatDetailTime(row.expireTime) || '-' }}</template>
       </el-table-column>
@@ -175,41 +185,72 @@
       <el-table-column prop="createAt" label="创建时间" min-width="180">
         <template #default="{ row }">{{ formatDetailTime(row.createAt) || '-' }}</template>
       </el-table-column>
-      <el-table-column label="操作" min-width="120" fixed="right">
+      <el-table-column label="操作" min-width="160" fixed="right" align="center" header-align="center">
         <template #default="{ row }">
-          <el-button type="primary" link @click="handleView(row)">查看</el-button>
+          <el-button class="btn-op-item primary" @click="handleView(row)">查看</el-button>
         </template>
       </el-table-column>
-    </el-table>
-
-    <!-- 分页 -->
-    <div class="pagination">
-      <el-pagination
-        :current-page="pagination.page"
-        :page-size="pagination.pageSize"
-        :total="pagination.total"
-        :page-sizes="[10, 20, 50, 100]"
-        layout="total, sizes, prev, pager, next"
-        @update:current-page="
-          (p) => {
-            pagination.page = p
-            loadData()
-          }
-        "
-        @update:page-size="
-          (s) => {
-            pagination.pageSize = s
-            pagination.page = 1
-            loadData()
-          }
-        "
-      />
-    </div>
+        </el-table>
+
+        <div class="pagination list-pager-bar">
+          <el-pagination
+            :current-page="pagination.page"
+            :page-size="pagination.pageSize"
+            :total="pagination.total"
+            :page-sizes="[10, 20, 50, 100]"
+            layout="total, sizes, prev, pager, next, jumper"
+            @update:current-page="
+              (p) => {
+                pagination.page = p
+                loadData()
+              }
+            "
+            @update:page-size="
+              (s) => {
+                pagination.pageSize = s
+                pagination.page = 1
+                loadData()
+              }
+            "
+          />
+        </div>
+      </div>
+    </section>
 
     <!-- 查看详情弹窗 -->
-    <el-dialog v-model="viewVisible" title="配送详情" width="1100px" destroy-on-close @closed="onViewDialogClosed">
+    <el-dialog
+      v-model="viewVisible"
+      title="配送详情"
+      width="1100px"
+      destroy-on-close
+      class="order-module-dialog delivery-detail-dialog"
+      modal-class="order-module-dialog-mask"
+      @closed="onViewDialogClosed"
+    >
+      <div class="dialog-scroll delivery-detail-dialog-scroll">
+        <div class="view-hero delivery-view-hero">
+          <div class="delivery-view-hero-icon">
+            <el-icon><Setting /></el-icon>
+          </div>
+          <div class="view-hero-main">
+            <h2>{{ viewData.taskNo || '-' }}</h2>
+            <div class="view-hero-tags">
+              <el-tag :type="getStatusType(viewData.status)" size="small">{{ getStatusText(viewData.status) }}</el-tag>
+              <el-tag type="primary" size="small">订单 {{ viewData.orderNo || '-' }}</el-tag>
+            </div>
+            <p class="view-hero-meta">
+              配送员:{{ viewData.deliverName || '-' }}
+              <span class="dot">·</span>
+              区域:{{ viewData.regionName || '-' }}
+              <span class="dot">·</span>
+              时效截止:{{ formatDetailTime(viewData.expireTime) || '-' }}
+            </p>
+          </div>
+        </div>
+
       <el-tabs v-model="viewDetailTab" class="delivery-detail-tabs">
         <el-tab-pane label="配送信息" name="detail">
+          <div class="view-section">
           <el-descriptions :column="2" border class="mb-4">
             <el-descriptions-item label="配送单号">{{ viewData.taskNo }}</el-descriptions-item>
             <el-descriptions-item label="订单编号">{{ viewData.orderNo }}</el-descriptions-item>
@@ -223,7 +264,9 @@
               {{ viewData.dispatchRemark }}
             </el-descriptions-item>
           </el-descriptions>
+          </div>
 
+          <div class="view-section">
           <div class="address-blocks">
             <el-card shadow="never" class="address-card address-card--supplier">
               <template #header>
@@ -307,7 +350,9 @@
               </div>
             </el-card>
           </div>
+          </div>
 
+          <div class="view-section">
           <h4 class="section-title">配送明细</h4>
           <p class="detail-hint">提示:双击商品行可查看商品详情(与订单明细一致)。</p>
           <el-table
@@ -349,6 +394,20 @@
               <template #default="{ row }">{{ formatMeasureCell(row.measureValue) }}</template>
             </el-table-column>
             <el-table-column prop="unitName" label="单位" min-width="72" align="center" />
+            <el-table-column prop="supplierUnitPrice" label="供货单价(元)" min-width="110" align="center">
+              <template #default="{ row }">
+                {{
+                  row.supplierUnitPrice != null && row.supplierUnitPrice !== ''
+                    ? `¥${formatMoney(row.supplierUnitPrice)}`
+                    : '-'
+                }}
+              </template>
+            </el-table-column>
+            <el-table-column prop="supplierAmount" label="供货金额(元)" min-width="110" align="center">
+              <template #default="{ row }">
+                {{ row.supplierAmount != null && row.supplierAmount !== '' ? `¥${formatMoney(row.supplierAmount)}` : '-' }}
+              </template>
+            </el-table-column>
             <el-table-column prop="unitPrice" label="单价(元)" min-width="100" align="center">
               <template #default="{ row }">
                 {{ row.unitPrice != null && row.unitPrice !== '' ? `¥${formatMoney(row.unitPrice)}` : '-' }}
@@ -366,9 +425,11 @@
               </template>
             </el-table-column>
           </el-table>
+          </div>
         </el-tab-pane>
 
         <el-tab-pane label="配送轨迹" name="timeline">
+          <div class="view-section">
           <div v-if="viewData.timeline && viewData.timeline.length > 0" class="timeline">
             <el-timeline>
               <el-timeline-item
@@ -386,8 +447,10 @@
             </el-timeline>
           </div>
           <el-empty v-else description="暂无配送轨迹" />
+          </div>
         </el-tab-pane>
       </el-tabs>
+      </div>
     </el-dialog>
 
     <ProductViewDialog v-model="productViewDialogVisible" :product-id="productViewTargetId" />
@@ -397,6 +460,7 @@
 <script setup lang="ts">
   import { ref, reactive, onMounted, computed } from 'vue'
   import { ElMessage } from 'element-plus'
+  import { Setting } from '@element-plus/icons-vue'
   import { fetchDeliveryList, fetchDeliveryDetail } from '@/api/business'
   import { fetchActiveRegions } from '@/api/admin'
   import ProductViewDialog from '@/components/business/product-view-dialog/index.vue'
@@ -721,7 +785,9 @@
 
 <style lang="scss" scoped>
 .page-container {
-  padding: 20px;
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
 }
 
 .delivery-list-table :deep(th.el-table__cell > .cell),
@@ -731,6 +797,51 @@
   justify-content: center;
 }
 
+/* 展开列单元格兜底:避免出现意外的“..”文本占位 */
+.delivery-list-table :deep(td.el-table__cell.el-table__expand-column > .cell) {
+  font-size: 0;
+  line-height: 0;
+  overflow: hidden;
+}
+
+/* 展开按钮:对齐商家结算管理-待结算订单树形样式 */
+.delivery-list-table :deep(.el-table__expand-icon) {
+  width: 28px;
+  height: 28px;
+  border-radius: 6px;
+  border: 1px solid var(--mint-border, #d9e8e2);
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
+  color: var(--mint-text-muted, #64748b);
+  background: var(--mint-surface, #fff);
+  font-size: 0;
+  line-height: 0;
+}
+
+.delivery-list-table :deep(.el-table__expand-icon:hover) {
+  background: var(--mint-primary-bg, #ecfdf5);
+  border-color: #10b981;
+  color: var(--mint-primary-dark, #059669);
+}
+
+.delivery-list-table :deep(.el-table__expand-icon--expanded) {
+  background: var(--mint-primary-bg, #ecfdf5);
+  border-color: #10b981;
+  color: var(--mint-primary-dark, #059669);
+}
+
+.delivery-list-table :deep(.el-table__expand-icon svg) {
+  width: 14px;
+  height: 14px;
+  transition: transform 0.15s ease;
+}
+
+.delivery-list-table :deep(.el-table__expand-icon--expanded svg) {
+  transform: rotate(180deg);
+}
+
 /* 展开行内容左对齐(不影响其它列居中策略) */
 .delivery-list-table :deep(td.el-table__expanded-cell) {
   /* Element Plus 默认会给 expanded-cell 加 padding,配合我们全局居中样式会出现“整体偏右/居中”的观感 */
@@ -744,53 +855,97 @@
   text-align: left !important;
 }
 
-.search-bar {
+.filter-block {
+  padding: 12px 16px 10px;
+  border-bottom: 1px solid #eef4f0;
   background: #fff;
-  padding: 20px;
-  border-radius: 8px;
-  margin-bottom: 16px;
 }
 
-.pagination {
+.filter-form :deep(.el-form-item) {
+  margin-bottom: 0;
+}
+
+.filter-form :deep(.el-form-item__label) {
+  font-size: 13px;
+  font-weight: 500;
+  color: var(--mint-text-muted, #64748b);
+  padding-bottom: 4px;
+  line-height: 1.2;
+}
+
+.filter-grid {
+  display: grid;
+  grid-template-columns: repeat(3, minmax(0, 1fr));
+  gap: 10px 14px;
+}
+
+.filter-actions-row {
   display: flex;
   justify-content: flex-end;
-  margin-top: 20px;
+  margin-top: 10px;
 }
 
-.delivery-row-expand-wrap {
-  /* 左侧预留 expand 箭头列(48px),让子表从“配送单号”列下方开始 */
-  padding: 8px 12px 8px calc(12px + 48px);
-  background: #fafbfc;
-  border-radius: 8px;
-  width: 100%;
+.filter-actions-end {
+  display: inline-flex;
+  align-items: center;
+  gap: 8px;
 }
 
-.delivery-row-expand-table {
-  width: max-content;
-  max-width: 100%;
+.pagination {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 0;
+  padding: 10px 16px 12px;
+  border-top: 1px solid var(--mint-border, #d9e8e2);
+  background: #fff;
 }
 
 .delivery-row-expand-table-wrap {
-  display: inline-block;
-  width: fit-content;
+  display: block;
+  width: 100%;
   max-width: 100%;
-  overflow-x: auto;
-  overflow-y: hidden;
-  border-radius: 8px;
+  overflow: hidden;
+  border-radius: 0;
   text-align: left;
+  position: relative;
+  background: #f8fafc;
+  padding: 12px 300px 14px 16px;
+}
 
-  /* 让横向滚动条在容器上出现,而不是把表格撑满整行 */
-  :deep(.el-table__inner-wrapper),
-  :deep(.el-table__header-wrapper),
-  :deep(.el-table__body-wrapper) {
-    width: max-content;
-  }
+.delivery-row-expand-table-wrap::before {
+  content: '';
+  position: absolute;
+  left: 0;
+  top: 0;
+  bottom: 0;
+  width: 3px;
+  background: #10b981;
 }
 
-.delivery-row-expand-block {
-  display: inline-block;
-  width: fit-content;
+.delivery-row-expand-wrap {
+  padding: 0;
+  background: #f8fafc;
+  border-radius: 0;
+}
+
+.delivery-row-expand-table {
+  width: 100%;
   max-width: 100%;
+  border: 1px solid var(--mint-border, #d9e8e2);
+  border-radius: 8px;
+  overflow: hidden;
+  background: #fff;
+}
+
+.delivery-row-expand-table :deep(th.el-table__cell) {
+  background: #e2e8f0 !important;
+  color: var(--mint-text-muted, #64748b);
+  font-size: 11px;
+  font-weight: 600;
+}
+
+.delivery-row-expand-table :deep(.el-table__cell) {
+  padding: 8px 10px;
 }
 
 .delivery-row-expand-summary {
@@ -845,8 +1000,81 @@
   margin-bottom: 20px;
 }
 
+.delivery-view-hero {
+  align-items: center;
+  border-bottom: 1px solid #d9e8e2;
+}
+
+.delivery-view-hero-icon {
+  width: 72px;
+  height: 72px;
+  border-radius: 12px;
+  border: 1px solid rgba(16, 185, 129, 0.2);
+  background: rgba(16, 185, 129, 0.08);
+  color: #059669;
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 28px;
+  flex: 0 0 auto;
+}
+
+.delivery-detail-dialog-scroll .view-hero {
+  display: flex;
+  gap: 16px;
+  padding: 20px 22px;
+  background: linear-gradient(135deg, #ecfdf5 0%, #ffffff 55%);
+}
+
+.delivery-detail-dialog-scroll .view-hero-main h2 {
+  margin: 0 0 8px;
+  font-size: 18px;
+  font-weight: 700;
+  color: #1e293b;
+}
+
+.delivery-detail-dialog-scroll .view-hero-tags {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 8px;
+  flex-wrap: wrap;
+}
+
+.delivery-detail-dialog-scroll .view-hero-meta {
+  margin: 0;
+  font-size: 13px;
+  color: #64748b;
+  line-height: 1.5;
+}
+
+.delivery-detail-dialog-scroll .view-hero-meta .dot {
+  margin: 0 6px;
+  opacity: 0.7;
+}
+
 .delivery-detail-tabs :deep(.el-tabs__content) {
-  padding-top: 8px;
+  padding-top: 0;
+}
+
+.delivery-detail-tabs :deep(.el-tabs__header) {
+  margin: 0;
+  padding: 0 22px;
+  border-bottom: 1px solid #d9e8e2;
+  background: #fff;
+}
+
+.delivery-detail-tabs :deep(.el-tabs__nav-wrap::after) {
+  background-color: transparent;
+}
+
+.view-section {
+  padding: 18px 22px;
+  border-bottom: 1px solid #d9e8e2;
+}
+
+.view-section:last-child {
+  border-bottom: 0;
 }
 
 .section-title {
@@ -1020,3 +1248,48 @@
   }
 }
 </style>
+
+<style lang="scss">
+  .order-module-dialog.el-dialog {
+    display: flex;
+    flex-direction: column;
+    max-height: calc(100vh - 64px);
+  }
+
+  .order-module-dialog .el-dialog__header {
+    flex-shrink: 0;
+    background: linear-gradient(135deg, #ecfdf5 0%, #ffffff 70%);
+    border-bottom: 1px solid var(--mint-border, #d9e8e2);
+  }
+
+  .order-module-dialog .el-dialog__body {
+    flex: 1 1 auto;
+    min-height: 0;
+    padding: 0 !important;
+    overflow: hidden;
+  }
+
+  .order-module-dialog .dialog-scroll {
+    height: min(720px, calc(100vh - 180px));
+    max-height: min(720px, calc(100vh - 180px));
+    overflow-y: auto;
+    overflow-x: hidden;
+    padding: 0;
+  }
+
+  .order-module-dialog .el-dialog__footer {
+    flex-shrink: 0;
+    background: #f8fafc;
+    border-top: 1px solid var(--mint-border, #d9e8e2);
+  }
+
+  .order-module-dialog .el-dialog__footer .el-button {
+    height: 36px;
+    border-radius: 8px;
+    font-weight: 600;
+  }
+
+  .order-module-dialog-mask {
+    backdrop-filter: none !important;
+  }
+</style>

+ 174 - 69
web-admin/src/views/system/system-config/index.vue

@@ -1,48 +1,106 @@
 <!-- 系统设置页面:订单 / 客服参数,数据存储于 biz_system_config(区域预计配送在「区域」维护) -->
 <template>
   <div class="page-container">
-    <section class="settings-section">
-      <h3 class="section-title">订单设置</h3>
-      <el-form :model="orderForm" label-width="200px" class="form">
-        <el-form-item label="订单自动取消时间">
-          <el-input-number v-model="orderForm.autoCancelMinutes" :min="5" :max="1440" />
-          <span class="unit-text">分钟(超时未支付自动取消)</span>
-        </el-form-item>
-        <el-form-item label="默认预计配送时长">
-          <el-input-number v-model="orderForm.defaultRegionExpectedDeliveryMinutes" :min="1" :max="10080" />
-          <span class="unit-text">分钟</span>
-          <div class="hint-text">
-            分配配送员时,若该区域未填写「预计配送(分钟)」,则配送截止时间 = 当前时间 + 本值(分钟);默认 600。
-          </div>
-        </el-form-item>
-        <el-form-item label="自动确认收货期限">
-          <el-input-number v-model="orderForm.autoConfirmReceiveDays" :min="1" :max="365" />
-          <span class="unit-text">天</span>
-          <div class="hint-text">
-            配送员确认送达后,客户超过该天数仍未点「确认收货」时,由系统自动确认;定时任务约每小时检查一次。
+    <header class="page-head">
+      <h1>系统设置</h1>
+      <p class="page-desc">统一维护订单规则与客服联系方式;保存后对全端生效。</p>
+    </header>
+
+    <section class="panel settings-panel settings-panel--order">
+      <div class="panel-top">
+        <h2>
+          <span class="title-ic" aria-hidden="true">
+            <el-icon><Setting /></el-icon>
+          </span>
+          订单设置
+        </h2>
+      </div>
+      <div class="panel-body">
+        <el-form :model="orderForm" label-position="top" class="settings-form">
+          <div class="form-grid">
+            <el-form-item>
+              <template #label>
+                <div class="field-label">
+                  <div class="field-label__title">订单自动取消时间</div>
+                  <div class="field-label__desc">超时未支付自动取消</div>
+                </div>
+              </template>
+              <div class="field-inline">
+                <el-input-number v-model="orderForm.autoCancelMinutes" :min="5" :max="1440" class="field-control" />
+                <span class="unit-text">分钟</span>
+              </div>
+            </el-form-item>
+
+            <el-form-item>
+              <template #label>
+                <div class="field-label">
+                  <div class="field-label__title">默认预计配送时长</div>
+                  <div class="field-label__desc">
+                    分配配送员时,若该区域未填写「预计配送(分钟)」,则配送截止时间 = 当前时间 + 本值(分钟);默认 600。
+                  </div>
+                </div>
+              </template>
+              <div class="field-inline">
+                <el-input-number
+                  v-model="orderForm.defaultRegionExpectedDeliveryMinutes"
+                  :min="1"
+                  :max="10080"
+                  class="field-control"
+                />
+                <span class="unit-text">分钟</span>
+              </div>
+            </el-form-item>
+
+            <el-form-item>
+              <template #label>
+                <div class="field-label">
+                  <div class="field-label__title">自动确认收货期限</div>
+                  <div class="field-label__desc">
+                    配送员确认送达后,客户超过该天数仍未点「确认收货」时,由系统自动确认;定时任务约每小时检查一次。
+                  </div>
+                </div>
+              </template>
+              <div class="field-inline">
+                <el-input-number v-model="orderForm.autoConfirmReceiveDays" :min="1" :max="365" class="field-control" />
+                <span class="unit-text">天</span>
+              </div>
+            </el-form-item>
           </div>
-        </el-form-item>
-      </el-form>
+        </el-form>
+      </div>
     </section>
 
-    <section class="settings-section">
-      <h3 class="section-title">客服设置</h3>
-      <el-form :model="contactForm" label-width="140px" class="form">
-        <el-form-item label="客服电话">
-          <el-input
-            v-model="contactForm.servicePhone"
-            maxlength="32"
-            show-word-limit
-            clearable
-            placeholder="如 400-xxx-xxxx 或 13800138000"
-          />
-          <div class="hint-text">客户端 / 供应商 / 配送员小程序「联系客服」一键拨号使用</div>
-        </el-form-item>
-      </el-form>
+    <section class="panel settings-panel">
+      <div class="panel-top">
+        <h2>
+          <span class="title-ic" aria-hidden="true">
+            <el-icon><PhoneFilled /></el-icon>
+          </span>
+          客服设置
+        </h2>
+      </div>
+      <div class="panel-body">
+        <el-form :model="contactForm" label-position="top" class="settings-form">
+          <div class="form-grid">
+            <el-form-item label="客服电话" class="form-col-span-2">
+              <el-input
+                v-model="contactForm.servicePhone"
+                maxlength="32"
+                show-word-limit
+                clearable
+                placeholder="如 400-xxx-xxxx 或 13800138000"
+              />
+              <div class="hint-text">客户端 / 供应商 / 配送员小程序「联系客服」一键拨号使用</div>
+            </el-form-item>
+          </div>
+        </el-form>
+      </div>
     </section>
 
-    <div class="form-actions">
-      <el-button type="primary" @click="saveAll" :loading="saveLoading">保存</el-button>
+    <div class="form-actions settings-actions">
+      <el-button class="btn-main" type="primary" @click="saveAll" :loading="saveLoading">
+        <el-icon><Check /></el-icon>保存
+      </el-button>
     </div>
   </div>
 </template>
@@ -50,6 +108,7 @@
 <script setup lang="ts">
   import { ref, reactive, onMounted } from 'vue'
   import { ElMessage } from 'element-plus'
+  import { Check, PhoneFilled, Setting } from '@element-plus/icons-vue'
   import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
   import { fetchSystemConfig, fetchUpdateSystemConfig } from '@/api/business'
 
@@ -113,52 +172,98 @@
 </script>
 
 <style lang="scss" scoped>
-.page-container {
-  padding: 20px;
-  background: #fff;
-  border-radius: 8px;
+.settings-panel {
+  overflow: hidden;
 }
 
-.settings-section {
-  max-width: 720px;
+.panel-body {
+  padding: 14px 16px 16px;
+}
 
-  & + & {
-    margin-top: 32px;
-    padding-top: 28px;
-    border-top: 1px solid #ebeef5;
-  }
+.settings-form :deep(.el-form-item) {
+  margin-bottom: 0;
+}
+
+.settings-form :deep(.el-form-item__label) {
+  /* label 在上:给内容区留足间距,避免与输入控件发生覆盖 */
+  height: auto !important;
+  line-height: 1.2;
+  padding-bottom: 10px;
+  display: block;
+}
+
+.settings-panel--order .settings-form :deep(.el-form-item__label) {
+  /* 订单设置:label 更大,说明更小(对齐设计图层级) */
+  color: var(--mint-text, #1e293b);
+}
+
+.field-label {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.field-label__title {
+  font-size: 13px;
+  font-weight: 700;
+  color: var(--mint-text, #1e293b);
+}
+
+.field-label__desc {
+  font-size: 11px;
+  font-weight: 500;
+  color: var(--mint-text-muted, #64748b);
+  line-height: 1.35;
+  display: block;
+  max-width: 100%;
+}
+
+.form-grid {
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: 12px 16px;
+}
+
+.form-col-span-2 {
+  grid-column: 1 / -1;
+}
+
+.field-inline {
+  display: inline-flex;
+  align-items: center;
+  gap: 10px;
+  flex-wrap: wrap;
+}
+
+.field-control {
+  width: 220px;
+  max-width: 100%;
 }
 
 .hint-text {
   margin-top: 6px;
   font-size: 12px;
-  color: #909399;
+  color: var(--mint-text-muted, #64748b);
   line-height: 1.5;
 }
 
-.section-title {
-  margin: 0 0 16px;
-  font-size: 16px;
-  font-weight: 600;
-  color: #303133;
-  line-height: 1.4;
-  padding-left: 12px;
-  border-left: 3px solid #10b981;
-}
-
-.form {
-  margin-top: 4px;
+.unit-text {
+  color: var(--mint-text-muted, #64748b);
+  font-size: 12px;
 }
 
-.unit-text {
-  margin-left: 8px;
-  color: #909399;
+.settings-actions {
+  display: flex;
+  justify-content: flex-end;
+  padding-top: 12px;
 }
 
-.form-actions {
-  max-width: 720px;
-  margin-top: 28px;
-  padding-top: 20px;
-  border-top: 1px solid #ebeef5;
+@media (max-width: 760px) {
+  .form-grid {
+    grid-template-columns: 1fr;
+  }
+  .field-control {
+    width: 100%;
+  }
 }
 </style>