CheckoutPage.vue 11.9 KB
<template>
  <AppLayout :title="orderComplete ? '支付成功' : '结账'" :show-back-button="!orderComplete" :hide-bottom-nav="true" @back-click="router.back()">
    <div v-if="cartItems.length === 0 && !orderComplete" class="h-screen flex flex-col items-center justify-center px-4">
      <FrostedGlass class="p-6 rounded-xl text-center">
        <svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 text-gray-400 mx-auto mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" />
        </svg>
        <h2 class="text-xl font-bold mb-2">购物车为空</h2>
        <p class="text-gray-600 mb-6">您的购物车中没有任何商品</p>
        <button
          @click="router.push('/courses')"
          class="w-full bg-gradient-to-r from-green-500 to-green-600 text-white py-3 rounded-xl font-medium"
        >
          浏览课程
        </button>
      </FrostedGlass>
    </div>

    <div v-else-if="orderComplete" class="h-screen flex flex-col items-center justify-center px-4">
      <FrostedGlass class="p-6 rounded-xl text-center">
        <div class="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
          <svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
          </svg>
        </div>
        <h2 class="text-2xl font-bold mb-2">支付成功!</h2>
        <p class="text-gray-600 mb-2">您的订单已经成功提交</p>
        <p class="text-gray-500 text-sm mb-6">订单号: {{ orderId }}</p>
        <button
          @click="handleBackToHome"
          class="w-full bg-gradient-to-r from-green-500 to-green-600 text-white py-3 rounded-xl font-medium"
        >
          返回首页
        </button>
      </FrostedGlass>
    </div>

    <div v-else class="pb-20">
      <!-- Order Summary -->
      <div class="p-4 bg-gradient-to-r from-green-500/10 to-blue-500/10">
        <FrostedGlass class="rounded-xl p-4">
          <h3 class="font-medium mb-3">订单摘要</h3>
          <div class="space-y-3">
            <div v-for="item in cartItems" :key="`${item.type}-${item.id}`" class="flex justify-between">
              <div class="flex flex-1">
                <div class="w-12 h-12 rounded-lg overflow-hidden mr-3 flex-shrink-0">
                  <img
                    :src="item.imageUrl || '/assets/images/course-placeholder.jpg'"
                    :alt="item.title"
                    class="w-full h-full object-cover"
                    @error="handleImageError"
                  />
                </div>
                <div>
                  <p class="font-medium text-sm line-clamp-1">{{ item.title }}</p>
                  <p class="text-xs text-gray-500">
                    {{ item.type === 'course' ? '课程' : '活动' }} · {{ item.quantity }} 份
                  </p>
                </div>
              </div>
              <div class="ml-2 text-right">
                <p class="font-medium text-sm">{{ formatPrice(item.price * item.quantity) }}</p>
                <p class="text-xs text-gray-500">{{ formatPrice(item.price) }} / 份</p>
              </div>
            </div>
          </div>

          <div class="mt-4 pt-3 border-t border-gray-200">
            <div class="flex justify-between items-center text-sm">
              <span class="text-gray-600">小计</span>
              <span class="font-medium">{{ formatPrice(getTotalPrice()) }}</span>
            </div>
            <div class="flex justify-between items-center text-sm mt-1">
              <span class="text-gray-600">优惠</span>
              <span class="text-red-500">- ¥0.00</span>
            </div>
            <div class="flex justify-between items-center mt-2 font-medium">
              <span>总计</span>
              <span class="text-lg text-green-600">{{ formatPrice(getTotalPrice()) }}</span>
            </div>
          </div>
        </FrostedGlass>
      </div>

      <!-- Checkout Form -->
      <div class="px-4 pt-4">
        <form @submit.prevent="handleSubmit">
          <FrostedGlass class="rounded-xl p-4 mb-4">
            <h3 class="font-medium mb-3">个人信息</h3>

            <div class="space-y-3">
              <div>
                <label class="block text-sm text-gray-600 mb-1">姓名 *</label>
                <input
                  v-model="formData.name"
                  type="text"
                  class="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm"
                  placeholder="请输入您的姓名"
                  required
                />
              </div>

              <div>
                <label class="block text-sm text-gray-600 mb-1">手机号码 *</label>
                <input
                  v-model="formData.phone"
                  type="tel"
                  class="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm"
                  placeholder="请输入您的手机号码"
                  required
                />
              </div>

              <div>
                <label class="block text-sm text-gray-600 mb-1">电子邮箱</label>
                <input
                  v-model="formData.email"
                  type="email"
                  class="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm"
                  placeholder="请输入您的邮箱(选填)"
                />
              </div>

              <div>
                <label class="block text-sm text-gray-600 mb-1">联系地址 *</label>
                <input
                  v-model="formData.address"
                  type="text"
                  class="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm"
                  placeholder="请输入您的详细地址"
                  required
                />
              </div>

              <div>
                <label class="block text-sm text-gray-600 mb-1">备注</label>
                <textarea
                  v-model="formData.notes"
                  class="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm resize-none h-20"
                  placeholder="有什么需要我们注意的事项?(选填)"
                />
              </div>
            </div>
          </FrostedGlass>

          <FrostedGlass class="rounded-xl p-4 mb-6">
            <h3 class="font-medium mb-3">支付方式</h3>
            <div class="space-y-2">
              <label class="flex items-center p-3 border border-gray-200 rounded-lg bg-white/50">
                <input
                  v-model="formData.paymentMethod"
                  type="radio"
                  value="wechat"
                  class="mr-3"
                />
                <span class="flex-1">微信支付</span>
                <svg class="h-6 w-6 text-green-500" viewBox="0 0 24 24" fill="currentColor">
                  <path d="M9.5,8.5m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0" />
                  <path d="M14.5,8.5m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0" />
                  <path d="M9.5,14.5m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0" />
                  <path d="M14.5,14.5m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0" />
                  <path d="M12,2C6.5,2 2,6.5 2,12s4.5,10 10,10s10,-4.5 10,-10S17.5,2 12,2zM19.8,11c-0.2,4.6 -4.1,7.8 -8.8,7.5c-3.8,-0.2 -7.2,-3.2 -7.5,-7.1C3.2,7 6.9,3.1 11.5,3C16.1,3 20,6.9 19.8,11z" />
                </svg>
              </label>

              <label class="flex items-center p-3 border border-gray-200 rounded-lg bg-white/50">
                <input
                  v-model="formData.paymentMethod"
                  type="radio"
                  value="alipay"
                  class="mr-3"
                />
                <span class="flex-1">支付宝</span>
                <svg class="h-6 w-6 text-blue-500" viewBox="0 0 24 24" fill="currentColor">
                  <path d="M12,2C6.5,2 2,6.5 2,12s4.5,10 10,10s10,-4.5 10,-10S17.5,2 12,2zM12,20.5c-4.7,0 -8.5,-3.8 -8.5,-8.5S7.3,3.5 12,3.5s8.5,3.8 8.5,8.5S16.7,20.5 12,20.5z" />
                  <path d="M12,6c-3.3,0 -6,2.7 -6,6s2.7,6 6,6s6,-2.7 6,-6S15.3,6 12,6zM16,13h-3v3h-2v-3H8v-2h3V8h2v3h3V13z" />
                </svg>
              </label>

              <label class="flex items-center p-3 border border-gray-200 rounded-lg bg-white/50">
                <input
                  v-model="formData.paymentMethod"
                  type="radio"
                  value="bank"
                  class="mr-3"
                />
                <span class="flex-1">银行卡</span>
                <svg class="h-6 w-6 text-gray-500" viewBox="0 0 24 24" fill="currentColor">
                  <path d="M20,4H4C2.9,4 2,4.9 2,6v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6C22,4.9 21.1,4 20,4zM20,18H4V12h16V18zM20,8H4V6h16V8z" />
                  <path d="M5,13h2v2h-2z" />
                  <path d="M9,13h2v2h-2z" />
                  <path d="M13,13h2v2h-2z" />
                  <path d="M17,13h2v2h-2z" />
                </svg>
              </label>
            </div>
          </FrostedGlass>

          <div class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-100 shadow-md px-4 py-3">
            <div class="flex justify-between items-center mb-3">
              <div>
                <span class="text-sm text-gray-500">总计:</span>
                <span class="text-lg font-bold text-green-600">{{ formatPrice(getTotalPrice()) }}</span>
              </div>
              <button
                type="button"
                @click="clearCart"
                class="text-sm text-red-500"
              >
                清空购物车
              </button>
            </div>
            <button
              type="submit"
              :disabled="isProcessing"
              class="w-full bg-gradient-to-r from-green-500 to-green-600 text-white py-3 rounded-xl font-medium disabled:opacity-70 flex items-center justify-center"
            >
              <template v-if="isProcessing">
                <div class="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin mr-2"></div>
                处理中...
              </template>
              <template v-else>
                确认支付
              </template>
            </button>
          </div>
        </form>
      </div>
    </div>
  </AppLayout>
</template>

<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import AppLayout from '@/components/layout/AppLayout.vue'
import FrostedGlass from '@/components/ui/FrostedGlass.vue'
import { useCart } from '@/contexts/cart'

const router = useRouter()
const { items: cartItems, getTotalPrice, handleCheckout, clearCart } = useCart()

// Form state
const formData = ref({
  name: '',
  phone: '',
  email: '',
  address: '',
  notes: '',
  paymentMethod: 'wechat'
})

// Loading and success states
const isProcessing = ref(false)
const orderComplete = ref(false)
const orderId = ref('')

// Format price with Chinese Yuan symbol
const formatPrice = (price) => {
  return `¥${price.toFixed(2)}`
}

// Handle image error
const handleImageError = (e) => {
  e.target.src = '/assets/images/course-placeholder.jpg'
}

// Handle form submission
const handleSubmit = async (e) => {
  if (!formData.value.name || !formData.value.phone || !formData.value.address) {
    alert('请填写必要信息')
    return
  }

  isProcessing.value = true

  try {
    // Process checkout
    const result = await handleCheckout(formData.value)

    if (result.success) {
      orderId.value = result.orderId
      orderComplete.value = true
    }
  } catch (error) {
    console.error('Checkout failed:', error)
    alert('支付失败,请重试')
  } finally {
    isProcessing.value = false
  }
}

// Handle navigation back to home after order completion
const handleBackToHome = () => {
  router.push('/')
}
</script>