在 Vue 3 中集成 BlockSuite:vue-basic 示例的编辑器封装与文档管理实战 📅 发布时间:2026/9/17 11:37:52 👁 浏览次数: 在 Vue 3 中集成 BlockSuitevue-basic 示例的编辑器封装与文档管理实战【免费下载链接】blocksuite Content editing tech stack for the web - BlockSuite is a toolkit for building editors and collaborative applications.项目地址: https://gitcode.com/GitHub_Trending/bl/blocksuite导读examples/vue-basic 是 BlockSuite 官方仓库中面向 Vue 生态的基础示例演示了如何用 Vue 3 组合式 API 将 BlockSuite 编辑器与文档集合Doc Collection封装为可复用的组件体系并实现基础的文档管理新建页面、侧边栏切换、跨文档跳转。读完本文你将掌握在 Vite Vue 3 项目中安装 BlockSuite 三个核心包、用Schema/DocCollection/AffineEditorContainer初始化编辑器、通过provide/inject在组件树中共享编辑器实例以及监听文档更新事件实现动态文档列表的关键代码模式。示例定位最小可运行的 Vue 封装范式该示例来自仓库根目录下独立的 examples 工作区。与 monorepo 内workspace:*引用的内部包不同examples 目录是一个独立安装 canary 版本的 pnpm workspace——它会从 npm 拉取发布版 BlockSuite 包而非依赖仓库源码编译因此可以直接复制到任意项目中作为集成起点。从 package.json 可以看到它依赖了三个 BlockSuite 包各自职责明确依赖职责blocksuite/store文档数据模型Doc、Schema、DocCollectionblocksuite/blocks内置的块定义AffineSchemas如 page、note、paragraph 等blocksuite/presets开箱即用的AffineEditorContainer编辑器容器与主题样式配套工程链为 Vite 5 vitejs/plugin-vue5 TypeScript vue-tsc构建时做类型检查脚手架由pnpm create vite生成。示例还声明了 StackBlitz 启动命令pnpm i pnpm dev方便在线即时体验。快速开始三步启动 vue-basic原 README 给出的启动流程非常精简结合 examples/dev.sh 可以还原完整执行链路git clone https://gitcode.com/GitHub_Trending/bl/blocksuite.git cd blocksuite/examples pnpm install pnpm dev vue-basic其中pnpm dev vue-basic对应 examples 根 package.json 中的脚本dev: ./dev.sh脚本拿到项目名参数后执行pnpm -C ./vue-basic dev最终落到 vue-basic 自身的dev: vite脚本见 examples/vue-basic/package.json由 Vite 启动开发服务器。其余常用命令pnpm build # 在 examples 根目录运行vue-tsc vite build产出类型检查后的生产构建 pnpm preview # 在 vue-basic 目录内预览构建产物注意由于 examples 是独立 workspace务必在examples目录下执行安装若在仓库根目录执行pnpm install会安装 monorepo 的完整依赖树与本示例的 canary 依赖策略不同。初始化编辑器从数据模型到 UI 容器编辑器实例的创建集中在 examples/vue-basic/src/editor/editor.ts 的initEditor()中它是整个示例的数据源核心完整代码逐段拆解如下。第一步注册块 Schemaimport { AffineEditorContainer } from blocksuite/presets; import { Doc, Schema } from blocksuite/store; import { DocCollection } from blocksuite/store; import { AffineSchemas } from blocksuite/blocks; import blocksuite/presets/themes/affine.css; export function initEditor() { const schema new Schema().register(AffineSchemas);Schema是 BlockSuite 中所有块结构的元信息注册表AffineSchemas则集中导出了内置块page、note、paragraph、surface、list、heading 等的 schema 定义其在blocksuite/blocks包中的声明入口见 packages/blocks/src/schemas.ts。这一行决定了编辑器能识别哪些块类型。第二步创建文档集合并初始化元数据const collection new DocCollection({ schema }); collection.meta.initialize();DocCollection文档集合是 BlockSuite 中管理多个Doc的容器对应一个工作空间包含多份文档的场景。meta.initialize()为集合初始化文档元数据管理能力后续collection.meta、collection.docs、collection.slots.docUpdated都依赖这一步。第三步创建首份文档并搭建块树const doc collection.createDoc({ id: page1 }); doc.load(() { const pageBlockId doc.addBlock(affine:page, {}); doc.addBlock(affine:surface, {}, pageBlockId); const noteId doc.addBlock(affine:note, {}, pageBlockId); doc.addBlock(affine:paragraph, {}, noteId); });createDoc创建一份新文档doc.load的回调在文档加载完成后执行这里用addBlock拼出了 BlockSuite 的最小文档骨架affine:page文档根块affine:surface挂在 page 下的画布块edgeless 模式使用affine:note笔记块承载富文本内容区域affine:paragraph笔记下的第一个空段落保证打开文档即有可编辑光标位置。addBlock(type, props, parentId)的第三个参数指定父块 id由此构建出 page → note → paragraph 的块树层级。这套块结构与仓库中对应块实现一一对应例如 packages/blocks/src/note-block、packages/blocks/src/paragraph-block。第四步实例化编辑器并绑定文档、处理文档间跳转const editor new AffineEditorContainer(); editor.doc doc; editor.slots.docLinkClicked.on(({ docId }) { const target Doccollection.getDoc(docId); editor.doc target; }); return { editor, collection }; }AffineEditorContainer是blocksuite/presets提供的现成编辑器 UI 容器直接赋值editor.doc即完成绑定。editor.slots.docLinkClicked是编辑器的文档链接点击事件槽当用户在文档内点击指向其他文档的链接时从collection.getDoc(docId)取出目标文档并切换editor.doc实现同集合内的跨文档导航。这个 slot 监听逻辑也是 Sidebar 文档切换的同一机制。组件分层provide/inject 驱动的状态共享示例把 BlockSuite 的状态通过 Vue 3 的provide/inject在组件树中共享这是它区别于 React 示例react-basic 的 EditorProvider.tsx 用 Context的 Vue 特色。EditorProvider.vue —— 顶层状态提供者script setup langts import { provide } from vue; import { initEditor } from ../editor/editor; import { DocCollection } from blocksuite/store; import { AffineEditorContainer } from blocksuite/presets; export interface AppState { editor: AffineEditorContainer; collection: DocCollection; } provide(appState, initEditor()); /scriptinitEditor()在provide调用时执行一次其返回值{ editor, collection }被注入到名为appState的上下文。AppState接口明确了两件关键资源编辑器实例与文档集合。App.vue —— 组合各功能区块template EditorProvider div classapp Sidebar / div classmain-content TopBar / EditorContainer / /div /div /EditorProvider /template应用被组织为左侧Sidebar文档列表 右侧TopBar/EditorContainer编辑器区域的经典布局整体包裹在EditorProvider之下所有后代组件都能inject到同一份appState。EditorContainer.vue —— 将编辑器挂入 DOMtemplate div classeditor-container refeditorContainerRef/div /template script setup langts import { inject, onMounted, ref } from vue; const { editor } injectAppState(appState)!; const editorContainerRef refHTMLDivElement(); onMounted(() { editorContainerRef.value editorContainerRef.value.appendChild(editor); }); /script这是最关键的挂载步骤AffineEditorContainer是一个原生 Web Component 风格的元素因此不能像普通 Vue 组件那样直接放入模板而要在onMounted时通过appendChild把编辑器元素挂载到模板引用refeditorContainerRef指向的容器 div 中。inject后的非空断言!表明该组件必须位于EditorProvider内部使用。文档管理侧边栏的动态文档列表Sidebar.vue 承担了基础文档管理的核心逻辑script setup langts import { inject, ref } from vue; import { AppState } from ./EditorProvider.vue; import { Doc } from blocksuite/store; const { editor, collection } injectAppState(appState)!; const docs refDoc[]( [...collection.docs.values()].map(blocks blocks.getDoc()) ); const updateDocs () (docs.value [...collection.docs.values()].map(blocks blocks.getDoc())); collection.slots.docUpdated.on(updateDocs); editor.slots.docLinkClicked.on(updateDocs); const selectDoc (doc: Doc) { editor.doc doc; updateDocs(); }; /script几个值得注意的实现细节collection.docs是一个Map每个 value 是文档的 blocks 包装对象需调用.getDoc()取出真正的Doc模板中通过doc.meta?.title || Untitled展示文档标题数据刷新订阅了两个事件collection.slots.docUpdated集合内文档新增/变更和editor.slots.docLinkClicked通过链接跳转了文档保证列表始终与集合状态同步selectDoc通过重新赋值editor.doc切换当前编辑文档并用editor?.doc doc高亮当前项TopBar.vue仅渲染标题文本保持示例最小化。这套事件订阅驱动 UI 刷新的模式与 editor.ts 中docLinkClicked的用法相互印证构成了示例的文档管理闭环集合增删文档 → 触发docUpdated→ Sidebar 刷新列表 → 点击/链接切换editor.doc→ 编辑器内容更新。数据模型视角doc、block 与 collection 的关系从源码结构可以梳理出 vue-basic 背后的 BlockSuite 数据模型三层关系这也是理解所有 BlockSuite 集成示例的前提DocCollection集合层拥有schema与文档元数据管理多个Doc提供createDoc、getDoc、docs、slots.docUpdated等 APIDoc文档层一份独立文档load后通过addBlock构建块树是编辑器绑定的直接对象editor.docBlock块层affine:page、affine:note、affine:paragraph等由Schema校验、由对应 block service 渲染。这一分层在 examples/custom-store演示自定义 store、examples/react-sqlite演示 SQLite 持久化等兄弟示例中被进一步深化若需要体验多编辑器实例或 IndexedDB 持久化可参照 examples/vanilla-indexeddb 与 examples/react-indexeddb。工程要点与扩展方向版本策略本示例固定引用0.15.0-canary-202406291027-8aed732这一 canary 版本见 examples/vue-basic/package.jsonexamples 根目录提供pnpm bump脚本pnpm -r up blocksuite/presetscanary blocksuite/blockscanary blocksuite/storecanary可统一升级三个包的 canary 版本。实际项目接入时建议锁定发布版本并按需升级。类型检查pnpm build会先执行vue-tsc对 Vue SFC 做完整类型检查再交给 Vite 构建可作为 CI 中的质量关卡。从示例到真实应用vue-basic 的封装模式可直接演进出生产级架构——把initEditor迁移到 Pinia/Vuex store 或组合式函数composable将docUpdated/docLinkClicked的订阅收敛到统一的文档管理模块再叠加 IndexedDB/SQLite/WebSocket 等持久化与同步能力即构成完整的 BlockSuite Vue 应用骨架。小结vue-basic 以最少的代码完整演示了 BlockSuite 在 Vue 3 中的标准集成路径Schema注册 →DocCollection建集 →Doc建文档与块树 →AffineEditorContainer绑定与挂载 →provide/inject共享状态 → 事件订阅驱动文档列表。对照同仓库的 react-basic、svelte-basic、solid-basic 等示例可见BlockSuite 的编辑器核心与框架无关各框架示例只差一层状态传递与 DOM 挂载的适配代码——这正是其作为内容编辑技术栈的框架无关性所在。【免费下载链接】blocksuite Content editing tech stack for the web - BlockSuite is a toolkit for building editors and collaborative applications.项目地址: https://gitcode.com/GitHub_Trending/bl/blocksuite创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考