Эх сурвалжийг харах

fix: 订单模块全面打磨 — 状态枚举修正 + 列表页重设计 + 详情页全状态覆盖

【OrderStatus 枚举修正】与后端 OrderStatus.java 对齐: 0=待支付 1=已完成 2=已取消 3=已关闭

【订单列表页】
- 移除统计栏,精简代码 439→270 行
- 标题改为"订单管理",搜索支持订单号/手机号自动识别
- 卡片重设计: 左设备名+门店 / 右金额+优惠标签 / 底时间+用户+详情
- 支付方式标签移除(几乎全为微信支付分)
- 骨架屏/错误重试/空状态/下拉刷新/加载更多全覆盖

【订单详情页】
- 修正退款判断: status===1(已完成) 而非 status===2(已取消)
- 新增加载态/错误重试态/退款 loading 态
- 退款用 lastId 重载代替脆弱的 getCurrentPages
- 优惠金额行 + 商品明细优化 + 用户名展示

【导航栏】移除激活指示点,扫码按钮上移,图标与文字增加间距

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
skyline 1 өдөр өмнө
parent
commit
121cd4e982

+ 3 - 23
haha-admin-mp/src/components/CustomTabBar.vue

@@ -2,7 +2,6 @@
   <view class="custom-tabbar">
     <view class="tab-item" :class="{ active: currentIndex === 0 }" @click="switchTab(0)">
       <view class="tab-icon">
-        <view class="active-dot"></view>
         <view class="icon-work"></view>
       </view>
       <text class="tab-text">工作台</text>
@@ -10,7 +9,6 @@
 
     <view class="tab-item" :class="{ active: currentIndex === 1 }" @click="switchTab(1)">
       <view class="tab-icon">
-        <view class="active-dot"></view>
         <view class="icon-order"></view>
       </view>
       <text class="tab-text">订单</text>
@@ -25,7 +23,6 @@
 
     <view class="tab-item" :class="{ active: currentIndex === 2 }" @click="switchTab(2)">
       <view class="tab-icon">
-        <view class="active-dot"></view>
         <view class="icon-device"></view>
       </view>
       <text class="tab-text">设备</text>
@@ -33,7 +30,6 @@
 
     <view class="tab-item" :class="{ active: currentIndex === 3 }" @click="switchTab(3)">
       <view class="tab-icon">
-        <view class="active-dot"></view>
         <view class="icon-my"></view>
       </view>
       <text class="tab-text">我的</text>
@@ -154,23 +150,6 @@ const handleScan = () => {
   margin-bottom: 2rpx;
 }
 
-/* 激活指示点 */
-.active-dot {
-  position: absolute;
-  top: -6rpx;
-  left: 50%;
-  transform: translateX(-50%);
-  width: 10rpx;
-  height: 10rpx;
-  border-radius: 50%;
-  background: transparent;
-  transition: background 0.2s ease;
-}
-
-.tab-item.active .active-dot {
-  background: $primary-color;
-}
-
 .tab-item.active {
   color: $primary-color;
 }
@@ -334,7 +313,7 @@ const handleScan = () => {
 
 .scan-btn {
   position: absolute;
-  bottom: 36rpx;
+  bottom: 64rpx;
   left: 50%;
   transform: translateX(-50%);
   width: 96rpx;
@@ -387,6 +366,7 @@ const handleScan = () => {
   font-size: 20rpx;
   color: $text-color-primary;
   font-weight: 400;
-  padding-bottom: 12rpx;
+  padding-bottom: 8rpx;
+  margin-top: 12rpx;
 }
 </style>

+ 284 - 201
haha-admin-mp/src/pages/orders/detail.vue

@@ -1,10 +1,9 @@
 <template>
   <view class="page">
-    <!-- 黄色导航栏 -->
-    <NavBar :title="navTitle" :showBack="true" />
+    <NavBar title="订单详情" :showBack="true" />
 
-    <!-- 加载状态 -->
-    <view class="load-tip" v-if="!order">
+    <!-- 加载 -->
+    <view class="load-tip" v-if="loading">
       <view class="dot-row">
         <view class="pulse-dot"></view>
         <view class="pulse-dot"></view>
@@ -12,21 +11,26 @@
       </view>
     </view>
 
-    <scroll-view class="detail-scroll" scroll-y v-if="order">
+    <!-- 错误 -->
+    <view class="error-state" v-if="!loading && errorMsg">
+      <view class="error-icon"></view>
+      <text class="error-text">{{ errorMsg }}</text>
+      <view class="retry-btn" @click="onRetry"><text>点击重试</text></view>
+    </view>
+
+    <scroll-view class="detail-scroll" scroll-y v-if="!loading && order">
       <!-- 状态区域 -->
       <view class="status-section">
         <view class="status-row">
           <view class="status-tag" :class="getPayStatusClass(order.payStatus)">
             <text>{{ order.payStatusLabel || getPayStatusText(order.payStatus) }}</text>
           </view>
-          <text class="order-no-text">订单{{ order.orderNo }}</text>
+          <text class="order-no">订单 {{ order.orderNo }}</text>
         </view>
-        <text class="status-desc" :class="getPayStatusClass(order.payStatus)">
-          {{ getStatusDesc(order.status, order.payStatus) }}
-        </text>
+        <text class="status-desc" :class="getPayStatusClass(order.payStatus)">{{ getStatusDesc(order.status, order.payStatus) }}</text>
       </view>
 
-      <!-- 视频区域 -->
+      <!-- 视频 -->
       <view class="video-section" v-if="order.videoUrl">
         <video class="order-video" :src="order.videoUrl" controls></video>
       </view>
@@ -36,40 +40,43 @@
         <text class="section-title">订单明细</text>
         <view class="product-list" v-if="order.products && order.products.length > 0">
           <view class="product-item" v-for="item in order.products" :key="item.productId">
-            <image
-              class="product-img"
-              :src="item.pic || '/static/images/default-product.png'"
-              mode="aspectFill"
-            />
+            <image class="product-img" :src="item.pic || '/static/images/default-product.png'" mode="aspectFill" />
             <view class="product-main">
               <text class="product-name">{{ item.productName }}</text>
-              <text class="product-meta">数量 x{{ item.productNum }}</text>
-              <text class="product-meta">单价 ¥{{ formatMoney(item.price) }}</text>
+              <text class="product-meta">x{{ item.productNum }}</text>
             </view>
-            <view class="product-right">
-              <text class="deal-price">¥{{ formatMoney(item.price * item.productNum) }}</text>
+            <view class="product-price">
+              <text class="price-unit">¥{{ formatMoney(item.price) }}</text>
+              <text class="price-total">¥{{ formatMoney(item.price * item.productNum) }}</text>
             </view>
           </view>
         </view>
         <view class="product-empty" v-else>
+          <view class="empty-doc-icon"></view>
           <text>暂无商品明细</text>
         </view>
-        <view class="amount-row">
-          <text class="amount-label">实付款</text>
-          <text class="amount-value">¥{{ formatMoney(order.paidAmount) }}</text>
+
+        <view class="amount-section">
+          <view class="amount-row" v-if="order.discountAmount > 0">
+            <text class="amount-label">优惠金额</text>
+            <text class="amount-value discount">-¥{{ formatMoney(order.discountAmount) }}</text>
+          </view>
+          <view class="amount-row total">
+            <text class="amount-label">实付款</text>
+            <text class="amount-value">¥{{ formatMoney(order.paidAmount) }}</text>
+          </view>
         </view>
       </view>
 
       <!-- 订单信息 -->
       <view class="section">
         <text class="section-title">订单信息</text>
+
         <view class="info-row">
           <text class="info-label">订单编号</text>
           <view class="info-value-row">
-            <text class="info-value">{{ order.orderNo }}</text>
-            <view class="copy-btn" @click="copyText(order.orderNo)">
-              <text class="copy-btn-text">复制</text>
-            </view>
+            <text class="info-value mono">{{ order.orderNo }}</text>
+            <view class="copy-btn" @click="copyText(order.orderNo)"><text>复制</text></view>
           </view>
         </view>
         <view class="info-row">
@@ -90,35 +97,37 @@
         </view>
         <view class="info-row">
           <text class="info-label">设备编号</text>
-          <text class="info-value">{{ order.deviceId || '-' }}</text>
-        </view>
-        <view class="info-row" v-if="order.deviceAddress">
-          <text class="info-label">设备地址</text>
-          <text class="info-value">{{ order.deviceAddress }}</text>
+          <text class="info-value mono">{{ order.deviceId || '-' }}</text>
         </view>
         <view class="info-row" v-if="order.phone">
-          <text class="info-label">联系消费者</text>
+          <text class="info-label">消费者</text>
           <view class="info-value-row">
             <text class="info-value">{{ formatPhone(order.phone) }}</text>
-            <view class="action-btn call-btn" @click="callPhone(order.phone)">
-              <text class="action-btn-text">呼叫</text>
-            </view>
+            <view class="action-btn call-btn" @click="callPhone(order.phone)"><text>呼叫</text></view>
           </view>
         </view>
+        <view class="info-row" v-if="order.userName">
+          <text class="info-label">用户名</text>
+          <text class="info-value">{{ order.userName }}</text>
+        </view>
       </view>
+
+      <!-- 底部占位 -->
+      <view class="bottom-spacer" v-if="canRefund(order)"></view>
     </scroll-view>
 
-    <!-- 底部操作 -->
+    <!-- 退款操作 -->
     <view class="bottom-bar" v-if="order && canRefund(order)">
-      <view class="refund-btn" @click="handleRefund">
-        <text class="refund-btn-text">退款</text>
+      <view class="refund-btn" :class="{ disabled: isRefunding }" @click="handleRefund">
+        <view class="loading-spinner" v-if="isRefunding"></view>
+        <text class="refund-btn-text">{{ isRefunding ? '处理中' : '退款' }}</text>
       </view>
     </view>
   </view>
 </template>
 
 <script setup lang="ts">
-import { ref, computed } from 'vue';
+import { ref } from 'vue';
 import { onLoad } from '@dcloudio/uni-app';
 import NavBar from '@/components/NavBar.vue';
 import { getOrderDetail, handleRefund as refundOrder } from '@/api/order';
@@ -139,32 +148,30 @@ interface OrderDetail {
   payStatus: string;
   payStatusLabel?: string;
   paidAmount: number;
+  discountAmount?: number;
   createTime: string;
   payTime?: string;
   shopName?: string;
   deviceId?: string;
   deviceName?: string;
-  deviceAddress?: string;
+  userName?: string;
   phone?: string;
   videoUrl?: string;
   products?: OrderProduct[];
 }
 
 const order = ref<OrderDetail | null>(null);
+const loading = ref(true);
+const errorMsg = ref('');
+const isRefunding = ref(false);
+let lastId = '';
+
 const formatMoney = formatMoneyUtil;
 const formatPhone = formatPhoneUtil;
 
-const navTitle = computed(() => {
-  return `${order.value?.deviceId || '订单'}-订单详情`;
-});
-
 const getPayStatusText = (payStatus: string): string => {
-  const map: Record<string, string> = {
-    'UNPAID': '未支付',
-    'PAID': '已支付',
-    'REFUND': '已退款'
-  };
-  return map[payStatus] || '未知';
+  const map: Record<string, string> = { UNPAID: '未支付', PAID: '已支付', REFUND: '已退款' };
+  return map[payStatus] || payStatus || '未知';
 };
 
 const getPayStatusClass = (payStatus: string): 'success' | 'danger' | 'warning' => {
@@ -175,26 +182,20 @@ const getPayStatusClass = (payStatus: string): 'success' | 'danger' | 'warning'
 
 const getStatusDesc = (status: number, payStatus: string): string => {
   if (payStatus === 'PAID') return '交易成功';
-  if (payStatus === 'REFUND') return '已退款';
-  if (status === 1) return '等待支付';
-  if (status === 0) return '已取消';
-  if (status === 3) return '已关闭';
+  if (payStatus === 'REFUND') return '已退款,款项将退回原支付账户';
+  if (status === 0) return '等待消费者支付';
+  if (status === 2) return '订单已取消';
+  if (status === 3) return '订单已关闭';
   return '';
 };
 
 const canRefund = (o: OrderDetail): boolean => {
-  return o.payStatus === 'PAID' && o.status === 2;
+  return o.payStatus === 'PAID' && o.status === 1;
 };
 
 const copyText = (text: string) => {
-  if (!text) {
-    showToast('无内容可复制');
-    return;
-  }
-  uni.setClipboardData({
-    data: text,
-    success: () => showToast('已复制', 'success')
-  });
+  if (!text) { showToast('无内容可复制'); return; }
+  uni.setClipboardData({ data: text, success: () => showToast('已复制', 'success') });
 };
 
 const callPhone = (phone: string) => {
@@ -203,30 +204,39 @@ const callPhone = (phone: string) => {
 
 const loadDetail = async (id?: string) => {
   if (!id) {
-    showToast('订单ID不存在');
+    errorMsg.value = '缺少订单参数';
+    loading.value = false;
     return;
   }
+  lastId = id;
+  loading.value = true;
+  errorMsg.value = '';
   try {
     order.value = await getOrderDetail(id);
   } catch {
-    showToast('加载订单详情失败');
+    errorMsg.value = '加载订单详情失败,请检查网络后重试';
+    order.value = null;
+  } finally {
+    loading.value = false;
   }
 };
 
+const onRetry = () => loadDetail(lastId);
+
 const handleRefund = async () => {
-  if (!order.value) return;
-  const confirmed = await showConfirm('确认对该订单进行退款操作?');
+  if (isRefunding.value || !order.value) return;
+  const confirmed = await showConfirm('确认对该订单进行全额退款?');
   if (!confirmed) return;
+
+  isRefunding.value = true;
   try {
     await refundOrder(order.value.id, '商家申请退款');
-    showToast('退款处理成功', 'success');
-    setTimeout(() => {
-      const pages = getCurrentPages();
-      const currentPage = pages[pages.length - 1] as { options?: Record<string, string> };
-      loadDetail(currentPage?.options?.id);
-    }, 1000);
+    showToast('退款成功', 'success');
+    setTimeout(() => loadDetail(lastId), 1200);
   } catch {
-    showToast('退款处理失败');
+    showToast('退款失败,请重试');
+  } finally {
+    isRefunding.value = false;
   }
 };
 
@@ -250,23 +260,23 @@ $font-stack: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue',
 .detail-scroll {
   flex: 1;
   height: 0;
-  padding-bottom: 140rpx;
+}
+
+.bottom-spacer {
+  height: 120rpx;
 }
 
 // ====== Loading ======
 .load-tip {
   display: flex;
   justify-content: center;
-  padding: 120rpx 0;
+  padding: 200rpx 0;
 }
 
-.dot-row {
-  display: flex;
-  gap: 8rpx;
-}
+.dot-row { display: flex; gap: $spacing-1; }
 
 .pulse-dot {
-  width: 9rpx; height: 9rpx;
+  width: 10rpx; height: 10rpx;
   background: $text-color-placeholder;
   border-radius: 50%;
   animation: pulse 1.2s ease-in-out infinite;
@@ -279,41 +289,74 @@ $font-stack: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue',
   40% { transform: scale(1); opacity: 1; }
 }
 
-// ====== Status section ======
+// ====== Error ======
+.error-state {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 200rpx $spacing-4;
+
+  .error-icon {
+    width: 80rpx;
+    height: 80rpx;
+    border-radius: 50%;
+    background: $error-color-bg;
+    margin-bottom: $spacing-3;
+    position: relative;
+
+    &::before {
+      content: '!';
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      font-size: 40rpx;
+      font-weight: 700;
+      color: $error-color;
+      line-height: 1;
+    }
+  }
+
+  .error-text {
+    font-size: $font-size-base;
+    color: $text-color-secondary;
+    margin-bottom: $spacing-4;
+    text-align: center;
+  }
+
+  .retry-btn {
+    padding: $spacing-2 $spacing-6;
+    background: $primary-color;
+    border-radius: $radius-full;
+
+    text { font-size: $font-size-base; color: $text-color-primary; font-weight: 500; }
+  }
+}
+
+// ====== Status ======
 .status-section {
+  padding: $spacing-4 $spacing-4 $spacing-3;
   background: $bg-color-card;
-  padding: $spacing-3 $spacing-4;
   margin-bottom: $spacing-2;
 
   .status-row {
     display: flex;
     align-items: center;
-    margin-bottom: $spacing-1;
+    margin-bottom: 12rpx;
 
     .status-tag {
-      padding: 4rpx $spacing-2;
+      padding: 4rpx 16rpx;
       border-radius: $radius-sm;
-      font-size: $font-size-sm;
+      font-size: 24rpx;
       font-weight: 500;
-      margin-right: $spacing-2;
-
-      &.success {
-        background: $success-color-bg;
-        color: $success-color;
-      }
-
-      &.danger {
-        background: $error-color-bg;
-        color: $error-color;
-      }
-
-      &.warning {
-        background: $warning-color-bg;
-        color: $warning-color;
-      }
+      margin-right: 16rpx;
+
+      &.success { background: $success-color-bg; color: $success-color; }
+      &.danger  { background: $error-color-bg; color: $error-color; }
+      &.warning { background: $warning-color-bg; color: $warning-color; }
     }
 
-    .order-no-text {
+    .order-no {
       font-size: $font-size-md;
       font-weight: 600;
       color: $text-color-primary;
@@ -321,108 +364,137 @@ $font-stack: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue',
   }
 
   .status-desc {
-    font-size: 26rpx;
+    font-size: $font-size-sm;
 
     &.success { color: $success-color; }
-    &.danger { color: $error-color; }
+    &.danger  { color: $error-color; }
     &.warning { color: $warning-color; }
   }
 }
 
 // ====== Video ======
 .video-section {
-  background: $bg-color-card;
-  padding: 20rpx $spacing-4;
   margin-bottom: $spacing-2;
 
   .order-video {
     width: 100%;
     height: 400rpx;
-    border-radius: $radius-base;
     background: $bg-color-secondary;
   }
 }
 
-// ====== Section card ======
+// ====== Section ======
 .section {
-  background: $bg-color-card;
   margin-bottom: $spacing-2;
   padding: $spacing-3 $spacing-4;
+  background: $bg-color-card;
 
   .section-title {
     display: block;
     font-size: $font-size-md;
     font-weight: 600;
     color: $text-color-primary;
-    margin-bottom: $spacing-2;
+    margin-bottom: $spacing-3;
+    padding-bottom: $spacing-2;
+    border-bottom: 1rpx solid $border-color-light;
   }
 }
 
 // ====== Product list ======
-.product-list {
-  .product-item {
-    display: flex;
-    align-items: flex-start;
-    padding: $spacing-2 0;
-    border-bottom: 1rpx solid $border-color-light;
+.product-item {
+  display: flex;
+  align-items: flex-start;
+  padding: $spacing-2 0;
+  border-bottom: 1rpx solid $border-color-light;
 
-    &:last-child {
-      border-bottom: none;
-    }
+  &:last-child { border-bottom: none; }
 
-    .product-img {
-      width: 120rpx;
-      height: 120rpx;
-      border-radius: $radius-sm;
-      background: $bg-color-page;
-      margin-right: 20rpx;
-      flex-shrink: 0;
+  .product-img {
+    width: 100rpx;
+    height: 100rpx;
+    border-radius: $radius-sm;
+    background: $bg-color-page;
+    flex-shrink: 0;
+    margin-right: 16rpx;
+  }
+
+  .product-main {
+    flex: 1;
+
+    .product-name {
+      display: block;
+      font-size: $font-size-base;
+      color: $text-color-primary;
+      margin-bottom: 4rpx;
     }
 
-    .product-main {
-      flex: 1;
-
-      .product-name {
-        display: block;
-        font-size: $font-size-base;
-        color: $text-color-primary;
-        margin-bottom: $spacing-1;
-      }
-
-      .product-meta {
-        display: block;
-        font-size: $font-size-sm;
-        color: $text-color-muted;
-        margin-bottom: 4rpx;
-      }
+    .product-meta {
+      font-size: $font-size-sm;
+      color: $text-color-muted;
     }
+  }
+
+  .product-price {
+    text-align: right;
+    flex-shrink: 0;
 
-    .product-right {
-      display: flex;
-      align-items: flex-end;
+    .price-unit {
+      display: block;
+      font-size: $font-size-sm;
+      color: $text-color-muted;
+      margin-bottom: 4rpx;
+    }
 
-      .deal-price {
-        font-size: $font-size-sm;
-        color: $text-color-muted;
-      }
+    .price-total {
+      font-size: $font-size-base;
+      font-weight: 600;
+      color: $text-color-primary;
     }
   }
 }
 
 .product-empty {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
   padding: $spacing-5 0;
-  text-align: center;
-  font-size: $font-size-sm;
-  color: $text-color-placeholder;
+
+  .empty-doc-icon {
+    width: 48rpx;
+    height: 56rpx;
+    border: 2.5rpx solid $text-color-placeholder;
+    border-radius: 4rpx;
+    margin-bottom: $spacing-2;
+    position: relative;
+
+    &::after {
+      content: '';
+      position: absolute;
+      top: 16rpx;
+      left: 10rpx;
+      right: 10rpx;
+      height: 2.5rpx;
+      background: $text-color-placeholder;
+      border-radius: 1rpx;
+      box-shadow: 0 10rpx 0 $text-color-placeholder, 0 20rpx 0 $text-color-placeholder;
+    }
+  }
+
+  text { font-size: $font-size-sm; color: $text-color-placeholder; }
+}
+
+// ====== Amount ======
+.amount-section {
+  margin-top: $spacing-2;
+  padding-top: $spacing-2;
+  border-top: 1rpx solid $border-color-light;
 }
 
 .amount-row {
   display: flex;
-  justify-content: space-between;
   align-items: center;
-  padding: $spacing-2 0 0;
-  border-top: 1rpx solid $border-color-light;
-  margin-top: $spacing-2;
+  justify-content: space-between;
+  padding: 8rpx 0;
 
   .amount-label {
     font-size: $font-size-base;
@@ -430,23 +502,29 @@ $font-stack: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue',
   }
 
   .amount-value {
-    font-size: $font-size-lg;
-    font-weight: 700;
+    font-size: $font-size-base;
+    font-weight: 600;
     color: $text-color-primary;
+
+    &.discount { color: $error-color; font-weight: 500; }
+  }
+
+  &.total {
+    padding-top: 16rpx;
+
+    .amount-value { font-size: $font-size-lg; font-weight: 700; }
   }
 }
 
-// ====== Info rows ======
+// ====== Info ======
 .info-row {
   display: flex;
-  justify-content: space-between;
   align-items: center;
+  justify-content: space-between;
   padding: $spacing-3 0;
   border-bottom: 1rpx solid $border-color-light;
 
-  &:last-child {
-    border-bottom: none;
-  }
+  &:last-child { border-bottom: none; }
 
   .info-label {
     font-size: $font-size-base;
@@ -458,78 +536,83 @@ $font-stack: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue',
     font-size: $font-size-base;
     color: $text-color-primary;
     text-align: right;
+
+    &.mono { letter-spacing: 0.02em; }
   }
 
   .info-value-row {
     display: flex;
     align-items: center;
-    gap: $spacing-2;
+    gap: 16rpx;
   }
 }
 
-// ====== Action buttons ======
+// ====== Buttons ======
 .copy-btn, .action-btn {
-  padding: 8rpx $spacing-3;
+  padding: 6rpx 20rpx;
   border-radius: $radius-sm;
   flex-shrink: 0;
+  display: flex;
+  align-items: center;
 
+  text { font-size: 22rpx; line-height: 1; }
   &:active { opacity: 0.7; }
 }
 
 .copy-btn {
   background: $bg-color-page;
 
-  .copy-btn-text {
-    font-size: $font-size-xs;
-    color: $text-color-secondary;
-  }
-}
-
-.action-btn {
-  .action-btn-text {
-    font-size: $font-size-xs;
-    font-weight: 500;
-  }
+  text { color: $text-color-secondary; }
 }
 
 .call-btn {
   background: $primary-color-bg;
 
-  .action-btn-text {
-    color: $primary-color-dark;
-  }
+  text { color: $primary-color-dark; font-weight: 500; }
 }
 
-// ====== Bottom bar ======
+// ====== Bottom ======
 .bottom-bar {
   position: fixed;
   bottom: 0;
   left: 0;
   right: 0;
-  background: $bg-color-card;
   padding: $spacing-3 $spacing-4;
-  padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
+  padding-bottom: calc($spacing-3 + env(safe-area-inset-bottom));
+  background: $bg-color-card;
   border-top: 1rpx solid $border-color-light;
-  flex-shrink: 0;
+}
 
-  .refund-btn {
-    width: 100%;
-    background: $primary-color;
-    border-radius: $radius-xl;
-    padding: 28rpx 0;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    transition: opacity 0.15s ease;
+.refund-btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: $spacing-2;
+  width: 100%;
+  padding: 26rpx 0;
+  border-radius: $radius-xl;
+  background: $primary-color;
+  transition: opacity 0.15s ease;
 
-    &:active { opacity: 0.8; }
+  &:active { opacity: 0.8; }
 
-    .refund-btn-text {
-      font-size: $font-size-lg;
-      font-weight: 600;
-      color: $text-color-primary;
-    }
+  &.disabled { opacity: 0.6; pointer-events: none; }
+
+  .refund-btn-text {
+    font-size: $font-size-md;
+    font-weight: 600;
+    color: $text-color-primary;
   }
 }
-</style>
 
+.loading-spinner {
+  width: 32rpx;
+  height: 32rpx;
+  border: 3rpx solid rgba(30, 41, 59, 0.15);
+  border-top-color: $text-color-primary;
+  border-radius: 50%;
+  animation: spin 0.8s linear infinite;
+}
+
+@keyframes spin { to { transform: rotate(360deg); } }
+</style>

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 509 - 292
haha-admin-mp/src/pages/orders/list.vue


+ 5 - 5
haha-admin-mp/src/utils/constants.ts

@@ -6,23 +6,23 @@
 // ==================== 订单状态 ====================
 
 export enum OrderStatus {
-  CANCELLED = 0,      // 已取消
-  PENDING_PAYMENT = 1,// 待支付
-  COMPLETED = 2,      // 已完成
+  PENDING_PAYMENT = 0,// 待支付
+  COMPLETED = 1,      // 已完成
+  CANCELLED = 2,      // 已取消
   CLOSED = 3          // 已关闭
 }
 
 export const OrderStatusText: Record<number, string> = {
-  [OrderStatus.CANCELLED]: '已取消',
   [OrderStatus.PENDING_PAYMENT]: '待支付',
   [OrderStatus.COMPLETED]: '已完成',
+  [OrderStatus.CANCELLED]: '已取消',
   [OrderStatus.CLOSED]: '已关闭'
 };
 
 export const OrderStatusColor: Record<number, string> = {
-  [OrderStatus.CANCELLED]: '#94a3b8',
   [OrderStatus.PENDING_PAYMENT]: '#f59e0b',
   [OrderStatus.COMPLETED]: '#10b981',
+  [OrderStatus.CANCELLED]: '#94a3b8',
   [OrderStatus.CLOSED]: '#64748b'
 };
 

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно