指南 · 基础
版本基线 1.9.0(已 deprecated)。本篇把「会加载保存」用到「懂模型」:
mode三态、documentBuffer哨兵状态、修订追踪(tracked changes)、批注、ref 方法,以及选择性 OOXML 往返的真实边界。
速查
- 模式:
editing直接修改、suggesting生成修订、viewing查看;readOnly是另一层输入开关 - 输入:buffer 加载文件、
null建空文档、undefined推迟挂载;预解析树走document - 修订:保存为 Word 原生
w:ins/w:del;接受/拒绝命令需要 ProseMirrorview - 主动保存:调用 ref 的
save();工具栏保存回调以所用适配器的 1.9.0 类型为准 - 往返:未改部件尽量原样保留,改过的 XML 会重写;这不是字节级相同或任意 Word 特性零损失
- 风险:1.9.0 已 deprecated 且仓库不可访问,必须用真实文档做 Word 重开回归
一、三种 mode:editing / suggesting / viewing
EditorMode = 'editing' | 'suggesting' | 'viewing'(默认 editing):
| mode | 行为 |
|---|---|
editing | 正常编辑,改动直接写入文档 |
suggesting | 修订模式:每次编辑变成一条修订(插入下划线、删除删除线),带作者与时间 |
viewing | 带工具栏的只读查看 |
const [mode, setMode] = useState<EditorMode>('editing');
<DocxEditor documentBuffer={buf} mode={mode} onModeChange={setMode} />;传
onModeChange表示你自己持有 mode 状态;不传则编辑器内部管理(用户可在工具栏的模式选择器切换)。
mode 与 readOnly 是两回事
readOnly 禁用一切输入手段(打字、工具栏、对话框)但保留完整分页渲染;mode 控制「编辑还是记修订还是只看」。想「能读但仍可加建议」用 mode="suggesting",想「纯查看器」用 readOnly + showToolbar={false}。两者相互独立。
二、documentBuffer 的三种哨兵状态
documentBuffer 接受 DocxInput(ArrayBuffer/Uint8Array/Blob/File),另有三种值含义不同:
<DocxEditor documentBuffer={buf} /> // 挂载该文档
<DocxEditor documentBuffer={null} /> // 立即挂一个空文档(从零录入)
<DocxEditor documentBuffer={undefined} /> // 推迟挂载,避免 fetch 在途的空状态闪烁若已持有解析好的
Document树(来自@eigenpal/docx-editor-core),用documentprop 传入即可跳过解析器。
三、修订追踪:写进 .docx 的是真 OOXML
在 suggesting 模式下,每次编辑都成为修订而非直接改动,并序列化为 Word 原生的 <w:ins> / <w:del>,Word 审阅窗格可识别这些记录;复杂结构仍需用目标 Word 版本回归。
<DocxEditor documentBuffer={buf} author="Jess Lin" mode="suggesting" />被追踪的不止行内文本,还包括:
- 文本插入/删除/替换(显示为「Replaced X with Y」)
- 段落结构:段落断点、段落属性变化
- 表格:行/单元格的增删合并、行/单元格/表格属性变化
- 图片:插入/删除
- 列表:编号变化(拒绝时同时回退文本与编号)
这些都作为真实 OOXML 修订往返,不是编辑器私有状态。每条修订记录创建时间戳;
author与日期都能经保存、重载后保留。
接受/拒绝修订(API)
侧栏按钮背后是 @eigenpal/docx-editor-core/prosemirror/commands 的 ProseMirror 命令:
import { acceptAllChanges } from '@eigenpal/docx-editor-core/prosemirror/commands';
// 用 onEditorViewReady 捕获 view,再对 view 运行命令
<DocxEditor documentBuffer={buf} onEditorViewReady={(v) => (viewRef.current = v)} />;
// ...
const view = viewRef.current;
if (view) acceptAllChanges()(view.state, view.dispatch);还有
acceptChangeById(id)/rejectChangeById(id)/rejectAllChanges()。枚举修订用extractTrackedChanges(state)。
四、用代码提一条修订:proposeChange
DocxEditorRef.proposeChange 不靠用户打字,按段落的 w14:paraId 锚定插入一条 tracked 替换(找不到段落或搜索文本时返回 false):
ref.current?.proposeChange({
paraId: 'ABC12300',
search: 'thirty (30) days',
replaceWith: 'sixty (60) days',
author: 'Contract Bot',
});这是「AI 红线(redlining)」构建于其上的原语。
五、回调:onChange / onSave / onError(React)
<DocxEditor
documentBuffer={buf}
onChange={(doc) => {/* 每次文档变更,参数是解析后的 Document */}}
onSave={(out) => {/* 用户经工具栏 Ctrl/Cmd+S 触发保存,out 是 ArrayBuffer */}}
onError={(err) => report(err) /* 解析/渲染错误从这里抛出 */}
/>区别:
ref.save()是你主动要字节(自动保存、自定义保存按钮);React 的onSave是用户通过内置 UI 触发保存时的回调。Vue 1.9.0 的公开 props 没有同名onSave,应由宿主按钮调用 ref。
六、「无损往返」到底是什么意思
.docx 是一堆 XML 部件的 ZIP,Word 还会写很多编辑器无须建模的 XML(书签、自定义 XML、邮件合并域、兼容性设置、VBA 工程……)。docx-editor 的管线是 解析 → 文档模型 → 编辑 → 序列化,保存时:
- 只重写它改过的部件(正文,以及被改动的页眉/页脚/批注/注释);
- 其余未触及部件(
styles.xml、主题、字体表、设置、媒体、关系、自定义 XML、VBA、嵌入字体、OLE 对象)尽量逐字节从原 ZIP 带过; - 未触及部件里的关系 ID、书签名、域代码、样式 ID、编号定义因而更有机会保持原样;
- 输出是 canonical OOXML:修订是 Word 审阅窗格认得的真
w:ins/w:del,主题色保持主题引用。
「无损」≠「字节级相同」
ZIP 容器会被重建、改动过的 XML 会被重新序列化,所以空白与部件顺序可能不同。目标是尽可能语义保留,但不能推导为所有 Word 构造都零损失。官方保真说明列出的已知例外包括部分 run 级 legacy VML shape(watermark 除外);仓库当前又无法提交 issue,因此要保留原件并对业务模板逐份做打开、保存、Word 重开与差异检查。
进入 指南 · 进阶:React 端 Yjs 协同、受控批注同步、headless 服务端处理、DocumentAgent 与模板填充、内容控件。