UniDoc说明中心下载 .udoc
UNIDOC / KNOWLEDGE IN MOTION

UniDoc 内嵌接入指南

从理解,到动手实现。步骤、协议与可复制的示例,都在这份文档中。

更新日期:2026-09-19。适用入口:https://app.unidoc.top/。本文依据当前仓库的编辑器实现,面向把 UniDoc 放入业务系统、邮件编辑器、侧栏或弹窗的前端接入方。

通过一个 iframe 嵌入编辑器,用 postMessage 载入内容、读取文档并接收变更通知。宿主控制外部容器尺寸和业务保存;UniDoc 负责正文编辑、工具栏、批注和文档转换。精简工具栏仍是可编辑文档,不代表只读。

可运行示例:unidoc-embed.html。它包含消息来源校验、就绪处理、请求配对、错误提示、动态适宽、全屏回退、UDoc 下载及重新载入。请通过 HTTP(S) 打开示例,不能直接双击以 file:// 运行。

在项目根目录启动示例宿主:

python -m http.server 8138 --bind 127.0.0.1

浏览器打开 http://127.0.0.1:8138/docs/examples/unidoc-embed.html。示例中的 iframe 默认连接线上编辑器;也可把示例 HTML 单独复制到自己的 HTTP(S) 站点,或修改其中 editorUrl 指向自己的 UniDoc 部署。

1. 最小接入

<div style="width:100%;height:720px;min-width:0">
  <iframe
    id="unidoc-frame"
    src="https://app.unidoc.top/?embed=1&amp;embedView=paged&amp;embedFit=width"
    title="UniDoc 文档编辑器"
    allow="fullscreen"
    allowfullscreen
    style="display:block;width:100%;height:100%;border:0">
  </iframe>
</div>

常用配置:

场景URL 查询参数
独立打开空白文档,保留完整功能区?blank=1
内嵌分页排版,随窗口适宽?embed=1&embedView=paged&embedFit=width
邮件、笔记等连续正文?embed=1&embedView=flow&embedFit=width
固定初始缩放为 80%?embed=1&embedView=paged&embedZoom=80
启动时显示完整功能区?embed=1&embedView=paged&embedUi=full&embedFit=width
会议纪要模板?embed=1&embedView=paged&template=builtin:meeting&embedFit=width

需要读写内容时,先安装消息监听器,再设置 iframe 的 src;不要只在 iframe 的 load 回调里开始监听 ready,否则可能漏接已经发出的通知。完整示例采用这一顺序。

2. URL 参数

参数值与默认行为
blank1:默认新建一张可编辑空白页,标题为“未命名文档”(英文浏览器为 Untitled document),不自动恢复上次 Vault。无需 embed,完整功能区保持可用。未传或其他值沿用原来的默认文档。
embed推荐 1 开启。未传或 offfalseno 关闭。0 是兼容旧版的“开启内嵌分页模式”,不是关闭。
embedViewflow:连续流式;paged:纸张分页。内嵌默认 flow。兼容别名 embed-view
embedUifullexpanded:完整功能区。未传:精简模式。兼容别名 embed-ui
embedZoom初始文档缩放百分数,例如 80,不是 0.8。限制在 10–500。未传、空白或非法值不覆盖编辑器的默认缩放。别名 embed-zoomzoom
embedFit推荐 width:根据 iframe 内画布宽度自动缩放。1trueyesfit 也启用;其余值不启用。别名 embed-fitfit
template内置模板键、模板名或当前浏览器保存的用户模板,见第 7 节。默认空白文档。
templateUrl自定义 .udoc 原生包或 UniDoc JSON 模板的 HTTP(S) 地址。可用相对地址,相对于 UniDoc 页面 解析。别名 template-url
templateNameURL 模板或命名模板载入后的显示名称,不负责保存到宿主。别名 template-name

优先级与精度:

  • 空白文档入口默认使用分页视图;显式 view=flow 或内嵌 embedView 仍生效。未传 blank=1 的普通入口继续显示灵感实验室。
  • blank=1 只控制启动种子:登录前保存的草稿、分享文档、明确指定的导入文档及内嵌模板仍按原流程载入。它不会在载入完成后清空文档,也不改变分享权限。
  • 再次直接打开或刷新空白入口会新建空白文档;需要保留编辑内容时请先保存 .udoc 或保存到云端。登录跳转前保存的草稿会正常恢复。
  • embed=0embed=paged 会强制选择分页,纵使另传 embedView=flow。新接入统一使用 embed=1,再设置 embedView
  • 同时传 templateUrltemplate 时,使用 templateUrl
  • 同时传 embedZoomembedFit=width 时,自动适宽会重新计算比例。固定比例场景不要启用适宽。
  • 初始缩放和 unidoc:zoom 沿用手动缩放步进:100% 以下约按 5 个百分点取整,100% 起按 10 个百分点取整;自动适宽按 0.1 个百分点计算。不要把显示缩放当作修改纸张尺寸。
  • 参数在编辑器启动时读取。单纯修改父页地址不会更新 iframe;不要通过反复更换 iframe src 实现缩放,以免重载文档。

构造 URL 时使用浏览器编码,避免模板名或 URL 内的 & 被当成另一参数:

const url = new URL('https://app.unidoc.top/');
url.search = new URLSearchParams({
  embed: '1',
  embedView: 'paged',
  embedFit: 'width',
  templateUrl: 'https://cdn.example.com/templates/contract.udoc',
  templateName: '合同草稿',
}).toString();

3. 工具栏与全屏

真实 iframe 中,精简模式初始为 passive:隐藏功能区和状态栏,先呈现正文;第一次点击画布后进入 home,显示一行“开始”编辑工具。顶部品牌、标题和标签行在精简模式隐藏。

内嵌工具栏把原来的 ¶ 显隐标记按钮替换为 UniDoc 全屏 SVG 图标,功能区末尾保留展开按钮:

  • 展开完整工具栏:在 homefull 之间切换功能区;不改变 iframe 大小。
  • UniDoc 全屏:调用浏览器 Fullscreen API;退出通常可按 Esc。它需要宿主 iframe 的 allow="fullscreen" / allowfullscreen 以及外层权限策略允许。

如果浏览器全屏不可用或请求失败,UniDoc 向父窗口发送:

{ type: 'unidoc:fullscreen', enabled: true }

宿主可将其解释为“展开到宿主页可用区域”,把容器设为 position:fixed;inset:0,再提供悬浮退出按钮。完整示例就是这样处理的。此方式填满网页区域,不会隐藏浏览器地址栏。

宿主在实际展开或退出后回传 { type: 'unidoc:fullscreen-state', enabled: true /* 或 false */ }。这让编辑器同步退出图标和手动缩放权限;不能只发出全屏请求便视为已经成功。宿主自己的退出按钮也应回传状态。

不要假设消息回调里再次调用 requestFullscreen() 一定成功;浏览器可能要求直接的用户点击。需要宿主主动进入浏览器全屏时,提供自己的按钮,在点击处理器中调用。unidoc:fullscreen-state 是宿主实际显示状态的通知,不会命令浏览器进入全屏。

4. 容器尺寸、实时缩放与适宽

外部尺寸与内部文档缩放是两件事:

精简且未全屏时,UniDoc 禁用内部手动缩放(快捷键、Ctrl/⌘+滚轮和双指手势);展开完整功能区或进入全屏后恢复。返回精简且非全屏状态时,恢复进入前的比例。宿主的 embedZoomunidoc:zoomembedFit=width 继续生效,因此宿主仍可按 iframe 大小控制文档显示。

控制目标接入方式
iframe 占用的宽、高宿主 CSS、Flex/Grid、拖动分隔栏或弹窗尺寸
文档自动匹配可用宽度URL embedFit=width;编辑器内的 ResizeObserver 监听画布变化
设置文档显示比例URL embedZoom=80 或消息 unidoc:zoom
弹窗从隐藏变为显示后重新适宽可补发 unidoc:resize
.editor-shell {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  height: 70vh; /* 或由业务布局指定明确高度 */
}
.editor-shell iframe {
  display: block;
  flex: 1;
  width: 100%;
  min-width: 0;
  min-height: 0;
  border: 0;
}
// frame 是实际 iframe 元素。正文命令需等待 ready,见下一节。
const editorOrigin = 'https://app.unidoc.top';
frame.contentWindow.postMessage({ type: 'unidoc:zoom', zoom: 80 }, editorOrigin);

// 内置监听通常已足够;宿主也可在布局调整后显式通知。
const observer = new ResizeObserver(() => {
  frame.contentWindow?.postMessage({ type: 'unidoc:resize' }, editorOrigin);
});
observer.observe(frame);
// 组件卸载时:observer.disconnect();

unidoc:resize 不接收 widthheight,也不替宿主改变 iframe 大小;它从实际画布重新测量,只在初始 URL 已启用适宽时生效。仅改变高度主要增加可见行数,适宽算法不按高度缩放。缩放作用于文档,工具栏依自身响应式布局显示。

目前没有运行时开启/关闭适宽的消息,没有缩放完成或缩放值变化回执。适宽模式下发送手动缩放后,下一次尺寸变化仍会重新适宽。固定比例与自动适宽请在初始化时选定;要重建实例,先保存原生草稿再恢复。

5. 消息协议完整表

正文桥仅在 真实 iframe + 显式 embed 模式 启用。直接在新标签页打开内嵌 URL 可预览外观,但不会向另一个标签页提供正文桥。

宿主 → UniDoc

type字段响应
unidoc:probe无必填业务字段尚未发送且初始化完成时,发送一次 unidoc:ready
unidoc:setidhtmldocudoc 三选一unidoc:content-setunidoc:error
unidoc:getidunidoc:content,包含 html
unidoc:get-udocid,推荐 format:'udoc3'unidoc:udoc,包含 udoc
unidoc:zoomzoom:百分数无回执
unidoc:resize无回执;启用适宽时重新计算

id 在底层允许省略,但正式接入应始终为正文请求生成唯一字符串。UniDoc 原样返回它,用来区分自动保存、预览和用户点击的并发请求。probezoomresize 不使用请求配对。

UniDoc → 宿主

type字段与含义
unidoc:readymode:'passive'|'home'|'full'view:'flow'|'paged'formats:['udoc3','udoc-json']template 对象
unidoc:content-setid:正文载入处理已返回
unidoc:contentidhtml:流式静态 HTML 完整字符串
unidoc:udocidudoc;原生包路径额外带 format:'udoc3'
unidoc:change变更提示,无正文内容、无服务端保存状态
unidoc:erroriderror:相应正文请求失败
unidoc:modemode:'home'|'full'view;表示工具栏状态变化
unidoc:fullscreenenabled:boolean;全屏请求失败或不可用时的宿主回退请求

ready.template 常见值:

{ status: 'blank', source: 'blank-seed' }
{ status: 'ready', source: 'builtin:meeting', name: '会议纪要' }
{ status: 'ready', source: 'url:https://cdn.example.com/t.udoc', name: 't.udoc' }
{ status: 'error', source: 'builtin:missing', error: '找不到嵌入模板:builtin:missing' }

ready.viewmode 消息中的 view 来自启动配置,不是用户后续切换视图的实时订阅。ready 在本 iframe 生命周期只发一次,重复 probe 不会要求宿主重新载入草稿。URL 模板处理结束后才发送,但它不是字体、图片、Typst 最终分页或截图全部完成的证明;content-set 也不是视觉排版完成事件。

消息发送与校验:

const editorOrigin = 'https://app.unidoc.top';
function onMessage(event) {
  if (event.origin !== editorOrigin) return;
  if (event.source !== frame.contentWindow) return;
  const message = event.data;
  if (!message || typeof message !== 'object' || Array.isArray(message)) return;
  // 按 type 分发,并用 id 解析对应请求。
}
window.addEventListener('message', onMessage);
frame.addEventListener('load', () => {
  frame.contentWindow.postMessage({ type: 'unidoc:probe' }, editorOrigin);
});
// 完成监听安装后,再赋值 frame.src 并挂载;完整可运行代码见示例。

即使设置了 referrerpolicy="no-referrer",真实 HTTP(S) 父窗口仍可用 probe 建立正文桥。编辑器优先绑定浏览器 referrer 的 origin;缺失时通过真实父窗口的有效探测或正文命令绑定一次。URL 中伪造 parent_origin 不会得到权限。

6. 载入、保存与错误处理

6.1 载入 HTML

frame.contentWindow.postMessage({
  type: 'unidoc:set', id: 'load-html-1',
  html: '<h1>项目周报</h1><p>请在这里填写内容。</p>',
}, editorOrigin);

html:'' 表示清空为可编辑空段落。该接口替换整篇正文,不是追加内容或增量补丁。收到 unidoc:content-set 才能把这次载入视为成功。

普通 HTML 走正文块导入;有内嵌 UniDoc 数据的导出 HTML 走相应恢复路径。它不等同于应用“打开 HTML”向导中的任意网页保真/自由编辑入口;不要预期发送一个带脚本、复杂全局 CSS 的网页,就能保留其全部运行行为。需要继续编辑已有自由网页文档时,优先恢复该文档的原生 UDoc。

6.2 载入面向 AI 的 UniDoc JSON

frame.contentWindow.postMessage({
  type: 'unidoc:set', id: 'load-doc-1',
  doc: {
    format: 'udoc', version: 3, unidoc_type: 'docs',
    basename: '项目周报',
    blocks: [
      { para_id: 1, html: '<h1>项目周报</h1>' },
      { para_id: 2, html: '<p>本周完成事项。</p>' },
    ],
  },
}, editorOrigin);

JSON 会经过严格校验。完整字段、资源、批注与 AI 编写规范见 公共 API 文档JSON Schema。不确定字段时使用已导出的文档,不要凭空构造内部 DOM 属性。

6.3 保存原生草稿(推荐)

frame.contentWindow.postMessage({
  type: 'unidoc:get-udoc', format: 'udoc3', id: 'save-native-1',
}, editorOrigin);

// 响应:{type:'unidoc:udoc', format:'udoc3', udoc:'udoc3:BASE64...', id:'save-native-1'}
// 恢复时把完整的 udoc 字符串原样传回:
frame.contentWindow.postMessage({
  type: 'unidoc:set', udoc: savedUdocString, id: 'restore-native-1',
}, editorOrigin);

宿主可保存整个 udoc3:... 字符串到业务存储,也可解码为二进制后保存 .udoc 文件。不能把带前缀的 Base64 文本直接写为 .udoc 示例包含正确的解码下载和文件编码恢复。

未传 format:'udoc3' 时,unidoc:get-udoc 返回旧版交换 JSON 字符串,响应不带原生 format 字段。它可用于调试/交换,但不能直接改名 .udoc;新接入保存草稿统一使用原生包路径。

此 iframe 原生草稿桥有 8 MiB 包大小限制;恢复时还限制解压单项与总量为 8 MiB、目录为 1 MiB,因此一个高度压缩的小包也可能在恢复时超限。限制不等于所有文件导入或服务端转换接口的上限。Base64 另有约三分之一传输膨胀。

原生导出遇到资源未内嵌、导出期间检测到正文变化、大小超限等情况会返回配对错误。失败时保留上一份成功草稿;待编辑停止/资源就绪后再试,不能用空结果覆盖已有数据。

6.4 获取展示 HTML

frame.contentWindow.postMessage({ type: 'unidoc:get', id: 'preview-1' }, editorOrigin);
// 响应:{type:'unidoc:content', html:'<!doctype html>...', id:'preview-1'}

这是流式、不可编辑的展示 HTML,不是 DOCX、PDF,也不是宿主提交成功的回执。展示 HTML 应按宿主的内容策略在隔离预览中使用;邮件客户端还会进一步过滤样式与脚本。保存可继续编辑的原稿时仍使用原生 UDoc。

6.5 自动保存与请求队列

unidoc:change 由正文编辑区 input 驱动,约 400 ms 防抖。它不承诺覆盖所有程序化改动、批注操作或外部 HTML 编辑面的变更,不能作为“任意文档状态绝不漏存”的版本日志。

建议接入流程:

  1. 收到 ready 后载入草稿,等待 content-set,再允许业务提交。
  2. 收到 change 标记待保存;合并连续变化,每次仅运行一个原生导出与保存任务。
  3. 导出后仍需等待宿主服务端写入成功,才显示“已保存”;失败保留脏状态和上一版草稿。
  4. 用户点击提交、切换业务记录或关闭编辑弹窗前,再显式读取一次原生文档。不要只依赖关闭页面时的异步请求。
  5. 导出/写入过程中发生新的变化,要再保存一次;不要让旧请求的晚到结果覆盖新版本。

set/get/get-udoc 按接收顺序串行执行,等待启动模板;最多 32 个待处理正文请求。超限返回 unidoc:error,某个请求失败不会中断后续队列。不要每个按键都导出整篇文档。

宿主应设置可配置的就绪/请求超时,并在卸载时清理监听、观察器和计时器。超时是宿主停止等待,不会取消编辑器中已排队的命令;尤其不要盲目重发超时的 set,以免后到的旧内容覆盖用户输入。当前协议没有取消或导出百分比进度消息。

7. 模板初始化

内置模板:

内容
blank空白文档
ppt16916:9 演示
ppt434:3 演示
meeting会议纪要
weekly项目周报
resume个人简历

template=meetingtemplate=builtin:meeting 都可用,也支持准确模板中文名。template=user:合同模板 按当前浏览器已保存的模板名匹配;template=user:0 按零起始索引匹配。用户模板属于 UniDoc origin 的浏览器存储,不能作为不同用户、设备或浏览器之间共享的模板目录。

templateUrl 的服务器必须允许 UniDoc origin 读取。跨域请求不携带 Cookie;同源请求使用同源凭据。对于 HTTPS 编辑器,应提供 HTTPS 模板以免混合内容被拦截。需要鉴权且不能公开读取的草稿,优先由宿主自己的业务接口获取,再通过 unidoc:set 传入。

JSON 响应通过 JSON 内容类型或 .json 路径识别,应包含 format:'udoc'blocks;其他响应交给 UDoc 解码器。templateUrl 不是任意 HTML 网页 URL 入口。

URL 模板是初始化内容。之后宿主发送 unidoc:set 会整体替换它,不会把草稿自动合并到模板。检查 ready.template.status 并显示模板错误;若始终没有 ready,按初始化失败处理,不要把空白页误判成模板载入成功。

8. 批注、Word 互转与同源 API

内嵌版使用编辑器同一套原生批注模型。需要保留批注内容、回复关系和锚定位置时,保存原生 UDoc;不要只摘出正文 HTML 或把黄色背景当作批注数据。

批注高亮是独立的 CSS Highlight 显示设置,不属于“开始”里的文字突出显示。编辑器内可控制批注高亮颜色/开关;该偏好保存在当前浏览器的 UniDoc origin 本地存储,不是 Word 正文背景色,也不是跨设备偏好同步。

跨域正文桥目前没有逐条批注 CRUD、批注颜色设置、文字选区操作、切换工具栏或 DOCX/PDF 直接导出的专用消息。不要发送猜测的 unidoc:add-commentunidoc:export-docx 等消息,未知命令没有对应处理。可使用编辑器 UI;文件转换另见 公共 API 文档。Word 的批注显示还受 Word 审阅视图控制,不能承诺浏览器选择的高亮色在 Word 中完全相同。

只有与编辑器同源的受控宿主/自动化才可调用页面对象,例如:

const editor = frame.contentWindow;
await editor.UniDocEmbed.ready; // 模板处理 Promise,不是渲染屏障
editor.UniDocEmbed.wake();
editor.UniDocEmbed.setExpanded(true);
console.log(editor.UniDocEmbed.config, editor.UniDocEmbed.template);
editor.UniDocComments.setHighlightSettings({ enabled: true, color: '#FFE992' });

UniDocEmbed 还提供 expandedpassive getter。浏览器同源要求协议、主机、端口一致;两个不同子域也不能直接这样访问。跨域宿主使用消息协议,避免依赖内部 DOM ID 操作编辑器。

9. Vue / React 等框架集成

不依赖特定前端框架,原则与完整 HTML 示例相同:

  • Vue:在 onMounted 安装桥与 ResizeObserver;在 onBeforeUnmount 清理。React:在 useEffect 初始化并返回清理函数。
  • 先安装 window 消息监听,再创建/导航 iframe,防止 ready 丢失;每个 iframe 使用独立的 pending 请求表和唯一 ID。
  • 不把整个文档内容绑定到会反复改变 iframe src 的响应式表达式;正文通过 unidoc:set 载入。
  • 切换文档先保存旧文档,再发送新的 set;不要在每次组件重渲染时重复载入。
  • React 开发 Strict Mode 会重复运行 effect,初始化与清理必须成对;示例约定一个桥对应一个 iframe 生命周期,重建 iframe 时重建桥。
  • 多实例同时存在时,除了 origin,还必须按 event.source 路由到正确 iframe。

10. 权限、登录与托管

项目接入要求
消息来源宿主检查精确 event.originevent.source,发送指定 targetOrigin;不要用 * 发送正文。
身份unidoc:ready 表示正文桥就绪,不表示宿主和 UniDoc 已共享登录;当前桥不提供 SSO 认证回执。
只读共享文档权限独立于 embed 外观;只读文档拒绝 unidoc:set。没有通用 readonly=1editable=1 参数供宿主切换权限。
存储接入业务数据库的保存由宿主实现;仅收到 content/udoc 不表示已入库。
父页 origin使用 HTTP(S),不要用 file:// 或会让父窗口变为 null origin 的沙箱环境。
iframe sandbox最小示例未加 sandbox。若业务要求添加,需要保留脚本和正常 origin,并按功能测试下载、表单、弹窗等权限;不要复制一个过度限制的 sandbox 后默认所有编辑功能可用。
Fullscreeniframe 的 allow 权限还受宿主 Permissions-Policy 与外层嵌套 iframe 约束。
CSP/防嵌宿主 frame-src 要允许编辑器;编辑器/代理的 frame-ancestors 或 X-Frame-Options 也可能限制宿主。以实际部署响应头为准。
跨站登录Cookie/浏览器存储策略可能影响云文档、账号等能力;正文消息桥通了不等于账号联调完成。

同一登录中心也不自动消除 iframe 的跨站会话限制。需要 UniMail 账号联动时,先阅读 SSO 兼容说明,不要把其中待实现的登录流程当作已有 API。

此文的“内嵌”指把 UniDoc 编辑器放入宿主 iframe。文档内部的 .html-embed 沙箱块是另一层功能;它不自动获得父业务系统的正文桥或账号权限。

11. 故障排查与验收

现象优先检查
iframe 空白/拒绝连接网络、HTTPS 混合内容、CSP/X-Frame-Options、容器是否有高度。
收不到 ready真实 iframe 是否带 embed=1;监听是否装晚;load 后是否 probe;父窗口是否有效 HTTP(S) origin;模板是否仍在加载。
ready 到了却没有模板检查 template.status;URL、CORS、HTTP 状态、文件类型;宿主是否又用 set 覆盖模板。
初始没有工具栏或全屏按钮精简 iframe 初始 passive,点击正文唤醒;需首屏完整功能区可传 embedUi=full
全屏图标只改变工具栏确认点击的是全屏图标而非展开功能区图标;查看 allow 和权限策略;接收 fullscreen 回退消息。
窗口变宽,文档没有跟随embedFit=width;先实际改变 iframe 宽度;显示隐藏容器后补发 resize。
手动 80% 后比例又变了启用了自动适宽;固定缩放场景去掉 embedFit。
宽度溢出/按钮挤压父级 Flex/Grid 添加 min-width:0;iframe 宽度 100%;不要对整个 iframe 施加 CSS transform 代替文档缩放。
设置正文报只读当前打开了只读共享文档;调整业务权限/文档入口,不要通过隐藏标签或覆盖 CSS 绕过。
UDoc 无法重新打开保存的是不是解码后的原生包;有无误把 JSON/Base64 文本命名 .udoc;是否超限或资源缺失。
变更后没自动保存change 不是全状态事务流;检查宿主保存逻辑,提交前显式取稿,并检查服务端保存结果。
请求超时后内容又变化超时未取消编辑器命令;核对 ID、队列和重复 set,必要时保存当前稿后重建实例。
Word 批注颜色不同批注 UI 高亮不等于正文文字高亮;检查批注数据、锚点、回复及 Word 审阅视图。

接入验收至少覆盖:首次启动/无 referrer;HTML 载入和清空;原生草稿下载再恢复;批注与回复保存再恢复;两种视图;窄宽容器和隐藏弹窗展开;允许/拒绝浏览器全屏;只读 set 拒绝;网络/资源失败不覆盖旧稿;多实例来源隔离;组件销毁后无残留监听。

本地已有协议回归:

node tests/embed-mode-contract.test.js
node --test tests/unimail-embed-bridge.test.cjs

这些检查覆盖 URL 与消息桥行为,不能代替目标宿主的登录、浏览器权限、真实文件及 Word 视觉往返验收。

12. 实现与相关文档