2025-10-26 09:11:04 +08:00

260 lines
9.4 KiB
TypeScript

'use client'
import { useEffect, useState } 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'
interface Material {
id: string
previewUrl: string
productModel: string
categoryName: string
sceneName: string
authorName: string
zipFileName: string
zipFileSize: string | number
downloadCount: number
likeCount: number
createdAt: string
}
export default function SharePage() {
const { data: session, status } = useSession()
const router = useRouter()
const params = useParams()
const materialId = params.id as string
const [material, setMaterial] = useState<Material | null>(null)
const [loading, setLoading] = useState(true)
const [downloading, setDownloading] = useState(false)
const [downloadProgress, setDownloadProgress] = useState(0)
useEffect(() => {
// 分享页面允许未登录访问
if (status !== 'loading') {
fetchMaterial()
}
}, [status, materialId])
const fetchMaterial = async () => {
try {
const response = await fetch(`/api/materials/share?id=${materialId}`)
if (response.ok) {
const data = await response.json()
setMaterial(data)
} else {
alert('素材不存在或已被删除')
router.push('/')
}
} catch (error) {
console.error('Error fetching material:', error)
alert('加载失败')
router.push('/')
} finally {
setLoading(false)
}
}
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 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('下载失败,请稍后重试')
}
} else {
alert('下载失败')
}
setDownloading(false)
setDownloadProgress(0)
})
xhr.addEventListener('error', () => {
alert('下载失败,请稍后重试')
setDownloading(false)
setDownloadProgress(0)
})
xhr.open('GET', `/api/materials/download?id=${material.id}`)
xhr.responseType = 'arraybuffer'
xhr.send()
}
if (loading) {
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
<main className="container mx-auto px-4 py-8">
<div className="text-center py-16">
<p className="text-gray-500 dark:text-gray-400 text-lg">加载中...</p>
</div>
</main>
</div>
)
}
if (!material) {
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
<Header session={session} />
<main className="container mx-auto px-4 py-8">
<div className="text-center py-16">
<p className="text-gray-500 dark:text-gray-400 text-lg">素材不存在</p>
</div>
</main>
</div>
)
}
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
<Header session={session} />
<main className="container mx-auto px-4 py-8">
<div className="max-w-4xl mx-auto">
<div className="bg-white dark:bg-gray-800 rounded-2xl shadow-lg overflow-hidden">
{/* Preview Image */}
<div className="relative w-full h-96 bg-gray-200 dark:bg-gray-700">
<Image
src={material.previewUrl}
alt={material.productModel}
fill
className="object-contain"
sizes="(max-width: 1024px) 100vw, 1024px"
/>
</div>
{/* Material Info */}
<div className="p-8">
<div className="mb-6">
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-2">
{material.productModel}
</h1>
<div className="flex items-center gap-3 mb-4">
<span className="inline-flex items-center px-3 py-1 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded-full text-sm font-medium">
{material.categoryName}
</span>
<span className="inline-flex items-center px-3 py-1 bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300 rounded-full text-sm font-medium">
{material.sceneName}
</span>
</div>
</div>
<div className="grid grid-cols-2 gap-6 mb-6">
<div>
<p className="text-sm text-gray-500 dark:text-gray-400 mb-1">作者</p>
<p className="text-lg font-medium text-gray-900 dark:text-white">{material.authorName}</p>
</div>
<div>
<p className="text-sm text-gray-500 dark:text-gray-400 mb-1">文件大小</p>
<p className="text-lg font-medium text-blue-600 dark:text-blue-400">{formatFileSize(material.zipFileSize)}</p>
</div>
<div>
<p className="text-sm text-gray-500 dark:text-gray-400 mb-1">下载次数</p>
<p className="text-lg font-medium text-gray-900 dark:text-white">{material.downloadCount}</p>
</div>
<div>
<p className="text-sm text-gray-500 dark:text-gray-400 mb-1">点赞数</p>
<p className="text-lg font-medium text-gray-900 dark:text-white">{material.likeCount}</p>
</div>
</div>
<div className="mb-6">
<p className="text-sm text-gray-500 dark:text-gray-400 mb-1">文件名</p>
<p className="text-sm text-gray-700 dark:text-gray-300 break-all">{material.zipFileName}</p>
</div>
<div className="mb-6">
<p className="text-sm text-gray-500 dark:text-gray-400 mb-1">上传时间</p>
<p className="text-sm text-gray-700 dark:text-gray-300">
{format(new Date(material.createdAt), 'yyyy-MM-dd HH:mm')}
</p>
</div>
{/* Download Button */}
<button
onClick={handleDownload}
disabled={downloading}
className="w-full btn-primary py-4 text-lg font-medium relative overflow-hidden disabled:opacity-75"
>
{downloading ? (
<>
<div
className="absolute left-0 top-0 bottom-0 bg-blue-600 transition-all duration-300"
style={{ width: `${downloadProgress}%` }}
/>
<span className="relative z-10 flex items-center justify-center gap-2">
<svg className="w-6 h-6 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
下载中 {downloadProgress}%
</span>
</>
) : (
<span className="flex items-center justify-center gap-2">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
立即下载
</span>
)}
</button>
<button
onClick={() => router.push('/')}
className="w-full mt-4 py-3 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-xl font-medium hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
>
返回首页
</button>
</div>
</div>
</div>
</main>
</div>
)
}