vue+element-ui使用 tinymce富文本编辑器步骤(上传图片+上传本地视频)以及遇到的一些bug

前言

最近接触前端,发现富文本使用频率比较高,在网上找了很多种方法也看了很多资料,现在来总结最近新使用的富文本编辑器tinymce(tinymce的介绍大家可以百度哈~)
tinymce的中文官网:tinymce的中文官网

前端项目都基于vue来开发的(本人是前端新手,如有不对地方,还请各位大佬指点)

以前用的都是vue-quill-editor富文本插件, 这个插件还不错,是vue自带的,比较轻巧,适用于功能不是很大,简单的富文本编辑器,等有时间再写一篇vue-quill-editor的使用心得,啰嗦这么久,进入正题啦~~(这是一篇很详细的教程,一步一步来一点都不难)

使用方法:

  1. 下载插件: npm install tinymce -S
  2. 安装之后,在 node_modules 中找到 tinymce/skins 目录,然后将 skins 目录拷贝到 static 目录下
但是我是使用 vue-cli 3.x 构建的 typescript 项目,所以就放到 public 目录下
  1. 新建一个根目录tinymce,因为tinymce 默认是英文界面,所以还需要下载一个中文语言包 也放到tinymce目录下

图片展示:
在这里插入图片描述

  1. 初始化数据
import tinymce from 'tinymce/tinymce'
import 'tinymce/themes/modern/theme'
import Editor from '@tinymce/tinymce-vue'

如果找不到 import 'tinymce/themes/modern/theme'
可以替换成 import 'tinymce/themes/silver/theme'

上传图片+本地视频的是

tinymce-vue 是一个组件,需要在 components 中注册,然后直接使用

注册组件:

<template>
  <div class="myEditor">
    <div class="tinymce-editor-wrapper">
      <editor
        v-model="content"
        :disabled="disabled"
        :init="options"
        initial-value="请输入内容..."
      />
    </div>
  </div>
</template>
 
<script>
import tinymce from 'tinymce/tinymce'
import Editor from '@tinymce/tinymce-vue'
import 'tinymce/themes/silver'
//引入图片上传服务器的接口
import { uploadOss } from "@/api/system/oss";
 
// 引入编辑器插件plugins
import 'tinymce/plugins/advlist'
import 'tinymce/plugins/autolink'
import 'tinymce/plugins/lists'
import 'tinymce/plugins/link'
import 'tinymce/plugins/image'
import 'tinymce/plugins/charmap'
import 'tinymce/plugins/print'
import 'tinymce/plugins/preview'
import 'tinymce/plugins/anchor'
import 'tinymce/plugins/searchreplace'
import 'tinymce/plugins/visualblocks'
import 'tinymce/plugins/fullscreen'
import 'tinymce/plugins/insertdatetime'
import 'tinymce/plugins/media'
import 'tinymce/plugins/table'
//引入工具栏图标
import 'tinymce/icons/default'


// 自定义工具栏
const toolbar =
  'undo redo | insert | styleselect | fontselect bold italic forecolor backcolor | alignleft aligncenter alignright |  outdent indent | link image media | fullscreen'
 // 引入插件
const plugins = [
  'advlist autolink lists link image charmap print preview anchor',
  'searchreplace visualblocks fullscreen',
  'insertdatetime media table'
]
 // 字体
const fonts =
  'Andale Mono=andale mono,times; Arial=arial,helvetica,sans-serif; 黑体=arial black,avant garde; Book Antiqua=book antiqua,palatino; Comic Sans MS=comic sans ms,sans-serif; Courier New=courier new,courier; Georgia=georgia,palatino; Helvetica=helvetica; Impact=impact,chicago; Symbol=symbol; 宋体=tahoma,arial,helvetica,sans-serif; Terminal=terminal,monaco; Times New Roman=times new roman,times; Trebuchet MS=trebuchet ms,geneva; Verdana=verdana,geneva; Webdings=webdings; Wingdings=wingdings,zapf dingbats'
 
export default {
  components: {
    Editor
  },
  props: {
    value: {
      type: String,
      default: ''
    },
    disabled: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      content: this.value,
      options: {
        language_url: '/tinymce/zh_CN.js', // 语言包的路径
        language: 'zh_CN', // 语言
        skin_url: '/tinymce/skins/ui/oxide', // 浅色
        plugins: plugins, // 插件
        toolbar: toolbar, // 工具栏
        font_formats: fonts, // 字体
        height: 900, // 编辑器高度
        branding: false, // 是否禁用“Powered by TinyMCE”
        menubar: true, // 顶部菜单栏显示 
        resVideo:'',    //上传视频的url
        uploaded:false,//有没有上传完成
        file_picker_types: 'media', 
        images_upload_handler: (blobInfo, success, failure) => {
          this.handleImageAdded(blobInfo, success, failure)
        },
        //be used to add custom file picker to those dialogs that have it.
        file_picker_callback: (callback, value, meta) => {
           this.open(callback, value, meta)
        }

      }
    }
  },
  watch: {
    value(val) {  //父子组建双向绑定
      this.content = val
    },
    content(val) {
      this.$emit('input', val)
    }
  },
  mounted() {
    tinymce.init({})
  },
  created() {
  },
  methods: {
    open(callback, value, meta){
      if (meta.filetype == 'media') {
            let input = document.createElement('input');//创建一个隐藏的input
            input.setAttribute('type', 'file');
            let that = this;
            input.onchange = function(){
                let file = this.files[0];//选取第一个文件
                that.uploadVideo(file,'media'); // 上传视频拿到url
                console.log('dongbug',that.options.uploaded)
                setTimeout(()=>{
                      if(that.options.uploaded){
                        console.log(that.resVideo,'true')
                        callback(that.resVideo) //将url显示在弹框输入框中
                      }else{
                    setTimeout(()=>{
                        //设置几秒后再去取数据
                        console.log(that.resVideo,'false')
                        callback(this.resVideo)
                    },5000)
                }
              },5000)
            }
           
            //触发点击
            input.click();
        }
    },
    // 插入视频的方法
    uploadVideo(file,type){
        const formdate = new FormData()
        formdate.set('file',file)
        console.log(formdate)
        uploadOss(formdate).then(response =>{
          if (response.code == 200) {
              if(type == 'media') {
                    this.resVideo = response.url
                    this.options.uploaded = true
                }
              console.log('dsdsdoooooooo', this.options.uploaded)
          }
          })
        },


    // 插入图片的方法
    handleImageAdded(blobInfo, success, failure) {
      console.log('图片',blobInfo.blob())
      let file = blobInfo.blob()
      const isLt8M = file.size / 1024 / 1024 < 20
      if (!isLt8M) {
        this.$message.error('图片大小不能超过 20MB!')
        return
      }
     const formdate = new FormData()
     formdate.set('file',blobInfo.blob())
     console.log('图片',formdate)
     uploadOss(formdate).then(response =>{
      if (response.code == 200) {
            let url = response.url
            success(url)
          } else {
            failure(response.message)
            this.$message.error(response.message)
          }
     })
    },
    async info() {  //回显内容
      console.log(this.mubanId)
      let { data } = await getDetail({
        templateId: this.mubanId
      })
      this.content = data
    }
  }
}
</script>
 
<style lang="scss" scoped></style>
 
<style>
.tox-silver-sink {
  z-index: 13000 !important;
}
</style>


引入组件:(根据自己存放组件的路径来哦)

import TinymceEditor  from '@/components/Tinymce/index'

注册组件:

components: {TinymceEditor },

使用组件:

<tinymce-editor 
             v-model="form.tables"
             v-if="open"
             />

说一下v-if=“open ”这块
因为我的富文本使用在弹出框,每次一点击修改就会显示和上传一样的内容,而且不可以修改
原因是因为弹窗是父组件,富文本是子组件,弹窗关闭的时候子组件没有触发销毁钩子函数,既然没有销毁,那么第二次打开弹窗的时候也就不会再次初始化,所以父组件的弹窗每次打开关闭、打开关闭,显示都是第一次的富文本内容。
解决方法:
所以要在使用富文本组件上加上v-if,使子组件随弹窗关闭进行销毁,随弹窗的打开,进行初始化

代码如下:

<el-dialog :title="title" :visible.sync="open" width="1000px" append-to-body>
      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
       <el-form-item label="内容" prop="tables">
            <tinymce-editor 
             v-model="form.tables"
             v-if="open"
             />
        </el-form-item>
      </el-form>    
</el-dialog >

在这里插入图片描述
还有遇到一种情况,富文本会出现这种情况:
在这里插入图片描述
工具栏的icon都显示not found
解决方法:
在tinymce组件中引入这段代码:import 'tinymce/icons/default'
就可以啦~

图片上传

还有个问题是图片的上传:(图片上传的方法)
在这里插入图片描述
inymce 提供了 images_upload_url等 api 让用户配置上传图片的相关参数
images_upload_handler 自定义一个上传方法

这个方法会提供三个参数:blobInfo, success, failure
其中 blobinfo 是一个对象,包含上传文件的信息:
successfailure 是函数,上传成功的时候向 success 传入一个图片地址,失败的时候向 failure 传入报错信息

视频上传

有了图片上传就少不了视频上传(编辑器一开始是没有本地视频上传的按钮,只有填写视频url)
在这里插入图片描述
需要引入对应的插件media

import 'tinymce/plugins/media'
const toolbar =
  'undo redo | insert | styleselect | fontselect bold italic forecolor backcolor | alignleft aligncenter alignright |  outdent indent | link image media | fullscreen'
   // 引入插件
const plugins = [
  'advlist autolink lists link image charmap print preview anchor',
  'searchreplace visualblocks fullscreen',
  'insertdatetime media table'
]

在data加上这四行

        resVideo:'',    //上传视频的url
        uploaded:false,//有没有上传完成
        file_picker_types: 'media', 
         file_picker_callback: (callback, value, meta) => {
           this.open(callback, value, meta)
        }

this.open是上传视频的方法(返回三个参数)
注意这一块
在这里插入图片描述
如果不加定时器,上传视频返回的uploaded会延迟,所以就会进入判断的else方法,url就不会显示到input框中

这些都设置完成后就会出现一个可以的上传本地文件的图标(同理也可以上传图片和文件)
在这里插入图片描述
但是预览效果为图片,不能预览
在这里插入图片描述

解决方法

找到tinymce的原文件下的media>plugin.js文件
找到下面源码、注释掉在这里插入图片描述

第二步:创建一个全局变量来存video的src
在这里插入图片描述

第三步:判断video标签 获取src值赋值给videoSource(写在注释的那一段代码那里):

if(node.name === 'video' && hasLiveEmbeds(editor) && global$8.ceFalse){
            console.log('videoSource===', videoSource)
            videoSource = ''
            if(node.attributes['map'] && node.attributes['map'].src){
              videoSource = node.attributes['map'].src
            }else{
              for(var ii=0;ii<node.attributes.length;ii++){
                if(node.attributes[ii].name == "src"){
                  videoSource = node.map.node.attributes[ii].value
                }
              }
            }
            if(node.firstChild && node.firstChild.value){
              var elel=node.firstChild && node.firstChild.value
              var objE = document.createElement("div");
             objE.innerHTML = elel;
              var dom = objE.getElementsByTagName('source')[0]
              videoSource = dom.getAttribute('src')
            }
            node.replace(createPreviewIframeNode(editor, node));
          }

第四步:在createPreviewIframeNode 方法中有两处修改:修改previewNode.attr中src:videoSource || node.attr(‘src’), 增加播放属性controls: ‘controls’。

var createPreviewIframeNode = function (editor, node) {
      var previewWrapper;
      var previewNode;
      var shimNode;
      var name = node.name;
      previewWrapper = new global$7('span', 1);
      previewWrapper.attr({
        'contentEditable': 'false',
        'style': node.attr('style'),
        'data-mce-object': name,
        'class': 'mce-preview-object mce-object-' + name
      });
      retainAttributesAndInnerHtml(editor, node, previewWrapper);
      previewNode = new global$7(name, 1);
      previewNode.attr({
        src: videoSource || node.attr('src'), // 修改
        controls: 'controls',    // 新增
        allowfullscreen: node.attr('allowfullscreen'),
        style: node.attr('style'),
        class: node.attr('class'),
        width: node.attr('width'),
        height: node.attr('height'),
        frameborder: '0'
      });
      shimNode = new global$7('span', 1);
      shimNode.attr('class', 'mce-shim');
      previewWrapper.append(previewNode);
      previewWrapper.append(shimNode);
      return previewWrapper;
    };

这样就可以正常显示了:

效果图

在这里插入图片描述

好了tinymce富文本的使用就写到这,如有不足,欢迎补充~~~

思路来源:参考链接,https://www.cnblogs.com/wisewrong/p/8985471.html
视频上传思路来源:https://blog.csdn.net/web_527965583_ld/article/details/107299416

Logo

前往低代码交流专区

更多推荐