vue2 使用wangEditor V5版本 详细步骤(小白可用) 可实现图片设置超链接 随意文字设置超链接 视频直传阿里云OSS

需求背景:

富文本编辑器需要实现:
1.PC端可实现随意图片设置超链接,移动端点击图片跳转设置的超链接

2.PC端可实现随意选中文字设置超链接,移动端点击文字跳转设置的超链接

3.PC端富文本编辑器可以快速上传大视频,不经后端直传阿里云oss

4.图片复制粘贴回显,复制过来文字字体样式不变

PC端使用的是若依系统前后端分离框架,vue2框架,调研一段时间后决定原quill富文本编辑器弃用,改成最新版本wangEditorV5版本可以实现所有需求

wangEditor v5版本官网链接:优势 | wangEditor

若依系统官网:http://www.ruoyi.vip/

安装:

npm install @wangeditor/editor --save
npm install @wangeditor/editor-for-vue --save

在components文件下封装wangeditor组件

组件页面html代码如下:

<template>
  <div>
    <div class="wrap">
      <!-- 工具栏 -->
      <Toolbar
        class="toolbar"
        :editor="editor"
        :defaultConfig="toolbarConfig"
      />
      <!-- 编辑器 -->
      <Editor
        class="editor"
        :defaultConfig="editorConfig"
        v-model="html"
        @onChange="onChange"
        @onCreated="onCreated"
      />
    </div>
  </div>
</template>

组件页面css样式:

.wrap{
  border: 1px solid #ccc;
  margin-top: 10px;
  .toolbar{
    border-bottom: 1px solid #ccc;
  }
  .editor{
    height: 400px; 
    overflow-y: hidden;
  }
}

组件页面引入wangeditor:

import { Editor, Toolbar } from "@wangeditor/editor-for-vue";

全局引入样式:

在main.js:

引入样式:
import '@wangeditor/editor/dist/css/style.css'// wangeditorV5版本样式


引入插件:(可供全局实用)
// WangEditorV5版本
import WangEditor from '@/components/WangEditor';


全局挂在插件:
Vue.component('WangEditor', WangEditor)

组件页面注册组件:

components: { Editor, Toolbar }

data中详细配置项:
 

  data() {
    return{
      editor: null,
      html: '',
      ossUploadKey: {},
      toolbarConfig: {
        //菜单不展示的配置可以在这里添加,如果不知道可以在下面methods方法中打印菜单配置出来看
        excludeKeys: [
          "emotion",
          "insertImage",
          "insertVideo",
          "fullScreen",
          "todo",
          "insertTable",
          "codeSelectLang",
          "codeBlock",
          "code",
        ],
        browerConnection: null
      },
      editorConfig: {
        // 所有的菜单配置,都要在 MENU_CONF 属性下
        MENU_CONF: {
          uploadImage: {
            // 单个文件的最大体积限制,默认为 2M
            maxFileSize: 200 * 1024 * 1024, // 200M
            // 最多可上传几个文件,默认为 100
            maxNumberOfFiles: 1,
            // 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
            allowedFileTypes: ['image/*'],
            // 自定义上传参数,例如传递验证的 token 等。参数会被添加到 formData 中,一起上传到服务端。
            meta: {
              token: getToken(),
              directoryKey: 1010,
            },
            headers: {
              token: getToken(),
            },
            // 超时时间,默认为 10 秒
            timeout: 60000 * 5, 
            metaWithUrl: false,
            customUpload: {},
          },
          uploadVideo: { customUpload: {} },
        },
      }
    }
  },

初始化组件:

//先在methods中定义函数:

methods: {
      onCreated(editor) {
      this.editor = Object.seal(editor); // 【注意】一定要用 Object.seal() 否则会报错
      //this.editor.getAllMenuKeys() 可以查询所有菜单配置
      console.log('Editor菜单配置', this.editor.getAllMenuKeys())
    },
    onChange(editor) {
      console.log("onChange", editor.getHtml()); // onChange 时获取编辑器最新内容
      // this.value = editor.getHtml();
      this.$emit("wangEditorChange", editor.getHtml())
    },

},

//组件销毁之前
beforeDestroy() {
    const editor = this.editor;
    if (editor == null) return;
    editor.destroy(); // 组件销毁时,及时销毁 editor ,重要!!!
  },

以下是我的项目当中需要改变的组件参数,可做参考:

  props: {
    editorValue: {
      type: String,
      default: '',
    },
    placeholder: {
      type: String,
      default: '请输入内容',
    },
    // 最大限制字数
    maxLength: {
      type: Number,
      default: 20000
    },
    // 富文本只读状态
    readOnly: {
      type: Boolean,
      default: false,
    },
    // 不需要视频
    noVideo: {
      type: Boolean,
      default: false,
    }
  },

到这一步,已经可以生成一个完整的demo,在你需要使用的页面调用组件,传入参数即可

图片配置参考:菜单配置 | wangEditor

视频配置参考:菜单配置 | wangEditor

接下来图片和视频的处理,如果你想要直传oss不经过后端,你需要提前配置好OSS参数

哎算了不想写了好累

直接上代码自己看,看不懂的V我50我有空给你找文件,或者oss上传的可以看我其他文章

<template>
  <div>
    <div class="wrap">
      <!-- 工具栏 -->
      <Toolbar
        class="toolbar"
        :editor="editor"
        :defaultConfig="toolbarConfig"
      />
      <!-- 编辑器 -->
      <Editor
        class="editor"
        :defaultConfig="editorConfig"
        v-model="html"
        @onChange="onChange"
        @onCreated="onCreated"
      />
    </div>
  </div>
</template>

<script>
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
import { getToken } from '@/utils/auth';
import { uploadFile, getConfig } from '@/api/tool/fileUpload';
import "@/components/WangEditor/lib/crypto1/crypto/crypto.js";
import "@/components/WangEditor/lib/crypto1/hmac/hmac.js";
import "@/components/WangEditor/lib/crypto1/sha1/sha1.js";
import Base64 from "@/components/WangEditor/lib/base64.js";
import { decryptByCBC } from '@/utils/des.js';

export default {
  name: "WangEditor",
  components: { Editor, Toolbar },
  props: {
    editorValue: {
      type: String,
      default: '',
    },
    placeholder: {
      type: String,
      default: '请输入内容',
    },
    // 最大限制字数
    maxLength: {
      type: Number,
      default: 20000
    },
    // 富文本只读状态
    readOnly: {
      type: Boolean,
      default: false,
    },
    // 不需要视频
    noVideo: {
      type: Boolean,
      default: false,
    }
  },
  data() {
    return{
      editor: null,
      html: '',
      ossUploadKey: {},
      toolbarConfig: {
        excludeKeys: [
          "emotion",
          "insertImage",
          "insertVideo",
          "fullScreen",
          "todo",
          "insertTable",
          "codeSelectLang",
          "codeBlock",
          "code"
        ]
      },
      editorConfig: {
        // 所有的菜单配置,都要在 MENU_CONF 属性下
        MENU_CONF: {
          uploadImage: {
            // 单个文件的最大体积限制,默认为 2M
            maxFileSize: 200 * 1024 * 1024, // 200M
            // 最多可上传几个文件,默认为 100
            maxNumberOfFiles: 1,
            // 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
            allowedFileTypes: ['image/*'],
            // 自定义上传参数,例如传递验证的 token 等。参数会被添加到 formData 中,一起上传到服务端。
            meta: {
              token: getToken(),
              directoryKey: 1010,
            },
            headers: {
              token: getToken(),
            },
            // 超时时间,默认为 10 秒
            timeout: 60000 * 5, 
            metaWithUrl: false,
            customUpload: {},
          },
          uploadVideo: { customUpload: {} },
          browerConnection: null
        },
      }
    }
  },
  created() {
    if(this.noVideo) {
      this.toolbarConfig.excludeKeys.push('uploadVideo')
    }
    this.editorConfig.placeholder = this.placeholder;
    this.editorConfig.maxLength = this.maxLength;
    this.editorConfig.readOnly = this.readOnly;
    this.editorConfig.MENU_CONF.uploadVideo.customUpload
    this.$set(this.editorConfig.MENU_CONF.uploadImage, 'customUpload', this.uploadImg);
    this.$set(this.editorConfig.MENU_CONF.uploadVideo, 'customUpload', this.uploadVideo);
    // 自定义上传视频-压缩视频
    this.$set(this.editorConfig.MENU_CONF.uploadVideo, 'customBrowseAndUpload', this.customBrowseAndUpload);
  },
  async mounted() {
    let res = await getConfig({environment: 'prd'});
    this.ossUploadKey = JSON.parse(decryptByCBC(res.msg));
    if(this.noVideo) {
      this.toolbarConfig.excludeKeys.push('uploadVideo','insertVideo','editVideoSize')
      document.getElementsByClassName('w-e-bar-item-group')[4].style.display = 'none';
    }
  },
  beforeDestroy() {
    this.browerConnection && this.browerConnection.close();
    const editor = this.editor;
    if (editor == null) return;
    editor.destroy(); // 组件销毁时,及时销毁 editor ,重要!!!
  },
  methods: {
    onCreated(editor) {
      this.editor = Object.seal(editor); // 【注意】一定要用 Object.seal() 否则会报错
      //this.editor.getAllMenuKeys() 可以查询所有菜单配置
      // console.log('Editor菜单配置', this.editor.getAllMenuKeys())
    },
    onChange(editor) {
      // console.log("onChange", editor.getHtml()); // onChange 时获取编辑器最新内容
      // this.value = editor.getHtml();
      this.$emit("wangEditorChange", editor.getHtml())
    },
    // 初始化上传需要的配置数据
    formatUploadParams() {
      let today = new Date();
      let tomorrow = new Date(today.getTime() + 24 * 60 * 60 * 1000);
      let policyText = {
          "expiration": tomorrow.toISOString(), //设置该Policy的失效时间,超过这个失效时间之后,就没有办法通过这个policy上传文件了,toISOString转换成带TZ格式的时间
          "conditions": [
            ["content-length-range", 0, 104857600 * 2] // 设置上传文件的大小限制,104857600字节 = 100兆
          ]
      };
      let policyBase64 = Base64.encode(JSON.stringify(policyText))
      let message = policyBase64
      let bytes = Crypto.HMAC(Crypto.SHA1, message, this.ossUploadKey.accessSecret, { asBytes: true });
      let signature = Crypto.util.bytesToBase64(bytes);
      let uploadParams = {
        'key' : 'editor/${filename}',
        'policy': policyBase64,
        'OSSAccessKeyId': this.ossUploadKey.accessId, 
        'success_action_status' : '200', //让服务端返回200,不然,默认会返回204
        'signature': signature,
      };
      return uploadParams;
    },
    async uploadVideo(file, insertFn)  {
      let apiUrl = 'https://oss.aidmed.net'
      let uParams = await this.formatUploadParams();
      uParams.name = file.name;
      await uploadFile(file, 1010, apiUrl, uParams).then(res => {
        if(apiUrl.indexOf('oss.aidmed') > -1) {
          res.msg = 'https://aidmed.oss-cn-shenzhen.aliyuncs.com/editor/' + uParams.name;
          insertFn(res.msg);
        }
      })
    },
    uploadImg(file, insertFn) {
      let isGif = file.type.includes("gif", 0);
      let isBigGif = file.type.includes("GIF", 0);
      if(isGif || isBigGif) {
        uploadFile(file).then(res => {
          let imgStr = process.env.VUE_APP_BASE_FILEURL + res.msg;
          insertFn(imgStr);
        })
      }else{
        let resultBlob = '';
        let image = new Image();
        image.src = URL.createObjectURL(file);
        image.onload = async () => {
          // 调用方法获取blob格式,方法写在下边
          resultBlob = await this.compressUpload(image, file);
          file = new File([resultBlob], file.name, {
            type: file.type,
            lastModified: Date.now()
          });
          uploadFile(file).then(res => {
            let imgStr = process.env.VUE_APP_BASE_FILEURL + res.msg;
            insertFn(imgStr);
          })
        }
      }
    },
    /* 图片压缩方法-canvas压缩 */
    compressUpload(image, file) {
       return new Promise((resolve, reject) => {
        let canvas = document.createElement('canvas');
        let ctx = canvas.getContext('2d');
        let initSize = image.src.length;
        let { width } = image, { height } = image;
        canvas.width = width;
        canvas.height = height;
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(image, 0, 0, width, height);
        
        // 进行最小压缩0.1
        let compressData = canvas.toDataURL(file.type || 'image/jpeg', 0.7);
        
        // 压缩后调用方法进行base64转Blob,方法写在下边
        let blobImg = this.convertBase64UrlToBlob(compressData);
        resolve(blobImg);
      })
    },
    // 自定义视频压缩页面跳转
    customBrowseAndUpload(insertFn) {
      this.$confirm("将为您打开新的窗口进行视频压缩,请勿关闭当前页面,视频上传完成后可关闭新打开的窗口!", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
        let url = process.env.NODE_ENV === 'development' ? 'http://localhost:8088/tool-web' : window.location.origin + '/tool-web/';
        window.open(url);
        this.browerConnection && this.browerConnection.close();
        this.initTagCommunication(insertFn);
      })
    },

    // 创建浏览器跨标签页通信
    initTagCommunication(insertFn) {
      this.browerConnection = null;
      // TODO: 创建一个BroadcastChannel的实例
      this.browerConnection = new BroadcastChannel('tagCommunication-channel');

      /**
       * TODO: 发送消息给所有的监听标签页
       * @param {any} 广播消息的内容
       */
      const sendMessages = (obj = {}) => {
        console.log("广播一条消息:", obj);
        this.browerConnection.postMessage(obj);
      }

      /**
       * TOOD: 接收消息
       * @param {Function} 接收消息的回调方法
       */
      const receiveMessages = (cb) => {
        if (cb) {
          this.browerConnection.addEventListener('message', (e) => {
            console.log("接收到的广播消息:", e);
            cb(e.data);
          })
        } else {
          console.error('回调函数是必传项');
        }
      }

      // TODO: 关闭接收广播 以便于JS的垃圾回收
      const closeMessage = () => {
        this.browerConnection.close();
      }

      // TODO: 设置获取到的消息进行处理
			const setMessage = (data) => {
				// TODO: 接收结束 关闭广播 
				// closeMessage();

        // 将传递过来的视频地址设置进富文本
        insertFn(data, '');
			}
			// TODO: 自执行函数进行接收获取到的广播消息
			receiveMessages(setMessage);
    }
    
  }
}
</script>

<style lang="scss" scoped>
.wrap{
  border: 1px solid #ccc;
  margin-top: 10px;
  .toolbar{
    border-bottom: 1px solid #ccc;
  }
  .editor{
    height: 400px; 
    overflow-y: hidden;
  }
}
::v-deep .w-e-modal{
  padding: 10px;
}
</style>

移动端处理:
看不懂就V我50告诉你

res.data.content = res.data.content?.replace(/data-href/g, 'longdesc');

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

peachSoda7

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值