Tinymce在vue中的插件编写

604 阅读2分钟

1 背景

项目中有一个推送富文本生成的需求,选取了tinymce作为富文本编辑器。但是tinymce原生功能无法完全满足需求,因此需要额外编写插件。

2 踩坑

我也不是特别理解为啥我选取的两种写法都会有报错,如下所示,一种是基于@tinymce/tinymce-vue,一种是tinymce/tinymce

2.1 写一个随意的插件

根据官网demo,新建一个文件plugin.js

tinymce.PluginManager.add('video', function(editor) {
  var openDialog = function () {
    return editor.windowManager.open({
      title: '这里是弹窗标题',
      body: {
        type: 'panel',
        items: [
          {
            type: 'input',
            name: 'title',
            label: 'Title'
          }
        ]
      },
      buttons: [
        {
          type: 'cancel',
          text: 'Close'
        },
        {
          type: 'submit',
          text: 'Save',
          primary: true
        }
      ],
      onSubmit: function (api) {
        var data = api.getData();
        // 将输入框内容插入到内容区光标位置
        editor.insertContent('插入的文字是: ' + data.title);
        api.close();
      }
    });
  };
  
  // 注册一个工具栏按钮名称
  editor.ui.registry.addButton('video', {
    text: '工具栏按钮名',
    onAction: function () {
      openDialog();
    }
  });

  // 注册一个菜单项名称 menu/menubar
  editor.ui.registry.addMenuItem('video', {
    text: 'Example菜单名',
    onAction: function() {
      openDialog();
    }
  });
  

  return {
    getMetadata: function () {
      return  {
        //插件名和链接会显示在“帮助”→“插件”→“已安装的插件”中
        name: "Cvideo",//插件名称
        url: "http://exampleplugindocsurl.com", //作者网址
      };
    }
  };
});

2.2 @tinymce/tinymce-vue

官网没有单独针对vue写一个demo,但是参照一下js版本的写法,应该是写在external_plugins,但是结果是不行的,直接报错了。而且tinymce这个变量也没定义,不知道怎么下手

image.png

<template>
  <div>
    <Editor
      api-key="0nza9k5j2wppkvic8e8qwuz30dxfihc3ducxffverenzsjuk"
      :init="init"
      v-model="formState.text"
    />
  </div>
</template>


<script setup>
import { reactive } from 'vue';
import Editor from '@tinymce/tinymce-vue';
import '../common/pluginsMce/plugin.js'
const formState = reactive({
  text:'',
})
const init  = {
  external_plugins: {
    video: '../common/pluginsMce/plugin.js'
  },
  plugins: 'lists link image table code help wordcount media video',
  toolbar: 'bold undo alignleft aligncenter alignright bullist numlist video',
  language:'zh-Hans',
  width:450,
  height: 550,
}
</script>

2.3 tinymce引入

这里有一个坑点,就是tinymce.init函数需要放在onmounted里面(或者放在一些异步的环节中),不然有可能初始化失败。很多博客中的引入方式是有问题,我结合了一下这些博客,用的是tinymce的6.4.2版本,vue组件如下(需要下好zh-Hans语言包)

  
<template>
  <div class="tinymce-box">
    <textarea id="tinydemo"></textarea>
  </div>
</template>

<script  setup>
import { onMounted } from 'vue';
import tinymce from 'tinymce/tinymce'; //tinymce核心文件
 
import 'tinymce/models/dom'; // 引入dom模块。从 Tinymce6,开始必须有此模块导入
import 'tinymce/themes/silver'; //默认主题
import 'tinymce/icons/default'; //引入编辑器图标icon,不引入则不显示对应图标
import 'tinymce/langs/zh-Hans'; //引入编辑器语言包
 
/* 引入编辑器插件
 * 位于 ./node_modules/tinymce/plugins 目录下,版本不同,插件会有所差异。根据自己版本来导入,若不存在的,不能导入,会报错。
 */
import 'tinymce/skins/ui/oxide/skin.min.css'
import 'tinymce/plugins/advlist'; //高级列表
import 'tinymce/plugins/anchor'; //锚点
import 'tinymce/plugins/autolink'; //自动链接
import 'tinymce/plugins/autoresize'; //编辑器高度自适应,注:plugins里引入此插件时,Init里设置的height将失效
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/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/template'; //内容模板
import 'tinymce/plugins/visualblocks'; //显示元素范围
import 'tinymce/plugins/visualchars'; //显示不可见字符
import 'tinymce/plugins/wordcount'; //字数统计
import '../common/pluginsMce/plugin.js';

const tinyInit = ()=>{
    tinymce.init({
    selector: '#tinydemo',
    language:'zh-Hans',
    promotion:false, //Upgrade是否开启
    branding: false, //tiny技术支持信息是否显示
    plugins: 'video codesample preview searchreplace autolink directionality visualblocks visualchars image link media template table charmap pagebreak nonbreaking anchor insertdatetime advlist lists wordcount autosave', //使用image插件
    menu: {
          file: {title: '文件', items: 'newdocument'},
          edit: {title: '编辑', items: 'undo redo | cut copy paste pastetext | selectall'},
          insert: {title: '插入', items: 'image link media | template hr'},
          view: {title: '查看', items: 'visualaid'},
          format: {title: '格式', items: 'bold italic underline strikethrough superscript subscript | formats | removeformat'},
          table: {title: '表格', items: 'inserttable tableprops deletetable | cell row column'},
          tools: {title: '工具', items: 'spellchecker code'},
    },
    toolbar: 'video preview codesample code forecolor backcolor styles directionality fontfamily fontsize searchreplace image  media link alignleft aligncenter alignright template table charmap pagebreak nonbreaking anchor insertdatetime advlist lists wordcount autosave ', //工具栏显示
    line_height_formats: '1 1.2 1.4 1.6 2', //行高
    font_size_formats: '12px 14px 16px 18px 20px 22px 24px 28px 32px 36px 48px 56px 72px', //字体大小
    font_family_formats:'微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun,serif;仿宋体=FangSong,serif;黑体=SimHei,sans-serif;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;',
    file_browser_callback_types: 'file image media',//这个根据个人需要配置
})
}

onMounted(()=>{
    tinyInit()
})
</script>

<style scoped>
.tinymce-box {
  width: 100%;
}
</style>

分别在这三个地方进行处理 image.png

得到结果

image.png

2.4 切换路由时的问题

使用上述方法,在切换路由时会出现tinymce初始化失败的问题。这是由于tinymce.init是在onMounted钩子中进行的,但是这个钩子在路由切换时不会执行。

因此我采用keep-alive来解决路由切换时的问题。

  1. home.vue中针对部分路由进行缓存(router-view在哪个文件就在哪个文件进行处理)
<router-view v-slot="{ Component }">
    <KeepAlive>
        <component
            :is="Component"
            :key="$route.name"
            v-if="$route?.meta?.keepAlive"
        />
    </KeepAlive>
        <component
            :is="Component"
            :key="$route.name"
            v-if="!$route?.meta?.keepAlive"
        />
</router-view>
  1. 在定义路由的index.js中定义keepAlive: true
{
    path: 'editor/:chapterId?',
    name: 'editor',
    component: Editor,
    meta: {
        title: "课程编辑器",
        keepAlive: true
    } 
}
  1. 使用onActivated, onDeactivated代替onMounted
import { onActivated, onDeactivated } from 'vue';
// 激活时初始化
onActivated(tinyInit)
// 退出后销毁
onDeactivated(()=>{
    tinymce.activeEditor.destroy()
})