实际开发中,富文本编辑器使用的总结

背景

应用于各种类型的文章的发布,先后使用tinyMce、UEditor、wangeditor。最终选择了wangeditor。因为tinyMce太重,UEditor不支持图片粘贴的前端配置,还需要静态引入UE资源,而且UE也没在维护。

各种富文本编辑器的使用

一、wangeditor(Vue2)

wangeditor官网

1、安装

yarn add @wangeditor/editor @wangeditor/editor-for-vue  

2、组件封装

局部导入,不需要在mian.js/main.ts里面操作

<template>
  <div>
    <Toolbar
        style="border-bottom: 1px solid #ccc"
        :editor="editor"
        :defaultConfig="toolbarConfig"
        :mode="mode"
      />
      <Editor
        style="height: 700px; overflow-y: hidden"
        v-model="msg"
        :defaultConfig="editorConfig"
        :mode="mode"
        @onCreated="onCreated"
        @customPaste="customPaste"
      />
  </div>
</template>
 
<script>
import {
    
     Editor, Toolbar } from '@wangeditor/editor-for-vue'
import {
    
    uploadArticleOrVideo,uploadVideo} from '@/api/content/content.js';
export default {
    
    
  components: {
    
     Editor, Toolbar },
  props : ['ifDisabled','content','height'],
  data() {
    
    
    return {
    
    
      editor: null,
      msg: '',
      toolbarConfig: {
    
    
        excludeKeys: ['fullScreen']
      },
      file: null,
      editorConfig: {
    
    
        placeholder: '请输入内容...',
        readOnly: false,
        scroll: false,
        MENU_CONF: {
    
    
          uploadImage: {
    
    
            customUpload: this.autoUp,
          },
          uploadVideo: {
    
    
            customUpload: this.uploadVideo
          }
        },
      },
      mode: 'default'
    }
  },
  watch : {
    
    
    content : {
    
    
      handler() {
    
    
        this.msg = this.content;
      },
      immediate:true
    },
    msg() {
    
    
      this.$emit('getEditContent',this.msg)
    }
  },
  methods: {
    
    
    async autoUp(file, insertFn) {
    
    
      let resultUrl = await this.customRequestImg(file);
      insertFn(resultUrl);
    },
    async uploadVideo(file,insertFn) {
    
    
      let {
    
    url,poster} = await this.customRequestVideo(file);
      insertFn(url,poster)
    },
    // 自定义上传图片
    async customRequestImg(file) {
    
    
      const formData = new FormData();
      formData.append('file',file);
      let {
    
     resData } = await uploadArticleOrVideo(formData);
      return resData.url;
    },
    // 自定义上传视频
    async customRequestVideo(file) {
    
    
      const loadMsg = this.$message.loading("上传中...", 0);
      const formData = new FormData();
      formData.append('file',file);
      let {
    
     resData } = await uploadVideo(formData);
      setTimeout(loadMsg, 0);  // 关闭loading,确保await完,才关闭loading
      this.$message.success('上传成功');
      return {
    
    
        url: resData.url: // 视频地址
        poster: resData.coverUrl // 视频封面
      }
    },
    onCreated(editor) {
    
    
      this.editor = Object.seal(editor) // 一定要用 Object.seal() ,否则会报错
    },
    // @customPaste="customPaste"  自定义粘贴
    async customPaste(editor,event) {
    
    
      const items = (event.clipboardData || window.clipboardData).items;
      let file = null;   // 保存image类型的file对象
      if (!items || items.length === 0) {
    
    
        this.$message.error("当前浏览器不支持本地粘贴");
        return;
      }
      // 搜索剪切板items
      for (let i = 0; i < items.length; i++) {
    
    
        if (items[i].type.indexOf("image") !== -1) {
    
    
          file = items[i].getAsFile();
          break;
        }
      }
      if (file) {
    
     // 说明粘贴的是图片
        // 禁止粘贴图片的默认行为 wangeditor默认可以粘贴word里面的图片,但不能粘贴微信、qq里面的图片
        event.preventDefault()
        let imgUrl = await this.customRequestImg(file);
        // 插入图片节点
        let imgNode = {
    
     type: 'image',src:imgUrl,children: [{
    
     text: '' }]}  // 节点数据格式不能变
        editor.insertNode(imgNode)
        // 禁止粘贴图片的默认行为
        return false
      }
    }
  },
  beforeDestroy() {
    
    
    const editor = this.editor
    if (editor == null) return
    editor.destroy() // 组件销毁时,及时销毁编辑器
  },
  created() {
    
    
    this.editorConfig.readOnly = this.ifDisabled;
  }
}
</script>
<style src="@wangeditor/editor/dist/css/style.css"></style>

3、父组件使用

import MyEditor from '../MyEditor.vue';
 components : {
    
    
    MyEditor,
  },
 <MyEditor  @getEditContent="getEditContent" :content="currentData.content" :ifDisabled="false"/>

二、UEditor(vue2)

1、安装

npm i vue-ueditor-wrap -S

2、在public目录下,引入UE静态资源

在这里插入图片描述
UE静态资源下载

3、组件封装

<template>
   <div>
    <vue-ueditor-wrap  
      v-model="msg" 
      :config="editorConfig"
      @beforeInit="addImageButton"
    ></vue-ueditor-wrap>
    <a-modal
      title="上传图片"
      :visible="ifShowUploadImgDialog"
      @cancel="ifShowUploadImgDialog = false"
    >
      <a-form-model style="display: flex" >
        <a-form-item style="display: flex">
            <template slot="label">
                <span style="font-size: 14px;color: #595959;">图片</span>
            </template>
            <a-upload
               :show-upload-list="false"
              :customRequest="customRequestImg"
              :multiple="false"
              list-type="picture-card"
            >
              <img v-if="apiDataUrl" style="width: 102px;height:102px;object-fit:cover;" :src="apiDataUrl" alt="avatar" />
              <div v-else> 
                <a-icon type="plus" />
                <div class="ant-upload-text">
                  上传图片
                </div>
              </div>
           </a-upload>
        </a-form-item>
        <div  style="margin-left: 100px;">
            <a-form-item  style="display: flex">
              <template slot="label">
                  <span style="font-size: 14px;color: #595959;">宽</span>
              </template>
              <a-input-number :min="0" addon-after="px" v-model="file.w"/>
          </a-form-item>
          <a-form-item style="display: flex">
              <template slot="label">
                  <span style="font-size: 14px;color: #595959;">高</span>
              </template>
              <a-input-number  :min="0"  v-model="file.h">
              </a-input-number>
          </a-form-item>
        </div>
      </a-form-model>
      <template template slot="footer">
            <a-button type="default" @click="ifShowUploadImgDialog = false">取消</a-button>
            <a-button type="primary" @click="sureUpImgFn">确定</a-button>
      </template>
    </a-modal>
    <a-modal
      title="上传视频"
      width="520px"
      :visible="ifShowUploadVideoDialog"
      @cancel="ifShowUploadVideoDialog = false"
    >
      <a-form-model :label-col="labelCol" :wrapper-col="wrapperCol">
        <a-form-item >
            <template slot="label">
                <span style="font-size: 14px;color: #595959;">视频</span>
            </template>
            <a-upload
              accept=".mp4,.ogg,.webm" 
              :customRequest="customRequestVideo"
              :multiple="false"
            >
              <a-button> <a-icon type="upload" /> 上传视频 </a-button>
            </a-upload>
        </a-form-item>
        <a-form-item >
            <template slot="label">
                <span style="font-size: 14px;color: #595959;">宽</span>
            </template>
            <a-input-number :min="0" addon-after="px" v-model="file.w"/>
        </a-form-item>
        <a-form-item>
            <template slot="label">
                <span style="font-size: 14px;color: #595959;">高</span>
            </template>
            <a-input-number :min="0"  v-model="file.h">
            </a-input-number>
        </a-form-item>
      </a-form-model>
      <template template slot="footer">
            <a-button type="default" @click="ifShowUploadVideoDialog = false">取消</a-button>
            <a-button type="primary" @click="sureUpVideoFn">确定</a-button>
      </template>
    </a-modal>
   </div>
</template>

<script>
import VueUeditorWrap from 'vue-ueditor-wrap';
import {uploadArticleOrVideo,uploadVideo} from '@/api/content/content.js';
export default {
  name: "MyEditor",
  props : ['ifDisabled','content','height'],
  data() {
    return {
        editorConfig: {
          autoHeightEnabled: false, //编译器不自动被内容撑高
          initialFrameHeight: 650, //初始容器高度
          initialFrameWith: "100%",
          readonly : false,
          UEDITOR_HOME_URL: "./UE/", //UEditor资源文件的存放路径
          allowDivTransToP: false,  // 阻止DIV 自动过滤为P标签
          // serverUrl: 'http://192.168.43.89:3000/ueconfig'  // //ueditor.szcloudplus.com/cos
        },
        msg : '',
        editorInstance : {},
        ifShowUploadImgDialog : false,
        ifShowUploadVideoDialog : false,
        editor : null,
        apiDataUrl : null,
        labelCol: { span: 5},
        wrapperCol: { span: 15 },
        file : {
          w : 100,
          h : 100
        }
    };
  },
  components : {
    VueUeditorWrap
  },
  watch : {
    content : {
      handler() {
        this.msg = this.content;
        
      },
      immediate:true
    },
    msg() {
      this.$emit('getEditContent',this.msg)
    }
  },
  methods : {
    readyFn(editorInstance) {
      this.editorInstance=editorInstance
    },
    // 自定义工具按钮
    addImageButton(editorId) {
      let that = this;
      window.UE.registerUI("autoimg", (editor, uname) => {
        const btnImg = new window.UE.ui.Button({
          name: 'my-img',
          title: '上传图片',
          onclick: function(){
            that.editor = editor;
            that.ifShowUploadImgDialog = true;
          }
        });
        return btnImg
      });
      window.UE.registerUI("autovideo", (editor, uname) => {
        const btnVideo = new window.UE.ui.Button({
          name: 'my-video',
          title: '上传视频',
          onclick: function(){
            that.editor = editor;
            that.ifShowUploadVideoDialog = true;
          }
        });
        return btnVideo
      });
    },
    customRequestImg(data) {
      const formData = new FormData();
      formData.append('file',data.file);
      uploadArticleOrVideo(formData).then(res=>{
          if(res.data.success) {
              this.apiDataUrl = window.customizeConfig.proposed === "NJ" ? window.customizeConfig.minioUrl + JSON.parse(res.data.data).url : 
              JSON.parse(res.data.data).url;
          } else {
              this.$message.error('上传失败')
          }
      })
    },
    customRequestVideo(data) {
      const formData = new FormData();
      formData.append('file',data.file);
      uploadVideo(formData).then(res=>{
          data.onSuccess(res, data.file);
          if(res.data.success) {
            this.apiDataUrl = window.customizeConfig.proposed === "NJ" ? window.customizeConfig.minioUrl + JSON.parse(res.data.data).url :
            JSON.parse(res.data.data).url;
          } else {
            this.$message.error('上传失败')
          }
      })
    },
    sureUpImgFn() {
      if(this.apiDataUrl) {
        this.editor.execCommand('insertimage', {
          src:this.apiDataUrl,
          _src: this.apiDataUrl,
          height : this.file.h,
          width : this.file.w
        });
        this.ifShowUploadImgDialog = false;
      } else {
        this.$message.warning("暂无上传文件")
      }
      
    },
    sureUpVideoFn() {
      if(this.apiDataUrl) {
        if(this.file.h && this.file.w) {
          this.editor.execCommand('insertHTML',`<video height=${this.file.h} width=${this.file.w} src="${this.apiDataUrl}" autoplay loop controls="controls"></video>`);
        } else {
          this.editor.execCommand('insertHTML',`<video src="${this.apiDataUrl}" autoplay loop controls="controls"></video>`);
        }
        this.ifShowUploadVideoDialog = false;
      } else {
        this.$message.warning("暂无上传文件")
      }
    }
  },
  created() {
    this.editorConfig.readonly = this.ifDisabled;
  }
};
</script>
<style>
  .tox-notifications-container {
      display: none;
  }
  .tox-statusbar {
    display: none !important;
  }
.edui-button.edui-for-my-img .edui-button-wrap .edui-button-body .edui-icon {
    background-image: url("~@/assets/img/ue/upImg.png") !important;
    background-size: contain;
}
.edui-button.edui-for-my-video .edui-button-wrap .edui-button-body .edui-icon {
    background-image: url("~@/assets/img/ue/upVideo.png") !important;
    background-size: contain;
}
textarea {
  display:none;
}
</style>

猜你喜欢

转载自blog.csdn.net/weixin_44224921/article/details/129831884