EmbedPDF Vue 版 完整正文文档 全网首发

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 权限解析优先级

  1. overrides 中的显式覆盖 → 最高优先
  2. enforceDocumentPermissions: false → 忽略 PDF 标志,未覆盖的默认允许
  3. 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(默认)、nldefreszh-CNzh-TWjasvpt-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 />useSearchuseSelection 等构建
  • 最小包体积:按需导入插件

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 生成,请遵循相关法律法规及《人工智能生成合成内容标识办法》使用与传播。

相关推荐
OpenTiny社区1 小时前
操作ArkTS页面跳转及路由相关心得
前端·typescript·web·opentiny
xiaohua0708day1 小时前
Lodash库
前端·javascript·vue.js
huakoh1 小时前
Claude Code 从零到上手指南:国产工具链复现80% Agent能力,DeepSeek+LangChain实战
前端
Ankkaya1 小时前
浏览器插件接入 Google 登录
前端
Asmewill1 小时前
DeepAgents学习笔记一(构建深度多智能体)
前端
万物皆对象6661 小时前
切换路由时页面空白问题(vue3)
前端·vue.js·typescript
突然好热1 小时前
TS 调试技巧
前端·javascript·typescript
h64648564h2 小时前
Flutter 国际化(i18n)全指南:一键切换中/英/日多语言
前端·javascript·flutter
令人头秃的代码0_02 小时前
AI时代下,如何做原子代码拆分
前端