深入掌握 vue-vben-admin 的 VbenTiptap 富文本编辑器:API、图片上传与自定义扩展实战指南
【免费下载链接】vue-vben-adminA modern vue admin panel built with Vue3, Shadcn UI, Vite, TypeScript, and Monorepo. It's fast!项目地址: https://gitcode.com/GitHub_Trending/vu/vue-vben-admin
本文基于 vue-vben-admin 仓库中的 Vben Tiptap Rich Text Editor 文档,结合 tiptap 插件源码 与官方 基础用法 Demo、图片上传 Demo,系统讲解
VbenTiptap与VbenTiptapPreview两个组件的能力边界、完整 API、工具栏功能、图片上传流程以及自定义扩展机制,让你能够在自己的业务页面中直接复用它,甚至基于它二次封装自己的富文本组件。
组件概览:一个基于 Tiptap 的现代化富文本编辑器
VbenTiptap是 vue-vben-admin 基于 Tiptap 封装的开箱即用富文本编辑器组件,支持富文本格式化、图片插入与图片上传,并内置一套完整的可视化工具栏。与它配套的还有一个只读预览组件VbenTiptapPreview,用于在详情页、卡片等场景展示编辑器产生的 HTML 内容。
组件定义与导出位于 packages/effects/plugins/src/tiptap/index.ts,通过@vben/plugins/tiptap统一对外提供:
export { default as VbenTiptapPreview } from './preview.vue'; export { default as VbenTiptap } from './tiptap.vue'; export * from './types';所有 Props、事件与扩展相关的 TypeScript 类型均收敛在 packages/effects/plugins/src/tiptap/types.ts,下文介绍的每个配置项都能在这里找到权威定义。
需要特别说明的是:框架提供的组件并不是约束。官方文档明确提示,如果当前实现无法满足需求,可以直接使用原生组件或自行封装,框架组件完全可以按需取舍。
基础用法:最小可运行的富文本表单
VbenTiptap的核心交互模型是v-model,绑定的值即编辑器内容的 HTML 字符串。官方 基础用法 Demo 给出了最小用法:
<script lang="ts" setup> import { ref } from 'vue'; import { VbenTiptap } from '@vben/plugins/tiptap'; const content = ref('<p>开始编辑你的内容...</p>'); </script> <template> <div> <VbenTiptap v-model="content" /> <div class="mt-4"> <p class="text-sm text-gray-500">当前内容:</p> <pre class="mt-2 p-2 bg-gray-100 rounded text-xs overflow-auto max-h-40"> {{ content }} </pre> </div> </div> </template>从组件源码 tiptap.vue 可以看到v-model的双向同步机制:编辑器onUpdate时读取editor.getHTML(),与当前modelValue不一致才回写,并同时派发change事件;反过来,当外部modelValue变化且与编辑器内容不同时,通过editor.commands.setContent(nextValue, { emitUpdate: false })主动重置内容(见 tiptap.vue)。这样既避免了循环更新,也保证了外部程序化修改内容时编辑器能即时响应。
完整 API 手册
VbenTiptap Props
下表完整收录 官方文档 与 types.ts 中定义的全部 Props:
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
modelValue(v-model) | 编辑器内容(HTML 字符串) | string | '' |
editable | 编辑器是否可编辑 | boolean | true |
toolbar | 是否显示工具栏 | boolean | true |
previewable | 是否显示预览按钮 | boolean | true |
placeholder | 占位提示文本 | string | 多语言ui.tiptap.placeholder |
minHeight | 内容区最小高度 | number \| string | 240 |
maxHeight | 内容区最大高度 | number \| string | 400 |
extensions | 自定义 Tiptap 扩展 | Extensions | - |
imageUpload | 图片上传配置 | ImageUploadOptions | - |
几点从源码确认的细节:
- 高度支持数字与字符串两种写法。源码中用
computed将数字统一转换为 px 字符串(见 tiptap.vue),并通过 CSS 变量--vben-tiptap-min-height/--vben-tiptap-max-height注入内容区样式,也就是说你也可以直接传'300px'或'50vh'这样的 CSS 尺寸。 - 占位符默认走国际化。默认值来自
$t('ui.tiptap.placeholder')(见 tiptap.vue),内置的多语言文案位于 packages/locales/src/langs 的ui.tiptap命名空间下。 editable支持运行时切换。源码通过watch监听props.editable并调用editor.setEditable(editable)(见 tiptap.vue),因此可以配合表单的「编辑/只读」模式动态切换。
VbenTiptap Events
组件只暴露一个change事件,参数类型为VbenTiptapChangeEvent(见 types.ts):
interface VbenTiptapChangeEvent { html: string; // HTML 内容 json: JSONContent; // JSON 结构(ProseMirror 文档树) text: string; // 纯文本内容 }该事件在编辑器每次内容更新时触发(见 tiptap.vue)。三种数据形态各有用途:html用于提交给后端存储,json适合做结构化数据迁移或程序化分析,text则可以直接用于字数统计或搜索摘要。
VbenTiptapPreview Props
预览组件用于只读展示 HTML 内容,其 Props(见 types.ts 与 官方文档):
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
content | 要预览的 HTML 内容 | string | '' |
minHeight | 最小高度 | number \| string | 160 |
class | 自定义类名 | any | - |
实现上,preview.vue 通过v-html渲染内容,并复用与编辑器内容区相同的vben-tiptap-content样式类,保证预览样式与编辑效果一致。因此在使用时必须注意content来自可信来源,避免直接渲染未经净化的用户输入造成 XSS 风险。
工具栏功能全景
工具栏定义在 toolbar.ts 的createToolbarGroups中,按功能分组渲染(分组间以竖线分隔),并在 tiptap.vue 中通过VbenIconButton与VbenPopover组合呈现。工具栏按钮均带有禁用态判断(can校验)与激活态高亮(isActive校验),交互细节完整。
格式化(Formatting)
| 功能 | 说明 | 底层命令 |
|---|---|---|
| 撤销 / 重做 | 撤销或重做编辑操作 | undo()/redo() |
| 清除格式 | 移除选中文本的全部格式 | clearNodes().unsetAllMarks() |
| 加粗 | 粗体 | toggleBold() |
| 斜体 | 斜体 | toggleItalic() |
| 下划线 | 下划线 | toggleUnderline() |
| 删除线 | 删除线 | toggleStrike() |
| 行内代码 | 行内代码标记 | toggleCode() |
结构(Structure)
| 功能 | 说明 | 底层命令 |
|---|---|---|
| 标题 | 段落与 H1-H4 标题切换 | toggleHeading({ level }) |
| 有序列表 | 编号列表 | toggleOrderedList() |
| 无序列表 | 项目符号列表 | toggleBulletList() |
| 引用块 | 引用样式 | toggleBlockquote() |
| 代码块 | 多行代码块 | toggleCodeBlock() |
标题按钮是一个带当前状态指示的下拉菜单:源码中getHeadingTriggerText会实时读取光标所在位置的标题级别并显示为P/H1~H4(见 toolbar.ts),激活项右侧会显示Check图标。
链接与图片(Links & Images)
| 功能 | 说明 |
|---|---|
| 插入链接 | 弹出prompt输入框插入或编辑超链接 |
| 移除链接 | 移除选中文本的链接 |
| 插入图片 | 通过 URL 插入图片 |
链接处理有一个值得注意的细节:handleLinkAction会先读取当前光标处link的 href 作为默认值回填到输入框,URL 为空时直接unsetLink()移除链接;normalizeLinkUrl会对未带协议的输入自动补全为https://前缀(见 toolbar.ts)。也就是说,用户在弹窗里输入example.com,最终落库的是https://example.com。
样式(Style)
| 功能 | 说明 |
|---|---|
| 文字颜色 | 预设调色板设置文字颜色 |
| 高亮颜色 | 设置文字背景高亮色 |
两个颜色工具都通过 Popover 弹出预设调色板(palette)。文字色与高亮色的预设色板定义在 toolbar.ts:基础色使用主题 CSS 变量(foreground/warning/success/destructive),并与@vben/preferences导出的COLOR_PRESETS合并;高亮色在此基础上叠加透明度。当前生效颜色会在工具栏按钮底部以一条小色条(indicatorColor)直观显示,并支持一键「清除」恢复默认。
对齐(Alignment)
| 功能 | 说明 | 底层命令 |
|---|---|---|
| 左对齐 | 文本左对齐 | setTextAlign('left') |
| 居中对齐 | 文本居中 | setTextAlign('center') |
| 右对齐 | 文本右对齐 | setTextAlign('right') |
其他
- 预览(Preview):点击后通过
useVbenModal打开一个全屏可关的模态框,内部用VbenTiptapPreview以 320px 最小高度渲染当前内容快照(见 tiptap.vue),适合在提交前确认排版效果。预览按钮由previewable控制。
图片上传:三种触发方式与完整上传链路
配置imageUpload后,工具栏的图片按钮会从单一的「插入 URL」变为下拉菜单,提供「上传(UPL)」与「URL」两个选项;同时编辑器会启用拖拽与粘贴上传能力。
官方 图片上传 Demo 演示了三种触发方式:
<script lang="ts" setup> import { ref } from 'vue'; import { type ImageUploadOptions, VbenTiptap } from '@vben/plugins/tiptap'; const content = ref(''); // Mock upload function with progress simulation const imageUpload: ImageUploadOptions = { accept: 'image/jpeg,image/png,image/gif,image/webp', maxSize: 5 * 1024 * 1024, // 5MB upload: async (_file, onProgress) => { // Simulate upload progress for (let i = 0; i <= 100; i += 10) { await new Promise((resolve) => setTimeout(resolve, 100)); onProgress?.(i); } // Return a mock image URL (using picsum for demo) return `https://picsum.photos/seed/${Date.now()}/800/400`; }, onUploadError: (error) => { console.error('Upload error:', error); }, }; </script> <template> <div> <VbenTiptap v-model="content" :image-upload="imageUpload" placeholder="尝试拖拽或粘贴图片..." /> </div> </template>ImageUploadOptions 配置详解
类型定义见 types.ts:
interface ImageUploadOptions { /** 允许的文件类型,默认 'image/*' */ accept?: string; /** 最大文件大小(字节),默认 5MB */ maxSize?: number; /** 上传失败回调,未提供时使用 alert 弹窗提示 */ onUploadError?: (error: unknown) => void; /** 上传函数,返回图片 URL,可选 onProgress 回调报告上传进度 */ upload: ( file: File, onProgress?: (percent: number) => void, ) => Promise<string>; }三种上传触发方式
- 文件选择:点击工具栏「上传」按钮,组件动态创建并点击一个隐藏的
<input type="file">(accept 取自配置),选中文件后走上传流程。 - 拖拽上传:通过 ProseMirror Plugin 的
handleDrop拦截拖拽事件(见 extensions.ts),并计算鼠标落点坐标将图片插入到对应位置。 - 粘贴上传:通过
handlePaste拦截剪贴板中的图片文件(见 extensions.ts),实现「复制图片 → 直接粘贴进编辑器」的流畅体验。
上传进度与占位展示
上传过程中,组件会在光标位置插入一张使用 blob URL 作为 src 的占位图片,并在其上层叠加加载指示:上传开始时显示 spinner 动画;一旦upload函数通过onProgress回调报告进度(percent > 0),则切换为进度条并实时刷新宽度(见 extensions.ts 的addNodeView实现)。上传完成后,blob URL 会被替换为upload函数返回的真实 URL,并移除data-uploading属性。
文件校验规则
validateFile(见 extensions.ts)实现了两层校验:
maxSize:文件字节数超过上限即报「文件过大」;accept:支持逗号分隔的 MIME 类型列表与type/*通配写法(如image/jpeg,image/png),逐项匹配file.type。
校验失败会调用onUploadError回调;若未配置,则回退到alert弹窗提示(见 extensions.ts)。
三个必须注意的使用红线
官方文档明确警告以下三点:
- 仅支持单图上传:拖拽或粘贴多张图片时,会弹出提示并只处理第一张图片(见 extensions.ts)。
- 上传过程中不要保存编辑器内容:此时图片 src 是临时 blob URL,
getHTML()拿到的内容无法持久化;应等待上传完成、src 替换为真实 URL 后再提交。 - 使用自定义
extensions时图片上传功能不可用:工具栏不会显示上传选项(详见下节)。
组件还在销毁时统一执行URL.revokeObjectURL清理所有 blob URL 并销毁编辑器实例(见 tiptap.vue),避免内存泄漏。
自定义扩展:完全掌控编辑器能力
通过extensionsProp 可以传入自定义 Tiptap 扩展数组,完全替换默认扩展集。官方示例:
<script setup lang="ts"> import { VbenTiptap } from '@vben/plugins/tiptap'; import StarterKit from '@tiptap/starter-kit'; import Underline from '@tiptap/extension-underline'; const extensions = [ StarterKit, Underline, // Other extensions... ]; </script> <template> <VbenTiptap v-model="content" :extensions="extensions" /> </template>传入extensions后,组件将不再调用createDefaultTiptapExtensions,默认扩展配置(标题级别、链接、对齐、颜色、高亮、占位符等)全部失效,你需要自行配置所有必需功能。同时注意两点限制(源码中亦有对应逻辑):
- 工具栏的图片上传选项只在使用默认扩展时出现:
tiptap.vue中const effectiveImageUpload = props.extensions ? undefined : props.imageUpload(见 tiptap.vue),因为自定义扩展可能并不包含uploadImage命令; - 工具栏的按钮可用性(
can)、激活态(isActive)完全由对应扩展是否注册决定,缺少扩展的按钮会自动置灰或不可用。
源码视角:默认扩展集包含什么
createDefaultTiptapExtensions(见 extensions.ts)由以下部分组成,了解它有助于你在自定义扩展时做能力对照:
| 扩展 | 关键配置 |
|---|---|
StarterKit | 标题级别限制为 H1-H4(heading.levels: [1,2,3,4]),内置 link 关闭(由下方 Link 扩展接管) |
TextAlign | 作用于heading与paragraph两类节点 |
TextStyle+Color | 文字颜色,作用于textStyle类型 |
Highlight | 开启多色高亮(multicolor: true) |
Link | autolink自动识别链接、defaultProtocol: 'https'、openOnClick: false、支持mailto/tel协议 |
Image/ 自定义 Image | 默认允许 base64,图片节点附加vben-tiptap__image类名;配置imageUpload时替换为扩展了上传命令与拖拽/粘贴插件的自定义 Image |
Placeholder | 占位文本(支持data-placeholder属性渲染) |
场景组合:把组件放进真实表单
将以上能力组合起来,一个典型的「文章编辑 + 详情展示」业务场景可以这样写:
<script lang="ts" setup> import { ref } from 'vue'; import { type ImageUploadOptions, VbenTiptap, VbenTiptapPreview, } from '@vben/plugins/tiptap'; const content = ref(''); const imageUpload: ImageUploadOptions = { maxSize: 2 * 1024 * 1024, upload: async (file, onProgress) => { const formData = new FormData(); formData.append('file', file); // 对接你的真实上传接口,进度通过 onProgress 回传 return await uploadApi(formData, onProgress); }, }; const editable = ref(true); </script> <template> <!-- 编辑态 --> <VbenTiptap v-model="content" :editable="editable" :image-upload="imageUpload" :min-height="320" :max-height="480" placeholder="请输入文章正文..." @change="({ text }) => console.log('字数:', text.length)" /> <!-- 只读展示态 --> <VbenTiptapPreview :content="content" :min-height="200" /> </template>小结
VbenTiptap系列组件为 vue-vben-admin 项目提供了「开箱即用、可深度定制」的富文本能力:内置完整工具栏覆盖格式化、结构、链接、颜色与对齐等高频编辑需求;imageUpload配置一处即可同时获得选择、拖拽、粘贴三种上传方式与进度反馈;extensions又保留了 Tiptap 生态的全部扩展空间。配合VbenTiptapPreview与change事件的多形态输出,可以轻松构建从编辑到预览、从存储到统计的完整内容链路。
相关资源:组件文档见 docs/src/en/components/common-ui/vben-tiptap.md,组件实现见 packages/effects/plugins/src/tiptap/,可运行示例见 docs/src/demos/vben-tiptap/basic/index.vue 与 docs/src/demos/vben-tiptap/image-upload/index.vue。
【免费下载链接】vue-vben-adminA modern vue admin panel built with Vue3, Shadcn UI, Vite, TypeScript, and Monorepo. It's fast!项目地址: https://gitcode.com/GitHub_Trending/vu/vue-vben-admin
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考