/** * SharePage 组件 * * 素材分享页面 * 显示素材详情、预览图、下载等功能 * 支持PSD/模特类型的多图展示 * 使用三层架构样式系统,支持深浅色主题 * * @created 2025-01-XX * @component */ 'use client' import { useEffect, useState, useCallback } from 'react' import { useSession } from 'next-auth/react' import { useRouter, useParams } from 'next/navigation' import Image from 'next/image' import { format } from 'date-fns' import Header from '@/components/Header' import { useDialog } from '@/components/ui/DialogProvider' import { useThemeStyles, useTheme, cn } from '@/lib/ui-styles' interface Material { id: string previewUrl: string productModel: string brandName?: string | null seriesName?: string | null skuCode?: string | null materialTexture?: string | null categoryName: string sceneName: string materialType: string renderer?: string | null renderTime?: string | null imageResolution?: string | null colorMode?: string | null authorName: string zipFileName: string zipFileSize: string | number downloadCount: number likeCount: number displayImages: string[] // PSD展示图片URLs createdAt: string } export default function SharePage() { const styles = useThemeStyles() const theme = useTheme() const { data: session, status } = useSession() const router = useRouter() const { alert } = useDialog() const params = useParams() const materialId = params.id as string const [material, setMaterial] = useState(null) const [loading, setLoading] = useState(true) const [downloading, setDownloading] = useState(false) const [downloadProgress, setDownloadProgress] = useState(0) const [showBackToTop, setShowBackToTop] = useState(false) const [aiEditEnabled, setAiEditEnabled] = useState( (process.env.NEXT_PUBLIC_AI_EDIT_ENABLED || '').toLowerCase() === 'true' ) const fetchMaterial = useCallback(async () => { try { const response = await fetch(`/api/materials/share?id=${materialId}`) if (response.ok) { const data = await response.json() setMaterial(data) // 记录浏览历史(仅登录用户) if (session?.user) { fetch('/api/user/history', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ materialId }), }).catch((err) => console.warn('记录浏览历史失败:', err)) } } else { alert({ title: '错误', message: '素材不存在或已被删除', variant: 'error', }) router.push('/') } } catch (error) { console.error('Error fetching material:', error) alert({ title: '加载失败', message: '加载失败', variant: 'error', }) router.push('/') } finally { setLoading(false) } }, [materialId, router, session]) useEffect(() => { if (status !== 'loading') { fetchMaterial() } }, [status, fetchMaterial]) useEffect(() => { const ensureFeatureFlags = async () => { if (aiEditEnabled) return try { const response = await fetch('/api/system-settings') if (response.ok) { const data = await response.json() if (data?.enableAiImageEdit) { setAiEditEnabled(true) } } } catch (err) { console.warn('加载系统设置失败(AI改图判断):', err) } } ensureFeatureFlags() }, [aiEditEnabled]) useEffect(() => { // 监听滚动,控制返回顶部按钮显示 const handleScroll = () => { setShowBackToTop(window.scrollY > 400) } window.addEventListener('scroll', handleScroll) return () => window.removeEventListener('scroll', handleScroll) }, []) const formatFileSize = (bytes: string | number): string => { const size = typeof bytes === 'string' ? parseInt(bytes) : bytes if (size === 0) return '未知' const units = ['B', 'KB', 'MB', 'GB'] const i = Math.floor(Math.log(size) / Math.log(1024)) return `${(size / Math.pow(1024, i)).toFixed(2)} ${units[i]}` } const scrollToTop = () => { window.scrollTo({ top: 0, behavior: 'smooth' }) } const renderAdditionalInfo = (variant: 'compact' | 'full' = 'compact') => { if (!material) return null const entries = [ { label: '品牌', value: material.brandName }, { label: '系列', value: material.seriesName }, { label: 'SKU', value: material.skuCode }, { label: '材质 / 工艺', value: material.materialTexture }, { label: '渲染耗时', value: material.renderTime }, { label: '输出分辨率', value: material.imageResolution }, { label: '颜色模式', value: material.colorMode }, ].filter(entry => entry.value) if (entries.length === 0) return null const labelClass = variant === 'compact' ? 'text-xs text-slate-500 dark:text-slate-400 mb-1' : 'text-sm text-slate-500 dark:text-slate-400 mb-1' const valueClass = variant === 'compact' ? 'text-sm font-medium text-slate-900 dark:text-white break-all' : 'text-base font-medium text-slate-900 dark:text-white break-all' return (
{entries.map((entry, index) => (

{entry.label}

{entry.value}

))}
) } const handleDownload = () => { if (!material) return setDownloading(true) setDownloadProgress(0) const xhr = new XMLHttpRequest() xhr.addEventListener('progress', (e) => { if (e.lengthComputable) { const percentComplete = Math.round((e.loaded / e.total) * 100) setDownloadProgress(percentComplete) } }) xhr.addEventListener('load', () => { if (xhr.status === 200) { try { const blob = new Blob([xhr.response], { type: 'application/zip' }) const contentDisposition = xhr.getResponseHeader('Content-Disposition') let filename = 'material.zip' if (contentDisposition) { const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/) if (filenameMatch && filenameMatch[1]) { filename = decodeURIComponent(filenameMatch[1].replace(/['"]/g, '')) } } const url = window.URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = filename document.body.appendChild(a) a.click() a.remove() window.URL.revokeObjectURL(url) } catch (error) { console.error('下载处理失败:', error) alert({ title: '下载失败', message: '下载失败,请稍后重试', variant: 'error', }) } } else { alert({ title: '下载失败', message: '下载失败', variant: 'error', }) } setDownloading(false) setDownloadProgress(0) }) xhr.addEventListener('error', () => { alert({ title: '下载失败', message: '下载失败,请稍后重试', variant: 'error', }) setDownloading(false) setDownloadProgress(0) }) xhr.open('GET', `/api/materials/download?id=${material.id}`) xhr.responseType = 'arraybuffer' xhr.send() } if (loading) { return (

加载中...

) } if (!material) { return (

素材不存在

) } return (
{(material.materialType === 'PSD' || material.materialType === '模特') && material.displayImages && material.displayImages.length > 0 ? ( /* PSD/模特类型:左右布局 */
{/* 左侧:展示图片(固定790px宽度) */}
{material.displayImages.map((imageUrl, index) => (
{`${material.productModel}
))}
{/* 右侧:素材信息(固定) */}

{material.productModel}

{material.categoryName} {material.sceneName} {material.materialType}

作者

{material.authorName}

文件大小

{formatFileSize(material.zipFileSize)}

下载

{material.downloadCount}

点赞

{material.likeCount}

文件名

{material.zipFileName}

上传时间

{format(new Date(material.createdAt), 'yyyy-MM-dd HH:mm')}

{renderAdditionalInfo('compact')}
{/* Download Button */} {/* AI改图(占位,默认隐藏。后续由开关控制显示) */} {aiEditEnabled && ( )}
) : material.materialType === '视频' ? ( /* 视频类型:视频播放布局 */
{/* 视频播放器 */}
{/* Material Info */}

{material.productModel}

{material.categoryName} {material.sceneName} 视频

作者

{material.authorName}

文件大小

{formatFileSize(material.zipFileSize)}

下载次数

{material.downloadCount}

点赞数

{material.likeCount}

文件名

{material.zipFileName}

上传时间

{format(new Date(material.createdAt), 'yyyy-MM-dd HH:mm')}

{/* Download Button */}
) : ( /* 非PSD/非视频类型:统一布局 */
{/* 普通预览图 */}
{material.productModel}
{/* Material Info */}

{material.productModel}

{material.categoryName} {material.sceneName}

作者

{material.authorName}

文件大小

{formatFileSize(material.zipFileSize)}

下载次数

{material.downloadCount}

点赞数

{material.likeCount}

文件名

{material.zipFileName}

上传时间

{format(new Date(material.createdAt), 'yyyy-MM-dd HH:mm')}

{renderAdditionalInfo('full')} {/* Download Button */}
)}
{/* 返回顶部按钮 */} {showBackToTop && ( )}
) }