增加pdf预览组件

master
LiuJiaNan 2025-10-22 17:54:38 +08:00
parent d7ccfda275
commit e566e8132b
5 changed files with 198 additions and 1 deletions

18
components/Pdf/index.d.ts vendored Normal file
View File

@ -0,0 +1,18 @@
import type { CSSProperties, FC } from "react";
export interface PdfProps {
/** pdf 文件地址 */
file: string;
/** 是否显示弹窗 */
visible?: boolean;
/** 关闭弹窗的方法 */
onCancel?: () => void;
/** 是否使用内联模式true为不使用弹窗默认为false */
inline?: boolean;
/** 内联模式下的样式 */
style?: CSSProperties;
}
declare const Pdf: FC<PdfProps>;
export default Pdf;

84
components/Pdf/index.js Normal file
View File

@ -0,0 +1,84 @@
import { Button, message, Modal, Spin } from "antd";
import { useState } from "react";
import { Document, Page, pdfjs } from "react-pdf";
import { getFileUrl } from "../../utils/index";
function Pdf(props) {
const {
visible = false,
onCancel,
file,
inline = false,
style = {},
} = props;
const fileUrl = getFileUrl();
const [numPages, setNumPages] = useState(0);
const [pdfWidth, setPdfWidth] = useState(600);
const [loading, setLoading] = useState(true);
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
"pdfjs-dist/build/pdf.worker.min.mjs",
import.meta.url,
).toString();
const onDocumentLoadSuccess = ({ numPages }) => {
setNumPages(numPages);
setLoading(false);
};
const onDocumentLoadError = () => {
setLoading(false);
message.error("加载 PDF 文件失败");
if (onCancel)
onCancel();
};
const onPageLoadSuccess = ({ width }) => {
setPdfWidth(width);
};
// 内联模式的PDF内容
const renderPdfContent = () => (
<>
{loading && (
<div style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "80vh" }}>
<Spin size="large" />
</div>
)}
<div style={{ height: "88vh", overflowY: "auto", padding: "24px", ...style }}>
<Document
file={fileUrl + file}
onLoadSuccess={onDocumentLoadSuccess}
onLoadError={onDocumentLoadError}
>
{
Array.from({ length: numPages }).map((el, index) => (
<Page key={`page_${index + 1}`} pageNumber={index + 1} onLoadSuccess={onPageLoadSuccess} />
))
}
</Document>
</div>
</>
);
// 如果是内联模式直接返回PDF内容
if (inline) {
return renderPdfContent();
}
// 默认弹窗模式
return (
<Modal
open={visible}
width={pdfWidth + 100}
title="PDF预览"
onCancel={onCancel}
footer={<Button onClick={onCancel}>关闭</Button>}
>
{renderPdfContent()}
</Modal>
);
}
export default Pdf;

18
components/PreviewPdf/index.d.ts vendored Normal file
View File

@ -0,0 +1,18 @@
import type { FC } from "react";
export interface PreviewPdfProps {
/** 文件列表,和 name、url 冲突 */
files?: { [p: string]: any }[];
/** 文件名字段名,传入 files 时会优先查找是否存在 name、fileName */
nameKey?: string;
/** 文件路径字段名,传入 files 时会优先查找是否存在 filePath */
urlKey?: string;
/** 单个文件名,和 files 冲突 */
name?: string;
/** 单个文件路径,和 files 冲突 */
url?: string;
}
declare const PreviewPdf: FC<PreviewPdfProps>;
export default PreviewPdf;

View File

@ -0,0 +1,76 @@
import { Button, Space } from "antd";
import { useState } from "react";
import Pdf from "../Pdf";
const PreviewPdf = (props) => {
const {
files = [],
nameKey = "",
urlKey = "",
name = "",
url = "",
} = props;
const [visible, setVisible] = useState(false);
const [src, setSrc] = useState("");
const previewPdf = (src) => {
setVisible(true);
setSrc(src);
};
const onCancel = () => {
setVisible(false);
setSrc("");
};
// 单个文件预览模式
if (files.length === 0 && name && url) {
return (
<>
<Space>
<span>{name}</span>
<Button type="primary" size="small" onClick={() => previewPdf(url)}>
预览
</Button>
</Space>
<Pdf
visible={visible}
file={src}
onCancel={onCancel}
/>
</>
);
}
// 多文件预览模式
if (files.length > 0 && !name && !url) {
return (
<>
{files.map(item => (
<div key={item.filePath || item[urlKey]} style={{ marginTop: 5 }}>
<Space>
<span>{item.name || item.fileName || item[nameKey]}</span>
<Button
type="primary"
size="small"
onClick={() => previewPdf(item.filePath || item[urlKey])}
>
预览
</Button>
</Space>
</div>
))}
<Pdf
visible={visible}
file={src}
onCancel={onCancel}
/>
</>
);
}
return null;
};
export default PreviewPdf;

View File

@ -28,6 +28,7 @@
"antd": "^5.27.6",
"dayjs": "^1.11.18",
"lodash-es": "^4.17.21",
"react": "^18.3.1"
"react": "^18.3.1",
"react-pdf": "^10.2.0"
}
}