Parcourir la source

C端小程序订单,扫码等

skyline il y a 4 mois
Parent
commit
a06a02438a

+ 53 - 0
haha-mp/src/api/device.ts

@@ -0,0 +1,53 @@
+/**
+ * 设备相关API
+ */
+
+import { post } from '../utils/request';
+
+/**
+ * 扫码开门请求参数
+ */
+export interface ScanDoorRequest {
+  deviceId: string;
+  userId: string;
+}
+
+/**
+ * 扫码开门响应数据
+ */
+export interface ScanDoorResponse {
+  doorOpened: boolean;
+  outTradeNo: string;
+  orderNo: string;
+  deviceId: string;
+  doorIndex: number;
+}
+
+/**
+ * 设备状态响应数据
+ */
+export interface DeviceStatusResponse {
+  deviceId: string;
+  status: string;
+  isOnline: boolean;
+}
+
+/**
+ * 扫码开门
+ * @param deviceId 设备ID(从二维码中解析)
+ */
+export const scanDoor = (deviceId: string): Promise<ScanDoorResponse> => {
+  return post<ScanDoorResponse>('/device/scan-open', {
+    deviceId
+  });
+};
+
+/**
+ * 检查设备状态
+ * @param deviceId 设备ID
+ */
+export const checkDeviceStatus = (deviceId: string): Promise<DeviceStatusResponse> => {
+  return post<DeviceStatusResponse>('/device/status', {
+    deviceId
+  });
+};

+ 84 - 0
haha-mp/src/api/order.ts

@@ -0,0 +1,84 @@
+/**
+ * 订单相关API
+ */
+
+import { post } from '../utils/request';
+
+/**
+ * 订单商品信息
+ */
+export interface OrderProduct {
+  id: string;
+  name: string;
+  price: number;
+  quantity: number;
+  image?: string;
+  subtotal?: number;
+}
+
+/**
+ * 订单信息
+ */
+export interface OrderInfo {
+  id: number;
+  orderNo: string;
+  outTradeNo: string;
+  hahaOrderNo: string;
+  deviceSn: string;
+  totalAmount: number;
+  payStatus: string;
+  status: number;
+  statusText?: string;
+  createTime: string;
+  payTime?: string;
+  videoUrl?: string;
+  confidence?: number;
+  products: OrderProduct[];
+}
+
+/**
+ * 订单列表请求参数
+ */
+export interface OrderListRequest {
+  status?: number; // 0-待支付,1-已完成,2-已取消
+}
+
+/**
+ * 订单详情请求参数
+ */
+export interface OrderDetailRequest {
+  orderId?: number;
+  orderNo?: string;
+  outTradeNo?: string;
+}
+
+/**
+ * 取消订单请求参数
+ */
+export interface CancelOrderRequest {
+  orderId: number;
+}
+
+/**
+ * 获取订单列表
+ * @param params 查询参数
+ */
+export const getOrderList = (params?: OrderListRequest): Promise<OrderInfo[]> => {
+  return post<OrderInfo[]>('/order/list', params || {});
+};
+
+/**
+ * 获取订单详情
+ * @param params 查询参数(必须包含orderId、orderNo或outTradeNo之一)
+ */
+export const getOrderDetail = (params: OrderDetailRequest): Promise<OrderInfo> => {
+  return post<OrderInfo>('/order/detail', params);
+};
+
+/**
+ * 取消订单
+ * @param orderId 订单ID
+ */
+export const cancelOrder = (orderId: number): Promise<void> => {
+  return post<void>('/order/cancel', { orderId });
+};

+ 2 - 2
haha-mp/src/pages.json

@@ -20,8 +20,8 @@
 		{
 			"path": "pages/refund/refund",
 			"style": {
-				"navigationBarTitleText": "退款申请",
-				"navigationBarBackgroundColor": "#FFFFFF",
+				"navigationBarTitleText": "退款商品",
+				"navigationBarBackgroundColor": "#FFD700",
 				"navigationBarTextStyle": "black"
 			}
 		},

+ 48 - 18
haha-mp/src/pages/index/index.vue

@@ -38,40 +38,70 @@
 
 <script setup lang="ts">
 import { ref } from 'vue'
-import { mockApi } from '../../utils/mock'
+import { scanDoor } from '../../api/device'
 
 const scanCode = () => {
   // 调用摄像头扫码
   uni.scanCode({
     success: async function (res) {
       console.log('扫码结果:', res.result);
-      // 使用mock数据模拟扫码开门
+      
+      // 从扫码结果中解析设备ID
+      // 假设二维码格式为: deviceId 或 JSON格式
+      let deviceId = res.result;
       try {
-        const response = await mockApi.scanDoor(res.result);
-        if (response.success) {
-          uni.showToast({
-            title: '开门成功',
-            icon: 'success'
-          });
-          // 模拟跳转到购物进行中页面
-          setTimeout(() => {
-            uni.navigateTo({
-              url: '/pages/shopping/shopping'
-            });
-          }, 1000);
+        // 尝试解析JSON格式的二维码
+        const qrData = JSON.parse(res.result);
+        if (qrData.deviceId) {
+          deviceId = qrData.deviceId;
         }
-      } catch (error) {
+      } catch (e) {
+        // 不是JSON格式,直接使用扫码结果作为deviceId
+      }
+      
+      // 显示加载提示
+      uni.showLoading({
+        title: '正在开门...',
+        mask: true
+      });
+      
+      try {
+        // 调用真实接口扫码开门
+        const response = await scanDoor(deviceId);
+        
+        // 隐藏加载提示
+        uni.hideLoading();
+        
+        // 开门成功
         uni.showToast({
-          title: '开门失败',
-          icon: 'error'
+          title: '开门成功',
+          icon: 'success'
         });
+        
+        // 将设备信息存储到本地,供购物页面使用
+        uni.setStorageSync('currentDeviceId', response.deviceId);
+        uni.setStorageSync('currentOutTradeNo', response.outTradeNo);
+        uni.setStorageSync('currentOrderNo', response.orderNo);
+        
+        // 跳转到购物进行中页面
+        setTimeout(() => {
+          uni.navigateTo({
+            url: '/pages/shopping/shopping'
+          });
+        }, 1000);
+      } catch (error: any) {
+        // 隐藏加载提示
+        uni.hideLoading();
+        
+        console.error('开门失败:', error);
+        // 错误信息已经在request工具中显示,这里不需要重复显示
       }
     },
     fail: function (err) {
       console.log('扫码失败:', err);
       uni.showToast({
         title: '扫码失败',
-        icon: 'error'
+        icon: 'none'
       });
     }
   });

+ 269 - 73
haha-mp/src/pages/orderDetail/orderDetail.vue

@@ -1,107 +1,243 @@
 <template>
   <view class="container">
-    <!-- 订单状态 -->
-    <view class="card status-card">
-      <view class="status-left">
-        <view class="status-icon-wrapper">
-          <text class="status-icon">✓</text>
-        </view>
-        <text class="status-text">已支付</text>
-      </view>
-      <text class="invoice-status">未开票</text>
+    <!-- 加载中 -->
+    <view v-if="loading" class="loading-wrapper">
+      <text>加载中...</text>
     </view>
     
-    <!-- 订单明细 -->
-    <view class="card detail-card">
-      <view class="card-header">
-        <text class="card-title">订单明细</text>
-      </view>
-      <view class="product-item">
-        <view class="product-image">
-          <image src="https://trae-api-cn.mchost.guru/api/ide/v1/text_to_image?prompt=small%20brown%20bread%20in%20plastic%20container&image_size=square" mode="aspectFill"></image>
-        </view>
-        <view class="product-info">
-          <text class="product-name">测试0.44</text>
-          <text class="product-price">销售单价 ¥0.44</text>
+    <!-- 订单详情 -->
+    <view v-else-if="order">
+      <!-- 订单状态 -->
+      <view class="card status-card">
+        <view class="status-left">
+          <view class="status-icon-wrapper" :class="getStatusClass(order.status)">
+            <text class="status-icon">✓</text>
+          </view>
+          <text class="status-text">{{ order.statusText || getStatusText(order.status) }}</text>
         </view>
-        <text class="product-quantity">x1</text>
+        <text class="invoice-status">未开票</text>
       </view>
-      <view class="amount-row">
-        <text class="amount-label">实付款</text>
-        <view class="amount-value-wrapper">
-          <text class="amount-prefix">合计</text>
-          <text class="amount-symbol">¥</text>
-          <text class="amount-integer">0.44</text>
+      
+      <!-- 订单明细 -->
+      <view class="card detail-card">
+        <view class="card-header">
+          <text class="card-title">订单明细</text>
         </view>
-      </view>
-      <view class="card-footer-actions">
-        <button class="action-btn-outline" @click="applyRefund">申请退款</button>
-        <button class="action-btn-primary" @click="printOrder">打印订单</button>
-        <button class="action-btn-primary" @click="exportImage">导出图片</button>
-      </view>
-    </view>
-    
-    <!-- 订单信息 -->
-    <view class="card info-card">
-      <view class="card-header">
-        <text class="card-title">订单信息</text>
-      </view>
-      <view class="info-list">
-        <view class="info-row">
-          <text class="info-label">订单编号</text>
-          <view class="info-value-group">
-            <text class="info-value">2601081537467989</text>
-            <view class="copy-btn">
-              <view class="copy-icon-box"></view>
-            </view>
+        
+        <!-- 商品列表 -->
+        <view v-for="(product, index) in order.products" :key="index" class="product-item">
+          <view class="product-image">
+            <image v-if="product.image" :src="product.image" mode="aspectFill"></image>
+            <view v-else class="image-placeholder"></view>
           </view>
+          <view class="product-info">
+            <text class="product-name">{{ product.name }}</text>
+            <text class="product-price">销售单价 ¥{{ product.price.toFixed(2) }}</text>
+          </view>
+          <text class="product-quantity">x{{ product.quantity }}</text>
         </view>
-        <view class="info-row">
-          <text class="info-label">下单时间</text>
-          <text class="info-value">2026-01-08 15:37:03</text>
+        
+        <!-- 总价 -->
+        <view class="amount-row">
+          <text class="amount-label">实付款</text>
+          <view class="amount-value-wrapper">
+            <text class="amount-prefix">合计</text>
+            <text class="amount-symbol">¥</text>
+            <text class="amount-integer">{{ order.totalAmount.toFixed(2) }}</text>
+          </view>
         </view>
-        <view class="info-row">
-          <text class="info-label">支付时间</text>
-          <text class="info-value">2026-01-08 15:37:49</text>
+        
+        <!-- 操作按钮 -->
+        <view class="card-footer-actions">
+          <button v-if="canRefund(order)" class="action-btn-outline" @click="applyRefund">申请退款</button>
+          <button class="action-btn-primary" @click="printOrder">打印订单</button>
+          <button class="action-btn-primary" @click="exportImage">导出图片</button>
         </view>
-        <view class="info-row">
-          <text class="info-label">支付方式</text>
-          <text class="info-value">微信</text>
+      </view>
+      
+      <!-- 订单信息 -->
+      <view class="card info-card">
+        <view class="card-header">
+          <text class="card-title">订单信息</text>
         </view>
-        <view class="info-row">
-          <text class="info-label">门店详情</text>
-          <view class="info-link">
-            <text class="link-text">查看详情</text>
-            <text class="link-arrow">></text>
+        <view class="info-list">
+          <view class="info-row">
+            <text class="info-label">订单编号</text>
+            <view class="info-value-group">
+              <text class="info-value">{{ order.orderNo }}</text>
+              <view class="copy-btn" @click="copyText(order.orderNo)">
+                <view class="copy-icon-box"></view>
+              </view>
+            </view>
+          </view>
+          <view class="info-row" v-if="order.hahaOrderNo">
+            <text class="info-label">哈哈订单号</text>
+            <view class="info-value-group">
+              <text class="info-value">{{ order.hahaOrderNo }}</text>
+              <view class="copy-btn" @click="copyText(order.hahaOrderNo)">
+                <view class="copy-icon-box"></view>
+              </view>
+            </view>
+          </view>
+          <view class="info-row">
+            <text class="info-label">下单时间</text>
+            <text class="info-value">{{ formatTime(order.createTime) }}</text>
+          </view>
+          <view class="info-row" v-if="order.payTime">
+            <text class="info-label">支付时间</text>
+            <text class="info-value">{{ formatTime(order.payTime) }}</text>
+          </view>
+          <view class="info-row">
+            <text class="info-label">支付方式</text>
+            <text class="info-value">微信</text>
+          </view>
+          <view class="info-row">
+            <text class="info-label">设备编号</text>
+            <text class="info-value">{{ order.deviceSn }}</text>
+          </view>
+          <view class="info-row" v-if="order.confidence">
+            <text class="info-label">AI识别置信度</text>
+            <text class="info-value">{{ (order.confidence * 100).toFixed(1) }}%</text>
+          </view>
+          <view class="info-row" v-if="order.videoUrl">
+            <text class="info-label">识别视频</text>
+            <view class="info-link" @click="viewVideo(order.videoUrl)">
+              <text class="link-text">查看视频</text>
+              <text class="link-arrow">></text>
+            </view>
           </view>
-        </view>
-        <view class="info-row">
-          <text class="info-label">设备名称</text>
-          <text class="info-value">长田</text>
-        </view>
-        <view class="info-row">
-          <text class="info-label">设备编号</text>
-          <text class="info-value">B142977</text>
         </view>
       </view>
     </view>
     
     <!-- 底部提示 -->
     <view class="footer-note">
-      <text>如有疑问,请联系商家:18371997424</text>
+      <text>如有疑问,请联系客服</text>
     </view>
   </view>
 </template>
 
 <script setup lang="ts">
 import { ref, onMounted } from 'vue';
+import { getOrderDetail, OrderInfo } from '../../api/order';
+
+const order = ref<OrderInfo | null>(null);
+const loading = ref(false);
+const orderId = ref<number | null>(null);
+
+/**
+ * 获取订单状态文本
+ */
+const getStatusText = (status: number) => {
+  const statusMap: any = {
+    0: '待支付',
+    1: '已完成',
+    2: '已取消'
+  };
+  return statusMap[status] || '未知';
+};
+
+/**
+ * 获取状态样式
+ */
+const getStatusClass = (status: number) => {
+  if (status === 1) return 'status-success';
+  if (status === 0) return 'status-pending';
+  if (status === 2) return 'status-cancelled';
+  return '';
+};
+
+/**
+ * 判断是否可以退款
+ */
+const canRefund = (order: OrderInfo) => {
+  return order.status === 1; // 只有已完成的订单可以退款
+};
+
+/**
+ * 格式化时间
+ */
+const formatTime = (time: string | undefined) => {
+  if (!time) return '-';
+  // 如果已经是格式化的时间字符串,直接返回
+  if (typeof time === 'string' && time.includes('-')) {
+    return time.replace('T', ' ').substring(0, 19);
+  }
+  return time;
+};
 
+/**
+ * 加载订单详情
+ */
+const loadOrderDetail = async () => {
+  if (!orderId.value) {
+    uni.showToast({
+      title: '订单ID不存在',
+      icon: 'none'
+    });
+    return;
+  }
+  
+  loading.value = true;
+  
+  try {
+    uni.showLoading({
+      title: '加载中...',
+      mask: true
+    });
+    
+    // 调用真实接口获取订单详情
+    const orderDetail = await getOrderDetail({ orderId: orderId.value });
+    order.value = orderDetail;
+    
+    uni.hideLoading();
+  } catch (error: any) {
+    uni.hideLoading();
+    console.error('加载订单详情失败:', error);
+    // 错误已在request工具中显示
+    setTimeout(() => {
+      uni.navigateBack();
+    }, 1500);
+  } finally {
+    loading.value = false;
+  }
+};
+
+/**
+ * 页面加载时获取订单详情
+ */
+onMounted(() => {
+  const pages = getCurrentPages();
+  const currentPage = pages[pages.length - 1] as any;
+  const options = currentPage.options || {};
+  
+  if (options.orderId) {
+    orderId.value = parseInt(options.orderId);
+    loadOrderDetail();
+  } else {
+    uni.showToast({
+      title: '订单ID不存在',
+      icon: 'none'
+    });
+    setTimeout(() => {
+      uni.navigateBack();
+    }, 1500);
+  }
+});
+
+/**
+ * 申请退款
+ */
 const applyRefund = () => {
+  if (!order.value) return;
   uni.navigateTo({
-    url: '/pages/refund/refund'
+    url: '/pages/refund/refund?orderId=' + order.value.id
   });
 };
 
+/**
+ * 打印订单
+ */
 const printOrder = () => {
   uni.showToast({
     title: '打印订单功能开发中',
@@ -109,12 +245,41 @@ const printOrder = () => {
   });
 };
 
+/**
+ * 导出图片
+ */
 const exportImage = () => {
   uni.showToast({
     title: '导出图片功能开发中',
     icon: 'none'
   });
 };
+
+/**
+ * 复制文本
+ */
+const copyText = (text: string) => {
+  uni.setClipboardData({
+    data: text,
+    success: () => {
+      uni.showToast({
+        title: '复制成功',
+        icon: 'success'
+      });
+    }
+  });
+};
+
+/**
+ * 查看视频
+ */
+const viewVideo = (url: string) => {
+  // TODO: 实现视频播放功能
+  uni.showToast({
+    title: '视频播放功能开发中',
+    icon: 'none'
+  });
+};
 </script>
 
 <style>
@@ -125,6 +290,15 @@ const exportImage = () => {
   box-sizing: border-box;
 }
 
+.loading-wrapper {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  min-height: 400rpx;
+  font-size: 28rpx;
+  color: #999;
+}
+
 /* 通用卡片样式 */
 .card {
   background-color: #ffffff;
@@ -157,6 +331,18 @@ const exportImage = () => {
   margin-right: 16rpx;
 }
 
+.status-icon-wrapper.status-success {
+  background-color: #7ED321;
+}
+
+.status-icon-wrapper.status-pending {
+  background-color: #FF9100;
+}
+
+.status-icon-wrapper.status-cancelled {
+  background-color: #999999;
+}
+
 .status-icon {
   color: #ffffff;
   font-size: 28rpx;
@@ -212,6 +398,16 @@ const exportImage = () => {
   border-radius: 8rpx;
 }
 
+.image-placeholder {
+  width: 100%;
+  height: 100%;
+  background-color: #f5f5f5;
+  border-radius: 8rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
 .product-info {
   flex: 1;
 }

+ 71 - 20
haha-mp/src/pages/orders/orders.vue

@@ -21,7 +21,7 @@
             </view>
             <view class="price-info">
               <text class="price-label">实付:</text>
-              <text class="price-value">¥{{ order.amount.toFixed(2) }}</text>
+              <text class="price-value">¥{{ order.totalAmount.toFixed(2) }}</text>
             </view>
             <view class="quantity-info">共{{ getQuantity(order) }}件</view>
           </view>
@@ -37,45 +37,96 @@
 
 <script setup lang="ts">
 import { ref, onMounted } from 'vue';
-import { mockApi } from '../../utils/mock';
+import { getOrderList, OrderInfo } from '../../api/order';
 
-const orders = ref<any[]>([]);
+const orders = ref<OrderInfo[]>([]);
+const loading = ref(false);
 
-const getStatusText = (status: string) => {
+/**
+ * 获取订单状态文本
+ */
+const getStatusText = (status: number) => {
   const statusMap: any = {
-    'completed': '已完成',
-    'pending': '识别中',
-    'paid': '已支付',
-    'refunded': '已退款'
+    0: '待支付',
+    1: '已完成',
+    2: '已取消'
   };
-  return statusMap[status] || status;
+  return statusMap[status] || '未知';
 };
 
-const getQuantity = (order: any) => {
+/**
+ * 计算订单商品总数量
+ */
+const getQuantity = (order: OrderInfo) => {
   if (!order.products) return 0;
   return order.products.reduce((acc: number, p: any) => acc + p.quantity, 0);
 };
 
-const canRefund = (order: any) => {
-  return order.status === 'completed' || order.status === 'paid';
+/**
+ * 判断是否可以申请退款
+ * 只有已完成的订单可以退款
+ */
+const canRefund = (order: OrderInfo) => {
+  return order.status === 1; // 1-已完成
 };
 
-onMounted(async () => {
-  const response: any = await mockApi.getOrders();
-  if (response.success) {
-    orders.value = response.data;
+/**
+ * 加载订单列表
+ */
+const loadOrders = async () => {
+  if (loading.value) return;
+  
+  loading.value = true;
+  
+  try {
+    uni.showLoading({
+      title: '加载中...',
+      mask: true
+    });
+    
+    // 调用真实接口获取订单列表
+    const orderList = await getOrderList();
+    orders.value = orderList;
+    
+    uni.hideLoading();
+    
+    if (orderList.length === 0) {
+      uni.showToast({
+        title: '暂无订单',
+        icon: 'none'
+      });
+    }
+  } catch (error: any) {
+    uni.hideLoading();
+    console.error('加载订单列表失败:', error);
+    // 错误已在request工具中显示
+  } finally {
+    loading.value = false;
   }
+};
+
+/**
+ * 页面加载时获取订单列表
+ */
+onMounted(() => {
+  loadOrders();
 });
 
-const applyRefund = (order: any) => {
+/**
+ * 申请退款
+ */
+const applyRefund = (order: OrderInfo) => {
   uni.navigateTo({
-    url: '/pages/refund/refund?id=' + order.id
+    url: '/pages/refund/refund?orderId=' + order.id
   });
 };
 
-const viewOrderDetail = () => {
+/**
+ * 查看订单详情
+ */
+const viewOrderDetail = (order: OrderInfo) => {
   uni.navigateTo({
-    url: '/pages/orderDetail/orderDetail'
+    url: '/pages/orderDetail/orderDetail?orderId=' + order.id
   });
 };
 </script>

+ 143 - 90
haha-mp/src/pages/refund/refund.vue

@@ -1,144 +1,197 @@
 <template>
   <view class="container">
-    <view class="title">退款申请</view>
-    <view class="refund-info">
-      <view class="info-item">
-        <view class="info-label">订单编号</view>
-        <view class="info-value">order_001</view>
-      </view>
-      <view class="info-item">
-        <view class="info-label">购买时间</view>
-        <view class="info-value">2026-01-23 10:30:00</view>
+    <!-- 退款商品 -->
+    <view class="section-item product-selector">
+      <text class="section-label">退款商品</text>
+      <text class="arrow-right">></text>
+    </view>
+    
+    <!-- 退款原因 -->
+    <view class="section-block reason-section">
+      <text class="section-title">退款原因</text>
+      <radio-group @change="handleReasonChange">
+        <label class="radio-item" v-for="(reason, index) in refundReasons" :key="index">
+          <view class="radio-wrapper">
+            <radio :value="reason.value" :checked="selectedReason === reason.value" color="#999999" />
+          </view>
+          <text class="radio-label">{{ reason.label }}</text>
+        </label>
+      </radio-group>
+    </view>
+    
+    <!-- 退款信息 -->
+    <view class="section-block info-section">
+      <text class="section-title">退款信息</text>
+      <view class="info-row">
+        <text class="info-label">订单编号:</text>
+        <text class="info-value">2601081537467989</text>
       </view>
-      <view class="info-item">
-        <view class="info-label">购买商品</view>
-        <view class="info-value">可口可乐 × 2</view>
+      <view class="info-row">
+        <text class="info-label">订单总金额:</text>
+        <text class="info-value">0.44</text>
       </view>
-      <view class="info-item">
-        <view class="info-label">支付金额</view>
-        <view class="info-value">¥7.00</view>
+      <view class="info-row">
+        <text class="info-label">退款数量:</text>
+        <text class="info-value">1</text>
       </view>
-    </view>
-    <view class="refund-reason">
-      <view class="reason-title">退款原因</view>
-      <textarea class="reason-input" placeholder="请输入退款原因"></textarea>
-    </view>
-    <view class="upload-section">
-      <view class="upload-title">上传凭证</view>
-      <view class="upload-area">
-        <view class="upload-item">+</view>
-        <view class="upload-item">+</view>
-        <view class="upload-item">+</view>
+      <view class="info-row">
+        <text class="info-label">退款总金额:</text>
+        <text class="info-value">0.44</text>
       </view>
     </view>
-    <button class="submit-button">提交退款申请</button>
+    
+    <!-- 确认提交按钮 -->
+    <button class="submit-btn" @click="submitRefund">确认提交</button>
   </view>
 </template>
 
 <script setup lang="ts">
 import { ref } from 'vue';
+
+const selectedReason = ref('');
+
+const refundReasons = [
+  { label: '订单扣款错误', value: '1' },
+  { label: '扣款金额与实际金额不符', value: '2' },
+  { label: '商品过期或变质', value: '3' },
+  { label: '其他(请填写投诉内容)', value: '4' }
+];
+
+const handleReasonChange = (e: any) => {
+  selectedReason.value = e.detail.value;
+};
+
+const submitRefund = () => {
+  if (!selectedReason.value) {
+    uni.showToast({
+      title: '请选择退款原因',
+      icon: 'none'
+    });
+    return;
+  }
+  
+  uni.showToast({
+    title: '退款申请提交成功',
+    icon: 'success'
+  });
+  
+  setTimeout(() => {
+    uni.navigateBack();
+  }, 1500);
+};
 </script>
 
 <style>
 .container {
   min-height: 100vh;
   background-color: #f5f5f5;
+  padding-top: 20rpx;
 }
 
-.title {
-  font-size: 36rpx;
-  font-weight: bold;
-  padding: 30rpx;
-  background-color: #fff;
-  border-bottom: 1rpx solid #eee;
+/* 退款商品选择器 */
+.section-item {
+  background-color: #ffffff;
+  padding: 28rpx 30rpx;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 20rpx;
 }
 
-.refund-info {
-  background-color: #fff;
-  margin: 20rpx 0;
-  padding: 30rpx;
+.section-label {
+  font-size: 30rpx;
+  color: #333333;
 }
 
-.info-item {
-  display: flex;
-  justify-content: space-between;
-  padding: 15rpx 0;
-  border-bottom: 1rpx solid #f0f0f0;
+.arrow-right {
+  font-size: 32rpx;
+  color: #cccccc;
 }
 
-.info-item:last-child {
-  border-bottom: none;
+/* 区域块 */
+.section-block {
+  background-color: #ffffff;
+  padding: 30rpx;
+  margin-bottom: 20rpx;
 }
 
-.info-label {
-  font-size: 28rpx;
-  color: #666;
+.section-title {
+  font-size: 30rpx;
+  color: #333333;
+  font-weight: 500;
+  margin-bottom: 30rpx;
+  display: block;
 }
 
-.info-value {
-  font-size: 28rpx;
-  color: #333;
+/* 选项列表 */
+radio-group {
+  display: block;
 }
 
-.refund-reason {
-  background-color: #fff;
-  padding: 30rpx;
-  margin: 20rpx 0;
+.radio-item {
+  display: flex;
+  align-items: center;
+  margin-bottom: 32rpx;
 }
 
-.reason-title {
-  font-size: 28rpx;
-  font-weight: bold;
-  margin-bottom: 20rpx;
+.radio-item:last-child {
+  margin-bottom: 0;
 }
 
-.reason-input {
-  width: 100%;
-  height: 200rpx;
-  border: 1rpx solid #eee;
-  border-radius: 10rpx;
-  padding: 20rpx;
-  font-size: 26rpx;
-  resize: none;
+.radio-wrapper {
+  margin-right: 16rpx;
+  display: flex;
+  align-items: center;
 }
 
-.upload-section {
-  background-color: #fff;
-  padding: 30rpx;
-  margin: 20rpx 0;
+.radio-wrapper radio {
+  transform: scale(1.1);
 }
 
-.upload-title {
+.radio-label {
   font-size: 28rpx;
-  font-weight: bold;
-  margin-bottom: 20rpx;
+  color: #333333;
+  line-height: 1.5;
 }
 
-.upload-area {
-  display: flex;
-  gap: 20rpx;
+/* 信息区域 */
+.info-section {
+  padding-bottom: 20rpx;
 }
 
-.upload-item {
-  width: 150rpx;
-  height: 150rpx;
-  border: 2rpx dashed #ddd;
-  border-radius: 10rpx;
+.info-row {
   display: flex;
   align-items: center;
-  justify-content: center;
-  font-size: 40rpx;
-  color: #999;
+  padding: 18rpx 0;
+}
+
+.info-label {
+  font-size: 28rpx;
+  color: #999999;
+  min-width: 200rpx;
 }
 
-.submit-button {
+.info-value {
+  font-size: 28rpx;
+  color: #333333;
+  flex: 1;
+  text-align: right;
+}
+
+/* 提交按钮 */
+.submit-btn {
   margin: 40rpx 30rpx;
-  background-color: #FFD700;
-  color: #333;
-  border: none;
-  padding: 25rpx;
+  background-color: #f8f8f8;
+  border: 1rpx solid #e8e8e8;
   border-radius: 10rpx;
   font-size: 32rpx;
-  font-weight: bold;
+  color: #333333;
+  padding: 24rpx 0;
+  line-height: 1;
+  font-weight: 500;
+}
+
+.submit-btn::after {
+  border: none;
 }
 </style>

+ 40 - 0
haha-mp/src/utils/config.ts

@@ -0,0 +1,40 @@
+/**
+ * API配置
+ */
+
+// 判断是否为开发环境
+// @ts-ignore
+const isDevelopment = import.meta.env.DEV || false;
+
+export const API_CONFIG = {
+  // 后端API基础URL
+  baseUrl: isDevelopment 
+    ? 'http://localhost:8080/api'  // 开发环境
+    : 'https://api.yourdomain.com/api',  // 生产环境
+  
+  // 请求超时时间(毫秒)
+  timeout: 30000,
+  
+  // 是否显示请求日志
+  enableLog: isDevelopment
+};
+
+/**
+ * API接口路径
+ */
+export const API_PATHS = {
+  // 用户相关
+  login: '/user/login',
+  getUserInfo: '/user/info',
+  
+  // 设备相关
+  scanDoor: '/device/scan',
+  checkDeviceStatus: '/device/status',
+  
+  // 订单相关
+  getOrders: '/order/list',
+  getOrderDetail: '/order/detail',
+  
+  // 商品相关
+  getProducts: '/goods/list'
+};

+ 164 - 0
haha-mp/src/utils/request.ts

@@ -0,0 +1,164 @@
+/**
+ * HTTP请求工具类
+ * 封装uni.request,统一处理请求和响应
+ */
+
+import { API_CONFIG } from './config';
+
+/**
+ * 请求配置接口
+ */
+interface RequestConfig {
+  url: string;
+  method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
+  data?: any;
+  header?: any;
+  timeout?: number;
+}
+
+/**
+ * 响应数据接口
+ */
+interface ResponseData<T = any> {
+  code: number;
+  message: string;
+  data?: T;
+}
+
+/**
+ * 从本地存储获取token
+ */
+const getToken = (): string => {
+  return uni.getStorageSync('haha-token-KuaiyuMan') || '';
+};
+
+/**
+ * 发起HTTP请求
+ * @param config 请求配置
+ * @returns Promise<响应数据>
+ */
+export const request = <T = any>(config: RequestConfig): Promise<T> => {
+  return new Promise((resolve, reject) => {
+    const { url, method = 'GET', data, header = {}, timeout = API_CONFIG.timeout } = config;
+    
+    // 构建完整URL
+    const fullUrl = url.startsWith('http') ? url : `${API_CONFIG.baseUrl}${url}`;
+    
+    // 添加token到请求头
+    const token = getToken();
+    if (token) {
+      header['haha-token-KuaiyuMan'] = token;
+    }
+    
+    // 添加Content-Type
+    if (!header['Content-Type']) {
+      header['Content-Type'] = 'application/json';
+    }
+    
+    // 打印请求日志
+    if (API_CONFIG.enableLog) {
+      console.log(`[请求] ${method} ${fullUrl}`, data);
+    }
+    
+    // 发起请求
+    uni.request({
+      url: fullUrl,
+      method,
+      data,
+      header,
+      timeout,
+      success: (res: any) => {
+        // 打印响应日志
+        if (API_CONFIG.enableLog) {
+          console.log(`[响应] ${method} ${fullUrl}`, res.data);
+        }
+        
+        const responseData = res.data as ResponseData<T>;
+        
+        // 处理HTTP状态码
+        if (res.statusCode !== 200) {
+          const errorMsg = `请求失败: HTTP ${res.statusCode}`;
+          uni.showToast({
+            title: errorMsg,
+            icon: 'none'
+          });
+          reject(new Error(errorMsg));
+          return;
+        }
+        
+        // 处理业务状态码
+        if (responseData.code === 200 || responseData.code === 0) {
+          // 成功
+          resolve(responseData.data as T);
+        } else if (responseData.code === 401) {
+          // 未登录或token过期
+          uni.showToast({
+            title: '请先登录',
+            icon: 'none'
+          });
+          // 清除token
+          uni.removeStorageSync('haha-token-KuaiyuMan');
+          // 跳转到登录页(如果有)
+          // uni.navigateTo({ url: '/pages/login/login' });
+          reject(new Error(responseData.message || '未登录'));
+        } else {
+          // 业务错误
+          const errorMsg = responseData.message || '操作失败';
+          uni.showToast({
+            title: errorMsg,
+            icon: 'none'
+          });
+          reject(new Error(errorMsg));
+        }
+      },
+      fail: (err: any) => {
+        // 网络错误
+        console.error(`[请求失败] ${method} ${fullUrl}`, err);
+        
+        let errorMsg = '网络请求失败';
+        if (err.errMsg) {
+          if (err.errMsg.includes('timeout')) {
+            errorMsg = '请求超时,请检查网络';
+          } else if (err.errMsg.includes('fail')) {
+            errorMsg = '网络连接失败,请检查网络';
+          }
+        }
+        
+        uni.showToast({
+          title: errorMsg,
+          icon: 'none'
+        });
+        
+        reject(new Error(errorMsg));
+      }
+    });
+  });
+};
+
+/**
+ * GET请求
+ */
+export const get = <T = any>(url: string, data?: any): Promise<T> => {
+  return request<T>({ url, method: 'GET', data });
+};
+
+/**
+ * POST请求
+ */
+export const post = <T = any>(url: string, data?: any): Promise<T> => {
+  return request<T>({ url, method: 'POST', data });
+};
+
+/**
+ * PUT请求
+ */
+export const put = <T = any>(url: string, data?: any): Promise<T> => {
+  return request<T>({ url, method: 'PUT', data });
+};
+
+/**
+ * DELETE请求
+ */
+export const del = <T = any>(url: string, data?: any): Promise<T> => {
+  return request<T>({ url, method: 'DELETE', data });
+};