EmbedPDF Vue 版 完整正文文档 全网首发
来源:www.embedpdf.com/docs/vue(最后更新 2026-04-15)
一、架构总览
EmbedPDF 提供 两种使用方式:
| Drop-in Viewer | Headless Composables | |
|---|---|---|
| 定位 | 开箱即用的完整 PDF 查看器 | 无 UI 的逻辑 + 渲染原语 |
| 包含 | 工具栏、侧边栏、缩略图 | Composables + 无样式组件 |
| 定制程度 | 主题色、功能开关 | 100% 自定义 |
| 包体积 | 较大(含完整 UI) | 最小(Tree-shakeable) |
| 上手时间 | 分钟级 | 小时级 |
| 适用场景 | 标准文档预览 | 自定义应用、编辑器、设计系统集成 |
选型指南:
| 使用场景 | 推荐 |
|---|---|
| 需要几分钟内上线查看器 | Drop-in Viewer |
| 需要完全匹配应用设计系统 | Headless Composables |
| 简单文档预览 | Drop-in Viewer |
| 构建文档编辑器或注释工具 | Headless Composables |
| 最小包体积 | Headless Composables |
| 不想写任何 UI 代码 | Drop-in Viewer |
技术引擎(两种方式共享):
- PDFium via WebAssembly(Chrome 同款渲染引擎)
- 虚拟化渲染(仅渲染可见页,1000+ 页流畅)
- TypeScript 全类型定义
- Tree-shakeable
二、Drop-in Viewer
2.1 安装与基本使用
bash
# npm
npm install @embedpdf/vue-pdf-viewer
# pnpm
pnpm add @embedpdf/vue-pdf-viewer
# yarn
yarn add @embedpdf/vue-pdf-viewer
# bun
bun add @embedpdf/vue-pdf-viewer
基本使用:
vue
<!-- App.vue -->
<script setup lang="ts">
import { PDFViewer } from '@embedpdf/vue-pdf-viewer';
</script>
<template>
<div style="height: 100vh;">
<PDFViewer
:config="{
src: 'https://snippet.embedpdf.com/ebook.pdf',
theme: { preference: 'light' }
}"
:style="{ width: '100%', height: '100%' }"
/>
</div>
</template>
2.2 Nuxt 3 适配
需要 <ClientOnly> 包裹或动态导入(因为使用了 Canvas/WebAssembly):
vue
<!-- pages/viewer.vue -->
<script setup lang="ts">
import { PDFViewer } from '@embedpdf/vue-pdf-viewer';
</script>
<template>
<div style="height: 100vh;">
<ClientOnly>
<PDFViewer
:config="{
src: 'https://snippet.embedpdf.com/ebook.pdf'
}"
:style="{ width: '100%', height: '100%' }"
/>
</ClientOnly>
</div>
</template>
动态导入方式:
vue
<!-- pages/viewer.vue -->
<script setup lang="ts">
import { defineAsyncComponent } from 'vue';
const PDFViewer = defineAsyncComponent(() =>
import('@embedpdf/vue-pdf-viewer').then((m) => m.PDFViewer)
);
</script>
<template>
<div style="height: 100vh;">
<ClientOnly>
<PDFViewer
:config="{
src: 'https://snippet.embedpdf.com/ebook.pdf'
}"
:style="{ width: '100%', height: '100%' }"
/>
</ClientOnly>
</div>
</template>
2.3 Config 配置项
| 选项 | 类型 | 说明 |
|---|---|---|
src |
string |
PDF 文档 URL 或路径 |
theme |
object |
主题配置 |
tabBar |
`'always' | 'multiple' |
disabledCategories |
string[] |
禁用的功能分类 |
i18n |
object |
国际化配置 |
annotations |
object |
注释默认配置(作者、工具等) |
pan |
object |
手型工具配置 |
zoom |
object |
缩放级别和限制 |
spread |
object |
双页展开布局 |
scroll |
object |
滚动方向和逻辑 |
documentManager |
object |
文档加载选项 |
permissions |
object |
权限与安全配置 |
export |
object |
导出配置 |
2.4 生命周期事件
| 事件 | 说明 |
|---|---|
@init |
容器初始化完成,可注册图标、获取 container |
@ready |
插件注册完成,可获取 PluginRegistry 操作所有插件 |
三、主题系统(Theme)
3.1 模式设置
vue
<script setup lang="ts">
import { PDFViewer } from '@embedpdf/vue-pdf-viewer';
</script>
<template>
<PDFViewer
:config="{
src: 'https://snippet.embedpdf.com/ebook.pdf',
theme: { preference: 'dark' } // 'light' | 'dark' | 'system'
}"
/>
</template>
3.2 颜色覆盖(深合并,只改需要改的)
vue
<!-- 自定义品牌色示例 -->
<script setup lang="ts">
import { PDFViewer } from '@embedpdf/vue-pdf-viewer';
</script>
<template>
<PDFViewer
:config="{
src: '/document.pdf',
theme: {
preference: 'system',
light: {
accent: {
primary: '#9333ea',
primaryHover: '#7e22ce',
primaryActive: '#6b21a8',
primaryLight: '#f3e8ff',
primaryForeground: '#fff'
}
},
dark: {
accent: {
primary: '#a855f7',
primaryHover: '#9333ea',
primaryActive: '#7e22ce',
primaryLight: '#581c87',
primaryForeground: '#fff'
}
}
}
}"
/>
</template>
3.3 主题结构
Accent(品牌色):
ts
accent: {
primary: string; // 主品牌色
primaryHover: string; // 悬停态
primaryActive: string; // 按下态
primaryLight: string; // 淡色(选中背景等)
primaryForeground: string; // 主色上的文字色
}
Background(背景层):
ts
background: {
app: string; // 文档区主背景
surface: string; // 工具栏、侧边栏、面板
surfaceAlt: string; // 次要工具栏
elevated: string; // 下拉菜单、弹窗
overlay: string; // 模态遮罩(通常为半透明 rgba)
input: string; // 输入框、复选框
}
Foreground(文字色):
ts
foreground: {
primary: string; // 标题、正文
secondary: string; // 标签、次要文字
muted: string; // 占位符、弱化文字
disabled: string; // 禁用态元素
onAccent: string; // 品牌色上的文字
}
Interactive(交互态):
ts
interactive: {
hover: string; // 标准按钮悬停背景
active: string; // 点击背景
selected: string; // 选中项背景(如当前工具)
focus: string; // 焦点环颜色
}
Border(边框):
ts
border: {
default: string; // 标准输入框、分割线
subtle: string; // 淡化分割线
strong: string; // 激活输入框、强调
}
Semantic States(语义状态):
ts
state: {
error: string;
errorLight: string;
warning: string;
warningLight: string;
success: string;
successLight: string;
info: string;
infoLight: string;
}
3.4 与应用主题同步
vue
<!-- ThemeSync.vue -->
<script setup lang="ts">
import { ref, watch } from 'vue';
import { PDFViewer, type EmbedPdfContainer } from '@embedpdf/vue-pdf-viewer';
interface Props {
theme: 'light' | 'dark';
}
const props = defineProps<Props>();
const container = ref<EmbedPdfContainer | null>(null);
const handleInit = (c: EmbedPdfContainer) => {
container.value = c;
};
watch(
() => props.theme,
(preference) => {
container.value?.setTheme({ preference });
}
);
</script>
<template>
<PDFViewer
@init="handleInit"
:config="{
src: 'https://snippet.embedpdf.com/ebook.pdf',
theme: { preference: theme }
}"
/>
</template>
四、UI 自定义
4.1 禁用功能(disabledCategories)
层级禁用------禁用父级会连带禁用所有子级:
vue
<script setup lang="ts">
import { PDFViewer } from '@embedpdf/vue-pdf-viewer';
</script>
<template>
<PDFViewer
:config="{
src: 'https://snippet.embedpdf.com/ebook.pdf',
disabledCategories: ['annotation', 'print', 'export']
}"
/>
</template>
可用分类:
| 组 | 可禁用的子分类 |
|---|---|
| Zoom | zoom, zoom-in, zoom-out, zoom-fit-page, zoom-fit-width, zoom-marquee, zoom-level |
| Annotation | annotation, annotation-markup, annotation-highlight, annotation-underline, annotation-strikeout, annotation-squiggly, annotation-ink, annotation-text, annotation-stamp |
| Form | form, form-textfield, form-checkbox, form-radio, form-select, form-listbox, form-fill-mode |
| Shapes | annotation-shape, annotation-rectangle, annotation-circle, annotation-line, annotation-arrow, annotation-polygon, annotation-polyline |
| Redaction | redaction, redaction-area, redaction-text, redaction-apply, redaction-clear |
| Document | document, document-open, document-close, document-print, document-capture, document-export, document-fullscreen, document-protect |
| Page | page, spread, rotate, scroll, navigation |
| Panel | panel, panel-sidebar, panel-search, panel-comment |
| Tools | tools, pan, pointer, capture |
| Selection | selection, selection-copy |
| History | history, history-undo, history-redo |
| Insert | insert, insert-rubber-stamp, insert-signature, insert-image |
4.2 高级自定义(访问 Viewer Registry)
vue
<!-- CustomViewer.vue -->
<script setup lang="ts">
import { ref } from 'vue';
import {
PDFViewer,
type EmbedPdfContainer,
type PluginRegistry,
type CommandsPlugin,
type UIPlugin,
} from '@embedpdf/vue-pdf-viewer';
const container = ref<EmbedPdfContainer | null>(null);
const handleInit = (c: EmbedPdfContainer) => {
container.value = c;
};
const handleReady = (registry: PluginRegistry) => {
const commands = registry.getPlugin<CommandsPlugin>('commands')?.provides();
const ui = registry.getPlugin<UIPlugin>('ui')?.provides();
if (!commands || !ui) return;
// 注册自定义命令
commands.registerCommand({
id: 'custom.hello',
label: 'Say Hello',
icon: 'smiley',
action: () => alert('Hello from my custom button!')
});
// 添加到工具栏(见下文)
};
</script>
<template>
<PDFViewer
@init="handleInit"
@ready="handleReady"
:config="{ src: '/doc.pdf' }"
/>
</template>
4.3 注册自定义图标
vue
<script setup lang="ts">
const handleInit = (c: EmbedPdfContainer) => {
container.value = c;
// 在 init 阶段注册图标
container.value.registerIcons({
smiley: {
viewBox: '0 0 24 24',
paths: [
{ d: 'M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0', stroke: 'currentColor', fill: 'none' },
{ d: 'M9 10l.01 0', stroke: 'currentColor', fill: 'none' },
{ d: 'M15 10l.01 0', stroke: 'currentColor', fill: 'none' },
{ d: 'M9.5 15a3.5 3.5 0 0 0 5 0', stroke: 'currentColor', fill: 'none' }
]
}
});
};
</script>
4.4 修改 Toolbar
ts
const schema = ui.getSchema();
const toolbar = schema.toolbars['main-toolbar'];
// 克隆并修改 items
const items = structuredClone(toolbar.items);
const rightGroup = items.find(item => item.id === 'right-group');
if (rightGroup) {
// 添加自定义按钮
rightGroup.items.push({
type: 'command-button',
id: 'smiley-button',
commandId: 'custom.hello',
variant: 'icon'
});
}
// 应用修改
ui.mergeSchema({
toolbars: { 'main-toolbar': { ...toolbar, items } }
});
五、安全与权限
5.1 核心概念
- 加密(Encryption):AES-256/RC4,是真正的密码学保护。分用户密码(打开文档)和所有者密码(完全访问)。解决"谁能访问"。
- 权限标志(Permission Flags):不是安全机制!只要用户能解密查看内容,就能截图、打印、另存。权限标志只是给合规查看器的"礼貌请求",无法从技术上强制执行。
5.2 配置
vue
<script setup lang="ts">
import { PDFViewer } from '@embedpdf/vue-pdf-viewer';
</script>
<template>
<PDFViewer
:config="{
src: '/document.pdf',
permissions: {
// 忽略 PDF 内部权限标志
enforceDocumentPermissions: false,
// 或覆盖特定标志
overrides: {
print: false, // 禁止打印
copyContents: true // 允许复制(即使 PDF 禁止)
}
}
}"
/>
</template>
5.3 权限解析优先级
overrides中的显式覆盖 → 最高优先enforceDocumentPermissions: false→ 忽略 PDF 标志,未覆盖的默认允许- PDF 内嵌标志 → 默认行为
5.4 支持的权限名称
| 名称 | 说明 |
|---|---|
print |
打印 |
printHighQuality |
高质量打印 |
modifyContents |
修改页面内容 |
copyContents |
选择和复制文本/图片 |
modifyAnnotations |
添加、编辑或删除注释 |
fillForms |
填写交互式表单字段 |
extractForAccessibility |
为屏幕阅读器提取内容 |
assembleDocument |
插入、旋转或删除页面 |
5.5 UI 自动适配
print被拒 → 打印按钮禁用/隐藏copyContents被拒 → 禁用文本选择modifyAnnotations被拒 → 注释工具禁用
六、插件详解
6.1 注释(Annotation)
配置:
vue
<template>
<PDFViewer
:config="{
annotation: {
annotationAuthor: 'John Doe',
}
}"
/>
</template>
设置活动工具:
vue
<script setup lang="ts">
import { ref } from 'vue';
import { PDFViewer, type PluginRegistry, type AnnotationPlugin } from '@embedpdf/vue-pdf-viewer';
const registry = ref<PluginRegistry | null>(null);
const handleReady = (r: PluginRegistry) => {
registry.value = r;
};
const activateHighlighter = () => {
const annotationPlugin = registry.value?.getPlugin<AnnotationPlugin>('annotation')?.provides();
annotationPlugin?.setActiveTool('highlight');
};
const activatePen = () => {
const annotationPlugin = registry.value?.getPlugin<AnnotationPlugin>('annotation')?.provides();
annotationPlugin?.setActiveTool('ink');
};
const deactivateTool = () => {
const annotationPlugin = registry.value?.getPlugin<AnnotationPlugin>('annotation')?.provides();
annotationPlugin?.setActiveTool(null);
};
</script>
<template>
<PDFViewer @ready="handleReady" :config="{ src: '/doc.pdf' }" />
</template>
工具 ID 列表:
| 工具 | ID | 说明 |
|---|---|---|
| 高亮 | 'highlight' |
文本高亮 |
| 下划线 | 'underline' |
文本下划线 |
| 墨迹/画笔 | 'ink' |
自由手绘 |
| 矩形 | 'square' |
方形/矩形 |
| 圆形 | 'circle' |
圆形/椭圆 |
| 自由文本 | 'freeText' |
自由文本框 |
| 便签 | 'text' |
便签注释 |
监听注释事件:
vue
<script setup lang="ts">
import { PDFViewer, type PluginRegistry } from '@embedpdf/vue-pdf-viewer';
const handleReady = (registry: PluginRegistry) => {
const annotationPlugin = registry.getPlugin('annotation')?.provides();
// 监听创建、更新和删除
annotationPlugin?.onAnnotationEvent((event) => {
const { type, annotation, pageIndex } = event;
if (type === 'create') {
console.log('Created annotation:', annotation.id);
} else if (type === 'delete') {
console.log('Deleted annotation:', annotation.id);
}
});
// 监听工具变化
annotationPlugin?.onActiveToolChange(({ tool }) => {
console.log('Active tool:', tool ? tool.name : 'Selection');
});
};
</script>
<template>
<PDFViewer @ready="handleReady" :config="{ src: '/doc.pdf' }" />
</template>
导出注释:
vue
<script setup lang="ts">
import { ref } from 'vue';
import {
PDFViewer,
type PluginRegistry,
type AnnotationPlugin,
type AnnotationTransferItem,
} from '@embedpdf/vue-pdf-viewer';
const exported = ref<AnnotationTransferItem[] | null>(null);
const handleReady = (registry: PluginRegistry) => {
const api = registry.getPlugin<AnnotationPlugin>('annotation')?.provides();
api?.exportAnnotations().wait(
(items) => {
exported.value = items;
console.log(`Exported ${items.length} annotations`);
},
(error) => console.error('Export failed', error),
);
};
</script>
导入注释:
vue
<script setup lang="ts">
api?.importAnnotations(exported.value);
</script>
对于印章(stamp),
ctx.data字段接受包含 PNG、JPEG 或 PDF 数据的原始ArrayBuffer,引擎通过 magic bytes 自动检测格式。
添加自定义印章工具:
vue
<script setup lang="ts">
import {
PDFViewer,
type PluginRegistry,
type AnnotationPlugin,
type AnnotationTool,
PdfAnnotationSubtype,
type PdfStampAnnoObject,
} from '@embedpdf/vue-pdf-viewer';
const handleReady = (registry: PluginRegistry) => {
const api = registry.getPlugin<AnnotationPlugin>('annotation')?.provides();
api?.addTool<AnnotationTool<PdfStampAnnoObject>>({
id: 'stampCheckmark',
name: 'Checkmark',
interaction: { exclusive: true, cursor: 'crosshair' },
matchScore: () => 0,
defaults: {
type: PdfAnnotationSubtype.STAMP,
imageSrc: '/circle-checkmark.png',
imageSize: { width: 30, height: 30 },
},
behavior: {
showGhost: true,
deactivateToolAfterCreate: true,
selectAfterCreate: true,
},
});
};
</script>
<template>
<PDFViewer @ready="handleReady" :config="{ src: '/doc.pdf' }" />
</template>
6.2 表单(Form)
加载含 AcroForm 字段的 PDF 自动可交互。默认锁定 form 分类,即填即用;切换到表单编辑模式可编辑控件。
配置:
vue
<template>
<PDFViewer
:config="{
src: '/form.pdf',
export: {
defaultFileName: 'filled-form.pdf'
}
}"
/>
</template>
访问表单插件:
vue
<script setup lang="ts">
import { ref } from 'vue';
import { PDFViewer, type PluginRegistry, type FormPlugin } from '@embedpdf/vue-pdf-viewer';
const registry = ref<PluginRegistry | null>(null);
const handleReady = (r: PluginRegistry) => {
registry.value = r;
};
const formScope = () => {
const formPlugin = registry.value?.getPlugin<FormPlugin>('form')?.provides();
return formPlugin?.forDocument('form-doc');
};
</script>
<template>
<PDFViewer @ready="handleReady" :config="{ src: '/form.pdf' }" />
</template>
监听字段变化:
vue
<script setup lang="ts">
const scope = formScope();
scope?.onFormReady((fields) => {
console.log('Fields discovered:', fields.length);
console.log('Initial values:', scope.getFormValues());
});
scope?.onFieldValueChange((event) => {
console.log('Changed widget:', event.annotationId);
console.log('Updated values:', scope.getFormValues());
});
</script>
程序化设置值:
vue
<script setup lang="ts">
const fillForm = async () => {
const scope = formScope();
await scope?.setFormValues({
First_Name: 'Jane',
Last_Name: 'Doe',
Email_Address: 'jane.doe@example.com',
}).toPromise();
};
</script>
Radio 按钮需要 PDF 中定义的精确导出值。Checkbox 传
"Off"清除,其他任意字符串会归一化为控件的选中导出值。
保存填写后的 PDF:
vue
<script setup lang="ts">
const saveFilledPdf = async () => {
const exportPlugin = registry.value?.getPlugin('export')?.provides();
const pdfBytes = await exportPlugin
?.forDocument('form-doc')
.saveAsCopy()
.toPromise();
};
</script>
6.3 文档管理(Document Manager)
配置:
vue
<template>
<PDFViewer
:config="{
documentManager: {
initialDocuments: [
{
url: 'https://snippet.embedpdf.com/ebook.pdf',
autoActivate: true,
documentId: 'ebook-embedpdf',
},
{
url: 'https://snippet.embedpdf.com/ebook.pdf',
autoActivate: false
}
],
maxDocuments: 10
},
tabBar: 'multiple'
}"
/>
</template>
从 URL 加载:
vue
<script setup lang="ts">
import { ref } from 'vue';
import { PDFViewer, type PluginRegistry } from '@embedpdf/vue-pdf-viewer';
const registry = ref<PluginRegistry | null>(null);
const handleReady = (r: PluginRegistry) => {
registry.value = r;
};
const openRemotePdf = () => {
const docManager = registry.value?.getPlugin('document-manager')?.provides();
docManager?.openDocumentUrl({
url: 'https://snippet.embedpdf.com/ebook.pdf',
documentId: 'invoice-123',
autoActivate: true
});
};
</script>
<template>
<PDFViewer @ready="handleReady" :config="{ src: '/doc.pdf' }" />
</template>
从 Buffer 加载(本地文件):
vue
<script setup lang="ts">
import { ref } from 'vue';
import { PDFViewer, type PluginRegistry } from '@embedpdf/vue-pdf-viewer';
const registry = ref<PluginRegistry | null>(null);
const handleReady = (r: PluginRegistry) => {
registry.value = r;
};
const handleFileSelect = async (event: Event) => {
const target = event.target as HTMLInputElement;
const file = target.files?.[0];
if (!file) return;
const buffer = await file.arrayBuffer();
const docManager = registry.value?.getPlugin('document-manager')?.provides();
docManager?.openDocumentBuffer({
buffer: buffer,
name: file.name,
autoActivate: true
});
};
</script>
<template>
<input type="file" accept=".pdf" @change="handleFileSelect" />
<PDFViewer @ready="handleReady" :config="{ src: '/doc.pdf' }" />
</template>
原生文件选择器:
vue
<script setup lang="ts">
const openFilePicker = () => {
const docManager = registry.value?.getPlugin('document-manager')?.provides();
docManager?.openFileDialog();
};
</script>
管理活动文档:
vue
<script setup lang="ts">
const switchDocument = () => {
const docManager = registry.value?.getPlugin('document-manager')?.provides();
docManager?.setActiveDocument('invoice-123');
};
const closeDocument = () => {
const docManager = registry.value?.getPlugin('document-manager')?.provides();
docManager?.closeDocument('invoice-123');
};
const closeAll = () => {
const docManager = registry.value?.getPlugin('document-manager')?.provides();
docManager?.closeAllDocuments();
};
</script>
事件:
| 事件 | Payload | 说明 |
|---|---|---|
onDocumentOpened |
DocumentState |
文档加载成功 |
onDocumentClosed |
documentId |
文档关闭 |
onActiveDocumentChanged |
{ previous, current } |
切换标签页 |
onDocumentError |
{ documentId, error } |
加载失败 |
vue
<script setup lang="ts">
import { PDFViewer, type PluginRegistry } from '@embedpdf/vue-pdf-viewer';
const handleReady = (registry: PluginRegistry) => {
const docManager = registry.getPlugin('document-manager')?.provides();
docManager?.onDocumentOpened((doc) => {
console.log(`Opened: ${doc.name} (${doc.id})`);
});
};
</script>
<template>
<PDFViewer @ready="handleReady" :config="{ src: '/doc.pdf' }" />
</template>
6.4 缩放(Zoom)
配置:
vue
<script setup lang="ts">
import { PDFViewer, ZoomMode } from '@embedpdf/vue-pdf-viewer';
</script>
<template>
<PDFViewer
:config="{
zoom: {
defaultZoomLevel: ZoomMode.FitPage,
minZoom: 0.5,
maxZoom: 3.0
}
}"
/>
</template>
缩放模式:
| 模式 | 说明 |
|---|---|
ZoomMode.Automatic |
自动寻找最佳适配 |
ZoomMode.FitPage |
整页适配视口 |
ZoomMode.FitWidth |
页面宽度适配视口宽度 |
编程控制:
vue
<script setup lang="ts">
import { ref } from 'vue';
import { PDFViewer, ZoomMode, type PluginRegistry, type ZoomPlugin } from '@embedpdf/vue-pdf-viewer';
const registry = ref<PluginRegistry | null>(null);
const handleReady = (r: PluginRegistry) => {
registry.value = r;
};
const zoomIn = () => {
const zoomPlugin = registry.value?.getPlugin<ZoomPlugin>('zoom')?.provides();
const docZoom = zoomPlugin?.forDocument('my-document-id');
docZoom?.zoomIn();
};
const zoomOut = () => {
const zoomPlugin = registry.value?.getPlugin<ZoomPlugin>('zoom')?.provides();
const docZoom = zoomPlugin?.forDocument('my-document-id');
docZoom?.zoomOut();
};
const fitWidth = () => {
const zoomPlugin = registry.value?.getPlugin<ZoomPlugin>('zoom')?.provides();
const docZoom = zoomPlugin?.forDocument('my-document-id');
docZoom?.requestZoom(ZoomMode.FitWidth);
};
</script>
<template>
<PDFViewer @ready="handleReady" :config="{ src: '/doc.pdf' }" />
</template>
监听缩放变化:
vue
<script setup lang="ts">
import { PDFViewer, type PluginRegistry } from '@embedpdf/vue-pdf-viewer';
const handleReady = (registry: PluginRegistry) => {
const zoomPlugin = registry.getPlugin('zoom')?.provides();
const docZoom = zoomPlugin?.forDocument('my-document-id');
docZoom?.onStateChange((state) => {
console.log('Current Zoom:', state.currentZoomLevel);
});
};
</script>
<template>
<PDFViewer @ready="handleReady" :config="{ src: '/doc.pdf' }" />
</template>
6.5 滚动与导航(Scroll)
配置:
vue
<script setup lang="ts">
import { PDFViewer, ScrollStrategy } from '@embedpdf/vue-pdf-viewer';
</script>
<template>
<PDFViewer
:config="{
documentManager: {
initialDocuments: [{
url: 'https://snippet.embedpdf.com/ebook.pdf',
documentId: 'my-doc'
}]
},
scroll: {
defaultStrategy: ScrollStrategy.Vertical,
defaultPageGap: 20
}
}"
/>
</template>
编程导航:
vue
<script setup lang="ts">
import { ref } from 'vue';
import { PDFViewer, type PluginRegistry } from '@embedpdf/vue-pdf-viewer';
const registry = ref<PluginRegistry | null>(null);
const handleReady = (r: PluginRegistry) => {
registry.value = r;
};
const nextPage = () => {
const scroll = registry.value?.getPlugin('scroll')?.provides();
scroll?.scrollToNextPage();
};
const goToPage = (pageNumber: number) => {
const scroll = registry.value?.getPlugin('scroll')?.provides();
scroll?.scrollToPage({ pageNumber });
};
</script>
<template>
<PDFViewer @ready="handleReady" :config="{ src: '/doc.pdf' }" />
</template>
指定文档导航:
vue
<script setup lang="ts">
const goToPage10 = () => {
const scroll = registry.value?.getPlugin('scroll')?.provides();
const docScroll = scroll?.forDocument('my-doc');
docScroll?.scrollToPage({ pageNumber: 10 });
};
</script>
加载后跳页:
vue
<script setup lang="ts">
import { PDFViewer, type PluginRegistry } from '@embedpdf/vue-pdf-viewer';
const handleReady = (registry: PluginRegistry) => {
const scroll = registry.getPlugin('scroll')?.provides();
scroll?.onLayoutReady((event) => {
if (event.documentId === 'my-doc' && event.isInitial) {
scroll.forDocument('my-doc').scrollToPage({
pageNumber: 3,
behavior: 'instant' // 初始加载瞬间跳转
});
}
});
};
</script>
<template>
<PDFViewer @ready="handleReady" :config="{ src: '/doc.pdf' }" />
</template>
监听页面变化:
vue
<script setup lang="ts">
import { PDFViewer, type PluginRegistry } from '@embedpdf/vue-pdf-viewer';
const handleReady = (registry: PluginRegistry) => {
const scroll = registry.getPlugin('scroll')?.provides();
scroll?.onPageChange((event) => {
console.log(`Doc: ${event.documentId}`);
console.log(`Current Page: ${event.pageNumber}`);
console.log(`Total Pages: ${event.totalPages}`);
});
};
</script>
<template>
<PDFViewer @ready="handleReady" :config="{ src: '/doc.pdf' }" />
</template>
6.6 手型工具(Pan)
配置:
vue
<template>
<PDFViewer
:config="{
pan: {
defaultMode: 'mobile' // 'mobile' | 'always' | 'never'
}
}"
/>
</template>
模式说明:
| 模式 | 说明 |
|---|---|
'mobile' |
默认。仅触屏设备默认启用手型工具,桌面端为文本选择模式 |
'always' |
始终默认手型工具 |
'never' |
始终为文本选择或其他模式 |
编程控制:
vue
<script setup lang="ts">
import { ref } from 'vue';
import { PDFViewer, type PluginRegistry, type PanPlugin } from '@embedpdf/vue-pdf-viewer';
const registry = ref<PluginRegistry | null>(null);
const handleReady = (r: PluginRegistry) => {
registry.value = r;
};
const togglePan = () => {
const panPlugin = registry.value?.getPlugin<PanPlugin>('pan')?.provides();
const docPan = panPlugin?.forDocument('my-doc-id');
docPan?.togglePan();
};
const enablePan = () => {
const panPlugin = registry.value?.getPlugin<PanPlugin>('pan')?.provides();
const docPan = panPlugin?.forDocument('my-doc-id');
docPan?.enablePan();
};
const disablePan = () => {
const panPlugin = registry.value?.getPlugin<PanPlugin>('pan')?.provides();
const docPan = panPlugin?.forDocument('my-doc-id');
docPan?.disablePan();
};
</script>
<template>
<PDFViewer @ready="handleReady" :config="{ src: '/doc.pdf' }" />
</template>
检查状态:
vue
<script setup lang="ts">
const isPanning = () => {
const panPlugin = registry.value?.getPlugin('pan')?.provides();
const docPan = panPlugin?.forDocument('my-doc-id');
return docPan?.isPanMode();
};
</script>
监听变化:
vue
<script setup lang="ts">
import { ref } from 'vue';
import { PDFViewer, type PluginRegistry } from '@embedpdf/vue-pdf-viewer';
const toolState = ref<'hand' | 'cursor'>('cursor');
const handleReady = (registry: PluginRegistry) => {
const panPlugin = registry.getPlugin('pan')?.provides();
// 订阅全局变化
panPlugin?.onPanModeChange(({ documentId, isPanMode }) => {
console.log(`Document ${documentId} pan mode is now: ${isPanMode}`);
});
// 或订阅特定文档
const docPan = panPlugin?.forDocument('my-doc');
docPan?.onPanModeChange((isPanMode) => {
toolState.value = isPanMode ? 'hand' : 'cursor';
});
};
</script>
<template>
<PDFViewer @ready="handleReady" :config="{ src: '/doc.pdf' }" />
</template>
6.7 导出与保存(Export)
导出的是包含所有修改(注释、涂黑、表单数据)的新 PDF。
配置:
vue
<template>
<PDFViewer
:config="{
export: {
defaultFileName: 'exported-document.pdf'
}
}"
/>
</template>
触发下载:
vue
<script setup lang="ts">
import { ref } from 'vue';
import { PDFViewer, type PluginRegistry } from '@embedpdf/vue-pdf-viewer';
const registry = ref<PluginRegistry | null>(null);
const handleReady = (r: PluginRegistry) => {
registry.value = r;
};
const triggerDownload = () => {
const exportPlugin = registry.value?.getPlugin('export')?.provides();
// 下载活动文档
exportPlugin?.download();
// 下载指定文档
// exportPlugin?.forDocument(documentId).download();
};
</script>
<template>
<PDFViewer @ready="handleReady" :config="{ src: '/doc.pdf' }" />
</template>
保存到服务器:
vue
<script setup lang="ts">
import { ref } from 'vue';
import { PDFViewer, type PluginRegistry } from '@embedpdf/vue-pdf-viewer';
const registry = ref<PluginRegistry | null>(null);
const handleReady = (r: PluginRegistry) => {
registry.value = r;
};
const handleSave = async () => {
const exportPlugin = registry.value?.getPlugin('export')?.provides();
// 1. 获取 PDF 数据为 ArrayBuffer
const arrayBuffer = await exportPlugin?.saveAsCopy().toPromise();
if (!arrayBuffer) return;
// 2. 转换为 Blob/File
const blob = new Blob([arrayBuffer], { type: 'application/pdf' });
const file = new File([blob], 'updated-doc.pdf');
// 3. 上传到服务器
const formData = new FormData();
formData.append('file', file);
formData.append('id', '12345');
await fetch('/api/documents/save', {
method: 'POST',
body: formData
});
console.log('Document saved successfully!');
};
</script>
<template>
<PDFViewer @ready="handleReady" :config="{ src: '/doc.pdf' }" />
</template>
6.8 国际化(i18n)
内置语言: en(默认)、nl、de、fr、es、zh-CN、zh-TW、ja、sv、pt-BR
配置:
vue
<template>
<PDFViewer
:config="{
i18n: {
defaultLocale: 'de',
fallbackLocale: 'en'
}
}"
/>
</template>
运行时切换语言:
vue
<script setup lang="ts">
import { ref } from 'vue';
import { PDFViewer, type PluginRegistry } from '@embedpdf/vue-pdf-viewer';
const registry = ref<PluginRegistry | null>(null);
const handleReady = (r: PluginRegistry) => {
registry.value = r;
};
const changeLanguage = (locale: string) => {
const i18n = registry.value?.getPlugin('i18n')?.provides();
i18n?.setLocale(locale);
};
</script>
<template>
<button @click="changeLanguage('de')">German</button>
<button @click="changeLanguage('fr')">French</button>
<button @click="changeLanguage('en')">English</button>
<PDFViewer @ready="handleReady" :config="{ src: '/doc.pdf' }" />
</template>
监听语言变化:
vue
<script setup lang="ts">
import { PDFViewer, type PluginRegistry } from '@embedpdf/vue-pdf-viewer';
const handleReady = (registry: PluginRegistry) => {
const i18n = registry.getPlugin('i18n')?.provides();
i18n?.onLocaleChange(({ previousLocale, currentLocale }) => {
console.log(`Language changed from ${previousLocale} to ${currentLocale}`);
});
};
</script>
<template>
<PDFViewer @ready="handleReady" :config="{ src: '/doc.pdf' }" />
</template>
自定义翻译:
vue
<script setup lang="ts">
import { PDFViewer } from '@embedpdf/vue-pdf-viewer';
const customSpanish = {
code: 'es',
name: 'Español',
translations: {
zoom: {
in: 'Acercar',
out: 'Alejar',
},
document: {
open: 'Abrir Documento',
}
}
};
</script>
<template>
<PDFViewer
:config="{
i18n: {
defaultLocale: 'es',
locales: [customSpanish]
}
}"
/>
</template>
七、Headless Composables
7.1 核心理念
- 你控制 UI:只提供逻辑和渲染原语,零样式
- Composable 设计 :通过组合
<RenderLayer />、<Scroller />、useSearch、useSelection等构建 - 最小包体积:按需导入插件
7.2 安装
bash
# npm
npm install @embedpdf/core @embedpdf/engines @embedpdf/plugin-document-manager @embedpdf/plugin-viewport @embedpdf/plugin-scroll @embedpdf/plugin-render
# pnpm
pnpm add @embedpdf/core @embedpdf/engines @embedpdf/plugin-document-manager @embedpdf/plugin-viewport @embedpdf/plugin-scroll @embedpdf/plugin-render
# yarn
yarn add @embedpdf/core @embedpdf/engines @embedpdf/plugin-document-manager @embedpdf/plugin-viewport @embedpdf/plugin-scroll @embedpdf/plugin-render
# bun
bun add @embedpdf/core @embedpdf/engines @embedpdf/plugin-document-manager @embedpdf/plugin-viewport @embedpdf/plugin-scroll @embedpdf/plugin-render
7.3 最小示例
vue
<!-- PDFViewer.vue -->
<script setup lang="ts">
import { usePdfiumEngine } from '@embedpdf/engines/vue';
import { EmbedPDF } from '@embedpdf/core/vue';
import { createPluginRegistration } from '@embedpdf/core';
// 导入必要的插件
import { ViewportPluginPackage, Viewport } from '@embedpdf/plugin-viewport/vue';
import { ScrollPluginPackage, Scroller } from '@embedpdf/plugin-scroll/vue';
import {
DocumentContent,
DocumentManagerPluginPackage,
} from '@embedpdf/plugin-document-manager/vue';
import { RenderLayer, RenderPluginPackage } from '@embedpdf/plugin-render/vue';
// 1. 使用 Vue composable 初始化引擎
const { engine, isLoading } = usePdfiumEngine();
// 2. 注册所需的插件
const plugins = [
createPluginRegistration(DocumentManagerPluginPackage, {
initialDocuments: [{ url: 'https://snippet.embedpdf.com/ebook.pdf' }],
}),
createPluginRegistration(ViewportPluginPackage),
createPluginRegistration(ScrollPluginPackage),
createPluginRegistration(RenderPluginPackage),
];
</script>
<template>
<div v-if="isLoading || !engine" class="loading-pane">
Loading PDF Engine...
</div>
<!-- 3. 用 <EmbedPDF> provider 包裹你的 UI -->
<div v-else style="height: 500px">
<EmbedPDF :engine="engine" :plugins="plugins" v-slot="{ activeDocumentId }">
<DocumentContent
v-if="activeDocumentId"
:document-id="activeDocumentId"
v-slot="{ isLoaded }"
>
<Viewport
v-if="isLoaded"
:document-id="activeDocumentId"
style="background-color: #f1f3f5"
>
<Scroller :document-id="activeDocumentId">
<template #default="{ page }">
<div :style="{ width: page.width + 'px', height: page.height + 'px' }">
<!-- RenderLayer 负责绘制页面 -->
<RenderLayer
:document-id="activeDocumentId"
:page-index="page.pageIndex"
/>
</div>
</template>
</Scroller>
</Viewport>
</DocumentContent>
</EmbedPDF>
</div>
</template>
<style scoped>
.loading-pane {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
</style>
在主应用中使用:
vue
<!-- App.vue -->
<script setup lang="ts">
import PDFViewer from './PDFViewer.vue';
</script>
<template>
<PDFViewer />
</template>
7.4 下一步
了解更多插件功能,参考 Understanding Plugins。
八、完整 API 参考
PDFViewerConfig.permissions
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enforceDocumentPermissions |
boolean |
true |
为 false 时忽略 PDF 内部权限标志 |
overrides |
object |
undefined |
权限名称到布尔值的映射(true = 允许,false = 拒绝) |
PDFViewerConfig 全量
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
src |
string |
--- | PDF URL 或路径 |
theme |
object |
--- | 主题配置(见主题系统) |
tabBar |
`'always' | 'multiple' | 'never'` |
disabledCategories |
string[] |
[] |
禁用功能分类 |
i18n |
object |
--- | 国际化配置 |
annotations |
object |
--- | 注释配置 |
pan |
object |
--- | 手型工具配置 |
zoom |
object |
--- | 缩放配置 |
spread |
object |
--- | 双页布局配置 |
scroll |
object |
--- | 滚动配置 |
documentManager |
object |
--- | 文档管理配置 |
permissions |
object |
--- | 权限配置 |
export |
object |
--- | 导出配置 |
本内容由 Coze AI 生成,请遵循相关法律法规及《人工智能生成合成内容标识办法》使用与传播。