处理按钮

This commit is contained in:
Xin Wang 2025-07-03 08:07:20 +08:00
parent c9990dfce3
commit 68427b5afc
2 changed files with 246 additions and 26 deletions

View File

@ -1,7 +1,7 @@
'use client'; // Add this to ensure it's a client component
import React, { useState, useRef } from 'react';
import { Edit2, Trash2, Play, Volume2, VolumeX } from 'lucide-react';
import { Edit2, Trash2, Play, Pause, Volume2, VolumeX, Maximize, Minimize } from 'lucide-react';
import { Button } from '@/components/ui/button';
import dynamic from 'next/dynamic';
@ -20,6 +20,8 @@ function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutP
const [hoveredId, setHoveredId] = useState<string | null>(null);
const [isPlaying, setIsPlaying] = useState<{ [key: string]: boolean }>({});
const [isMuted, setIsMuted] = useState<{ [key: string]: boolean }>({});
const [volume, setVolume] = useState<{ [key: string]: number }>({});
const [isFullscreen, setIsFullscreen] = useState<{ [key: string]: boolean }>({});
const videoRefs = useRef<{ [key: string]: HTMLVideoElement | null }>({});
const handleMouseEnter = (id: string) => {
@ -43,10 +45,9 @@ function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutP
if (video) {
if (video.paused) {
// 在用户主动播放时取消静音
video.muted = false;
video.muted = isMuted[id] !== false;
video.play();
setIsPlaying(prev => ({ ...prev, [id]: true }));
setIsMuted(prev => ({ ...prev, [id]: false }));
} else {
video.pause();
setIsPlaying(prev => ({ ...prev, [id]: false }));
@ -63,6 +64,34 @@ function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutP
}
};
const handleVolumeChange = (id: string, newVolume: number) => {
const video = videoRefs.current[id];
if (video) {
video.volume = newVolume;
setVolume(prev => ({ ...prev, [id]: newVolume }));
}
};
const toggleFullscreen = (id: string, event: React.MouseEvent) => {
event.stopPropagation();
const video = videoRefs.current[id];
if (video) {
if (!document.fullscreenElement) {
// 进入全屏
video.requestFullscreen?.() ||
(video as any).webkitRequestFullscreen?.() ||
(video as any).msRequestFullscreen?.();
setIsFullscreen(prev => ({ ...prev, [id]: true }));
} else {
// 退出全屏
document.exitFullscreen?.() ||
(document as any).webkitExitFullscreen?.() ||
(document as any).msExitFullscreen?.();
setIsFullscreen(prev => ({ ...prev, [id]: false }));
}
}
};
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 p-6">
{videos.map((video) => (
@ -82,39 +111,31 @@ function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutP
loop
muted={isMuted[video.id] !== false} // 默认静音除非明确设置为false
playsInline
onLoadedData={() => {
// 设置默认音量
if (videoRefs.current[video.id] && !volume[video.id]) {
videoRefs.current[video.id]!.volume = 0.8;
setVolume(prev => ({ ...prev, [video.id]: 0.8 }));
}
}}
/>
{/* 播放按钮遮罩 */}
<div
className={`absolute inset-0 flex items-center justify-center bg-black/40 transition-opacity duration-300
${hoveredId === video.id ? 'opacity-100' : 'opacity-0'}
${hoveredId === video.id && !isPlaying[video.id] ? 'opacity-100' : 'opacity-0'}
`}
onClick={() => togglePlay(video.id)}
>
<Play className={`w-12 h-12 text-white/90 transition-transform duration-300
${isPlaying[video.id] ? 'scale-90 opacity-0' : 'scale-100 opacity-100'}`}
/>
<Play className="w-12 h-12 text-white/90" />
</div>
{/* 操作按钮 */}
{/* 顶部操作按钮 */}
<div
className={`absolute top-4 right-4 flex gap-2 transition-all duration-300 transform
${hoveredId === video.id ? 'translate-y-0 opacity-100' : 'translate-y-[-10px] opacity-0'}
`}
>
<Button
variant="ghost"
size="icon"
className="w-8 h-8 rounded-full bg-black/40 hover:bg-black/60 backdrop-blur-sm"
onClick={(e) => toggleMute(video.id, e)}
title={isMuted[video.id] !== false ? "取消静音" : "静音"}
>
{isMuted[video.id] !== false ? (
<VolumeX className="w-4 h-4 text-white" />
) : (
<Volume2 className="w-4 h-4 text-white" />
)}
</Button>
<Button
variant="ghost"
size="icon"
@ -132,6 +153,84 @@ function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutP
<Trash2 className="w-4 h-4 text-white" />
</Button>
</div>
{/* 底部控制区域 */}
<div
className={`absolute bottom-4 left-4 right-4 transition-all duration-300 transform
${hoveredId === video.id ? 'translate-y-0 opacity-100' : 'translate-y-[10px] opacity-0'}
`}
>
{/* 音量控制滑块 */}
{isPlaying[video.id] && (
<div className="flex items-center gap-2 bg-black/60 rounded-full px-3 py-2 backdrop-blur-sm mb-3">
<Volume2 className="w-4 h-4 text-white" />
<input
type="range"
min="0"
max="1"
step="0.1"
value={volume[video.id] || 0.8}
onChange={(e) => handleVolumeChange(video.id, parseFloat(e.target.value))}
className="flex-1 h-1 bg-white/20 rounded-lg appearance-none cursor-pointer
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3
[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white
[&::-webkit-slider-thumb]:cursor-pointer [&::-webkit-slider-thumb]:shadow-lg
[&::-moz-range-thumb]:w-3 [&::-moz-range-thumb]:h-3 [&::-moz-range-thumb]:rounded-full
[&::-moz-range-thumb]:bg-white [&::-moz-range-thumb]:cursor-pointer
[&::-moz-range-thumb]:border-none [&::-moz-range-thumb]:shadow-lg"
style={{
background: `linear-gradient(to right, white 0%, white ${(volume[video.id] || 0.8) * 100}%, rgba(255,255,255,0.2) ${(volume[video.id] || 0.8) * 100}%, rgba(255,255,255,0.2) 100%)`
}}
/>
<span className="text-xs text-white/70 w-8 text-center">
{Math.round((volume[video.id] || 0.8) * 100)}%
</span>
</div>
)}
{/* 控制按钮组 */}
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="icon"
className="w-8 h-8 rounded-full bg-black/40 hover:bg-black/60 backdrop-blur-sm"
onClick={() => togglePlay(video.id)}
title={isPlaying[video.id] ? "暂停" : "播放"}
>
{isPlaying[video.id] ? (
<Pause className="w-4 h-4 text-white" />
) : (
<Play className="w-4 h-4 text-white" />
)}
</Button>
<Button
variant="ghost"
size="icon"
className="w-8 h-8 rounded-full bg-black/40 hover:bg-black/60 backdrop-blur-sm"
onClick={(e) => toggleMute(video.id, e)}
title={isMuted[video.id] !== false ? "取消静音" : "静音"}
>
{isMuted[video.id] !== false ? (
<VolumeX className="w-4 h-4 text-white" />
) : (
<Volume2 className="w-4 h-4 text-white" />
)}
</Button>
<Button
variant="ghost"
size="icon"
className="w-8 h-8 rounded-full bg-black/40 hover:bg-black/60 backdrop-blur-sm"
onClick={(e) => toggleFullscreen(video.id, e)}
title={isFullscreen[video.id] ? "退出全屏" : "全屏"}
>
{isFullscreen[video.id] ? (
<Minimize className="w-4 h-4 text-white" />
) : (
<Maximize className="w-4 h-4 text-white" />
)}
</Button>
</div>
</div>
</div>
{/* 视频信息 */}

View File

@ -1,7 +1,7 @@
'use client'; // Add this to ensure it's a client component
import React, { useState, useRef, useEffect } from 'react';
import { ChevronLeft, ChevronRight, Volume2, VolumeX } from 'lucide-react';
import { ChevronLeft, ChevronRight, Volume2, VolumeX, Play, Pause } from 'lucide-react';
import { Button } from '@/components/ui/button';
import dynamic from 'next/dynamic';
@ -17,6 +17,8 @@ function VideoScreenLayoutComponent({ videos }: VideoScreenLayoutProps) {
const [currentIndex, setCurrentIndex] = useState(0);
const [isAnimating, setIsAnimating] = useState(false);
const [isMuted, setIsMuted] = useState(true); // 默认静音
const [volume, setVolume] = useState(0.8); // 添加音量状态
const [isPlaying, setIsPlaying] = useState(true); // 播放状态
const containerRef = useRef<HTMLDivElement>(null);
const videoRefs = useRef<(HTMLVideoElement | null)[]>([]);
@ -60,6 +62,23 @@ function VideoScreenLayoutComponent({ videos }: VideoScreenLayoutProps) {
}
};
// 音量控制函数
const handleVolumeChange = (newVolume: number) => {
setVolume(newVolume);
const currentVideo = videoRefs.current[currentIndex];
if (currentVideo) {
currentVideo.volume = newVolume;
}
};
// 应用音量设置到视频元素
const applyVolumeSettings = (videoElement: HTMLVideoElement) => {
if (videoElement) {
videoElement.volume = volume;
videoElement.muted = isMuted;
}
};
// 处理切换
const handleSlide = (direction: 'prev' | 'next') => {
if (isAnimating) return;
@ -74,14 +93,51 @@ function VideoScreenLayoutComponent({ videos }: VideoScreenLayoutProps) {
// 动画结束后重置状态并同步新视频的静音状态
setTimeout(() => {
setIsAnimating(false);
// 同步新视频的静音状态到UI
// 同步新视频的静音状态到UI并应用音量设置
const newVideo = videoRefs.current[newIndex];
if (newVideo) {
setIsMuted(newVideo.muted);
applyVolumeSettings(newVideo);
// 根据当前播放状态控制新视频
if (isPlaying) {
newVideo.play().catch(() => {
setIsPlaying(false);
});
} else {
newVideo.pause();
}
}
}, 500);
};
// 音量设置变化时应用到当前视频
useEffect(() => {
const currentVideo = videoRefs.current[currentIndex];
if (currentVideo) {
applyVolumeSettings(currentVideo);
}
}, [volume, isMuted, currentIndex]);
// 播放状态变化时应用到当前视频
useEffect(() => {
const currentVideo = videoRefs.current[currentIndex];
if (currentVideo) {
if (isPlaying) {
currentVideo.play().catch(() => {
// 处理自动播放策略限制
setIsPlaying(false);
});
} else {
currentVideo.pause();
}
}
}, [isPlaying, currentIndex]);
// 播放/暂停控制
const togglePlay = () => {
setIsPlaying(!isPlaying);
};
return (
<div className="relative w-full h-[600px] overflow-hidden bg-[var(--background)]">
{/* 视频面板容器 */}
@ -106,17 +162,39 @@ function VideoScreenLayoutComponent({ videos }: VideoScreenLayoutProps) {
src={video.url}
suppressHydrationWarning
className="w-full h-full object-cover"
autoPlay
autoPlay={index === currentIndex ? isPlaying : false}
loop
muted={index === currentIndex ? isMuted : true} // 只有当前视频受状态控制
playsInline
onLoadedData={() => {
if (index === currentIndex && videoRefs.current[index]) {
applyVolumeSettings(videoRefs.current[index]!);
// 根据播放状态决定是否播放
if (isPlaying) {
videoRefs.current[index]!.play().catch(() => {
setIsPlaying(false);
});
}
}
}}
onPlay={() => {
if (index === currentIndex) {
setIsPlaying(true);
}
}}
onPause={() => {
if (index === currentIndex) {
setIsPlaying(false);
}
}}
/>
{/* 视频标题和控制 - 只在中间面板显示 */}
{index === currentIndex && (
<>
{/* 音量控制按钮 */}
<div className="absolute top-4 right-4">
{/* 音量控制区域 */}
<div className="absolute top-4 right-4 flex items-center gap-2">
{/* 静音按钮 */}
<Button
variant="ghost"
size="icon"
@ -130,6 +208,49 @@ function VideoScreenLayoutComponent({ videos }: VideoScreenLayoutProps) {
<Volume2 className="w-5 h-5 text-white" />
)}
</Button>
{/* 音量滑块 */}
<div className="flex items-center gap-1 bg-black/40 rounded-full px-2 py-2 backdrop-blur-sm">
<input
type="range"
min="0"
max="1"
step="0.1"
value={volume}
onChange={(e) => handleVolumeChange(parseFloat(e.target.value))}
className="w-12 h-1 bg-white/20 rounded-lg appearance-none cursor-pointer
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3
[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white
[&::-webkit-slider-thumb]:cursor-pointer [&::-webkit-slider-thumb]:shadow-lg
[&::-moz-range-thumb]:w-3 [&::-moz-range-thumb]:h-3 [&::-moz-range-thumb]:rounded-full
[&::-moz-range-thumb]:bg-white [&::-moz-range-thumb]:cursor-pointer
[&::-moz-range-thumb]:border-none [&::-moz-range-thumb]:shadow-lg"
style={{
background: `linear-gradient(to right, white 0%, white ${volume * 100}%, rgba(255,255,255,0.2) ${volume * 100}%, rgba(255,255,255,0.2) 100%)`
}}
/>
<span className="text-xs text-white/70 w-6 text-center">
{Math.round(volume * 100)}%
</span>
</div>
</div>
{/* 底部控制区域 */}
<div className="absolute bottom-16 left-4">
{/* 播放/暂停按钮 */}
<Button
variant="ghost"
size="icon"
className="w-10 h-10 rounded-full bg-black/40 hover:bg-black/60 backdrop-blur-sm"
onClick={togglePlay}
title={isPlaying ? "暂停" : "播放"}
>
{isPlaying ? (
<Pause className="w-5 h-5 text-white" />
) : (
<Play className="w-5 h-5 text-white" />
)}
</Button>
</div>
{/* 视频标题 */}