纯前端实现Word 文档读取与导出的方案详解
目录

概述
本方案支持 Word 文档(.docx)的导入和导出,实现了编辑器与 Office 文档格式之间的无缝转换。整体架构如下:
Word .docx 文件 ↓ (导入) mammoth 库解析 ↓ HTML 格式 ↓ Tiptap 编辑器 ↓ JSON Content ↓ (导出) docx 库生成 ↓ Word .docx 文件 核心依赖库
| 库名 | 版本 | 用途 |
|---|---|---|
| mammoth | 1.11.0 | Word 文档导入,将 .docx 转换为 HTML |
| docx | 9.1.0 | Word 文档导出,将 JSON 转换为 .docx |
| markdown-it | 14.1.0 | Markdown 文档导入 |
Word 文档导入
文件位置
1. 导入流程
.docx.md.txt用户选择文件FormData 上传文件验证文件类型?mammoth 解析markdown-it 解析纯文本解析生成 HTML样式清理返回给前端Tiptap 渲染
2. API 接口
端点: POST /api/import
请求格式: multipart/form-data
{ file: File // 上传的文件对象}响应格式:
{ success:true, html:string,// 转换后的 HTML format:'docx'|'markdown'|'text',// 原始格式 warnings?:string[],// 警告信息(如有) filename:string// 文件名}文件限制:
- 最大文件大小: 10MB
- 支持格式:
.docx,.md,.markdown,.txt
3. Word 文档解析 (mammoth)
核心函数: docxToHtml
asyncfunctiondocxToHtml(buffer: ArrayBuffer):Promise<ImportedDocumentResult>{ const nodeBuffer = Buffer.from(buffer);const{ value, messages }=await mammoth.convertToHtml({ buffer: nodeBuffer },{ // 样式映射:将 Word 样式映射到 HTML 标签 styleMap:['p[style-name="Heading 1"] => h1:fresh','p[style-name="Heading 2"] => h2:fresh','p[style-name="Heading 3"] => h3:fresh','p[style-name="Heading 4"] => h4:fresh',],// 图片处理:转换为 base64 内联图片 convertImage: mammoth.images.inline(async(image)=>{ const base64 =await image.read('base64');return{ src:`data:${ image.contentType};base64,${ base64}`,};}),},);// 清理样式(移除 text-indent 等)const sanitized =removeInlineTextIndentStyles(value.trim())||'<p></p>';// 提取警告信息const warnings = messages ?.filter((message)=> message.type==='warning').map((message)=> message.message);return{ format:'docx', html: sanitized, warnings: warnings && warnings.length >0? warnings :undefined,};}样式映射策略
mammoth 通过 styleMap 配置将 Word 内置样式映射到 HTML 标签:
| Word 样式 | HTML 标签 | 说明 |
|---|---|---|
| Heading 1 | <h1> | 一级标题,fresh 表示强制创建新标签 |
| Heading 2 | <h2> | 二级标题 |
| Heading 3 | <h3> | 三级标题 |
| Heading 4 | <h4> | 四级标题 |
| Normal (默认) | <p> | 普通段落 |
fresh 修饰符: 确保每个样式都生成独立的新标签,避免合并到父元素。
图片处理
convertImage: mammoth.images.inline(async(image)=>{ const base64 =await image.read('base64');return{ src:`data:${ image.contentType};base64,${ base64}`,};})工作原理:
- mammoth 检测到 Word 文档中的图片
- 读取图片二进制数据并转换为 base64 编码
- 生成 data URL:
data:image/png;base64,iVBORw0KGgo... - 在 HTML 中内联显示,无需外部图片文件
优点:
- ✅ 所有资源自包含,无需额外请求
- ✅ 编辑器可直接渲染
- ✅ 支持离线编辑
缺点:
- ❌ 大图片会增加文档体积
- ❌ base64 编码比原始二进制大约 33%
4. 样式清理
removeInlineTextIndentStyles 函数
functionremoveInlineTextIndentStyles(html:string):string{ return html.replace(/(style=)(['"])([^'"]*)(\2)/gi,(_match, prefix:string, quote:string, styles:string)=>{ const filtered = styles .split(';').map((item)=> item.trim()).filter((item)=> item.length >0&&!/^text-indent\s*:/i.test(item),);if(filtered.length ===0){ return'';}return`${ prefix}${ quote}${ filtered.join('; ')}${ quote}`;},);}目的: 移除 Word 文档中的 text-indent 样式,避免与编辑器的首行缩进规则冲突。
示例:
<!-- 清理前 --><pstyle="text-indent: 2em;font-size: 14px;">段落内容</p><!-- 清理后 --><pstyle="font-size: 14px;">段落内容</p>5. 纯文本和 Markdown 导入
纯文本导入
functiontextToHtml(buffer: ArrayBuffer): ImportedDocumentResult { const text =normalizeTextContent(buffer).trim();// 检测段落分隔符(双换行)const hasDoubleBreak =/\n{2,}/.test(text);const rawBlocks = hasDoubleBreak ? text.split(/\n{2,}/): text.split(/\n/);const paragraphs = rawBlocks .map((block)=> block.replace(/\n+/g,'\n').trim()).filter(Boolean).map((paragraph)=>{ if(!hasDoubleBreak){ return`<p>${ escapeHtml(paragraph)}</p>`;}// 段落内的单换行转换为 <br>const lines = paragraph.split('\n').map((line)=>escapeHtml(line));return`<p>${ lines.join('<br>