vue 封装富文本组件(tinymce)

3,944 阅读1分钟

1.导入 tinymce 依赖

npm i tinymce

2.show me the code

MyEditor.vue

<template>
  <!-- 添加visibility: hidden防止初始化之前显示出来 -->
  <textarea :id="editorId" style="visibility: hidden" />
</template>

<script>
import tinymce from "tinymce/tinymce";
// 把tinymce插件引入抽取为一个文件
import { contentStyle } from "./plugins";
import { uploadFile } from "~/utils";

export default {
  name: "MyEditor",
  props: {
    // 富文本内容
    value: {
      type: String,
      default: "",
    },
    // 自定义选项
    opt: {
      type: Object,
      default: () => {},
    },
    // 控制禁用状态
    disabled: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      // 生成随机id,用uuid可能会更好
      editorId: `editor-${Math.floor(Math.random() * 1000000)}`,
      // 富文本实例
      editor: null,
    };
  },
  watch: {
    // 实现v-model更新内容
    value(val, oldVal) {
      if (this.editor && val !== oldVal && val !== this.editor.getContent()) {
        this.editor.setContent(val);
      }
    },
    // 控制禁用
    disabled(val) {
      if (this.editor !== null) {
        this.editor.setMode(val ? "readonly" : "design");
      }
    },
  },
  mounted() {
    // 初始化富文本
    this.init();
  },
  destroyed() {
    // 销毁实例
    if (this.editor) {
      this.editor.destroy();
      this.editor = null;
    }
  },
  methods: {
    init() {
      // 默认配置
      const opt = {
        // selector dom 来初始化
        selector: "#" + this.editorId,
        // menubar 不想顶栏太高所以隐藏掉了
        menubar: false,
        // 指定使用的插件
        plugins: [
          "autolink lists link image",
          "searchreplace charmap emoticons",
          "media help paste wordcount codesample hr preview",
        ],
        // 选择显示在 toolbar 上的功能
        toolbar:
          "bold italic underline strikethrough | forecolor backcolor removeformat | " +
          "fontselect fontsizeselect formatselect | alignleft aligncenter alignright alignjustify | " +
          "bullist numlist | outdent indent | subscript superscript | hr | " +
          "charmap emoticons | link image media codesample | preview help",
        // 指定使用的字体,默认没有中文字体
        font_formats:
          "Arial=arial,helvetica,sans-serif; Courier New=courier new,courier,monospace; AkrutiKndPadmini=Akpdmi-n;宋体=宋体;黑体=黑体;仿宋=仿宋;微软雅黑=Microsoft YaHei;楷体-GB2312=楷体-GB2312",
        // 引入语言包,也要引入下载好的js文件,下载地址(https://www.tiny.cloud/get-tiny/language-packages/)
        language: "zh_CN",
        height: 400,
        // 工具栏显示模式,默认只显示一行
        toolbar_mode: "wrap",
        // 如果不用cdn,要手动引入皮肤样式
        skin: false,
        content_css: false,
        content_style: contentStyle,
        // 图片上传
        automatic_uploads: true,
        file_picker_types: "image",
        images_upload_handler: (blobInfo, success, failure) => {
          // 调用了一个公共函数,会返回上传好的图片地址
          uploadFile({ filename: blobInfo.filename(), blob: blobInfo.blob() })
            .then((res) => {
              success(res);
            })
            .catch((err) => {
              console.debug(err);
              failure(err);
            });
        },
        // 合并配置
        ...this.opt,
        // 富文本实例化之前执行的回调
        setup: (editor) => {
          this.editor = editor;
          // 注册初始化的事件
          editor.on("init", (e) => this.initSetup(e));
        },
      };

      // 参数
      tinymce.init(opt);
    },
    initSetup() {
      if (!this.editor) return;
      this.editor.setContent(this.value);

      // 注册富文本更新的事件,监听更新实现v-model
      this.editor.on("change keyup undo redo", () => {
        const content = this.editor.getContent();
        this.$emit("input", content);
      });
    },
  },
};
</script>

<style lang="scss" scope>
// 引入富文本皮肤
@import "~tinymce/skins/ui/oxide/skin.css";
</style>

plugins.js

import "tinymce/themes/silver";
// 富文本语言包
import "./zh_CN";

import "tinymce/icons/default/icons";

import "tinymce/plugins/autolink";
import "tinymce/plugins/codesample";
import "tinymce/plugins/hr";
import "tinymce/plugins/link";
import "tinymce/plugins/lists";
import "tinymce/plugins/image";
import "tinymce/plugins/media";
import "tinymce/plugins/paste";
import "tinymce/plugins/preview";
import "tinymce/plugins/searchreplace";
import "tinymce/plugins/wordcount";
import "tinymce/plugins/charmap";
import "tinymce/plugins/emoticons";
import "tinymce/plugins/emoticons/js/emojis";
import "tinymce/plugins/help";
import "tinymce/plugins/codesample";

// 富文本 iframe 的样式,这里只引入了图片缩放的样式
const contentStyle =
  ".mce-content-body audio[data-mce-selected],.mce-content-body embed[data-mce-selected],.mce-content-body img[data-mce-selected],.mce-content-body object[data-mce-selected],.mce-content-body video[data-mce-selected]{outline:3px solid #b4d7ff}" +
  ".mce-content-body img::selection{background:0 0}" +
  ".mce-content-body div.mce-resizehandle{background-color:#4099ff;border-color:#4099ff;border-style:solid;border-width:1px;box-sizing:border-box;height:10px;position:absolute;width:10px;z-index:1298}.mce-content-body div.mce-resizehandle:hover{background-color:#4099ff}.mce-content-body div.mce-resizehandle:nth-of-type(1){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(2){cursor:nesw-resize}.mce-content-body div.mce-resizehandle:nth-of-type(3){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(4){cursor:nesw-resize}.mce-content-body .mce-resize-backdrop{z-index:10000}.mce-content-body .mce-clonedresizable{cursor:default;opacity:.5;outline:1px dashed #000;position:absolute;z-index:10001}.mce-content-body .mce-clonedresizable.mce-resizetable-columns td,.mce-content-body .mce-clonedresizable.mce-resizetable-columns th{border:0}.mce-content-body .mce-resize-helper{background:#555;background:rgba(0,0,0,.75);border:1px;border-radius:3px;color:#fff;display:none;font-family:sans-serif;font-size:12px;line-height:14px;margin:5px 10px;padding:5px;position:absolute;white-space:nowrap;z-index:10002}";

export { contentStyle };

example.vue

<template>
  <DydEditor v-model="content" :disabled="isDisabled" />
</template>

效果图

效果图

参考

tinyMCE 官方文档
vue-element-admin 文档和源码
vue-vben-admin 源码