xdd-rich-editor-vue3

A vue3 high quality rich text editor
2,223
Install
npm install xdd-rich-editor-vue3
Latest Version:2.1.21
Vue:3
License:MIT
Last Updated:Oct 18, 2026
Links: npm
Maintainer: linfso

tiptap-editor-vue3

简体中文 | English

基于 Tiptap 3 + Vue 3 + TypeScript + Vite 开发的高质量富文本编辑器组件库。

  • 开箱即用:工具栏、气泡菜单、表格右键菜单、目录侧栏、图片浮动选中等全部内置
  • 内置中英文国际化(基于 vue-i18n)
  • 支持浅色 / 深色 / 跟随系统三种主题,CSS 变量可自由定制
  • 完整 TypeScript 类型,支持按需导入与 Hooks 二次组装
  • 在线演示:https://zekofront.github.io/tiptap-editor-vue3/

功能清单

分类 已实现功能
文字标记 加粗、斜体、下划线、删除线、高亮、字体颜色、背景颜色、上标、下标、行内代码
块级节点 标题(H1–H6)、段落、无序列表、有序列表、任务列表(可勾选)、引用块、分隔线、代码块(lowlight 语法高亮)、图片、表格、超链接、Emoji(: 触发)
布局与行为 文本对齐(左 / 中 / 右 / 两端)、行间距、拖拽手柄(拖动块)、RTL 阿拉伯文方向
编辑体验 顶部工具栏、选区气泡菜单、图片气泡菜单、表格右键菜单与“边框和底纹”、目录侧栏(文档导航)、占位符、字符数限制、撤销 / 重做、清空文档、Word 风格多级列表样式、外部 @ 人员弹窗
组件级能力 只读视图(XddRichEditorView)、useEditor / useEditorEvents Hooks 自行组装、浅色 / 深色 / 系统主题、zh-CN / en-US 国际化、可自定义气泡菜单按钮、动态上传地址 / Token、CSS 变量主题定制

安装

npm install tiptap-editor-vue3
# 或
pnpm add tiptap-editor-vue3
# 或
yarn add tiptap-editor-vue3

项目需要 Vue 3 环境(仅 Vue 为外部依赖,其它依赖如 Tiptap、naive-ui、vue-i18n 等已打入产物)。


快速开始

方式一:全局安装(推荐)

// main.ts
import { createApp } from "vue";
import XddRichEditorVue3 from "tiptap-editor-vue3";
import "tiptap-editor-vue3/dist/css/style.css";
import App from "./App.vue";

const app = createApp(App);
app.use(XddRichEditorVue3);
app.mount("#app");
<!-- App.vue -->
<template>
    <XddRichEditorVue3
        v-model="content"
        :is-editable="true"
        :character-count="20000"
        locale="zh-CN"
        theme="system"
        placeholder="请输入内容..."
        @ready="onReady"
        @update="onUpdate"
    />
</template>

<script setup lang="ts">
import { ref } from "vue";
import type { Editor, EditorUpdatePayload } from "tiptap-editor-vue3";

const content = ref("<p>初始内容</p>");

const onReady = (editor: Editor) => {
    console.log("editor ready", editor);
};

const onUpdate = ({ html, json }: EditorUpdatePayload) => {
    console.log(html, json);
};
</script>

使用 v-model 双向绑定内容。传入 HTML 字符串(默认)或 Tiptap JSON 对象;通过 outputFormat prop 切换 update:modelValue 发送的格式。

方式二:按需导入

<template>
    <XddRichEditorVue3 v-model:is-editable="editable" @update="onUpdate" />
</template>

<script setup lang="ts">
import { ref } from "vue";
import { XddRichEditorVue3, type EditorUpdatePayload } from "tiptap-editor-vue3";
import "tiptap-editor-vue3/dist/css/style.css";

const editable = ref(true);
const onUpdate = (p: EditorUpdatePayload) => console.log(p.html);
</script>

只读视图

<template>
    <XddRichEditorView :editor="editor" />
</template>

<script setup lang="ts">
import { XddRichEditorView, useEditor } from "tiptap-editor-vue3";
import StarterKit from "@tiptap/starter-kit";

const { editor } = useEditor({
    extensions: () => [StarterKit],
    content: () => "<p>Hello</p>",
    editable: () => false
});
</script>

Props(参数列表)

参数 类型 默认值 说明
modelValue (v-model) string | JSONContent | null "" 编辑器内容;支持 HTML 字符串或 Tiptap JSON
outputFormat 'html' | 'json' 'html' update:modelValue 发送的格式
defaultConfig Record<string, any> | null null 透传给底层 new Editor(...) 的额外配置
extensions AnyExtension[] [] 自定义扩展数组;不传则使用内置完整扩展集
isEditable boolean true 是否可编辑
isShowToolbar boolean true 是否显示顶部工具栏
isEnabledContent boolean true 渲染内容区(关闭时仅工具栏)
characterCount number | string 10000 字符数限制;超过禁止输入
uploadUrl string | (() => string) undefined 图片、文件、视频上传接口地址;函数形式会在每次上传前重新取值
uploadToken string | (() => string) undefined 上传令牌;函数形式会在每次上传前重新取值
uploadTokenHeader string Authorization 令牌请求头名称
uploadTokenPrefix string Bearer 令牌前缀;传空字符串时直接发送原始 Token
uploadFieldName string file multipart 上传字段名
locale 'zh-CN' | 'en-US' 'zh-CN' 界面语言(工具栏提示、表格菜单、占位符等)
theme 'system' | 'light' | 'dark' 'system' 主题色彩;system 跟随操作系统 prefers-color-scheme
placeholder string i18n 默认值 空文档占位符;不传时使用当前语言默认值
customDubbleMenus IDubbleMenu[] [] 自定义气泡菜单按钮列表
editorWrapperClass string | any[] | Record<string, any> undefined 最外层容器自定义类
editorToolkitClass string | any[] | Record<string, any> undefined 工具栏自定义类
editorContentClass string 'custom-tiptap-content' 内容区 ProseMirror 容器类
editorContentsNavClass string | any[] | Record<string, any> undefined 目录侧栏自定义类

IDubbleMenu 等类型从包内导出:import type { IDubbleMenu, EditorProps } from "tiptap-editor-vue3"。


Events(事件列表)

事件 负载(payload) 说明
update:modelValue string | JSONContent 内容变化(v-model 同步);默认 HTML 字符串,outputFormat="json" 时为 JSON
ready editor: Editor 编辑器实例创建完成,等价于 Tiptap create
update { editor, html, json } 内容变化;html / json 已自动获取,无需再调用 editor.getHTML()
selection-update { editor } 光标 / 选区变化
transaction { editor, transaction } 任意 ProseMirror 事务
focus { editor, event: FocusEvent } 编辑器获得焦点
blur { editor, event: FocusEvent } 编辑器失去焦点
before-create { editor } 实例创建前
content-error { editor, error } 解析初始内容失败
mention-request Tev3MentionRequestPayload 输入 @ 或点击工具栏 @ 时触发,外部弹窗选择人员后调用 payload.insert()
destroy — 实例销毁

update 事件负载类型:

import type { EditorUpdatePayload } from "tiptap-editor-vue3";

interface EditorUpdatePayload {
    editor: Editor;
    html: string;
    json: JSONContent;
}

外部 @ 人员弹窗

组件不再内置人员建议列表,也不会弹出“张三、李四”等默认数据。输入 @ 或点击工具栏 @ 后会触发外部弹窗请求:

<template>
    <XddRichEditorVue3
        v-model="content"
        @mention-request="openMentionPicker"
    />

    <n-modal v-model:show="mentionVisible" preset="card" title="选择人员">
        <button
            v-for="item in people"
            :key="item.id"
            @click="selectMention(item)"
        >
            {{ item.name }}
        </button>
    </n-modal>
</template>

<script setup lang="ts">
import { ref } from "vue";
import type {
    Tev3MentionItem,
    Tev3MentionRequestPayload
} from "tiptap-editor-vue3";

const people: Tev3MentionItem[] = [];
const mentionVisible = ref(false);
const mentionRequest = ref<Tev3MentionRequestPayload | null>(null);

const openMentionPicker = (payload: Tev3MentionRequestPayload) => {
    mentionRequest.value = payload;
    mentionVisible.value = true;
};

const selectMention = (item: Tev3MentionItem) => {
    mentionRequest.value?.insert(item);
    mentionVisible.value = false;
};
</script>

payload 包含 query、from、to、source 和 insert()。insert({ id, name }) 会删除输入框中的 @ 并写入人员节点,最终 HTML 中保留 data-id 与 data-name。

也可以通过 defaultConfig.mention.onRequest 接管弹窗请求:

const defaultConfig = {
    mention: {
        onRequest: (payload) => {
            openMentionPicker(payload);
        }
    }
};

表格边框和底纹

在表格单元格上点击右键,选择“边框和底纹”。面板支持:

  • 无、方框、全部、网格、自定义
  • 线型、颜色、粗细
  • 上、下、左、右、内部边框
  • 应用于整张表格或当前选区单元格
  • 3×3 实时预览

表格插入默认按每列 150px 计算初始宽度,后续可通过右下角拖动柄自由缩放。


上传接口

上传地址和 Token 支持通过组件属性动态传入,函数会在每次上传前重新执行:

<XddRichEditorVue3
    :upload-url="getUploadUrl"
    :upload-token="getUploadToken"
    upload-token-header="X-Token"
    :upload-token-prefix="''"
    upload-field-name="file"
/>

默认按以下响应结构读取上传结果中的 url:

{
    "code": 200,
    "msg": "操作成功",
    "fileName": "demo.pdf",
    "url": "https://example.com/static/demo.pdf",
    "fileId": "dbff10a5-8bf5-46a8-8eda-9e77dfe75256"
}

图片、文件和视频内容只保存服务器返回的 URL,不把 base64 或本地 Blob 写入正文。


导入文档(TXT 和 Word)

编辑器支持通过工具栏中的导入按钮导入 TXT 和 Word(DOC、DOCX)文档的内容。点击导入按钮后,会出现一个下拉菜单,可以选择导入 TXT 文件或 Word 文件。选择文件后,内容会被解析并插入到编辑器中。成功和错误信息会使用本地化的消息进行提示。

当前版本默认隐藏 Word 导入 / 导出入口,底层实现仍保留;需要启用时,注册 Imports 扩展并恢复工具栏导出按钮。

图片上传

图片上传统一通过 defaultConfig.uploadImage 配置,规则非常简单:

  • imageLink / customUpload 是函数 → 走用户自定义逻辑,弹窗把 URL / 文件交给你,由你自己 editor.commands.setImage(...) 插入;
  • 否则 → 走组件内部默认实现(链接 Tab 直接 setImage({ src })、上传 Tab 转 base64 插入)。

accept / maxSize / maxCount 始终生效,组件先做校验再交给用户回调;用户没选图片就点"立即上传"时会弹本地化提示。

自定义图片上传,严格遵循下面配置规则,否则不生效:

<template>
    <XddRichEditorVue3
        v-model="content"
        :default-config="defaultConfig"
        @ready="onReady"
    />
</template>

<script setup lang="ts">
import { shallowRef } from "vue";
import type { Editor, Tev3DefaultConfig } from "tiptap-editor-vue3";

const editors = shallowRef<Editor | null>(null);

const onReady = (editor: Editor) => {
    editors.value = editor;
};

const defaultConfig: Tev3DefaultConfig = {
    uploadImage: {
        accept: "image/png,image/jpeg,image/webp",
        maxSize: 5 * 1024 * 1024, // 单张最大 5MB
        maxCount: 9,
        imageLink: (link: string) => {
            console.log(link, editors.value, "imageLink");
            editors.value?.commands.setImage({ src: link });
        },
        customUpload: async (files) => {
            console.log(files, editors.value, "customUpload");
            for (let i = 0; i < files.length; i++) {
                if (files[i]) setImageOne(files[i] as File);
            }
        }
    }
};

const setImageOne = (file: File) => {
    const reader = new FileReader();
    reader.onload = event => {
        const base64 = event.target?.result as string;
        editors.value?.commands.setImage({ src: base64 });
    };
    reader.readAsDataURL(file);
};
</script>
字段 类型 默认值 说明
accept string image/png,image/jpeg,image/gif,image/webp,image/svg+xml HTML accept 语法,过滤选择器并校验类型
maxSize number 10 * 1024 * 1024 单张图片大小上限(字节),超过弹错误提示并跳过
maxCount number Infinity 单次最多选择图片数量
imageLink (url: string) => void — "图片链接" 选项卡确认时的回调。是函数 → 走用户自定义;否则走默认 setImage({ src })
customUpload (files: File[] | FileList) => void | Promise<void> — "上传图片" 选项卡确认时的回调。是函数 → 走用户自定义;否则文件转 base64 直接插入

导出类型:Tev3DefaultConfig、Tev3UploadImageConfig。


主题(明暗模式)

通过 theme prop 控制:

<XddRichEditorVue3 theme="system" />
<XddRichEditorVue3 theme="light" />
<XddRichEditorVue3 theme="dark" />
  • system:自动跟随系统 prefers-color-scheme,系统切换会实时响应;
  • light / dark:固定使用对应主题。

底层通过 data-tev3-theme="light|dark" 数据属性切换,所有颜色基于 CSS 变量。如需自定义品牌色,覆盖以下常用变量即可:

.vue3-tiptap-editor[data-tev3-theme="light"] {
    --theme-color: #2080f0;
    --tev3-toolbar-bg: #fafbfc;
}

.vue3-tiptap-editor[data-tev3-theme="dark"] {
    --theme-color: #36ad6a;
    --tev3-editor-bg: #15161a;
}

JS 端可使用 useTheme() 自行实现切换:

import { ref } from "vue";
import { useTheme, type Tev3Theme } from "tiptap-editor-vue3";

const theme = ref<Tev3Theme>("system");
const { resolvedTheme } = useTheme(() => theme.value); // -> 'light' | 'dark'

国际化

内置 zh-CN / en-US,通过 locale prop 切换;任意时刻也可调用 setLocale() 全局切换。

import { setLocale, t } from "tiptap-editor-vue3";

setLocale("en-US");
console.log(t("toolbar.bold")); // "Bold"
<XddRichEditorVue3 locale="en-US" />

切换 locale 时工具栏、表格菜单、目录、占位符会自动刷新。

如需自定义文案,可在自己的项目中调用 tev3I18n.global.mergeLocaleMessage('zh-CN', { ... }) 覆盖。


进阶:使用 Hooks 自行组装

若内置工具栏不满足需求,可直接复用 useEditor / useEditorEvents 自己组合 UI:

<template>
    <div>
        <button @click="editor?.chain().focus().toggleBold().run()">Bold</button>
        <XddRichEditorView :editor="editor" />
    </div>
</template>

<script setup lang="ts">
import {
    XddRichEditorView,
    useEditor,
    useEditorEvents,
    type EditorUpdatePayload
} from "tiptap-editor-vue3";
import StarterKit from "@tiptap/starter-kit";

const emit = defineEmits<{ (e: "update", p: EditorUpdatePayload): void }>();

const { editor, editable, toggleEditable } = useEditor({
    extensions: () => [StarterKit],
    content: () => "<p>Hello</p>",
    editable: () => true
});

useEditorEvents(editor, { emit });
</script>

useEditor 返回:

字段 类型 说明
editor ShallowRef<Editor | null> 实例(onMounted 后就绪)
editable Ref<boolean> 可编辑状态
rtl Ref<boolean> RTL 排版
toggleEditable () => void 切换只读
toggleRtl () => void 切换 RTL
create / destroy () => void 手动控制生命周期

包导出

// 组件
export { XddRichEditorVue3, XddRichEditorView };

// Composables
export { useEditor, useEditorEvents, useTev3I18n };

// Props / 类型
export { editorProps };
export type {
    EditorProps,
    UseEditorOptions,
    UseEditorReturn,
    EditorUpdatePayload,
    EditorEventName,
    Tev3Locale
};

// 国际化
export { tev3I18n, t, setLocale, getLocale, installTev3I18n, SUPPORTED_LOCALES, zhCN, enUS };

// 主题
export { SUPPORTED_THEMES, DEFAULT_THEME, resolveTheme, useTheme };
export type { Tev3Theme, Tev3ResolvedTheme };

// Tiptap 常用 API(避免重复装包)
export { Editor, EditorContent, NodeViewWrapper, mergeAttributes };
export type { XddRichEditor, JSONContent, Content, AnyExtension };

开发命令

npm run dev           # 启动本地 playground
npm run build         # 构建组件库(dist/)
npm run build:demo    # 构建 playground 演示站(dist-demo/)
npm run preview:demo  # 本地预览演示站
npm run fmt           # 格式化(oxfmt)
npm run lint          # 代码检查(oxlint)

在线演示(GitHub Pages)


参考链接


License

MIT

Related Packages

vue-bulma-quill

Your powerful, rich text editor for Vue

11,507 32
eeed-editor

基于Vue3 + Tiptap的极简富文本编辑器

6,872 2
@codemirror/lang-vue

Vue template support for the CodeMirror code editor

71,371,417 29
@gaoding/collage-editor

An editor to help user to create collage photos easily

4,989

Version History

Version Released License
2.1.21 MIT
2.1.19 MIT
2.1.17 MIT
2.1.12 MIT
2.1.11 MIT