257 lines
8.8 KiB
TypeScript
257 lines
8.8 KiB
TypeScript
"use client"
|
|
|
|
import type React from "react"
|
|
|
|
import { useState, useRef } from "react"
|
|
import Image from "next/image"
|
|
import { ArrowLeft, Heart, MessageCircle, Share, Play, Volume2, VolumeX } from "lucide-react"
|
|
import Link from "next/link"
|
|
|
|
interface VideoData {
|
|
id: number
|
|
venue: string
|
|
title: string
|
|
description: string
|
|
likes: number
|
|
comments: number
|
|
shares: number
|
|
videoUrl: string
|
|
thumbnail: string
|
|
isLiked: boolean
|
|
}
|
|
|
|
const mockVideos: VideoData[] = [
|
|
{
|
|
id: 1,
|
|
venue: "RONIN黄金篮球馆",
|
|
title: "精彩三分球集锦",
|
|
description: "今日比赛精彩瞬间,连续三分命中!#篮球 #精彩瞬间",
|
|
likes: 1234,
|
|
comments: 89,
|
|
shares: 45,
|
|
videoUrl: "/placeholder.svg?height=800&width=400&text=Basketball+Highlights",
|
|
thumbnail: "/placeholder.svg?height=800&width=400&text=Basketball+Game+1",
|
|
isLiked: false,
|
|
},
|
|
{
|
|
id: 2,
|
|
venue: "Panda惊怒熊猫运动俱乐部",
|
|
title: "激烈对抗瞬间",
|
|
description: "双方激烈对抗,精彩防守反击!#运动 #篮球对抗",
|
|
likes: 2156,
|
|
comments: 156,
|
|
shares: 78,
|
|
videoUrl: "/placeholder.svg?height=800&width=400&text=Basketball+Defense",
|
|
thumbnail: "/placeholder.svg?height=800&width=400&text=Basketball+Game+2",
|
|
isLiked: true,
|
|
},
|
|
{
|
|
id: 3,
|
|
venue: "星光篮球场",
|
|
title: "完美扣篮时刻",
|
|
description: "力量与技巧的完美结合,震撼扣篮!#扣篮 #力量",
|
|
likes: 3421,
|
|
comments: 234,
|
|
shares: 123,
|
|
videoUrl: "/placeholder.svg?height=800&width=400&text=Basketball+Dunk",
|
|
thumbnail: "/placeholder.svg?height=800&width=400&text=Basketball+Game+3",
|
|
isLiked: false,
|
|
},
|
|
{
|
|
id: 4,
|
|
venue: "蓝天体育馆",
|
|
title: "团队配合精彩",
|
|
description: "完美的团队配合,流畅的传球配合!#团队 #配合",
|
|
likes: 987,
|
|
comments: 67,
|
|
shares: 34,
|
|
videoUrl: "/placeholder.svg?height=800&width=400&text=Team+Play",
|
|
thumbnail: "/placeholder.svg?height=800&width=400&text=Basketball+Game+4",
|
|
isLiked: false,
|
|
},
|
|
]
|
|
|
|
export default function ImmersivePage() {
|
|
const [currentIndex, setCurrentIndex] = useState(0)
|
|
const [videos, setVideos] = useState(mockVideos)
|
|
const [isPlaying, setIsPlaying] = useState(true)
|
|
const [isMuted, setIsMuted] = useState(false)
|
|
const containerRef = useRef<HTMLDivElement>(null)
|
|
const startY = useRef(0)
|
|
const currentY = useRef(0)
|
|
|
|
const handleTouchStart = (e: React.TouchEvent) => {
|
|
startY.current = e.touches[0].clientY
|
|
}
|
|
|
|
const handleTouchMove = (e: React.TouchEvent) => {
|
|
currentY.current = e.touches[0].clientY
|
|
}
|
|
|
|
const handleTouchEnd = () => {
|
|
const deltaY = startY.current - currentY.current
|
|
const threshold = 50
|
|
|
|
if (Math.abs(deltaY) > threshold) {
|
|
if (deltaY > 0 && currentIndex < videos.length - 1) {
|
|
// 向上滑动,下一个视频
|
|
setCurrentIndex(currentIndex + 1)
|
|
} else if (deltaY < 0 && currentIndex > 0) {
|
|
// 向下滑动,上一个视频
|
|
setCurrentIndex(currentIndex - 1)
|
|
}
|
|
}
|
|
}
|
|
|
|
const handleLike = (videoId: number) => {
|
|
setVideos(
|
|
videos.map((video) => {
|
|
if (video.id === videoId) {
|
|
return {
|
|
...video,
|
|
isLiked: !video.isLiked,
|
|
likes: video.isLiked ? video.likes - 1 : video.likes + 1,
|
|
}
|
|
}
|
|
return video
|
|
}),
|
|
)
|
|
}
|
|
|
|
const formatNumber = (num: number) => {
|
|
if (num >= 1000) {
|
|
return (num / 1000).toFixed(1) + "k"
|
|
}
|
|
return num.toString()
|
|
}
|
|
|
|
const currentVideo = videos[currentIndex]
|
|
|
|
return (
|
|
<div className="fixed inset-0 bg-black overflow-hidden">
|
|
{/* Header - Minimized */}
|
|
<div className="absolute top-0 left-0 right-0 z-20 p-3">
|
|
<div className="flex items-center justify-between">
|
|
<Link href="/">
|
|
<div className="w-8 h-8 bg-black bg-opacity-30 rounded-full flex items-center justify-center">
|
|
<ArrowLeft className="w-4 h-4 text-white" />
|
|
</div>
|
|
</Link>
|
|
<div className="text-white text-sm font-medium">沉浸模式</div>
|
|
<div className="w-8 h-8"></div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Video Container */}
|
|
<div
|
|
ref={containerRef}
|
|
className="h-full w-full relative"
|
|
onTouchStart={handleTouchStart}
|
|
onTouchMove={handleTouchMove}
|
|
onTouchEnd={handleTouchEnd}
|
|
style={{
|
|
transform: `translateY(-${currentIndex * 100}%)`,
|
|
transition: "transform 0.3s ease-out",
|
|
}}
|
|
>
|
|
{videos.map((video, index) => (
|
|
<div key={video.id} className="absolute inset-0 w-full h-full" style={{ top: `${index * 100}%` }}>
|
|
{/* Video Background */}
|
|
<div className="relative w-full h-full">
|
|
<Image
|
|
src={video.thumbnail || "/placeholder.svg"}
|
|
alt={video.title}
|
|
fill
|
|
className="object-cover"
|
|
priority={Math.abs(index - currentIndex) <= 1}
|
|
/>
|
|
|
|
{/* Video Overlay */}
|
|
<div className="absolute inset-0 bg-black bg-opacity-10" />
|
|
|
|
{/* Play/Pause Button - Minimized */}
|
|
<div
|
|
className="absolute inset-0 flex items-center justify-center"
|
|
onClick={() => setIsPlaying(!isPlaying)}
|
|
>
|
|
{!isPlaying && (
|
|
<div className="w-12 h-12 bg-white bg-opacity-20 rounded-full flex items-center justify-center">
|
|
<Play className="w-6 h-6 text-white ml-0.5" fill="currentColor" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right Side Actions - Minimized */}
|
|
<div className="absolute right-3 bottom-24 flex flex-col items-center space-y-4">
|
|
{/* Like Button */}
|
|
<div className="flex flex-col items-center">
|
|
<button
|
|
onClick={() => handleLike(video.id)}
|
|
className={`w-9 h-9 rounded-full flex items-center justify-center ${
|
|
video.isLiked ? "bg-red-500" : "bg-black bg-opacity-20"
|
|
}`}
|
|
>
|
|
<Heart
|
|
className={`w-4 h-4 ${video.isLiked ? "text-white" : "text-white"}`}
|
|
fill={video.isLiked ? "currentColor" : "none"}
|
|
/>
|
|
</button>
|
|
<span className="text-white text-xs mt-1 font-medium">{formatNumber(video.likes)}</span>
|
|
</div>
|
|
|
|
{/* Comment Button */}
|
|
<div className="flex flex-col items-center">
|
|
<button className="w-9 h-9 bg-black bg-opacity-20 rounded-full flex items-center justify-center">
|
|
<MessageCircle className="w-4 h-4 text-white" />
|
|
</button>
|
|
<span className="text-white text-xs mt-1 font-medium">{formatNumber(video.comments)}</span>
|
|
</div>
|
|
|
|
{/* Share Button */}
|
|
<div className="flex flex-col items-center">
|
|
<button className="w-9 h-9 bg-black bg-opacity-20 rounded-full flex items-center justify-center">
|
|
<Share className="w-4 h-4 text-white" />
|
|
</button>
|
|
<span className="text-white text-xs mt-1 font-medium">{formatNumber(video.shares)}</span>
|
|
</div>
|
|
|
|
{/* Volume Button */}
|
|
<button
|
|
onClick={() => setIsMuted(!isMuted)}
|
|
className="w-9 h-9 bg-black bg-opacity-20 rounded-full flex items-center justify-center"
|
|
>
|
|
{isMuted ? <VolumeX className="w-4 h-4 text-white" /> : <Volume2 className="w-4 h-4 text-white" />}
|
|
</button>
|
|
</div>
|
|
|
|
{/* Bottom Info - Minimized */}
|
|
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/50 to-transparent p-3 pb-6">
|
|
<div className="max-w-xs">
|
|
<h3 className="text-white font-medium text-base mb-1">{video.venue}</h3>
|
|
<p className="text-white text-sm opacity-90 leading-relaxed line-clamp-2">{video.description}</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Progress Indicator - Minimized */}
|
|
<div className="absolute right-1 top-1/2 transform -translate-y-1/2 flex flex-col space-y-1">
|
|
{videos.map((_, idx) => (
|
|
<div
|
|
key={idx}
|
|
className={`w-0.5 h-6 rounded-full ${idx === currentIndex ? "bg-white" : "bg-white bg-opacity-30"}`}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Swipe Hint - Minimized */}
|
|
{currentIndex === 0 && (
|
|
<div className="absolute bottom-16 left-1/2 transform -translate-x-1/2 text-white text-xs opacity-60 animate-bounce">
|
|
上滑查看更多
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|