gongxiangp2/components/DocumentAssetViewer.tsx

161 lines
4.9 KiB
TypeScript

'use client'
import { useEffect, useMemo, useState } from 'react'
import Image from 'next/image'
export interface ViewerAssetItem {
id: string
fileName: string
fileUrl: string
fileKind: 'pdf' | 'image'
}
interface DocumentAssetViewerProps {
title: string
items: ViewerAssetItem[]
}
function formatLabel(fileName: string, index: number): string {
const normalized = String(fileName || '').trim()
return normalized || `文件 ${index + 1}`
}
export default function DocumentAssetViewer({ title, items }: DocumentAssetViewerProps) {
const normalizedItems = useMemo(
() =>
items
.filter((item) => item.fileUrl)
.map((item, index) => ({
...item,
fileName: formatLabel(item.fileName, index),
})),
[items],
)
const [selectedId, setSelectedId] = useState(normalizedItems[0]?.id || '')
useEffect(() => {
setSelectedId(normalizedItems[0]?.id || '')
}, [normalizedItems])
const selectedItem = normalizedItems.find((item) => item.id === selectedId) || normalizedItems[0]
const handleOpenFile = () => {
if (!selectedItem?.fileUrl) {
return
}
window.open(selectedItem.fileUrl, '_blank', 'noopener,noreferrer')
}
const handleDownloadFile = async () => {
if (!selectedItem?.fileUrl) {
return
}
try {
const response = await fetch(selectedItem.fileUrl)
if (!response.ok) {
throw new Error('文件下载失败')
}
const blob = await response.blob()
const objectUrl = URL.createObjectURL(blob)
const anchor = document.createElement('a')
anchor.href = objectUrl
anchor.download = selectedItem.fileName
document.body.appendChild(anchor)
anchor.click()
document.body.removeChild(anchor)
URL.revokeObjectURL(objectUrl)
} catch (error) {
console.error('[DocumentAssetViewer] 下载失败,改为直接打开原文件:', error)
window.open(selectedItem.fileUrl, '_blank', 'noopener,noreferrer')
}
}
if (!selectedItem) {
return (
<div className="flex min-h-[400px] items-center justify-center bg-[#fafafa] text-sm text-[#86898e]">
暂无可预览文件
</div>
)
}
return (
<div className="w-full bg-white">
<div className="border-b border-[#e7e8ea] px-4 py-3">
<div className="mb-3 text-sm font-medium text-[#1a1c1d]">{title}</div>
<div className="flex flex-wrap gap-2">
{normalizedItems.map((item) => {
const isActive = item.id === selectedItem.id
return (
<button
key={item.id}
type="button"
onClick={() => setSelectedId(item.id)}
className={`rounded-[8px] px-3 py-1.5 text-xs transition-colors ${
isActive
? 'bg-[#1a1c1d] text-white'
: 'border border-[#e7e8ea] text-[#6b7075] hover:border-[#1a1c1d] hover:text-[#1a1c1d]'
}`}
>
{item.fileKind === 'pdf' ? 'PDF' : '图片'} · {item.fileName}
</button>
)
})}
</div>
</div>
<div className="flex items-center justify-between border-b border-[#e7e8ea] px-4 py-3">
<div className="min-w-0">
<div className="truncate text-sm font-medium text-[#1a1c1d]">{selectedItem.fileName}</div>
<div className="mt-1 text-xs text-[#86898e]">
{selectedItem.fileKind === 'pdf' ? 'PDF 文档预览' : '图片原图预览'}
</div>
</div>
<div className="flex items-center gap-2">
<button
type="button"
onClick={handleOpenFile}
className="rounded-[8px] border border-[#e7e8ea] px-3 py-2 text-sm text-[#1a1c1d] transition-colors hover:border-[#1a1c1d]"
>
打开原文件
</button>
<button
type="button"
onClick={() => {
void handleDownloadFile()
}}
className="rounded-[8px] bg-[#1a1c1d] px-3 py-2 text-sm text-white transition-colors hover:bg-[#2a2d2f]"
>
下载原文件
</button>
</div>
</div>
<div className="bg-[#fafafa]">
{selectedItem.fileKind === 'pdf' ? (
<iframe
src={`${selectedItem.fileUrl}#toolbar=0&navpanes=0&scrollbar=1`}
className="w-full"
style={{ height: '980px', border: 'none' }}
title={selectedItem.fileName}
/>
) : (
<div className="flex items-center justify-center p-6">
<Image
src={selectedItem.fileUrl}
alt={selectedItem.fileName}
width={790}
height={1200}
unoptimized
className="h-auto w-full object-contain"
style={{ maxWidth: '790px' }}
/>
</div>
)}
</div>
</div>
)
}