CheckoutPage.jsx 14.4 KB
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import AppLayout from '../../components/layout/AppLayout';
import FrostedGlass from '../../components/ui/FrostedGlass';
import { useCart } from '../../contexts/CartContext';

/**
 * CheckoutPage component handles the order checkout process
 * with user information collection and payment method selection
 * 
 * @returns {JSX.Element} CheckoutPage component
 */
const CheckoutPage = () => {
  const navigate = useNavigate();
  const { cartItems, getTotalPrice, handleCheckout, clearCart } = useCart();
  
  // Form state
  const [formData, setFormData] = useState({
    name: '',
    phone: '',
    email: '',
    address: '',
    notes: '',
    paymentMethod: 'wechat'
  });
  
  // Loading and success states
  const [isProcessing, setIsProcessing] = useState(false);
  const [orderComplete, setOrderComplete] = useState(false);
  const [orderId, setOrderId] = useState('');
  
  // Format price with Chinese Yuan symbol
  const formatPrice = (price) => {
    return ${price.toFixed(2)}`;
  };
  
  // Handle form input changes
  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));
  };
  
  // Handle form submission
  const handleSubmit = async (e) => {
    e.preventDefault();
    
    if (!formData.name || !formData.phone || !formData.address) {
      alert('请填写必要信息');
      return;
    }
    
    setIsProcessing(true);
    
    try {
      // Process checkout
      const result = await handleCheckout(formData);
      
      if (result.success) {
        setOrderId(result.orderId);
        setOrderComplete(true);
      }
    } catch (error) {
      console.error('Checkout failed:', error);
      alert('支付失败,请重试');
    } finally {
      setIsProcessing(false);
    }
  };
  
  // Handle navigation back to home after order completion
  const handleBackToHome = () => {
    navigate('/');
  };

  // If cart is empty, redirect to home
  if (cartItems.length === 0 && !orderComplete) {
    return (
      <AppLayout title="结账">
        <div className="h-screen flex flex-col items-center justify-center px-4">
          <FrostedGlass className="p-6 rounded-xl text-center">
            <svg xmlns="http://www.w3.org/2000/svg" className="h-16 w-16 text-gray-400 mx-auto mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={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 className="text-xl font-bold mb-2">购物车为空</h2>
            <p className="text-gray-600 mb-6">您的购物车中没有任何商品</p>
            <button 
              onClick={() => navigate('/courses')}
              className="w-full bg-gradient-to-r from-green-500 to-green-600 text-white py-3 rounded-xl font-medium"
            >
              浏览课程
            </button>
          </FrostedGlass>
        </div>
      </AppLayout>
    );
  }

  // Show order completion screen
  if (orderComplete) {
    return (
      <AppLayout title="支付成功">
        <div className="h-screen flex flex-col items-center justify-center px-4">
          <FrostedGlass className="p-6 rounded-xl text-center">
            <div className="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" className="h-10 w-10 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
              </svg>
            </div>
            <h2 className="text-2xl font-bold mb-2">支付成功!</h2>
            <p className="text-gray-600 mb-2">您的订单已经成功提交</p>
            <p className="text-gray-500 text-sm mb-6">订单号: {orderId}</p>
            <button 
              onClick={handleBackToHome}
              className="w-full bg-gradient-to-r from-green-500 to-green-600 text-white py-3 rounded-xl font-medium"
            >
              返回首页
            </button>
          </FrostedGlass>
        </div>
      </AppLayout>
    );
  }

  return (
    <AppLayout title="结账" showBackButton onBackClick={() => navigate(-1)}>
      <div className="pb-20">
        {/* Order Summary */}
        <div className="p-4 bg-gradient-to-r from-green-500/10 to-blue-500/10">
          <FrostedGlass className="rounded-xl p-4">
            <h3 className="font-medium mb-3">订单摘要</h3>
            <div className="space-y-3">
              {cartItems.map((item) => (
                <div key={`${item.type}-${item.id}`} className="flex justify-between">
                  <div className="flex flex-1">
                    <div className="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}
                        className="w-full h-full object-cover"
                        onError={(e) => {
                          e.target.onerror = null;
                          e.target.src = "/assets/images/course-placeholder.jpg";
                        }}
                      />
                    </div>
                    <div>
                      <p className="font-medium text-sm line-clamp-1">{item.title}</p>
                      <p className="text-xs text-gray-500">
                        {item.type === 'course' ? '课程' : '活动'} · {item.quantity}
                      </p>
                    </div>
                  </div>
                  <div className="ml-2 text-right">
                    <p className="font-medium text-sm">{formatPrice(item.price * item.quantity)}</p>
                    <p className="text-xs text-gray-500">{formatPrice(item.price)} / 份</p>
                  </div>
                </div>
              ))}
            </div>

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

        {/* Checkout Form */}
        <div className="px-4 pt-4">
          <form onSubmit={handleSubmit}>
            <FrostedGlass className="rounded-xl p-4 mb-4">
              <h3 className="font-medium mb-3">个人信息</h3>
              
              <div className="space-y-3">
                <div>
                  <label className="block text-sm text-gray-600 mb-1">姓名 *</label>
                  <input
                    type="text"
                    name="name"
                    value={formData.name}
                    onChange={handleInputChange}
                    className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm"
                    placeholder="请输入您的姓名"
                    required
                  />
                </div>
                
                <div>
                  <label className="block text-sm text-gray-600 mb-1">手机号码 *</label>
                  <input
                    type="tel"
                    name="phone"
                    value={formData.phone}
                    onChange={handleInputChange}
                    className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm"
                    placeholder="请输入您的手机号码"
                    required
                  />
                </div>
                
                <div>
                  <label className="block text-sm text-gray-600 mb-1">电子邮箱</label>
                  <input
                    type="email"
                    name="email"
                    value={formData.email}
                    onChange={handleInputChange}
                    className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm"
                    placeholder="请输入您的邮箱(选填)"
                  />
                </div>
                
                <div>
                  <label className="block text-sm text-gray-600 mb-1">联系地址 *</label>
                  <input
                    type="text"
                    name="address"
                    value={formData.address}
                    onChange={handleInputChange}
                    className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm"
                    placeholder="请输入您的详细地址"
                    required
                  />
                </div>
                
                <div>
                  <label className="block text-sm text-gray-600 mb-1">备注</label>
                  <textarea
                    name="notes"
                    value={formData.notes}
                    onChange={handleInputChange}
                    className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm resize-none h-20"
                    placeholder="有什么需要我们注意的事项?(选填)"
                  />
                </div>
              </div>
            </FrostedGlass>
            
            <FrostedGlass className="rounded-xl p-4 mb-6">
              <h3 className="font-medium mb-3">支付方式</h3>
              <div className="space-y-2">
                <label className="flex items-center p-3 border border-gray-200 rounded-lg bg-white/50">
                  <input
                    type="radio"
                    name="paymentMethod"
                    value="wechat"
                    checked={formData.paymentMethod === 'wechat'}
                    onChange={handleInputChange}
                    className="mr-3"
                  />
                  <span className="flex-1">微信支付</span>
                  <svg className="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 className="flex items-center p-3 border border-gray-200 rounded-lg bg-white/50">
                  <input
                    type="radio"
                    name="paymentMethod"
                    value="alipay"
                    checked={formData.paymentMethod === 'alipay'}
                    onChange={handleInputChange}
                    className="mr-3"
                  />
                  <span className="flex-1">支付宝</span>
                  <svg className="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 className="flex items-center p-3 border border-gray-200 rounded-lg bg-white/50">
                  <input
                    type="radio"
                    name="paymentMethod"
                    value="bank"
                    checked={formData.paymentMethod === 'bank'}
                    onChange={handleInputChange}
                    className="mr-3"
                  />
                  <span className="flex-1">银行卡</span>
                  <svg className="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 className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-100 shadow-md px-4 py-3">
              <div className="flex justify-between items-center mb-3">
                <div>
                  <span className="text-sm text-gray-500">总计:</span>
                  <span className="text-lg font-bold text-green-600">{formatPrice(getTotalPrice())}</span>
                </div>
                <button 
                  type="button" 
                  onClick={() => clearCart()}
                  className="text-sm text-red-500"
                >
                  清空购物车
                </button>
              </div>
              <button
                type="submit"
                disabled={isProcessing}
                className="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"
              >
                {isProcessing ? (
                  <>
                    <div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin mr-2"></div>
                    处理中...
                  </>
                ) : (
                  '确认支付'
                )}
              </button>
            </div>
          </form>
        </div>
      </div>
    </AppLayout>
  );
};

export default CheckoutPage;