2025-06-22 11:34:32 +08:00

151 lines
4.3 KiB
TypeScript

'use client'
import Link from 'next/link'
import Image from 'next/image'
import { useState } from 'react'
import { ShoppingCart, Check } from 'lucide-react'
interface Component {
id: string
name: string
brand: string
model: string
price: number
description?: string | null
imageUrl?: string | null
stock: number
componentType?: {
id: string
name: string
}
}
interface ComponentCardProps {
component: Component
}
export function ComponentCard({ component }: ComponentCardProps) {
const [isAdding, setIsAdding] = useState(false)
const [isAdded, setIsAdded] = useState(false)
const addToCart = async (e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation()
if (component.stock <= 0 || isAdding) return
const token = localStorage.getItem('token')
if (!token) {
alert('请先登录')
return
}
setIsAdding(true)
try {
const response = await fetch('/api/cart', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
componentId: component.id,
quantity: 1
})
})
if (response.ok) {
setIsAdded(true)
setTimeout(() => setIsAdded(false), 2000)
} else if (response.status === 401) {
localStorage.removeItem('token')
localStorage.removeItem('user')
alert('登录已过期,请重新登录')
window.location.href = '/login'
} else {
const data = await response.json()
alert(data.message || '添加失败')
}
} catch (error) {
console.error('添加到购物车失败:', error)
alert('添加失败')
} finally {
setIsAdding(false)
}
}
return (
<div className="bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow duration-300 overflow-hidden">
<Link href={`/components/${component.id}`}>
<div className="relative h-48 bg-gray-100">
{component.imageUrl ? (
<Image
src={component.imageUrl}
alt={component.name}
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
className="object-cover"
/>
) : (
<div className="flex items-center justify-center h-full">
<span className="text-gray-400 text-4xl">📦</span>
</div>
)}
</div>
</Link>
<div className="p-4">
<div className="text-sm text-gray-500 mb-1">{component.brand}</div>
<Link href={`/components/${component.id}`}>
<h3 className="font-semibold text-lg mb-2 line-clamp-2 hover:text-blue-600 transition-colors">
{component.name}
</h3>
</Link>
{component.description && (
<p className="text-gray-600 text-sm mb-3 line-clamp-2">
{component.description}
</p>
)}
<div className="flex justify-between items-center mb-3">
<span className="text-2xl font-bold text-blue-600">
¥{component.price.toLocaleString()}
</span>
<span className={`text-sm px-2 py-1 rounded ${component.stock > 0
? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800'
}`}>
{component.stock > 0 ? `库存 ${component.stock}` : '缺货'}
</span>
</div>
<button
onClick={addToCart}
disabled={component.stock <= 0 || isAdding}
className={`w-full py-2 px-4 rounded-lg font-medium transition-colors flex items-center justify-center ${component.stock <= 0
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
: isAdded
? 'bg-green-600 text-white'
: 'bg-blue-600 text-white hover:bg-blue-700'
}`}
>
{isAdding ? (
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
) : isAdded ? (
<>
<Check className="h-4 w-4 mr-2" />
</>
) : (
<>
<ShoppingCart className="h-4 w-4 mr-2" />
{component.stock <= 0 ? '缺货' : '加入购物车'}
</>
)}
</button>
</div>
</div>
)
}