CheckoutPage.vue 14.3 KB
<template>
  <AppLayout :title="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.cover || 'https://cdn.ipadbiz.cn/mlaj/images/default_block.png'"
                    :alt="item.title"
                    class="w-full h-full object-cover"
                    @error="handleImageError"
                  />
                </div>
                <div class="flex-1">
                  <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>
                <button
                  v-if="mode === 'multiple'"
                  @click="() => {
                    itemToDelete = item
                    showConfirmDialog = true
                  }"
                  class="text-red-500 hover:text-red-600 p-1 -mr-1"
                  title="删除"
                >
                  <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
                  </svg>
                </button>
              </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">姓名 <span class="text-red-500">*</span></label>
                <input
                  v-model="formData.receive_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">手机号码 <span class="text-red-500">*</span></label>
                <input
                  v-model="formData.receive_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.receive_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">联系地址 <span class="text-red-500">*</span></label>
                <input
                  v-model="formData.receive_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.pay_type"
                  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
                v-if="mode === 'multiple'"
                type="button"
                @click="() => {
                  showConfirmDialog = true
                  itemToDelete = null
                }"
                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>

    <!-- Confirm Dialog -->
    <ConfirmDialog
      v-model:show="showConfirmDialog"
      title="确认删除"
      :message="itemToDelete ? `确定要删除 ${itemToDelete.title || '此商品'} 吗?` : '确定要清空课程吗?'"
      @confirm="() => {
        if (itemToDelete) {
          if (cartItems.length === 1) {
            clearCart()
          } else {
            removeFromCart(itemToDelete.id, itemToDelete.type)
          }
          itemToDelete = null
        } else {
          clearCart()
        }
      }"
      @cancel="itemToDelete = null"
    />
  </AppLayout>
</template>

<script setup>
import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import AppLayout from '@/components/layout/AppLayout.vue'
import FrostedGlass from '@/components/ui/FrostedGlass.vue'
import ConfirmDialog from '@/components/ui/ConfirmDialog.vue'
import { useCart } from '@/contexts/cart'
import { useTitle } from '@vueuse/core';
const $route = useRoute();
const $router = useRouter();
useTitle($route.meta.title);
const router = useRouter()
const { items: cartItems, mode, getTotalPrice, handleCheckout, clearCart, removeFromCart } = useCart()

// Form state
const formData = ref({
  receive_name: '',
  receive_phone: '',
  receive_email: '',
  receive_address: '',
  notes: '',
  pay_type: 'WeChat'
})

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

// Confirm dialog state
const showConfirmDialog = ref(false)
const itemToDelete = ref(null)

// Format price with Chinese Yuan symbol
const formatPrice = (price) => {
  if (!price && price !== 0) return '¥0.00'
  return `¥${Number(price).toFixed(2)}`
}

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

// Handle form submission
const handleSubmit = async (e) => {
  try {
    // 表单验证
    if (!formData.value.receive_name?.trim()) {
      throw new Error('请输入姓名')
    }
    if (!formData.value.receive_phone?.trim()) {
      throw new Error('请输入手机号码')
    }
    if (!formData.value.receive_address?.trim()) {
      throw new Error('请输入联系地址')
    }
    if (!formData.value.pay_type) {
      throw new Error('请选择支付方式')
    }

    isProcessing.value = true

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

    if (!result) {
      throw new Error('支付处理失败,请重试')
    }

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

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