360 lines
15 KiB
TypeScript
360 lines
15 KiB
TypeScript
"use client"
|
||
|
||
import { useRef, useState } from "react"
|
||
import Image from "next/image"
|
||
import { Card, CardContent } from "@/components/ui/card"
|
||
import { Button } from "@/components/ui/button"
|
||
import { ChevronLeft, ChevronRight, Info } from "lucide-react"
|
||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
|
||
|
||
export default function RobotsShowcase() {
|
||
const scrollContainerRef = useRef<HTMLDivElement>(null)
|
||
const [showLeftArrow, setShowLeftArrow] = useState(false)
|
||
const [showRightArrow, setShowRightArrow] = useState(true)
|
||
const [activeIndex, setActiveIndex] = useState(0)
|
||
|
||
const robots = [
|
||
{
|
||
id: 1,
|
||
name: "初号步兵",
|
||
image: "/placeholder.svg?height=400&width=600",
|
||
category: "INFANTRY",
|
||
description:
|
||
"Our flagship defense robot, equipped with advanced armor plating and a 360° rotating turret system.",
|
||
specs: ["Weight: 15kg", "Max Speed: 3m/s", "Battery Life: 30min", "Weapon: Launcher"],
|
||
},
|
||
{
|
||
id: 2,
|
||
name: "初号英雄",
|
||
image: "/placeholder.svg?height=400&width=600",
|
||
category: "HERO",
|
||
description: "High-mobility attack unit designed for rapid engagement and strategic positioning.",
|
||
specs: ["Weight: 12kg", "Max Speed: 4.5m/s", "Battery Life: 25min", "Weapon: Dual Launcher System"],
|
||
},
|
||
{
|
||
id: 3,
|
||
name: "零号哨兵",
|
||
image: "/placeholder.svg?height=400&width=600",
|
||
category: "SENTRY",
|
||
description:
|
||
"Lightweight reconnaissance drone providing battlefield intelligence and limited attack capabilities.",
|
||
specs: ["Weight: 2kg", "Max Speed: 10m/s", "Flight Time: 15min", "Sensors: 4K Camera, Infrared"],
|
||
},
|
||
{
|
||
id: 4,
|
||
name: "零号工程",
|
||
image: "/placeholder.svg?height=400&width=600",
|
||
category: "ENGINEER",
|
||
description: "Specialized support unit capable of on-field repairs and resource management during matches.",
|
||
specs: ["Weight: 18kg", "Max Speed: 2m/s", "Battery Life: 40min", "Tools: Mechanical Arm, Repair Module"],
|
||
},
|
||
]
|
||
|
||
const scroll = (direction: "left" | "right") => {
|
||
if (scrollContainerRef.current) {
|
||
const { current: container } = scrollContainerRef
|
||
const scrollAmount = container.clientWidth * 0.8
|
||
const cardWidth = container.querySelector(".card-hover-effect")?.clientWidth || 0
|
||
const gap = 24 // 6 * 4px (gap-6)
|
||
|
||
if (direction === "left") {
|
||
container.scrollBy({ left: -scrollAmount, behavior: "smooth" })
|
||
setActiveIndex((prev) => (prev === 0 ? robots.length - 1 : prev - 1))
|
||
} else {
|
||
container.scrollBy({ left: scrollAmount, behavior: "smooth" })
|
||
setActiveIndex((prev) => (prev === robots.length - 1 ? 0 : prev + 1))
|
||
}
|
||
|
||
// 检查是否需要无限滚动
|
||
setTimeout(() => {
|
||
if (container) {
|
||
// 如果滚动到最左边(克隆的最后一个元素)
|
||
if (container.scrollLeft < cardWidth / 2) {
|
||
// 无动画地跳到真正的最后一组元素
|
||
container.scrollTo({
|
||
left: container.scrollWidth - cardWidth * 2,
|
||
behavior: "auto",
|
||
})
|
||
}
|
||
// 如果滚动到最右边(克隆的第一个元素)
|
||
else if (container.scrollLeft > container.scrollWidth - cardWidth * 1.5) {
|
||
// 无动画地跳到真正的第一组元素
|
||
container.scrollTo({
|
||
left: cardWidth,
|
||
behavior: "auto",
|
||
})
|
||
}
|
||
|
||
setShowLeftArrow(container.scrollLeft > 0)
|
||
setShowRightArrow(container.scrollLeft < container.scrollWidth - container.clientWidth - 10)
|
||
}
|
||
}, 500)
|
||
}
|
||
}
|
||
|
||
const handleScroll = () => {
|
||
if (scrollContainerRef.current) {
|
||
const { current: container } = scrollContainerRef
|
||
const cardWidth = container.querySelector(".card-hover-effect")?.clientWidth || 0
|
||
const gap = 24 // 6 * 4px (gap-6)
|
||
|
||
// 计算当前查看的卡片索引
|
||
const scrollPosition = container.scrollLeft
|
||
const itemWidth = cardWidth + gap
|
||
const index = Math.round(scrollPosition / itemWidth) - 1 // -1 因为有一个克隆元素在开头
|
||
|
||
if (index >= 0 && index < robots.length) {
|
||
setActiveIndex(index)
|
||
}
|
||
|
||
setShowLeftArrow(container.scrollLeft > 0)
|
||
setShowRightArrow(container.scrollLeft < container.scrollWidth - container.clientWidth - 10)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<section id="robots" className="py-20 relative overflow-hidden">
|
||
<div className="absolute right-0 top-0 bottom-0 red-section z-0" />
|
||
|
||
<div className="container mx-auto px-4 relative z-10">
|
||
<div className="flex flex-col md:flex-row gap-8 mb-12">
|
||
<div className="md:w-1/3">
|
||
<div className="red-accent pl-4">
|
||
<h2 className="text-3xl font-bold mb-2 uppercase">机器人</h2>
|
||
<p className="text-xs text-muted-foreground uppercase mb-4 font-mono">ROBOTS</p>
|
||
</div>
|
||
<div className="diagonal-line mb-8"></div>
|
||
<div className="content-overlay">
|
||
<p className="text-muted-foreground leading-relaxed">
|
||
探索我们为 RoboMaster 竞赛设计的尖端机器人,每款机器人都针对赛场上的特定战术角色而设计。
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="relative">
|
||
{showLeftArrow && (
|
||
<Button
|
||
variant="outline"
|
||
size="icon"
|
||
className="absolute left-0 top-1/2 -translate-y-1/2 z-10 bg-background/80 backdrop-blur-sm border-white/10 button-hover-effect"
|
||
onClick={() => scroll("left")}
|
||
>
|
||
<ChevronLeft className="h-6 w-6" />
|
||
</Button>
|
||
)}
|
||
|
||
<div
|
||
ref={scrollContainerRef}
|
||
className="flex gap-6 overflow-x-auto scrollbar-hide py-4 px-2 snap-x snap-mandatory"
|
||
onScroll={handleScroll}
|
||
>
|
||
{/* 添加末尾项到开头 */}
|
||
<Card
|
||
key={`clone-end-${robots[robots.length - 1].id}`}
|
||
className="min-w-[300px] md:min-w-[350px] border-white/10 bg-background/60 backdrop-blur-sm flex-shrink-0 card-hover-effect snap-center"
|
||
>
|
||
<CardContent className="p-0">
|
||
<div className="relative">
|
||
<Image
|
||
src={robots[robots.length - 1].image || "/placeholder.svg"}
|
||
alt={robots[robots.length - 1].name}
|
||
width={350}
|
||
height={200}
|
||
className="w-full h-48 object-cover"
|
||
/>
|
||
<div className="absolute top-0 right-0 bg-primary text-primary-foreground text-xs px-3 py-1">
|
||
{robots[robots.length - 1].category}
|
||
</div>
|
||
<div className="absolute bottom-0 left-0 p-2">
|
||
<p className="text-xs text-primary font-mono">{`0${robots.length}/0${robots.length}`}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="p-4">
|
||
<div className="flex justify-between items-start mb-2">
|
||
<h3 className="text-xl font-bold">{robots[robots.length - 1].name}</h3>
|
||
<TooltipProvider>
|
||
<Tooltip>
|
||
<TooltipTrigger asChild>
|
||
<Button variant="ghost" size="icon" className="h-8 w-8 button-hover-effect">
|
||
<Info className="h-4 w-4" />
|
||
</Button>
|
||
</TooltipTrigger>
|
||
<TooltipContent className="max-w-[250px]">
|
||
<div className="space-y-2">
|
||
<p className="font-bold">Technical Specifications:</p>
|
||
<ul className="text-xs space-y-1">
|
||
{robots[robots.length - 1].specs.map((spec, index) => (
|
||
<li key={index}>{spec}</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
</TooltipContent>
|
||
</Tooltip>
|
||
</TooltipProvider>
|
||
</div>
|
||
|
||
<p className="text-sm text-muted-foreground mb-4 leading-relaxed">
|
||
{robots[robots.length - 1].description}
|
||
</p>
|
||
|
||
<Button variant="outline" size="sm" className="w-full border-white/10 button-hover-effect">
|
||
View Details
|
||
</Button>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{robots.map((robot, index) => (
|
||
<Card
|
||
key={robot.id}
|
||
className="min-w-[300px] md:min-w-[350px] border-white/10 bg-background/60 backdrop-blur-sm flex-shrink-0 card-hover-effect snap-center"
|
||
onMouseEnter={() => setActiveIndex(index)}
|
||
>
|
||
<CardContent className="p-0">
|
||
<div className="relative">
|
||
<Image
|
||
src={robot.image || "/placeholder.svg"}
|
||
alt={robot.name}
|
||
width={350}
|
||
height={200}
|
||
className="w-full h-48 object-cover"
|
||
/>
|
||
<div className="absolute top-0 right-0 bg-primary text-primary-foreground text-xs px-3 py-1">
|
||
{robot.category}
|
||
</div>
|
||
<div className="absolute bottom-0 left-0 p-2">
|
||
<p className="text-xs text-primary font-mono">{`0${index + 1}/0${robots.length}`}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="p-4">
|
||
<div className="flex justify-between items-start mb-2">
|
||
<h3 className="text-xl font-bold">{robot.name}</h3>
|
||
<TooltipProvider>
|
||
<Tooltip>
|
||
<TooltipTrigger asChild>
|
||
<Button variant="ghost" size="icon" className="h-8 w-8 button-hover-effect">
|
||
<Info className="h-4 w-4" />
|
||
</Button>
|
||
</TooltipTrigger>
|
||
<TooltipContent className="max-w-[250px]">
|
||
<div className="space-y-2">
|
||
<p className="font-bold">Technical Specifications:</p>
|
||
<ul className="text-xs space-y-1">
|
||
{robot.specs.map((spec, index) => (
|
||
<li key={index}>{spec}</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
</TooltipContent>
|
||
</Tooltip>
|
||
</TooltipProvider>
|
||
</div>
|
||
|
||
<p className="text-sm text-muted-foreground mb-4 leading-relaxed">{robot.description}</p>
|
||
|
||
<Button variant="outline" size="sm" className="w-full border-white/10 button-hover-effect">
|
||
View Details
|
||
</Button>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
))}
|
||
|
||
{/* 添加开头项到末尾 */}
|
||
<Card
|
||
key={`clone-start-${robots[0].id}`}
|
||
className="min-w-[300px] md:min-w-[350px] border-white/10 bg-background/60 backdrop-blur-sm flex-shrink-0 card-hover-effect snap-center"
|
||
>
|
||
<CardContent className="p-0">
|
||
<div className="relative">
|
||
<Image
|
||
src={robots[0].image || "/placeholder.svg"}
|
||
alt={robots[0].name}
|
||
width={350}
|
||
height={200}
|
||
className="w-full h-48 object-cover"
|
||
/>
|
||
<div className="absolute top-0 right-0 bg-primary text-primary-foreground text-xs px-3 py-1">
|
||
{robots[0].category}
|
||
</div>
|
||
<div className="absolute bottom-0 left-0 p-2">
|
||
<p className="text-xs text-primary font-mono">{`01/0${robots.length}`}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="p-4">
|
||
<div className="flex justify-between items-start mb-2">
|
||
<h3 className="text-xl font-bold">{robots[0].name}</h3>
|
||
<TooltipProvider>
|
||
<Tooltip>
|
||
<TooltipTrigger asChild>
|
||
<Button variant="ghost" size="icon" className="h-8 w-8 button-hover-effect">
|
||
<Info className="h-4 w-4" />
|
||
</Button>
|
||
</TooltipTrigger>
|
||
<TooltipContent className="max-w-[250px]">
|
||
<div className="space-y-2">
|
||
<p className="font-bold">技术参数:</p>
|
||
<ul className="text-xs space-y-1">
|
||
{robots[0].specs.map((spec, index) => (
|
||
<li key={index}>{spec}</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
</TooltipContent>
|
||
</Tooltip>
|
||
</TooltipProvider>
|
||
</div>
|
||
|
||
<p className="text-sm text-muted-foreground mb-4 leading-relaxed">{robots[0].description}</p>
|
||
|
||
<Button variant="outline" size="sm" className="w-full border-white/10 button-hover-effect">
|
||
View Details
|
||
</Button>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
{showRightArrow && (
|
||
<Button
|
||
variant="outline"
|
||
size="icon"
|
||
className="absolute right-0 top-1/2 -translate-y-1/2 z-10 bg-background/80 backdrop-blur-sm border-white/10 button-hover-effect"
|
||
onClick={() => scroll("right")}
|
||
>
|
||
<ChevronRight className="h-6 w-6" />
|
||
</Button>
|
||
)}
|
||
|
||
{/* 添加索引指示器 */}
|
||
<div className="flex justify-center mt-6 gap-2">
|
||
{robots.map((_, index) => (
|
||
<button
|
||
key={index}
|
||
className={`w-2 h-2 rounded-full transition-all ${
|
||
activeIndex === index ? "bg-primary w-4" : "bg-gray-500"
|
||
}`}
|
||
onClick={() => {
|
||
if (scrollContainerRef.current) {
|
||
const cardWidth = scrollContainerRef.current.querySelector(".card-hover-effect")?.clientWidth || 0
|
||
const gap = 24 // 6 * 4px (gap-6)
|
||
scrollContainerRef.current.scrollTo({
|
||
left: (index + 1) * (cardWidth + gap), // +1 for the cloned element at start
|
||
behavior: "smooth",
|
||
})
|
||
setActiveIndex(index)
|
||
}
|
||
}}
|
||
/>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
)
|
||
}
|