Vue.js - 富文本编辑器Tiptap使用详解13(将内容导出为Word)
作者:hangge | 2026-03-02 08:43
有时我们需要导出 Tiptap 内容为 Word (.docx),本文演示如何借助 html-docx-js + FileSaver 来快速实现纯前端导出,以及通过后端进行导出这两种方法。



十三、将内容导出为 Word(客户端导出)
1,安装依赖
要实现客户端导出,我们需要先安装 html-docx-js 和 file-saver 这两个依赖库。
npm install html-docx-js file-saver
2,样例代码
<template>
<div class="p-4">
<div class="mb-3 flex gap-2">
<button @click="exportDocx" class="btn">导出为 Word</button>
</div>
<div class="editor-box border rounded">
<editor-content :editor="editor" />
</div>
</div>
</template>
<script>
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import { EditorContent } from '@tiptap/vue-2'
import Link from '@tiptap/extension-link'
import Image from '@tiptap/extension-image'
// 推荐:TableKit 一次引入所有 table 相关扩展
import { TableKit } from '@tiptap/extension-table'
import { TextStyleKit } from '@tiptap/extension-text-style'
import { Color } from '@tiptap/extension-color'
import Highlight from '@tiptap/extension-highlight'
import htmlDocx from 'html-docx-js/dist/html-docx' // html-docx-js
import { saveAs } from 'file-saver'
export default {
components: { EditorContent },
data() { return { editor: null } },
mounted() {
this.editor = new Editor({
extensions: [StarterKit, Link, Image, TableKit, TextStyleKit,
Color.configure({
types: ['textStyle'],
}),
// multicolor: true 允许自定义颜色
Highlight.configure({
multicolor: true,
}),
],
content: `
<p>欢迎访问<a href="https://www.hangge.com">hangge.com</a></p>
<p><img src="https://www.hangge.com/blog/images/logo.png" alt="logo"></p>
<p></p>
<p>测试<strong>加粗</strong>测试<em>斜体</em>测试<u>下划线</u>测试<s>删除线</s></p>
<p></p>
<p><span style="color: rgb(255, 102, 255);">修改文字颜色</span></p>
<p><span style="font-size: 22px;">调整文字大小</span></p>
<p></p>
<p>下面是一个3x3表格</p>
<table>
<thead>
<tr><th>ID</th><th>用户名称</th><th>用户年龄</th></tr>
</thead>
<tbody>
<tr><td>1</td><td>刘小龙</td><td>88</td></tr>
<tr><td>2</td><td>张大伟</td><td>66</td></tr>
</tbody>
</table>
`,
})
},
methods: {
exportDocx() {
const html = `
<html>
<head>
<meta charset="utf-8">
<style>
/* 控制导出样式(简化) */
body { font-family: "Arial", sans-serif; }
table { border-collapse: collapse; }
th { background: #F9FAFB; font-weight: 600; }
td, th { border: 1px solid #ccc; padding: 4px; }
</style>
</head>
<body>${this.editor.getHTML()}</body>
</html>
`
// html-docx-js: 返回 ArrayBuffer / Blob
const converted = htmlDocx.asBlob(html) // 注意:不同版本 API 可能是 asBlob / asHTML
saveAs(converted, 'document.docx')
}
},
beforeDestroy() { this.editor?.destroy() }
}
</script>
<style scoped>
.btn { padding:6px 10px; border-radius:4px; border:1px solid #ddd; background:#fff; cursor:pointer }
.editor-box { min-height: 220px; padding: 12px; background:#fff; }
/* ====== 关键:为编辑器内 table 添加样式 ====== */
/* Vue2 scoped 下使用深度选择器确保样式能命中 ProseMirror 渲染的元素 */
/* 如果你的环境不识别 ::v-deep,请试试 >>> 或 /deep/ */
::v-deep .ProseMirror table {
width: 100%;
border-collapse: collapse;
table-layout: auto;
margin: 6px 0;
}
/* 单元格边框与内边距 */
::v-deep .ProseMirror th,
::v-deep .ProseMirror td {
border: 1px solid #d1d5db; /* 灰色边框,可按需调整 #e5e7eb #cbd5e1 等 */
padding: 8px 10px;
vertical-align: middle;
}
/* 表头样式(可选) */
::v-deep .ProseMirror th {
background: #f9fafb;
font-weight: 600;
}
/* 表格在窄屏时不要换行太多(可选) */
::v-deep .ProseMirror td {
word-break: break-word;
}
/* 如果使用 Tailwind 的 prose 可能会有默认的 table 样式,下面加个更高权重以覆盖 */
::v-deep .ProseMirror table,
::v-deep .ProseMirror th,
::v-deep .ProseMirror td {
/* 提高优先级避免被 reset 覆盖 */
border-color: #d1d5db;
}
/* Tiptap 表格单元格被选中时的高亮效果(与官网一致) */
::v-deep .ProseMirror .selectedCell {
background-color: rgba(200, 200, 255, 0.4) !important;
/* 防止 reset 覆盖 */
}
/** 下面是解决列宽拖拽调整时鼠标样式问题的 CSS */
/* 提升优先级:靠右侧 5~8px 时鼠标应变成 col-resize */
::v-deep .ProseMirror table td,
::v-deep .ProseMirror table th {
position: relative;
}
::v-deep .ProseMirror table td::after,
::v-deep .ProseMirror table th::after {
content: "";
position: absolute;
right: -3px; /* 命中区域 */
top: 0;
width: 6px;
height: 100%;
cursor: col-resize !important;
}
</style>
3,运行测试
(1)打开页面,点击“导出为 Word”按钮即可生成 docx 格式的 Word 文件。

(2)打开文件可以看到里面内容如下:

附:通过后端进行将内容导出为 Word
1,实现原理
前端把 editor.getHTML()(或带全套 head/styles 的完整 HTML)POST 到后端 → 后端用 docx4j 生成返回文档流 → 前端触发下载。
2,后端服务接口准备
这里我们使用 SpringBoot 项目作为后台服务,其提供 /export-docx 接口,接收 JSON { html: "...", filename?: "xxx.docx" },返回 application/vnd.openxmlformats-officedocument.wordprocessingml.document 二进制流。具体实现参考我们之前文章:
3,前端代码
<template>
<div class="p-4">
<div class="mb-3 flex gap-2">
<button @click="exportViaServer" class="btn">服务端导出为 Word</button>
</div>
<div class="editor-box border rounded">
<editor-content :editor="editor" />
</div>
</div>
</template>
<script>
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import { EditorContent } from '@tiptap/vue-2'
import Link from '@tiptap/extension-link'
import Image from '@tiptap/extension-image'
// 推荐:TableKit 一次引入所有 table 相关扩展
import { TableKit } from '@tiptap/extension-table'
import { TextStyleKit } from '@tiptap/extension-text-style'
import { Color } from '@tiptap/extension-color'
import Highlight from '@tiptap/extension-highlight'
export default {
components: { EditorContent },
data() { return { editor: null } },
mounted() {
this.editor = new Editor({
extensions: [StarterKit, Link, Image, TableKit, TextStyleKit,
Color.configure({
types: ['textStyle'],
}),
// multicolor: true 允许自定义颜色
Highlight.configure({
multicolor: true,
}),
],
content: `
<p>欢迎访问<a href="https://www.hangge.com">hangge.com</a></p>
<p><img src="https://www.hangge.com/blog/images/logo.png" alt="logo"></p>
<p></p>
<p>测试<strong>加粗</strong>测试<em>斜体</em>测试<u>下划线</u>测试<s>删除线</s></p>
<p></p>
<p><span style="color: rgb(255, 102, 255);">修改文字颜色</span></p>
<p><span style="font-size: 22px;">调整文字大小</span></p>
<p></p>
<p>下面是一个3x3表格</p>
<table>
<thead>
<tr><th>ID</th><th>用户名称</th><th>用户年龄</th></tr>
</thead>
<tbody>
<tr><td>1</td><td>刘小龙</td><td>88</td></tr>
<tr><td>2</td><td>张大伟</td><td>66</td></tr>
</tbody>
</table>
`,
})
},
methods: {
async exportViaServer() {
const html = this.editor.getHTML();
console.log('html', html);
// 内联一些样式,提升导出效果
const style = `
<style>
body{ font-family: 'Arial', sans-serif; font-size:12pt; color:#111; }
table { border-collapse: collapse; width:100%;}
th { background: #F9FAFB; font-weight: 600; }
td, th { border: 1px solid #ccc; }
</style>
`;
const fullHtml = `<html><head><meta charset="utf-8">${style}</head>
<body>${html}</body>
</html>`;
try {
const resp = await fetch('http://localhost:8088/export-docx', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ html: fullHtml, filename: 'tiptap-export.docx' })
});
if (!resp.ok) {
const txt = await resp.text();
throw new Error('导出失败:' + txt);
}
const blob = await resp.blob();
// 下载
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'tiptap-export.docx';
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
} catch (err) {
console.error(err);
alert('导出失败:' + err.message);
}
}
},
beforeDestroy() { this.editor?.destroy() }
}
</script>
<style scoped>
.btn { padding:6px 10px; border-radius:4px; border:1px solid #ddd; background:#fff; cursor:pointer }
.editor-box { min-height: 220px; padding: 12px; background:#fff; }
/* ====== 关键:为编辑器内 table 添加样式 ====== */
/* Vue2 scoped 下使用深度选择器确保样式能命中 ProseMirror 渲染的元素 */
/* 如果你的环境不识别 ::v-deep,请试试 >>> 或 /deep/ */
::v-deep .ProseMirror table {
width: 100%;
border-collapse: collapse;
table-layout: auto;
margin: 6px 0;
}
/* 单元格边框与内边距 */
::v-deep .ProseMirror th,
::v-deep .ProseMirror td {
border: 1px solid #d1d5db; /* 灰色边框,可按需调整 #e5e7eb #cbd5e1 等 */
padding: 8px 10px;
vertical-align: middle;
}
/* 表头样式(可选) */
::v-deep .ProseMirror th {
background: #f9fafb;
font-weight: 600;
}
/* 表格在窄屏时不要换行太多(可选) */
::v-deep .ProseMirror td {
word-break: break-word;
}
/* 如果使用 Tailwind 的 prose 可能会有默认的 table 样式,下面加个更高权重以覆盖 */
::v-deep .ProseMirror table,
::v-deep .ProseMirror th,
::v-deep .ProseMirror td {
/* 提高优先级避免被 reset 覆盖 */
border-color: #d1d5db;
}
/* Tiptap 表格单元格被选中时的高亮效果(与官网一致) */
::v-deep .ProseMirror .selectedCell {
background-color: rgba(200, 200, 255, 0.4) !important;
/* 防止 reset 覆盖 */
}
/** 下面是解决列宽拖拽调整时鼠标样式问题的 CSS */
/* 提升优先级:靠右侧 5~8px 时鼠标应变成 col-resize */
::v-deep .ProseMirror table td,
::v-deep .ProseMirror table th {
position: relative;
}
::v-deep .ProseMirror table td::after,
::v-deep .ProseMirror table th::after {
content: "";
position: absolute;
right: -3px; /* 命中区域 */
top: 0;
width: 6px;
height: 100%;
cursor: col-resize !important;
}
</style>
4,运行测试
(1)启动后台服务,访问前端页面,点击“服务端导出为 Word”按钮后,浏览器会自动通过后端生成并下载 Word 文件。

(2)打开下载下来的 docx 文件,可以看到文件内容如下:

全部评论(0)