Ver código fonte

前端样式更新

“陈博论” 9 meses atrás
pai
commit
ba273bf0d7

+ 23 - 1
web/src/views/frontend/Me.vue

@@ -38,7 +38,8 @@
 </template>
 
 <script setup lang="ts">
-import { computed, markRaw, reactive, ref, type Component } from 'vue'
+import { computed, markRaw, onMounted, reactive, ref, watch, type Component } from 'vue'
+import { useRoute } from 'vue-router'
 import { Goods, HomeFilled, OfficeBuilding, ShoppingCartFull, User } from '@element-plus/icons-vue'
 import Header from './components/header.vue'
 // 导入各个功能组件
@@ -52,6 +53,7 @@ defineOptions({ name: 'ClientMe' })
 
 type MenuItem = { label: string; value: string; component: Component; icon: Component }
 
+const route = useRoute()
 const activeMenu = ref('home')
 const menuList = reactive<MenuItem[]>([
   { label: '我的主页', value: 'home', component: markRaw(Info), icon: markRaw(HomeFilled) },
@@ -66,8 +68,28 @@ const currentComponent = computed(() => {
   return current ? current.component : Info
 })
 
+// 根据路由query参数设置激活的菜单
+const initActiveMenu = () => {
+  const tab = route.query.tab as string
+  if (tab && menuList.some(item => item.value === tab)) {
+    activeMenu.value = tab
+  }
+}
+
+// 监听路由query变化,更新激活的菜单
+watch(() => route.query.tab, (newTab) => {
+  if (newTab && typeof newTab === 'string' && menuList.some(item => item.value === newTab)) {
+    activeMenu.value = newTab
+  }
+})
+
 const handleMenuChange = (value: any) => {
 }
+
+// 初始化时根据路由query设置激活菜单
+onMounted(() => {
+  initActiveMenu()
+})
 </script>
 
 <style scoped lang="scss">

+ 61 - 12
web/src/views/frontend/Order.vue

@@ -123,6 +123,7 @@
         :pay-status="payStatus"
         @close="handlePayDialogClose"
         @confirm="handlePaySuccess"
+        @expired="handlePayExpired"
       />
 
       <!-- 下半部分:宝贝详情 -->
@@ -180,8 +181,8 @@ const isEnterprise = computed(() => Number(goodsInfo.value?.type) === 2)
 const isSeatBasedEnterprise = computed(() => {
   if (!isEnterprise.value) return false
   const cardType = Number(goodsInfo.value?.card_type)
-  // 企业套餐:次卡(1)、积分卡(2)、月卡(6)、年卡(7)按件数购买,其余按席位
-  return ![1, 2, 6, 7].includes(cardType)
+  // 企业套餐:次卡(1)、积分卡(2),其余按席位
+  return ![1, 2].includes(cardType)
 })
 
 // 获取卡类型文本
@@ -347,10 +348,11 @@ const startPayStatusPolling = (orderId, orderNo) => {
       }
       const res = await checkOrderStatus(checkData)
       
-      // 支付状态判断:1-已支付,0-待支付,2-支付失败,3-已取消
-      const status = res.data?.order.pay_status
+      // 支付状态:1-已支付,0-待支付,2-支付失败,3-已取消(兼容数字/字符串)
+      const rawStatus = res.data?.order?.pay_status
+      const status = rawStatus != null ? String(rawStatus) : ''
       
-      if (status == '1') {
+      if (status === '1') {
         // 支付成功
         payStatus.value = 'success'
         stopPayStatusPolling()
@@ -364,11 +366,11 @@ const startPayStatusPolling = (orderId, orderNo) => {
           }
         }
         
-        // 3秒后自动关闭弹窗
+        // 3秒后自动关闭弹窗并跳转成功页
         setTimeout(() => {
           handlePaySuccess()
         }, 3000)
-      } else if (status === '2' || status == '3') {
+      } else if (status === '2' || status === '3') {
         // 支付失败或已取消
         payStatus.value = 'failed'
         stopPayStatusPolling()
@@ -381,7 +383,19 @@ const startPayStatusPolling = (orderId, orderNo) => {
       // 查询失败不中断轮询,继续尝试(除非超过最大次数)
       if (payCheckCount.value > MAX_PAY_CHECK_COUNT) {
         stopPayStatusPolling()
-        router.push({ name: 'success', params: { id: currentOrderInfo.value.id } })
+
+        const orderId = currentOrderInfo.value.order_id || currentOrderInfo.value.id
+        const orderNo = currentOrderInfo.value.order_no
+
+        if (orderId || orderNo) {
+          router.push({
+            name: 'userSuccess',
+            query: {
+              order_id: orderId,
+              order_no: orderNo
+            }
+          })
+        }
       }
     }
   }, 3000)
@@ -406,15 +420,50 @@ const handlePayDialogClose = () => {
   qrCodeLoading.value = false
 }
 
+// 支付二维码倒计时结束处理
+const handlePayExpired = () => {
+  stopPayStatusPolling()
+  payDialogVisible.value = false
+
+  const orderId = currentOrderInfo.value.order_id || currentOrderInfo.value.id
+  const orderNo = currentOrderInfo.value.order_no
+
+  if (orderId || orderNo) {
+    router.push({
+      name: 'userSuccess',
+      query: {
+        order_id: orderId,
+        order_no: orderNo
+      }
+    })
+  }
+
+  qrCodeUrl.value = ''
+  payStatus.value = ''
+  currentOrderInfo.value = {}
+  qrCodeLoading.value = false
+}
+
 // 支付成功处理
 const handlePaySuccess = () => {
   stopPayStatusPolling()
   payDialogVisible.value = false
   ElMessage.success('支付成功!')
-  
-  // 可以在这里跳转到订单详情页或订单列表页
-  router.push({ name: 'success', params: { id: currentOrderInfo.value.id } })
-  
+
+  // 使用订单详情接口需要的参数跳转到成功页
+  const orderId = currentOrderInfo.value.order_id || currentOrderInfo.value.id
+  const orderNo = currentOrderInfo.value.order_no
+
+  if (orderId || orderNo) {
+    router.push({
+      name: 'userSuccess',
+      query: {
+        order_id: orderId,
+        order_no: orderNo
+      }
+    })
+  }
+
   // 重置状态
   qrCodeUrl.value = ''
   payStatus.value = ''

+ 3 - 2
web/src/views/frontend/components/PayQrCodeDialog.vue

@@ -97,7 +97,7 @@
 </template>
 
 <script setup>
-import { computed, ref, watch, onMounted, onBeforeUnmount } from 'vue'
+import { computed, ref, watch, onBeforeUnmount } from 'vue'
 import { ElMessage } from 'element-plus'
 import { Loading, CircleCheck, CircleClose, ChatDotRound, Coin } from '@element-plus/icons-vue'
 import QrcodeVue from 'qrcode.vue'
@@ -129,7 +129,7 @@ const props = defineProps({
   }
 })
 
-const emit = defineEmits(['update:visible', 'close', 'confirm'])
+const emit = defineEmits(['update:visible', 'close', 'confirm', 'expired'])
 
 const dialogVisible = computed({
   get: () => props.visible,
@@ -189,6 +189,7 @@ const startCountdown = () => {
         clearInterval(countdownTimer.value)
         countdownTimer.value = null
         ElMessage.warning('支付二维码已过期,请重新下单')
+        emit('expired')
       }
     }, 1000)
   }

+ 12 - 6
web/src/views/frontend/components/header.vue

@@ -26,7 +26,7 @@
   
         <!-- 中间:PC 显示搜索框,移动端显示“购物”入口 -->
         <div class="header-center">
-          <div v-if="!isMobile" class="search-box">
+          <!-- <div v-if="!isMobile" class="search-box">
             <el-icon class="search-icon">
               <Search />
             </el-icon>
@@ -37,9 +37,9 @@
               class="search-input"
               @keyup.enter="handleSearch"
             >
-          </div>
+          </div> -->
           <div
-            v-else
+            v-if="isMobile"
             class="mobile-shop-entry"
             @click="goToShopping"
           >
@@ -235,8 +235,11 @@ const goToShopping = () => {
 }
 
 const goToOrders = () => {
-  // 跳转到订单查询页面
-  router.push('/user/me')
+  // 跳转到订单查询页面,并激活购买记录tab
+  router.push({
+    path: '/user/me',
+    query: { tab: 'purchase-history' }
+  })
   closeAllMenus()
 }
 
@@ -247,7 +250,10 @@ const goToLogin = () => {
 
 const goToProfile = () => {
   // 跳转到个人中心
-  router.push('/user/me')
+  router.push({
+    path: '/user/me',
+    query: { tab: 'home' }
+  })
   closeAllMenus()
 }
 

+ 162 - 38
web/src/views/frontend/components/success.vue

@@ -7,18 +7,18 @@
     <div class="success-body">
       <el-card class="success-card" shadow="hover">
         <!-- 顶部成功图标与标题 -->
-        <div class="success-header">
+        <div class="success-header" :class="`status-${statusLevel}`">
           <div class="success-icon-wrapper">
             <div class="success-icon">
               <el-icon class="success-icon-inner">
-                <SuccessFilled />
+                <component :is="statusIcon" />
               </el-icon>
             </div>
           </div>
           <div class="success-text">
-            <h2 class="success-title">下单成功</h2>
+            <h2 class="success-title">{{ statusTitle }}</h2>
             <p class="success-subtitle">
-              您的订单已经创建成功,请尽快完成支付或前往订单中心查看详情。
+              {{ statusSubtitle }}
             </p>
           </div>
         </div>
@@ -38,6 +38,20 @@
             <div class="info-value">{{ orderInfo.orderId }}</div>
           </div>
 
+          <div class="info-row">
+            <div class="info-label">订单状态</div>
+            <div class="info-value">
+              <el-tag
+                v-if="orderInfo.payStatusText || orderInfo.orderStatusText"
+                :type="statusTagType"
+                size="small"
+              >
+                {{ orderInfo.payStatusText || orderInfo.orderStatusText }}
+              </el-tag>
+              <span v-else>--</span>
+            </div>
+          </div>
+
           <div class="info-row">
             <div class="info-label">商品名称</div>
             <div class="info-value">{{ orderInfo.goodsName }}</div>
@@ -52,14 +66,6 @@
             <div class="info-label">下单时间</div>
             <div class="info-value">{{ orderInfo.createdAt }}</div>
           </div>
-
-          <div
-            v-if="orderInfo.contact"
-            class="info-row"
-          >
-            <div class="info-label">接收邮箱</div>
-            <div class="info-value">{{ orderInfo.contact }}</div>
-          </div>
         </div>
 
         <!-- 操作按钮 -->
@@ -96,10 +102,18 @@
 </template>
 
 <script setup>
-import { computed, onMounted, reactive } from 'vue'
+import { computed, onMounted, reactive, ref } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
-import { SuccessFilled } from '@element-plus/icons-vue'
+import {
+  SuccessFilled,
+  WarningFilled,
+  CircleCloseFilled,
+  InfoFilled
+} from '@element-plus/icons-vue'
 import Header from './header.vue'
+import { checkOrderStatus } from '/@/api/frontend/order'
+
+
 
 defineOptions({ name: 'ClientOrderSuccess' })
 
@@ -108,11 +122,15 @@ const router = useRouter()
 
 const orderInfo = reactive({
   orderId: '',
+  orderNo: '',
   goodsName: '',
   amount: 0,
   payMethod: '',
   contact: '',
-  createdAt: ''
+  createdAt: '',
+  payStatus: '', // 1已支付 0待支付 2失败 3取消
+  payStatusText: '',
+  orderStatusText: ''
 })
 
 const displayAmount = computed(() => {
@@ -129,32 +147,108 @@ const displayPayMethod = computed(() => {
   return map[orderInfo.payMethod] || orderInfo.payMethod
 })
 
-const initFromRoute = () => {
+// 订单状态级别:success / warning / danger / info
+const statusLevel = computed(() => {
+  const status = String(orderInfo.payStatus || '')
+  if (status === '1') return 'success'
+  if (status === '0' || status === '') return 'pending'
+  if (status === '2' || status === '3') return 'failed'
+  return 'info'
+})
+
+// 不同状态使用不同图标
+const statusIcon = computed(() => {
+  if (statusLevel.value === 'success') return SuccessFilled
+  if (statusLevel.value === 'failed') return CircleCloseFilled
+  if (statusLevel.value === 'pending') return WarningFilled
+  return InfoFilled
+})
+
+const statusTitle = computed(() => {
+  if (statusLevel.value === 'success') return '支付成功'
+  if (statusLevel.value === 'failed') return '订单未完成'
+  if (statusLevel.value === 'pending') return '订单待支付'
+  return '订单状态'
+})
+
+const statusSubtitle = computed(() => {
+  if (statusLevel.value === 'success') {
+    return orderInfo.orderStatusText || '您的订单已支付成功,可前往订单中心查看详情。'
+  }
+  if (statusLevel.value === 'failed') {
+    return orderInfo.payStatusText || '订单已取消或支付失败,如有疑问可联系商家处理。'
+  }
+  if (statusLevel.value === 'pending') {
+    return '订单已创建但尚未完成支付,您可以前往订单中心继续支付或查看详情。'
+  }
+  return '您可以在订单中心查看当前订单的详细状态。'
+})
+
+const statusTagType = computed(() => {
+  if (statusLevel.value === 'success') return 'success'
+  if (statusLevel.value === 'failed') return 'danger'
+  if (statusLevel.value === 'pending') return 'warning'
+  return 'info'
+})
+
+const loading = ref(false)
+
+// 从后台查询订单详情
+const fetchOrderDetail = async () => {
   const query = route?.query || {}
-  const now = new Date()
-  const defaultDate = `${now.getFullYear()}-${(now.getMonth() + 1)
-    .toString()
-    .padStart(2, '0')}-${now
-    .getDate()
-    .toString()
-    .padStart(2, '0')} ${now
-    .getHours()
-    .toString()
-    .padStart(2, '0')}:${now
-    .getMinutes()
-    .toString()
-    .padStart(2, '0')}`
-
-  orderInfo.orderId = query.orderId || query.out_trade_no || '示例订单号 202312030001'
-  orderInfo.goodsName = query.goodsName || query.subject || '惊鸿·日卡'
-  orderInfo.amount = query.amount || query.totalAmount || query.total_fee || 25
-  orderInfo.payMethod = query.payMethod || query.pay_method || 'alipay'
-  orderInfo.contact = query.email || query.contact || ''
-  orderInfo.createdAt = query.createdAt || defaultDate
+  const orderId = query.order_id
+  const orderNo = query.order_no
+
+  // 如果没有携带必要参数,直接返回(可以根据需要做兜底处理)
+  if (!orderId && !orderNo) {
+    return
+  }
+
+  loading.value = true
+  try {
+    const params = {}
+    if (orderId) params.order_id = orderId
+    if (orderNo) params.order_no = orderNo
+
+    const res = await checkOrderStatus(params)
+    const order = res?.data?.order || res?.data
+    if (!order) return
+
+    // 将接口返回的数据映射到页面展示所需字段
+    orderInfo.orderId = order.order_no || order.id || ''
+    orderInfo.orderNo = order.order_no || ''
+    orderInfo.goodsName =
+      order.remark ||
+      order.package_name ||
+      order.product_name ||
+      order.title ||
+      ''
+    orderInfo.amount = order.pay_amount || order.total_amount || 0
+    orderInfo.payMethod = order.pay_method || ''
+    orderInfo.createdAt =
+      order.order_time_formatted ||
+      order.order_time ||
+      order.create_time_formatted ||
+      order.create_time ||
+      ''
+
+    // 状态相关
+    const rawPayStatus = order.pay_status
+    orderInfo.payStatus = rawPayStatus != null ? String(rawPayStatus) : ''
+    orderInfo.payStatusText = order.pay_status_text || ''
+    orderInfo.orderStatusText = order.order_status_text || ''
+  } catch (e) {
+    console.error('加载订单详情失败', e)
+  } finally {
+    loading.value = false
+  }
 }
 
 const goToOrders = () => {
-  router.push('/user/orders')
+  router.push({
+    path: '/user/me',
+    query: { tab: 'purchase-history' }
+  })
 }
 
 const goHome = () => {
@@ -162,7 +256,7 @@ const goHome = () => {
 }
 
 onMounted(() => {
-  initFromRoute()
+  fetchOrderDetail()
 })
 </script>
 
@@ -236,6 +330,36 @@ onMounted(() => {
   }
 }
 
+.success-header.status-success .success-icon {
+  background: #f0f9eb;
+  color: #67c23a;
+  box-shadow: 0 0 0 6px rgba(103, 194, 58, 0.12);
+}
+
+.success-header.status-pending .success-icon {
+  background: #fdf6ec;
+  color: #e6a23c;
+  box-shadow: 0 0 0 6px rgba(230, 162, 60, 0.16);
+}
+
+.success-header.status-failed .success-icon {
+  background: #fef0f0;
+  color: #f56c6c;
+  box-shadow: 0 0 0 6px rgba(245, 108, 108, 0.16);
+}
+
+.success-header.status-success .success-title {
+  color: #303133;
+}
+
+.success-header.status-pending .success-title {
+  color: #e6a23c;
+}
+
+.success-header.status-failed .success-title {
+  color: #f56c6c;
+}
+
 .success-text {
   flex: 1;
   min-width: 0;