import {useEffect,useRef,useState} from 'react'; import type {PDFDocumentProxy,RenderTask} from 'pdfjs-dist'; import {ChevronLeft,ChevronRight,Minus,Plus,Download} from 'lucide-react'; export default function PdfPreview({url,token='',name='document.pdf'}:{url:string;token?:string;name?:string}){ const canvas=useRef(null),container=useRef(null); const [pdf,setPdf]=useState(null),[page,setPage]=useState(1),[zoom,setZoom]=useState(1),[width,setWidth]=useState(450),[error,setError]=useState(''),[loading,setLoading]=useState(true),[retry,setRetry]=useState(0); useEffect(()=>{const node=container.current;if(!node)return;const observer=new ResizeObserver(entries=>setWidth(Math.max(100,entries[0].contentRect.width-32)));observer.observe(node);return()=>observer.disconnect();},[]); useEffect(()=>{let alive=true;let task:ReturnType|undefined;const abort=new AbortController();setPdf(null);setPage(1);setZoom(1);setError('');setLoading(true); (async()=>{const lib=await import('pdfjs-dist');if(!alive)return;lib.GlobalWorkerOptions.workerSrc='/pdfjs/pdf.worker.min.mjs';const r=await fetch(url,{signal:abort.signal,headers:token?{Authorization:`Bearer ${token}`}:{}});if(!r.ok)throw new Error(r.status===401?'请输入正确的 API 访问令牌后预览。':'文档读取失败,请重试。');const bytes=new Uint8Array(await r.arrayBuffer());if(!alive)return;task=lib.getDocument({data:bytes,cMapUrl:'/pdfjs/cmaps/',cMapPacked:true,standardFontDataUrl:'/pdfjs/standard_fonts/',wasmUrl:'/pdfjs/wasm/'});const doc=await task.promise;if(alive)setPdf(doc);})().catch(e=>{if(alive&&e.name!=='AbortError'){setError(e.name==='PasswordException'?'此 PDF 已加密,请先解密后上传。':e.message||'PDF 无法读取,请检查文件是否损坏。');setLoading(false);}}); return()=>{alive=false;abort.abort();void task?.destroy();};},[url,token,retry]); useEffect(()=>{if(!pdf||!canvas.current)return;let alive=true,render:RenderTask|undefined;setLoading(true); (async()=>{const p=await pdf.getPage(page);if(!alive||!canvas.current)return;const base=p.getViewport({scale:1});const viewport=p.getViewport({scale:(width/base.width)*zoom});const c=canvas.current,ratio=Math.min(window.devicePixelRatio||1,2);c.width=Math.ceil(viewport.width*ratio);c.height=Math.ceil(viewport.height*ratio);c.style.width=viewport.width+'px';c.style.height=viewport.height+'px';render=p.render({canvas:c,viewport,transform:ratio===1?undefined:[ratio,0,0,ratio,0,0]});await render.promise;if(alive)setLoading(false);})().catch(e=>{if(alive&&e.name!=='RenderingCancelledException'){setError('PDF 页面渲染失败,请重试。');setLoading(false);}}); return()=>{alive=false;render?.cancel();};},[pdf,page,width,zoom]); async function download(){try{const r=await fetch(url,{headers:token?{Authorization:`Bearer ${token}`}:{}});if(!r.ok)throw Error();const u=URL.createObjectURL(await r.blob());const a=document.createElement('a');a.href=u;a.download=name;a.click();setTimeout(()=>URL.revokeObjectURL(u),1000);}catch{setError('下载失败,请检查访问权限。');}} return
{pdf?`${page} / ${pdf.numPages}`:'— / —'}
{error?

{error}

:<>
{loading?'正在渲染 PDF…':`第 ${page} 页,共 ${pdf?.numPages||0} 页`}
}
; }