React 组件
inklayer-react 提供 PdfAnnotator 和 PdfViewer 两个顶层组件,基于 React 18+ 构建,使用 Zustand 管理状态,支持 ThemeProvider 和国际化。
安装
npm install inklayer-react
基础导入
import { PdfAnnotator, PdfViewer } from 'inklayer-react'
import 'inklayer-react/style'
// 类型
import type { PdfAnnotatorProps, PdfViewerProps, User, IAnnotationStore } from 'inklayer-react'
PdfAnnotator
完整的 PDF 批注组件,内置工具栏、批注编辑、侧边栏、保存/加载/导出功能。
Props
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
| url | string | URL | — | PDF 文件的 URL 地址。与 data 互斥 |
| data | string | number[] | ArrayBuffer | Uint8Array | Uint16Array | Uint32Array | — | PDF 的二进制数据。与 url 互斥 |
| appearance | 'auto' | 'dark' | 'light' | 'auto' | UI 外观模式。auto 跟随系统主题 |
| theme | 见下方主题色表格 | 'violet' | UI 主题色 |
| title | React.ReactNode | 'PDF ANNOTATOR' | 批注器标题 |
| locale | 'zh-CN' | 'en-US' | 'zh-CN' | 界面语言 |
| initialScale | 'auto' | 'page-actual' | 'page-fit' | 'page-width' | number | 'auto' | 初始缩放比例。'page-actual' 实际尺寸,'page-fit' 适应页宽,'page-width' 页宽模式 |
| layoutStyle | React.CSSProperties | undefined | 容器样式。必须显式传入,否则组件高度为 0。详见下方说明 |
| user | { id: string, name: string } | { id: 'null', name: 'unknown' } | 当前用户信息,用于批注归属 |
| enableNativeAnnotations | boolean | false | 是否启用 PDF.js 原生批注渲染 |
| enableRange | boolean | 'auto' | 'auto' | PDF 范围加载模式。'auto' 自动判断 |
| defaultOptions | DeepPartial<PdfAnnotatorOptions> | — | 默认批注配置选项(颜色、签名、印章) |
| initialAnnotations | Annotation[] | [] | 初始批注数据(用于回显已有批注) |
| defaultShowAnnotationsSidebar | boolean | false | 批注侧边栏默认是否展开 |
| actions | ReactNode | ((props: ActionsProps) => ReactNode) | — | 自定义操作区域,渲染在工具栏右侧。支持函数形式接收操作上下文 |
主题色选项
| 颜色值 | 说明 |
|---|---|
gray | 灰色 |
gold | 金色 |
bronze | 青铜色 |
brown | 棕色 |
yellow | 黄色 |
amber | 琥珀色 |
orange | 橙色 |
tomato | 番茄色 |
red | 红色 |
ruby | 红宝石色 |
crimson | 深红色 |
pink | 粉色 |
plum | 梅子色 |
purple | 紫色 |
violet | 紫罗兰色(默认) |
iris | 鸢尾花色 |
indigo | 靛蓝色 |
blue | 蓝色 |
cyan | 青色 |
teal | 蓝绿色 |
jade | 翡翠色 |
green | 绿色 |
grass | 草绿色 |
lime | 青柠色 |
mint | 薄荷色 |
sky | 天空色 |
事件回调
| 事件 | 签名 | 触发时机 |
|---|---|---|
| onSave | (annotations: Annotation[]) => void | 用户点击保存按钮后触发 |
| onLoad | () => void | PDF 加载完成后触发 |
| onAnnotationAdded | (annotation: Annotation) => void | 新批注创建后触发 |
| onAnnotationDeleted | (id: string) => void | 批注删除后触发 |
| onAnnotationSelected | (annotation: Annotation | null, isClick: boolean) => void | 批注被选中/取消选中时触发 |
| onAnnotationUpdated | (annotation: Annotation) => void | 批注更新(移动/编辑)后触发 |
完整示例:带持久化的批注器
import { PdfAnnotator } from 'inklayer-react'
import 'inklayer-react/style'
import type { Annotation } from 'inklayer-react'
import { useState, useCallback } from 'react'
export default function AnnotatorPage() {
const [annotations, setAnnotations] = useState<Annotation[]>([])
const handleSave = useCallback((annotations: Annotation[]) => {
setAnnotations(annotations)
// 将批注数据发送到后端
fetch('/api/annotations', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(annotations),
})
}, [])
return (
<PdfAnnotator
url="/document.pdf"
locale="zh-CN"
layoutStyle={{ height: '96vh' }}
enableRange="auto"
defaultShowAnnotationsSidebar={true}
user={{ id: 'editor-1', name: '张三' }}
initialAnnotations={annotations}
defaultOptions={{
colors: ['#FF0000', '#00FF00', '#0000FF'],
signature: {
defaultSignature: [],
defaultFont: [{ label: '楷体', value: 'STKaiti', external: false }]
},
stamp: { defaultStamp: [] }
}}
onSave={handleSave}
onLoad={() => console.log('PDF 加载完成')}
onAnnotationAdded={(a) => console.log('新增批注:', a.id)}
onAnnotationDeleted={(id) => console.log('删除批注:', id)}
actions={(props) => (
<>
<button onClick={() => props.save()}>保存</button>
<button onClick={() => console.log(props.getAnnotations())}>获取批注</button>
<button onClick={() => props.exportToExcel('export')}>导出 Excel</button>
<button onClick={() => props.exportToPdf('export')}>导出 PDF</button>
</>
)}
/>
)
}
⚠️ 关于 layoutStyle
PdfAnnotator 和 PdfViewer 内部采用绝对定位布局,组件尺寸完全由 layoutStyle 控制,不会自适应父容器高度。layoutStyle 是标准的 React.CSSProperties 对象,但所有数值必须以字符串形式传入(带 CSS 单位):
// ✅ 正确
<PdfAnnotator layoutStyle={{ width: '100%', height: '600px' }} ... />
<PdfAnnotator layoutStyle={{ width: '100vw', height: '100vh' }} ... />
// ❌ 错误(数值不带单位,组件无法正确计算尺寸)
<PdfAnnotator layoutStyle={{ width: 800, height: 600 }} ... />
常用布局方案:
// 方案 A:全屏占满
<PdfAnnotator layoutStyle={{ width: '100vw', height: '100vh' }} ... />
// 方案 B:嵌入有固定高度的容器
<div style={{ height: '600px' }}>
<PdfAnnotator layoutStyle={{ width: '100%', height: '100%' }} ... />
</div>
// 方案 C:减去顶部导航栏高度
<PdfAnnotator layoutStyle={{ width: '100%', height: 'calc(100vh - 64px)' }} ... />
如果页面空白,第一个排查步骤:打开 React DevTools,检查
PdfAnnotator的 DOM 元素是否有高度。
关于 onSave
onSave 在用户点击工具栏右侧的「保存」按钮时触发。回调收到的 annotations 是完整的 Annotation[] 格式,直接保存此数组即可,无需额外转换:
const handleSave = (annotations: Annotation[]) => {
// annotations 已是完整数据,可直接发送
fetch('/api/annotations', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(annotations),
})
}
后端存储建议:如果需要在后端结构化存储批注数据,由后端自行决定存储格式。前端只需通过
onSave获取完整的Annotation[]并发送即可。
PdfViewer
轻量级 PDF 查看器,不包含批注编辑功能。适合只需浏览 PDF 的场景。
Props(继承 PdfAnnotator 基础属性,新增以下)
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
| showTextLayer | boolean | true | 是否显示文本选区层 |
| showAnnotations | boolean | false | 是否显示已有批注 |
| defaultActiveSidebarKey | string | null | null | 默认展开的侧边栏面板 key |
| toolbar | ReactNode | (ctx) => ReactNode | — | 自定义工具栏(支持 render props 获取上下文) |
| sidebar | SidebarPanel[] | — | 自定义侧边栏面板 |
查看器完整示例
import { PdfViewer } from 'inklayer-react'
import 'inklayer-react/style'
export default function ViewerPage() {
return (
<PdfViewer
url="/document.pdf"
showTextLayer={true}
showAnnotations={true}
layoutStyle={{ height: '96vh' }}
enableRange="auto"
defaultActiveSidebarKey={null}
actions={(context) => (
<>
<button onClick={() => context.print()}>打印</button>
<button onClick={() => context.download('file')}>下载</button>
<button onClick={context.toggleSidebar}>切换侧边栏</button>
</>
)}
toolbar={(context) => (
<div>
<button onClick={() => context.pdfViewer?.scrollPageIntoView({ pageNumber: 1 })}>
跳到第1页
</button>
</div>
)}
sidebar={[{
key: 'custom-sidebar',
title: '自定义面板',
icon: <span>🔧</span>,
render: (context) => (
<div>
<button onClick={context.toggleSidebar}>关闭</button>
</div>
)
}]}
onDocumentLoaded={(pdfViewer) => console.log('PDF 加载完成:', pdfViewer)}
onEventBusReady={(eventBus) => {
eventBus?.on('pagerendered', (e) => console.log('页面渲染:', e.pageNumber))
}}
/>
)
}
SidebarPanel 类型
sidebar prop 接受 SidebarPanel[],定义自定义侧边栏面板:
interface SidebarPanel {
key: string // 面板唯一 key
title: React.ReactNode // 面板标题
icon: React.ReactNode // 面板图标
render: (context: PdfViewerContextValue) => React.ReactNode // 面板内容渲染函数
}
PdfViewerContextValue 类型
actions、toolbar、sidebar 的函数形式接收 PdfViewerContextValue 对象:
interface PdfViewerContextValue {
pdfViewer: PDFViewer | null // PDFViewer 实例
currentPage: number // 当前页码(0-based)
totalPages: number // 总页数
scale: number // 当前缩放比例
print: () => void // 打印
download: (filename?: string) => void // 下载
toggleSidebar: () => void // 切换侧边栏
scrollPageIntoView: (opts: { pageNumber: number }) => void // 滚动到指定页
}