161 lines
4.9 KiB
TypeScript
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>
|
|
)
|
|
}
|