统一创建入口:图片名称展示

This commit is contained in:
北枳 2025-10-20 15:25:27 +08:00
parent 1662ee026c
commit 90964aab74

View File

@ -1,9 +1,10 @@
"use client"; "use client";
import { useState } from 'react'; import { useState } from 'react';
import { Image } from 'antd'; import { Image, Tooltip } from 'antd';
import NextImage from 'next/image'; import NextImage from 'next/image';
import { EyeOutlined, SwapOutlined, UserOutlined, CameraOutlined, BulbOutlined } from '@ant-design/icons'; import { EyeOutlined, SwapOutlined, UserOutlined, CameraOutlined, BulbOutlined } from '@ant-design/icons';
import { useDeviceType } from '@/hooks/useDeviceType';
import type { PhotoPreviewSectionProps, PhotoType } from './types'; import type { PhotoPreviewSectionProps, PhotoType } from './types';
import './styles.css'; import './styles.css';
@ -24,6 +25,7 @@ export default function PhotoPreviewSection({
const [previewVisible, setPreviewVisible] = useState(false); const [previewVisible, setPreviewVisible] = useState(false);
const [previewImage, setPreviewImage] = useState(''); const [previewImage, setPreviewImage] = useState('');
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null); const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
const { isMobileDevice } = useDeviceType();
/** /**
* Get icon for photo type * Get icon for photo type
@ -93,58 +95,90 @@ export default function PhotoPreviewSection({
data-alt="photo-list-container" data-alt="photo-list-container"
className="flex gap-2 overflow-x-auto overflow-y-hidden photo-list-scrollbar pb-1" className="flex gap-2 overflow-x-auto overflow-y-hidden photo-list-scrollbar pb-1"
> >
{photos.map((photo, index) => ( {photos.map((photo, index) => {
<div const photoName = photo.name || `${getTypeLabel(photo.type)} ${index + 1}`;
key={photo.id || `photo-${index}`}
data-alt="photo-item" return (
className="relative flex-shrink-0 w-16 h-16 rounded-[16px] overflow-hidden border border-white/10 hover:border-cyan-400/60 transition-all duration-200 group bg-black/20"
onMouseEnter={() => setHoveredIndex(index)}
onMouseLeave={() => setHoveredIndex(null)}
>
{/* Photo Image */}
<div className="w-full h-full relative">
<NextImage
src={photo.url}
alt={`${getTypeLabel(photo.type)} ${index + 1}`}
fill
className="object-cover"
unoptimized
/>
</div>
{/* Hover Overlay with Preview and Replace Icons */}
<div <div
data-alt="hover-overlay" key={photo.id || `photo-${index}`}
className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex items-center justify-center gap-1 z-[5]" data-alt="photo-item-wrapper"
className="flex-shrink-0 flex flex-col gap-1"
> >
{/* Preview Button */} {/* Photo Image Container */}
<button <div
data-alt="preview-button" data-alt="photo-item"
className="w-5 h-5 rounded-full bg-cyan-400/90 hover:bg-cyan-400 flex items-center justify-center transition-all duration-200 shadow-lg" className="relative w-16 h-16 rounded-[16px] overflow-hidden border border-white/10 hover:border-cyan-400/60 transition-all duration-200 group bg-black/20"
onClick={(e) => handlePreviewClick(e, photo.url)} onMouseEnter={() => setHoveredIndex(index)}
onMouseLeave={() => setHoveredIndex(null)}
> >
<EyeOutlined className="text-xs text-black" /> {/* Photo Image */}
</button> <div className="w-full h-full relative">
<NextImage
src={photo.url}
alt={photoName}
fill
className="object-cover"
unoptimized
/>
</div>
{/* Replace Button */} {/* Hover Overlay with Preview and Replace Icons */}
<button <div
data-alt="replace-button" data-alt="hover-overlay"
className="w-5 h-5 rounded-full bg-cyan-400/90 hover:bg-cyan-400 flex items-center justify-center transition-all duration-200 shadow-lg" className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex items-center justify-center gap-1 z-[5]"
onClick={(e) => handleReplaceClick(e, index)} >
> {/* Preview Button */}
<SwapOutlined className="text-xs text-black" /> <button
</button> data-alt="preview-button"
</div> className="w-5 h-5 rounded-full bg-cyan-400/90 hover:bg-cyan-400 flex items-center justify-center transition-all duration-200 shadow-lg"
onClick={(e) => handlePreviewClick(e, photo.url)}
>
<EyeOutlined className="text-xs text-black" />
</button>
{/* Type Icon - Bottom Left Corner */} {/* Replace Button */}
<div <button
data-alt="type-icon" data-alt="replace-button"
className="absolute bottom-1 left-1 w-4 h-4 bg-black/60 backdrop-blur-sm text-cyan-400 rounded-sm flex items-center justify-center z-[6]" className="w-5 h-5 rounded-full bg-cyan-400/90 hover:bg-cyan-400 flex items-center justify-center transition-all duration-200 shadow-lg"
> onClick={(e) => handleReplaceClick(e, index)}
{getTypeIcon(photo.type)} >
<SwapOutlined className="text-xs text-black" />
</button>
</div>
{/* Type Icon - Bottom Left Corner */}
<div
data-alt="type-icon"
className="absolute bottom-1 left-1 w-4 h-4 bg-black/60 backdrop-blur-sm text-cyan-400 rounded-sm flex items-center justify-center z-[6]"
>
{getTypeIcon(photo.type)}
</div>
</div>
{/* Photo Name - Below Image */}
{isMobileDevice ? (
// H5: Direct display with line-clamp
<div
data-alt="photo-name-mobile"
className="w-16 text-white/70 text-[10px] leading-tight line-clamp-2 text-center px-0.5"
style={{ wordBreak: 'break-word' }}
>
{photoName}
</div>
) : (
// PC: Tooltip on hover
<Tooltip title={photoName} placement="bottom">
<div
data-alt="photo-name-desktop"
className="w-16 text-white/70 text-xs text-center truncate px-0.5 cursor-default"
>
{photoName}
</div>
</Tooltip>
)}
</div> </div>
</div> );
))} })}
</div> </div>
{/* Preview Modal - Using absolute positioning to avoid layout space */} {/* Preview Modal - Using absolute positioning to avoid layout space */}