Vue 3 完美集成 TinyMCE :从零打造专业级企业富文本编辑器

在 Vue 3 项目开发中,选择一个既能满足复杂排版需求,又具备良好扩展性的富文本编辑器是关键。TinyMCE 作为行业老牌王者,其 6.x 版本与 Vue 3 (Composition API) 的结合堪称完美。

本文将为您提供一份全功能封装模板,包含:插件全量引入、中文汉化、Base64 图片自动上传、代码高亮及 SEO 优化配置

为什么 TinyMCE 是 Vue 3 的首选?

  • 生态成熟:官方提供 @tinymce/tinymce-vue 组件,深度适配 Vue 响应式系统。
  • 功能全面:从表格编辑、媒体嵌入到代码预览,开源版已涵盖 90% 的需求。
  • 高度可控:通过 init 配置项,你可以精确控制生成的 HTML 结构,这对于 SEO(搜索引擎优化) 至关重要。

核心实现:Vue 3 + TypeScript 封装组件

以下代码实现了编辑器的按需加载与本地化配置。

1. 安装基础依赖

在项目中执行:

Bash

npm install @tinymce/tinymce-vue tinymce tinymce-i18n

2. 完整组件代码

你可以将此代码保存为 TinymceEditor.vue

代码段

<template>
  <div class="tinymce-editor-wrapper">
    <Editor
      v-model="editorContent"
      :init="editorInit"
      :disabled="disabled"
      license-key="gpl"
      @update:model-value="handleChange"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, watch, computed } from 'vue'
import Editor from '@tinymce/tinymce-vue'

// 核心
import 'tinymce/tinymce'
import 'tinymce/themes/silver'
import 'tinymce/icons/default'
import 'tinymce/models/dom'

// 加载所有开源插件
import 'tinymce/plugins/advlist' // 高级列表
import 'tinymce/plugins/anchor' // 锚点
import 'tinymce/plugins/autolink' // 自动链接
import 'tinymce/plugins/autoresize' // 自动调整高度
import 'tinymce/plugins/autosave' // 自动保存
import 'tinymce/plugins/charmap' // 字符映射
import 'tinymce/plugins/code' // 代码
import 'tinymce/plugins/codesample' // 代码示例
import 'tinymce/plugins/directionality' // 文本方向
import 'tinymce/plugins/emoticons' // 表情符号
import 'tinymce/plugins/emoticons/js/emojis' // 表情符号-表情列表

import 'tinymce/plugins/fullscreen' // 全屏
import 'tinymce/plugins/help' // 帮助
import 'tinymce/plugins/image' // 图片
import 'tinymce/plugins/importcss' // 导入 CSS
import 'tinymce/plugins/insertdatetime' // 插入日期时间
import 'tinymce/plugins/link' // 链接
import 'tinymce/plugins/lists' // 列表
import 'tinymce/plugins/media' // 媒体
import 'tinymce/plugins/nonbreaking' // 非断行空格
import 'tinymce/plugins/pagebreak' // 分页符
import 'tinymce/plugins/preview' // 预览
import 'tinymce/plugins/quickbars' // 快速工具栏
import 'tinymce/plugins/save' // 保存
import 'tinymce/plugins/searchreplace' // 搜索替换
import 'tinymce/plugins/table' // 表格
import 'tinymce/plugins/help/js/i18n/keynav/zh_CN.js' // 帮助-键盘导航中文
import 'tinymce/plugins/visualblocks' // 可视化块
import 'tinymce/plugins/visualchars' // 可视化字符
import 'tinymce/plugins/wordcount' // 字数统计
import 'tinymce/plugins/advlist' // 高级列表
import 'tinymce/plugins/autolink' // 自动链接
import 'tinymce/plugins/code' // 代码
import 'tinymce/plugins/image' // 图片
import 'tinymce/plugins/link' // 链接
import 'tinymce/plugins/lists' // 列表
import 'tinymce/plugins/table' // 表格
import 'tinymce/plugins/wordcount' // 字数统计

// 样式
import 'tinymce/skins/ui/oxide/skin.min.css'
import 'tinymce-i18n/langs/zh_CN'
import contentUiCss from 'tinymce/skins/ui/oxide/content.css?raw'

const props = withDefaults(
  defineProps<{
    modelValue?: string
    height?: number | string
    maxHeight?: number | string
    disabled?: boolean
    placeholder?: string
  }>(),
  {
    modelValue: '',
    height: 500,
    disabled: false,
    placeholder: '请输入内容...',
    key: 'tinymce-editor'
  }
)

const emit = defineEmits<{
  'update:modelValue': [value: string]
  change: [value: string]
}>()

const editorContent = ref(props.modelValue)

// 外部值同步
watch(
  () => props.modelValue,
  (val) => {
    if (val !== editorContent.value) {
      editorContent.value = val || ''
    }
  },
  { immediate: true }
)

const handleChange = (value: string) => {
  emit('update:modelValue', value)
  emit('change', value)
}

const editorInit = computed(() => ({
  height: Number(props.height) || 500,
  width: '100%',
  language: 'zh_CN', // 语言:中文

  image_caption: true, // 图片标题
  quickbars_selection_toolbar: 'bold italic | quicklink h2 h3 blockquote quickimage quicktable', // 快速工具栏:选中文本/表格时浮动出现的快捷按钮
  noneditable_class: 'mceNonEditable', // 非可编辑区域的类名,用于标识内容区域
  toolbar_mode: 'wrap', // 工具栏模式:滑动显示

  // UI
  menubar: false, // 禁用菜单栏
  branding: false, // 禁用品牌标识
  promotion: false, // 禁用推广提示
  statusbar: true, // 显示状态栏

  // 自动保存:每 30 秒触发一次,仅当内容有变化时
  autosave_ask_before_unload: true,
  autosave_interval: '30s',
  autosave_prefix: '{path}{query}-{id}-',
  autosave_restore_when_empty: false,
  autosave_retention: '2m',
  image_advtab: true,

  // 已加载插件列表,按需启用;如需新增插件,请在上方 import 并在 plugins 字符串中追加
  plugins: [
    'advlist',
    'anchor',
    'autolink',
    'autoresize',
    'autosave',
    'charmap',
    'code',
    'codesample',
    'directionality',
    'emoticons',
    'fullscreen',
    'help',
    'image',
    'importcss',
    'insertdatetime',
    'link',
    'lists',
    'media',
    'nonbreaking',
    'pagebreak',
    'preview',
    'quickbars',
    'save',
    'searchreplace',
    'table',
    'visualblocks',
    'visualchars',
    'wordcount'
  ],

  // 工具栏按钮配置
  toolbar:
    'undo redo  | blocks fontfamily fontsize | bold italic underline strikethrough | align numlist bullist | link image | table media | lineheight outdent indent| forecolor backcolor removeformat | charmap emoticons | code fullscreen preview ',

  // 粘贴时自动清理格式,保留基本样式
  paste_webkit_styles: 'color font-size font-weight font-style',
  paste_data_images: true, // 允许粘贴 base64 图片

  // 表格默认样式:无边框、紧凑模式
  table_default_attributes: { class: 'tinymce-table' },
  table_default_styles: { 'border-collapse': 'collapse', width: '100%' },

  // 代码块高亮主题:与主流代码编辑器保持一致
  codesample_languages: [
    { text: 'HTML/XML', value: 'markup' },
    { text: 'JavaScript', value: 'javascript' },
    { text: 'CSS', value: 'css' },
    { text: 'TypeScript', value: 'typescript' },
    { text: 'Vue', value: 'vue' },
    { text: 'Python', value: 'python' },
    { text: 'Java', value: 'java' },
    { text: 'SQL', value: 'sql' },
    { text: 'Shell', value: 'shell' }
  ],

  // 全屏时保留顶部工具栏,避免遮挡
  toolbar_sticky: true,
  toolbar_sticky_offset: 60,

  // 允许拖拽调整高度
  resize: true,
  min_height: Number(props.height) || 500,
  max_height: Number(props.maxHeight) || 0,

  // 自定义快捷键:与主流编辑器保持一致
  custom_shortcuts: {
    'meta+s': 'save', // Cmd/Ctrl + S 触发保存事件
    'meta+shift+s': 'codesample' // Cmd/Ctrl + Shift + S 插入代码块
  },

  // 禁用不可见字符提示,减少视觉干扰
  visualchars_default_state: false,

  // 字体大小下拉选项
  font_size_formats: '12px 14px 16px 18px 20px 24px 30px 36px',

  // 行高下拉选项
  line_height_formats: '1 1.2 1.4 1.6 1.8 2 2.5 3',

  // 颜色预设:与系统主题保持一致
  color_map: [
    '000000',
    'Black',
    '333333',
    'Dark gray',
    '5A5A5A',
    'Gray',
    '888888',
    'Light gray',
    'CCCCCC',
    'Silver',
    'FFFFFF',
    'White',
    'FF4D4F',
    'Red',
    'FFA940',
    'Orange',
    'FADB14',
    'Yellow',
    '73D13D',
    'Green',
    '40A9FF',
    'Blue',
    '9254DE',
    'Purple'
  ],

  // 字体,显示全部
  font_family_formats:
    '宋体=宋体;黑体=黑体;微软雅黑=微软雅黑;仿宋=仿宋;楷体=楷体;幼圆=幼圆;隶书=隶书;Arial=Arial;Arial Black=Arial Black;Comic Sans MS=Comic Sans MS;Courier New=Courier New;Georgia=Georgia;Helvetica=Helvetica;Impact=Impact;Lucida Console=Lucida Console;Lucida Sans Unicode=Lucida Sans Unicode;Palatino Linotype=Palatino Linotype;Tahoma=Tahoma;Times New Roman=Times New Roman;Trebuchet MS=Trebuchet MS;Verdana=Verdana;Wingdings=Wingdings;',

  // 图片上传失败后回退为 base64,保证用户感知一致
  images_upload_error_handler: (message: any) => {
    console.warn('[TinyMCE] 图片上传失败,已回退为 base64 模式:', message)
  },

  // 编辑器初始化完成后的回调
  setup: (editor: any) => {
    editor.on('init', () => {
      // 自动聚焦到编辑器
      editor.focus()
    })
    // 监听保存快捷键
    editor.addShortcut('meta+s', '保存内容', () => {
      emit('change', editorContent.value)
    })
  },
  placeholder: props.placeholder, // 占位符

  object_resizing: 'img', // 图片调整大小:仅允许水平调整

  content_style: `${contentUiCss}
    body {
      font-family: '微软雅黑', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial;
      font-size: 14px;
      line-height: 1.6;
      color: #333;
    }
    p {
      margin: 0 0 1em;
    }
  `,

  /**
   * 本地 Base64 上传
   */
  images_upload_handler: (blobInfo: any) =>
    new Promise((resolve, reject) => {
      const reader = new FileReader()
      reader.onload = () => resolve(reader.result as string)
      reader.onerror = () => reject('Image read failed')
      reader.readAsDataURL(blobInfo.blob())
    })
}))
</script>

<style scoped>
.tinymce-editor-wrapper {
  width: 100%;
}

:deep(.tox-tinymce) {
  border-radius: 6px;
  border: 1px solid var(--el-border-color);
}

:deep(.tox-edit-area__iframe) {
  background-color: #fff;
}
</style>

针对 SEO 的深度优化策略

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇