Files
page-agent/pages/test-pages/complex-test.tsx
2025-09-29 16:33:15 +08:00

533 lines
20 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from 'react'
import { Link } from 'wouter'
interface CartItem {
id: number
name: string
price: number
quantity: number
image: string
}
interface WizardStep {
id: number
title: string
description: string
completed: boolean
}
export default function ComplexTestPage() {
const [currentStep, setCurrentStep] = useState(1)
const [cartItems, setCartItems] = useState<CartItem[]>([
{ id: 1, name: 'iPhone 15 Pro', price: 7999, quantity: 1, image: 'https://picsum.photos/100/100?random=1' },
{ id: 2, name: 'MacBook Air', price: 8999, quantity: 1, image: 'https://picsum.photos/100/100?random=2' }
])
const [wizardData, setWizardData] = useState({
personalInfo: { name: '', email: '', phone: '' },
address: { street: '', city: '', zipCode: '' },
payment: { cardNumber: '', expiryDate: '', cvv: '' }
})
const [wizardSteps, setWizardSteps] = useState<WizardStep[]>([
{ id: 1, title: '个人信息', description: '填写基本信息', completed: false },
{ id: 2, title: '收货地址', description: '填写收货地址', completed: false },
{ id: 3, title: '支付方式', description: '选择支付方式', completed: false },
{ id: 4, title: '确认订单', description: '确认订单信息', completed: false }
])
const [showConfirmDialog, setShowConfirmDialog] = useState(false)
const [isProcessing, setIsProcessing] = useState(false)
const [orderComplete, setOrderComplete] = useState(false)
// 购物车操作
const updateQuantity = (id: number, newQuantity: number) => {
if (newQuantity <= 0) {
removeItem(id)
return
}
setCartItems(prev =>
prev.map(item =>
item.id === id ? { ...item, quantity: newQuantity } : item
)
)
}
const removeItem = (id: number) => {
setCartItems(prev => prev.filter(item => item.id !== id))
}
const addItem = () => {
const newItem: CartItem = {
id: Date.now(),
name: `新产品 ${cartItems.length + 1}`,
price: Math.floor(Math.random() * 5000) + 1000,
quantity: 1,
image: `https://picsum.photos/100/100?random=${Date.now()}`
}
setCartItems(prev => [...prev, newItem])
}
const getTotalPrice = () => {
return cartItems.reduce((total, item) => total + item.price * item.quantity, 0)
}
// 向导步骤验证
const validateStep = (step: number): boolean => {
switch (step) {
case 1:
return !!(wizardData.personalInfo.name && wizardData.personalInfo.email && wizardData.personalInfo.phone)
case 2:
return !!(wizardData.address.street && wizardData.address.city && wizardData.address.zipCode)
case 3:
return !!(wizardData.payment.cardNumber && wizardData.payment.expiryDate && wizardData.payment.cvv)
default:
return true
}
}
const goToStep = (step: number) => {
// 验证当前步骤
if (step > currentStep && !validateStep(currentStep)) {
alert('请完成当前步骤的必填信息')
return
}
// 更新步骤完成状态
if (step > currentStep) {
setWizardSteps(prev =>
prev.map(s =>
s.id === currentStep ? { ...s, completed: true } : s
)
)
}
setCurrentStep(step)
}
const handleInputChange = (section: string, field: string, value: string) => {
setWizardData(prev => ({
...prev,
[section]: {
...prev[section as keyof typeof prev],
[field]: value
}
}))
}
const handleSubmitOrder = async () => {
setIsProcessing(true)
// 模拟处理时间
await new Promise(resolve => setTimeout(resolve, 3000))
// 模拟随机失败
if (Math.random() < 0.2) {
setIsProcessing(false)
alert('订单提交失败,请重试')
return
}
setIsProcessing(false)
setOrderComplete(true)
setShowConfirmDialog(false)
}
const resetWizard = () => {
setCurrentStep(1)
setWizardData({
personalInfo: { name: '', email: '', phone: '' },
address: { street: '', city: '', zipCode: '' },
payment: { cardNumber: '', expiryDate: '', cvv: '' }
})
setWizardSteps(prev => prev.map(s => ({ ...s, completed: false })))
setOrderComplete(false)
setShowConfirmDialog(false)
}
if (orderComplete) {
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 flex items-center justify-center">
<div className="max-w-md mx-auto text-center">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-8">
<div className="text-6xl mb-4">🎉</div>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">
</h2>
<p className="text-gray-600 dark:text-gray-300 mb-6">
</p>
<div className="space-y-3">
<button
type="button"
onClick={resetWizard}
className="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-md transition-colors"
>
</button>
<Link href="/test-pages" className="block w-full bg-gray-600 hover:bg-gray-700 text-white py-2 px-4 rounded-md transition-colors text-center">
</Link>
</div>
</div>
</div>
</div>
)
}
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 py-8">
<div className="max-w-6xl mx-auto px-4">
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-2">
</h1>
<p className="text-gray-600 dark:text-gray-300">
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* 购物车区域 */}
<div className="lg:col-span-1">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6 sticky top-8">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
({cartItems.length})
</h3>
<div className="space-y-4 mb-6">
{cartItems.map(item => (
<div key={item.id} className="flex items-center space-x-3 p-3 border border-gray-200 dark:border-gray-600 rounded-lg">
<img
src={item.image}
alt={item.name}
className="w-12 h-12 object-cover rounded"
/>
<div className="flex-1 min-w-0">
<h4 className="text-sm font-medium text-gray-900 dark:text-white truncate">
{item.name}
</h4>
<p className="text-sm text-gray-500 dark:text-gray-400">
¥{item.price.toLocaleString()}
</p>
</div>
<div className="flex items-center space-x-2">
<button
onClick={() => updateQuantity(item.id, item.quantity - 1)}
className="w-6 h-6 flex items-center justify-center bg-gray-200 dark:bg-gray-600 rounded text-sm hover:bg-gray-300 dark:hover:bg-gray-500"
>
-
</button>
<span className="text-sm font-medium w-8 text-center">
{item.quantity}
</span>
<button
onClick={() => updateQuantity(item.id, item.quantity + 1)}
className="w-6 h-6 flex items-center justify-center bg-gray-200 dark:bg-gray-600 rounded text-sm hover:bg-gray-300 dark:hover:bg-gray-500"
>
+
</button>
<button
onClick={() => removeItem(item.id)}
className="w-6 h-6 flex items-center justify-center bg-red-500 text-white rounded text-sm hover:bg-red-600"
>
×
</button>
</div>
</div>
))}
</div>
<button
onClick={addItem}
className="w-full mb-4 py-2 px-4 border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg text-gray-500 dark:text-gray-400 hover:border-blue-500 hover:text-blue-500 transition-colors"
>
+
</button>
<div className="border-t border-gray-200 dark:border-gray-600 pt-4">
<div className="flex justify-between items-center text-lg font-semibold text-gray-900 dark:text-white">
<span>:</span>
<span>¥{getTotalPrice().toLocaleString()}</span>
</div>
</div>
</div>
</div>
{/* 向导区域 */}
<div className="lg:col-span-2">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow">
{/* 步骤指示器 */}
<div className="px-6 py-4 border-b border-gray-200 dark:border-gray-600">
<div className="flex items-center justify-between">
{wizardSteps.map((step, index) => (
<div key={step.id} className="flex items-center">
<button
onClick={() => goToStep(step.id)}
className={`w-10 h-10 rounded-full flex items-center justify-center text-sm font-medium transition-colors ${
step.completed
? 'bg-green-500 text-white'
: step.id === currentStep
? 'bg-blue-500 text-white'
: 'bg-gray-200 dark:bg-gray-600 text-gray-500 dark:text-gray-400'
}`}
>
{step.completed ? '✓' : step.id}
</button>
{index < wizardSteps.length - 1 && (
<div className={`w-16 h-1 mx-2 ${
step.completed ? 'bg-green-500' : 'bg-gray-200 dark:bg-gray-600'
}`} />
)}
</div>
))}
</div>
<div className="mt-4">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
{wizardSteps[currentStep - 1].title}
</h3>
<p className="text-gray-600 dark:text-gray-300">
{wizardSteps[currentStep - 1].description}
</p>
</div>
</div>
{/* 步骤内容 */}
<div className="p-6">
{currentStep === 1 && (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
*
</label>
<input
type="text"
value={wizardData.personalInfo.name}
onChange={(e) => handleInputChange('personalInfo', 'name', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
placeholder="请输入您的姓名"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
*
</label>
<input
type="email"
value={wizardData.personalInfo.email}
onChange={(e) => handleInputChange('personalInfo', 'email', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
placeholder="请输入您的邮箱"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
*
</label>
<input
type="tel"
value={wizardData.personalInfo.phone}
onChange={(e) => handleInputChange('personalInfo', 'phone', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
placeholder="请输入您的手机号"
/>
</div>
</div>
)}
{currentStep === 2 && (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
*
</label>
<input
type="text"
value={wizardData.address.street}
onChange={(e) => handleInputChange('address', 'street', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
placeholder="请输入详细地址"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
*
</label>
<input
type="text"
value={wizardData.address.city}
onChange={(e) => handleInputChange('address', 'city', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
placeholder="请输入城市"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
*
</label>
<input
type="text"
value={wizardData.address.zipCode}
onChange={(e) => handleInputChange('address', 'zipCode', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
placeholder="请输入邮政编码"
/>
</div>
</div>
)}
{currentStep === 3 && (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
*
</label>
<input
type="text"
value={wizardData.payment.cardNumber}
onChange={(e) => handleInputChange('payment', 'cardNumber', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
placeholder="请输入银行卡号"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
*
</label>
<input
type="text"
value={wizardData.payment.expiryDate}
onChange={(e) => handleInputChange('payment', 'expiryDate', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
placeholder="MM/YY"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
CVV *
</label>
<input
type="text"
value={wizardData.payment.cvv}
onChange={(e) => handleInputChange('payment', 'cvv', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
placeholder="CVV"
/>
</div>
</div>
</div>
)}
{currentStep === 4 && (
<div className="space-y-6">
<div>
<h4 className="text-lg font-medium text-gray-900 dark:text-white mb-4"></h4>
<div className="space-y-4">
<div className="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
<h5 className="font-medium text-gray-900 dark:text-white mb-2"></h5>
<p className="text-sm text-gray-600 dark:text-gray-300">
{wizardData.personalInfo.name} | {wizardData.personalInfo.email} | {wizardData.personalInfo.phone}
</p>
</div>
<div className="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
<h5 className="font-medium text-gray-900 dark:text-white mb-2"></h5>
<p className="text-sm text-gray-600 dark:text-gray-300">
{wizardData.address.street}, {wizardData.address.city} {wizardData.address.zipCode}
</p>
</div>
<div className="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
<h5 className="font-medium text-gray-900 dark:text-white mb-2"></h5>
<p className="text-sm text-gray-600 dark:text-gray-300">
**** **** **** {wizardData.payment.cardNumber.slice(-4)}
</p>
</div>
</div>
</div>
</div>
)}
</div>
{/* 导航按钮 */}
<div className="px-6 py-4 border-t border-gray-200 dark:border-gray-600 flex justify-between">
<button
onClick={() => goToStep(currentStep - 1)}
disabled={currentStep === 1}
className="px-4 py-2 text-gray-600 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
</button>
{currentStep < 4 ? (
<button
onClick={() => goToStep(currentStep + 1)}
disabled={!validateStep(currentStep)}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white rounded-md transition-colors"
>
</button>
) : (
<button
onClick={() => setShowConfirmDialog(true)}
disabled={cartItems.length === 0}
className="px-4 py-2 bg-green-600 hover:bg-green-700 disabled:bg-green-400 text-white rounded-md transition-colors"
>
</button>
)}
</div>
</div>
</div>
</div>
{/* 确认对话框 */}
{showConfirmDialog && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 max-w-md w-full mx-4">
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-4">
</h3>
<p className="text-gray-600 dark:text-gray-300 mb-6">
¥{getTotalPrice().toLocaleString()}
</p>
<div className="flex justify-end space-x-3">
<button
onClick={() => setShowConfirmDialog(false)}
disabled={isProcessing}
className="px-4 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50 transition-colors"
>
</button>
<button
onClick={handleSubmitOrder}
disabled={isProcessing}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white rounded-md transition-colors flex items-center"
>
{isProcessing ? (
<>
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
...
</>
) : (
'确认提交'
)}
</button>
</div>
</div>
</div>
)}
{/* 返回链接 */}
<div className="mt-12 pt-8 border-t border-gray-200 dark:border-gray-700">
<Link href="/" className="text-blue-600 hover:text-blue-500 dark:text-blue-400">
</Link>
</div>
</div>
</div>
)
}