AwsLinker/app/components/products/ProductsClientComponent.tsx
2025-09-16 17:19:58 +08:00

209 lines
11 KiB
TypeScript

'use client';
import { useState } from 'react';
import Link from 'next/link';
import ProductModal from './ProductModal';
interface Product {
name: string;
desc: string;
price: string;
features: string[];
badge?: string;
popular?: boolean;
}
interface Category {
title: string;
products: Product[];
}
interface ProductsClientComponentProps {
categories: Category[];
contactText: string;
locale: string;
}
export default function ProductsClientComponent({ categories, contactText, locale }: ProductsClientComponentProps) {
const [selectedProduct, setSelectedProduct] = useState<Product | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [clickedProduct, setClickedProduct] = useState<string | null>(null);
// 类别标题到锚点ID的映射
const getCategoryId = (title: string) => {
const mapping: { [key: string]: string } = {
'云服务器': 'cloud-server',
'雲服務器': 'cloud-server',
'Cloud Server': 'cloud-server',
'安全防护': 'security',
'安全防護': 'security',
'Security': 'security',
'存储服务': 'storage',
'存儲服務': 'storage',
'Storage': 'storage',
'数据库服务': 'database',
'數據庫服務': 'database',
'Database': 'database',
'网络服务': 'network',
'網絡服務': 'network',
'Network': 'network',
'人工智能': 'ai',
'人工智慧': 'ai',
'AI': 'ai'
};
return mapping[title] || title.toLowerCase().replace(/\s+/g, '-');
};
const handleProductClick = (product: Product) => {
setSelectedProduct(product);
setIsModalOpen(true);
};
const handleCardClick = (productName: string) => {
setClickedProduct(clickedProduct === productName ? null : productName);
};
const handleCloseModal = () => {
setIsModalOpen(false);
setSelectedProduct(null);
};
return (
<>
{/* Products Section */}
<section className="py-20">
<div className="container mx-auto px-6">
{categories.map((category, categoryIndex) => (
<div key={categoryIndex} id={getCategoryId(category.title)} className="mb-20 scroll-mt-20">
<div className="text-center mb-16">
<h2 className="text-4xl font-bold text-gray-900 mb-4">
{category.title}
</h2>
<div className="w-24 h-1 bg-gradient-to-r from-blue-600 to-purple-600 mx-auto rounded-full"></div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8">
{category.products.map((product, productIndex) => (
<div
key={productIndex}
onClick={() => handleCardClick(product.name)}
className={`bg-white rounded-xl shadow-sm hover:shadow-xl transition-all duration-300 overflow-hidden hover:-translate-y-2 relative group cursor-pointer ${
clickedProduct === product.name ? 'ring-2 ring-blue-500 ring-opacity-50' : ''
}`}
>
{/* 产品标签 */}
{(product as any).badge && (
<div className="absolute top-3 left-3 z-10">
<span className="bg-blue-600 text-white text-xs font-semibold px-2 py-1 rounded-md shadow-lg">
{(product as any).badge}
</span>
</div>
)}
{/* 热门标签 */}
{(product as any).popular && (
<div className="absolute top-3 right-3 z-10">
<span className="bg-gradient-to-r from-orange-400 to-red-500 text-white text-xs font-bold px-2 py-1 rounded-md shadow-lg">
🔥
</span>
</div>
)}
<div className="p-6 pt-10">
<div className="mb-4">
<h3 className="text-xl font-bold text-gray-900 mb-3 leading-tight group-hover:text-blue-600 transition-colors duration-300">
{product.name}
</h3>
<p className="text-gray-600 text-sm leading-relaxed overflow-hidden" style={{
display: '-webkit-box',
WebkitLineClamp: 3,
WebkitBoxOrient: 'vertical'
}}>
{product.desc}
</p>
</div>
<div className="mb-6">
<div className="text-2xl font-bold text-blue-600 mb-4 flex items-baseline">
{product.price}
{product.price.includes('/月') && (
<span className="text-sm text-gray-500 ml-2 font-normal">
</span>
)}
</div>
<div className="space-y-2">
{product.features.slice(0, 4).map((feature, featureIndex) => (
<div
key={featureIndex}
className="flex items-center text-gray-700 text-sm"
>
<svg
className="w-4 h-4 text-green-500 mr-2 flex-shrink-0"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clipRule="evenodd"
/>
</svg>
<span className="truncate">{feature}</span>
</div>
))}
{product.features.length > 4 && (
<div className="text-xs text-gray-500 mt-2">
+{product.features.length - 4}
</div>
)}
</div>
</div>
<div className="space-y-3">
<Link
href={
locale === 'zh-CN'
? '/contact'
: `/${locale}/contact`
}
onClick={(e) => e.stopPropagation()}
className={`w-full py-3 px-4 rounded-lg font-semibold transition-all duration-300 text-center block text-sm ${
clickedProduct === product.name
? 'bg-gradient-to-r from-blue-600 to-purple-600 text-white hover:from-blue-700 hover:to-purple-700 shadow-lg'
: 'bg-blue-600 text-white hover:bg-blue-700'
}`}
>
{contactText}
</Link>
<button
onClick={(e) => {
e.stopPropagation();
handleProductClick(product);
}}
className="w-full py-2 px-4 border border-gray-300 text-gray-700 rounded-lg font-medium hover:bg-gray-50 transition-colors duration-300 text-sm"
>
</button>
</div>
</div>
</div>
))}
</div>
</div>
))}
</div>
</section>
{/* 产品详情模态框 */}
<ProductModal
product={selectedProduct}
isOpen={isModalOpen}
onClose={handleCloseModal}
contactText={contactText}
locale={locale}
/>
</>
);
}