npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@embedpdf-editor/vue3-chapter-viewer

v1.2.20

Published

Vue 3 版章节 PDF 阅读器。它把多份章节 PDF 组织成一本连续滚动的书,并内置划线/高亮、附注、段落书签、章节目录和标注导入导出能力。

Readme

@embedpdf-editor/vue3-chapter-viewer

Vue 3 版章节 PDF 阅读器。它把多份章节 PDF 组织成一本连续滚动的书,并内置划线/高亮、附注、段落书签、章节目录和标注导入导出能力。

底层使用 @embedpdf/core 插件体系和 PDFium 引擎;Vue 3 包只负责提供 SFC 组件、composition hooks 和类型导出。

安装

pnpm add @embedpdf-editor/vue3-chapter-viewer

Vite

包内导出的是一段 Vite 配置,不是 Vite plugin。它用于补齐 scheduler alias 和 PDFium 引擎的预构建配置;@embedpdf/engines 已经是本包依赖,业务项目不需要单独安装。

// vite.config.ts
import { defineConfig, mergeConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { chapterViewerViteResolve } from '@embedpdf-editor/vue3-chapter-viewer/vite';

export default mergeConfig(
  defineConfig({
    plugins: [vue()],
  }),
  chapterViewerViteResolve(),
);

如果不用 mergeConfig,需要手动合并 resolve.aliasoptimizeDeps.include,不要直接覆盖现有配置。

快速开始

推荐只传 optionseditorOptionsfeatures 仍兼容,但已作为低层/旧写法保留。

<script setup lang="ts">
import {
  ChapterPdfViewer,
  usePdfiumEngine,
  type ChapterViewerOptions,
} from '@embedpdf-editor/vue3-chapter-viewer';

const { engine, isLoading, error } = usePdfiumEngine();

const options: ChapterViewerOptions = {
  manifest: {
    chapters: [
      {
        chapterId: 'ch-1',
        title: '第一章',
        globalPageRange: [1, 12],
        localPageRange: [0, 11],
        source: { url: '/pdfs/chapter-1.pdf' },
      },
    ],
  },
  notes: {
    loadNotes: async () => [],
    onCreateNote: async () => ({ noteId: crypto.randomUUID() }),
    onUpdateNote: async () => {},
    // 两阶段删除:业务先确认外部数据,返回 true 才让插件擦 PDF 笔记
    onRequestDeleteNote: async () => true,
  },
  bookmarks: {
    load: async () => [],
    persist: async () => {},
    onRequestRemove: async () => true,
  },
  features: {
    markup: true,
    bookmarks: true,
    notes: true,
    selectionToolbar: true,
    zoom: { pageWidth: 720 },
  },
};
</script>

<template>
  <div class="reader">
    <div v-if="error">PDF engine failed: {{ error.message }}</div>
    <div v-else-if="isLoading || !engine">Loading PDF engine...</div>
    <ChapterPdfViewer
      v-else
      :engine="engine"
      :options="options"
      class-name="reader-fill"
      viewport-class-name="reader-viewport"
    />
  </div>
</template>

<style scoped>
.reader {
  height: 100vh;
  min-height: 0;
}

.reader-fill {
  height: 100%;
  min-height: 0;
}
</style>

配置总览

| 层级 | 说明 | | --- | --- | | ChapterViewerOptions | ChapterPdfVieweroptionsmanifestnotesbookmarksfeatures 等 | | ChapterViewerConfig | options.features:划线样式、图标、缩放、选区工具栏 | | ChapterPdfViewer props | class-namebuild-selection-menu@extra-selection-action 等 |

省略 features 时默认全部开启。字段说明与 React 包 README 一致,下文为 Vue 3 用法示例。


ChapterViewerOptions

| 字段 | 必填 | 说明 | | --- | --- | --- | | manifest | 是 | ChapterManifest可响应式更新,见 ChapterPdfViewer 一节) | | notes | 是 | NoteCallbacks(须 onCreateNoteonRequestCreateNote) | | bookmarks | 是 | ParagraphBookmarkCallbacks(删除须 onRequestRemove 返回 true) | | chapterPdfLoader | 否 | 全局 IChapterPdfLoader | | overlapStrategy | 否 | 默认 { kind: 'first-wins' } | | features | 否 | 见 features 配置 |

Manifest

type ChapterManifest = {
  chapters: Array<{
    chapterId: string;
    title: string;
    globalPageRange: [number, number];
    localPageRange: [number, number];
    source?:
      | { url: string }
      | { buffer: ArrayBuffer | Uint8Array | DataView }
      | {
          load: () => Promise<
            { url: string } | { buffer: ArrayBuffer | Uint8Array | DataView }
          >;
        }
      | { urls: string[]; segmentPageThreshold: number }; // legacy
    segmentPageThreshold?: number;
    encrypted?: boolean;
    ownedGlobalPages?: number[];
  }>;
  totalGlobalPages?: number;
};

globalPageRangelocalPageRange 都是闭区间,且页数必须一致。chapterId 在整本书内唯一;单 URL 章时 documentId === chapterId,分段章内部为 chapterId#s0#s1…(业务存储仍只用 chapterId)。

| 字段 | 说明 | | --- | --- | | chapterId / title | 唯一 ID 与标题 | | globalPageRange / localPageRange | 全局与 PDF 内闭区间,页数须一致 | | source | url | buffer | load(),见下文 | | encrypted | 可选标记;密码流见 加密 PDF | | ownedGlobalPages | overlapStrategy: explicit 时使用 |

notes / bookmarks 回调

NoteCallbacks loadNotesonCreateNote | onRequestCreateNote(二选一)、onRequestEditNoteonUpdateNoteonRequestDeleteNotePromise<boolean>,true 才真删)、onDeleteSuccess。创建时直接给 record(含 positionselectedText,可 JSON 直存);complete({ noteId, content?, nodeId? })。编辑/删除回调为 { record, nodeId? }

ParagraphBookmarkCallbacks loadonAdded{ bookmark, record, position })、persistonRequestRemove{ record, nodeId? },返回 true 才删除)、onRemoveSuccess

nodeId:业务库主键,不参与渲染。创建时可写入 record.nodeId 或回调返回值,删除回调会带上:

  • 笔记complete({ noteId, nodeId }) 或事先写 record.nodeIdonCreateNote 也可返回 { noteId, nodeId? }
  • 划线/高亮onCreated 内写 record.nodeId = …return { nodeId }(支持 async)
  • 书签onAdded 内写 record.nodeId = …return { nodeId }(支持 async)
  • 导入时写在 NoteAnchor / ParagraphBookmark / markup[].nodeId 上;仍可用 setMarkupAnnotationNodeId 手动登记

features.markup 单条事件:

  • onCreated:划词后 { kind, record, position, selectedText, ... }
  • onRequestDelete / onDeleted{ record, nodeId? }返回 true 才真删

record 里的 markup 现在额外带 localPageIndexglobalPageIndexglobalPageNumber,便于业务直接落库和外部定位。外部滚动可直接用:scrollToMarkupRecord(viewerRef.value, markupRecord)scrollToNoteRecord(viewerRef.value, noteRecord)scrollToBookmarkRecord(viewerRef.value, bookmarkRecord)

章节级全量持久化用 subscribeChapterMarkupChanges(registry, listener)(每次变更后推该章 markup 快照),与上面单条事件互补。

事件全集:11-events-callbacks-and-component-api.md

嵌套目录树与重叠页

侧栏目录支持任意层级ChapterTreePanel + ChapterTreeNode.children)。引擎滚动用的 manifest.chapters扁平列表:父节点页范围可包含子节点,相邻/重叠全局页由 owner 策略 决定只渲染一个章节的 PDF,避免同页画两遍。

import {
  buildChapterViewerCatalog,
  overlapStrategyForSamePageOwner,
  type ChapterViewerOptions,
} from '@embedpdf-editor/vue3-chapter-viewer';

// 从业务树生成 tree + manifest(深度优先 flatten,顺序影响重叠页归属)
const { tree, manifest } = buildChapterViewerCatalog([
  { chapterId: 'part-1', title: '第一篇', startPage: 1, endPage: 50, source: { url: '/p1.pdf' } },
  {
    chapterId: 'sec-1',
    title: '1.1 节',
    startPage: 5,
    endPage: 20,
    source: { url: '/s1.pdf' },
    children: [/* ... */],
  },
]);

const options: ChapterViewerOptions = {
  manifest,
  overlapStrategy: overlapStrategyForSamePageOwner('last'), // 重叠页用 manifest 中「后出现」的章节
  // overlapStrategy: { kind: 'first-wins' }, // 默认:先出现的章节
  notes: { /* ... */ },
  bookmarks: { /* ... */ },
};

| overlapStrategy | 同一全局页多个章节条目时 | | --- | --- | | { kind: 'first-wins' }(默认) | 使用 manifest 中先出现 的章节的 PDF | | { kind: 'last-wins' } | 使用 后出现 的章节(目录 flatten 后,重叠页常以「当前页最后一节」为准) | | { kind: 'explicit' } | 各章在 ownedGlobalPages 上声明归属页 | | { kind: 'custom', resolve } | 完全自定义 |

简写:overlapStrategyForSamePageOwner('first' | 'last')

注意:仅作分组、没有独立 PDF 的父节点不要放进 flatten 后的 manifest;只保留需要加载 PDF 的节点。Demo:examples/chapter-viewer-demo-vue3/src/demo/load-catalog.ts

PDF 加载与预处理

与 React 包一致,推荐 三步 chapterPdfLoaderloadChapterUrls → 可选 openPdf
loadChapterUrls 每章只请求一次(引擎缓存);PDF 按段各打开一次。

import type { ChapterViewerOptions, ChapterPdfLoadContext } from '@embedpdf-editor/vue3-chapter-viewer';

const options: ChapterViewerOptions = {
  manifest: {
    chapters: [
      {
        chapterId: 'ch-1',
        title: '第一章',
        globalPageRange: [1, 13],
        localPageRange: [0, 12],
        segmentPageThreshold: 5,
      },
    ],
  },
  chapterPdfLoader: {
    async loadChapterUrls(chapter) {
      const res = await getOneChap(chapter.chapterId);
      const raw = res.data.resourceUrl;
      return Array.isArray(raw) ? raw : raw ? [raw] : [];
    },
    async openPdf(ctx: ChapterPdfLoadContext) {
      return { url: ctx.url };
    },
  },
  notes: { /* ... */ },
  bookmarks: { /* ... */ },
};

静态单文件仍可用 source.url / load()。兼容 loadPdf(chapter, segmentIndex)

03-manifest.md · 12-segmented-pdf-and-per-chapter-storage.md

按章持久化(笔记 / 书签)

NoteAnchor / ParagraphBookmark.anchorchapterIdlocalPageIndex 与单 URL 章相同。全书一次加载用 loadNotes / bookmarks.load按章按需见下节。划线备份用 exportChapterAnnotations(归档键为 chapterId)。

按章按需加载标注(笔记 / 书签 / 划线)

配置 options.annotations.loadChapterAnnotations 后,滚动到焦点章会自动拉取并 importChapterAnnotations;也可通过 ref.loadChapterAnnotations(chapterId) 手动触发(ref 仍只传章节 ID)。回调入参为 { chapterId, chapter }chapter 为 manifest 中的 ChapterDescriptor

const options: ChapterViewerOptions = {
  manifest: { chapters: [...] },
  // 勿再配置 notes.loadNotes / bookmarks.load 一次拉全书
  notes: { onCreateNote: async () => ({ noteId: crypto.randomUUID() }), /* ... */ },
  bookmarks: { persist: async () => {}, onRequestRemove: async () => true },
  annotations: {
    async loadChapterAnnotations({ chapterId, chapter }) {
      const res = await fetch(`/api/chapters/${chapterId}/annotations`).then((r) => r.json());
      // chapter.title / chapter.globalPageRange / chapter.segmentPageThreshold …
      return res; // { notes?, bookmarks?, markup? } 或 null
    },
    importOptions: { mode: 'replace' },
    autoLoadOnActiveChapter: true, // 默认 true
  },
};

事件:@chapter-annotations-loading@chapter-annotations-loaded@chapter-annotations-error

外部删除后同步渲染器

如果业务侧在外部列表里删除了笔记 / 书签 / 划线高亮,删除接口成功后再调用 ChapterPdfViewer ref,把渲染器内存里的对应标记移除。这里不会再触发 onRequestDeleteNoteonRequestRemovefeatures.markup.onRequestDelete,避免二次请求后端。

<script setup lang="ts">
import { useTemplateRef } from 'vue';
import {
  ChapterPdfViewer,
  type ChapterPdfViewerExpose,
  type SerializableAnnotationTransferItem,
} from '@embedpdf-editor/vue3-chapter-viewer';

const viewerRef = useTemplateRef<ChapterPdfViewerExpose>('viewer');

async function deleteNote(noteId: string) {
  await api.deleteNote(noteId);
  viewerRef.value?.removeNote(noteId);
}

async function deleteBookmark(bookmarkId: string) {
  await api.deleteBookmark(bookmarkId);
  viewerRef.value?.removeBookmark(bookmarkId);
}

async function deleteMarkup(record: SerializableAnnotationTransferItem) {
  await api.deleteMarkup(record.nodeId ?? record.annotation.id);
  await viewerRef.value?.removeMarkupAnnotation(record);
}
</script>

<template>
  <ChapterPdfViewer ref="viewer" :engine="engine" :options="options" />
</template>

加密 PDF 与 passwordProvider

import { CallbackPasswordProvider } from '@embedpdf-editor/vue3-chapter-viewer';

// 在 createPdfChapterEditor / 自定义 bundle 中传入
const passwordProvider = new CallbackPasswordProvider(async (chapter, attempt) =>
  askPassword(chapter.chapterId, attempt),
);

features 配置

ChapterViewerConfig:每项可 true | false | 对象。

| 模块 | 主要字段 | | --- | --- | | markup | enabledstylescolor/thickness/offsetY/opacity)、annotationMenuenabledrenderMenu)、onClick(与 annotationMenu 互斥) | | bookmarks | enabledmarkerrenderIconiconSizeonClick)、hoverrenderAddIconiconSize) | | notes | enabledmarkerrenderIconrenderMenuActionsiconSizeoffsetX/offsetYhighlightColorhighlightStylealwaysVisibleonClickselectedOutline) | | zoom | enabled(默认 true)、min 0.5、max 3、initial 1、pageWidth;实际上限由 [data-chapter-scroll-viewport] 宽度决定,resize 时自动更新 | | scrollViewport | background(默认 #f1f5f9),滚动视口背景 | | page | background(默认 #ffffff),单页 PDF 画布背景;运行时 ref.setPdfPageBackground(color) | | selectionToolbar | enabledselectionBackground(划词底色)、hiddenBuiltinActions(含 copy)、renderCopyIconextraActions | | pageOverlays | enableddefaultBoxStyle(任意 CSS)、renderContentonClickctx.payload) |

默认划线 offsetYsquiggly4。内置工具条顺序:copy → 划线类 → 扩展 → note

pageOverlays 详见 13-page-overlays.md


ChapterPdfViewer 组件

| Prop / 事件 | 默认 | 说明 | | --- | --- | --- | | engine / options | — | 必填;options.manifest 可响应式更新(内部 setManifest) | | class-name / viewport-class-name | — | 布局 | | @initialized / :on-initialized | — | 插件就绪,拿到 registry | | @active-chapter-change | — | 滚动焦点变化:{ chapterId, globalPageIndex, globalPageNumber, localPageIndex } | | @chapter-annotations-loading / loaded / error | — | 按需标注加载生命周期(须配置 options.annotations) | | @pdf-page-background-change | — | PDF 页背景色变更:{ background } | | refChapterPdfViewerExpose) | — | scrollToChapterloadChapterAnnotations(chapterId)isChapterAnnotationsLoadedsetPdfPageBackgroundgetPdfPageBackground | | build-selection-menu | — | 包装划词菜单 | | annotation-selection-menu | — | 点击已有划线标注的菜单 | | redaction-selection-menu | — | Redaction 层菜单 | | show-note-markers | true | 笔记角标 | | show-bookmark-markers | true | 书签角标 | | show-redaction-layer | false | 脱敏层 | | @extra-selection-action | — | 扩展工具条 | | render-page-overlay | — | 每页叠加层 | | #prepend | — | 视口上方插槽 | | #default | — | 视口内插槽 | | #loading | — | 插件未 ready 时 |

动态换章

<script setup lang="ts">
const options = ref<ChapterViewerOptions>({
  manifest: { chapters: [] },
  notes: { /* ... */ },
  bookmarks: { /* ... */ },
});

async function reload() {
  options.value.manifest.chapters = await fetchChapters();
}
</script>

<template>
  <ChapterPdfViewer :engine="engine" :options="options" @initialized="onReady" />
</template>

保持 optionsref 即可;不要computed(() => createChapterViewerBundle(options)) 随 manifest 重建 plugins。裸 EmbedPDF 时用 useSyncChapterManifest


Worker 与 WASM

usePdfiumEngine() 默认使用 @embedpdf/engines 内置的 PDFium CDN wasm 地址,并默认启用 worker:

usePdfiumEngine();

如果业务要求内网部署、固定版本或自定义 CDN,再传入自托管地址(完整 URL 或站点相对路径):

usePdfiumEngine({ wasmUrl: '/assets/pdfium.wasm', worker: true });

// 自有 OSS / CDN(示例)
usePdfiumEngine({
  wasmUrl:
    'https://hep-editor.oss-cn-beijing.aliyuncs.com/public/editor-public/js/pdfium.wasm',
  worker: true,
});

详见 docs/get-started/01-installation.md

使用 worker: true 时,部署环境需要允许 worker 加载 wasm。若只在 worker 模式失败,可先用 worker: false 定位资源或响应头问题。

features 配置示例

完整字段表见 React README 的 features 配置

划线颜色、粗细与位置

features: {
  markup: {
    styles: {
      highlight: { color: '#fef08a', opacity: 0.45 },
      underline: { color: '#dc2626', thickness: 1.5, offsetY: 2.5 },
      squiggly: { color: '#dc2626', thickness: 1.5, offsetY: 4 },
      strikeout: { color: '#64748b', offsetY: 0 },
    },
  },
},

offsetY 为 PDF 点、正值向下;波浪线默认 4,减轻盖住字形。markup: false 关闭划线。

点击已有划线:删除

选中高亮/下划线/波浪线/删除线后,默认在标注下方显示「删除」。可自定义:

import { h } from 'vue';

features: {
  markup: {
    annotationMenu: {
      renderMenu: ({ onDelete }) =>
        h('button', { type: 'button', onClick: onDelete }, '移除'),
    },
  },
},

PdfChapterViewport 上传 annotation-selection-menu 时宿主优先,未渲染时再回退默认删除菜单。

笔记、书签图标

import { h } from 'vue';

features: {
  notes: {
    marker: {
      renderIcon: () => h('img', { src: '/icons/note.svg', width: 20, height: 20 }),
      iconSize: 22,
      // 始终显示图标(默认 false:仅 hover 高亮时显示);
      // true 时 hover 感知区只覆盖图标本身,不影响该段划词。
      alwaysVisible: true,
      renderMenuActions: ({ onEdit, onDelete }) =>
        h('div', [
          h('button', { onClick: onEdit }, '编辑'),
          h('button', { onClick: onDelete }, '删除'),
        ]),
    },
  },
  bookmarks: {
    marker: { renderIcon: () => h('img', { src: '/icons/bookmark.svg' }) },
    hover: { renderAddIcon: () => h('span', '+') },
  },
},

选区工具栏

复制(默认开启,浮窗最左侧)

import { h } from 'vue';

features: {
  selectionToolbar: {
    selectionBackground: 'rgba(255, 193, 7, 0.35)',
    // hiddenBuiltinActions: ['copy'],
    renderCopyIcon: () =>
      h('span', { 'aria-hidden': true, style: { fontSize: '18px' } }, '📋'),
  },
},
import { copyTextToClipboard } from '@embedpdf-editor/vue3-chapter-viewer';

await copyTextToClipboard('文本');

扩展按钮(含划选文本)

features: {
  selectionToolbar: {
    markupColors: ['#93c5fd', '#fde047', '#fca5a5'],
    extraActions: [
      {
        id: 'translate',
        label: '翻译',
        onClick: ({ selectedText, chapterId, localPageIndex }) => {
          void openTranslate(selectedText, chapterId, localPageIndex);
        },
      },
    ],
  },
},

也可在 ChapterPdfViewer 上用 @extra-selection-action。回调含 selectedText
更多字段见 React 包 features.selectionToolbar07-selection-toolbar

页内矩形叠加层 pageOverlays

按 PDF 坐标绘制矩形框,适用于后端 QR 热点、自定义浮层。数据可通过 importChapterAnnotationspageOverlays 字段注入。

import { h } from 'vue';
import {
  importChapterAnnotations,
  qrTargetToPageOverlayRecord,
} from '@embedpdf-editor/vue3-chapter-viewer';

features: {
  pageOverlays: {
    defaultBoxStyle: {
      border: '2px dashed #ef4444',
      boxShadow: '0 0 0 1px rgba(239, 68, 68, 0.3)',
      cursor: 'pointer',
    },
    renderContent: ({ payload }) =>
      h('span', { style: { fontSize: '10px' } }, payload ?? 'QR'),
    onClick: ({ overlay, payload }) => {
      console.log('[点击矩形框]', overlay.overlayId, payload);
    },
  },
},
  • onClick / renderContent 参数为 PageOverlayRenderContext,含 payloadcontent.kind === 'qr'metadata.payload
  • defaultBoxStyle 支持任意 CSS,不限于边框简写
  • 后端 0~1 比例坐标用 qrTargetToPageOverlayRecord 转换

完整说明:13-page-overlays.md。Demo:examples/chapter-viewer-demo-vue3

进阶组合

内置 ChapterPdfViewer 已完成插件注册和章节视口渲染。需要插入自定义 shell、目录、工具栏或直接访问 registry 时,可以使用低层组合:

<!-- 动态 manifest:优先用 ChapterPdfViewer :options="options" -->

<script setup lang="ts">
import {
  EmbedPDF,
  PdfChapterViewport,
  createChapterViewerBundle,
} from '@embedpdf-editor/vue3-chapter-viewer';

const { plugins, features } = createChapterViewerBundle(options);
</script>

<template>
  <EmbedPDF :engine="engine" :plugins="plugins" @initialized="onReady">
    <template #default="{ pluginsReady }">
      <PdfChapterViewport v-if="pluginsReady" :features="features" />
    </template>
  </EmbedPDF>
</template>

EmbedPDF 且需动态换章时,在子组件内调用 useSyncChapterManifest(() => options.manifest)(不可在 EmbedPDF 外调用)。

createChapterViewerEditorOptions() 只生成旧式 editor 配置,不含 features;新代码优先 ChapterPdfViewer + 响应式 options

标注导入导出

import {
  exportChapterAnnotations,
  exportAllChapterAnnotations,
  importChapterAnnotationsArchive,
  CHAPTER_ANNOTATIONS_ARCHIVE_VERSION,
  useRegistry,
} from '@embedpdf-editor/vue3-chapter-viewer';

| 选项 | 说明 | | --- | --- | | mode | replace | merge | | bookmarks / notes / markup / pageOverlays | 导入导出子集,默认 true | | ensureChapterLoaded | 默认 true;导出含 markup 的分段章会加载 全部段 | | persistNotes / persistBookmarks | 导入后写回存储 |

归档按 chapterId 分桶。详见 10-annotations-io.md12-segmented-pdf-and-per-chapter-storage.mdReact README 标注章节

教程索引

docs/get-started/README.md(含 wasmUrl、划词复制、事件全集 11、分段 12、矩形叠加层 13

常见问题

| 现象 | 处理 | | --- | --- | | 一直停在引擎加载 | 如果传了自定义 wasmUrl,检查地址是否 200、MIME/跨源头是否正确 | | 只显示空白 | 确认外层容器有高度,且章节 source.url 可访问 | | 页码或滚动错位 | 检查 globalPageRangelocalPageRange 页数是否一致 | | 划词笔记没有保存 | 实现 notes.onCreateNote 或自定义 onRequestCreateNote 流程 | | 书签删除无效 | bookmarks.onRequestRemove 需要返回 true 才会删除 | | 分段章业务 ID 混乱 | 对外只用 chapterId + localPageIndex,勿用 chapterId#sN |