ueditor组件

阅读: 3438    发布时间: 2018-09-05 10:21:02

github地址:https://github.com/501351981/vue_yii_cms

做一个后台,离不开文本编辑器,百度的ueditor经常用,这次改用vue写后台,需要再次集成一下,遇到很多问题,搞了一下午,整理一下,希望能够帮到后来者。

目标:

希望封装的ueditor组件,尽可能简单,期望是这样的,能够像input一样简单,可以实现v-mode

<ueditor v-model="content"></ueditor>

实现

复制ueditor文件

将下载的ueditor放到static/lib路径下面 ,这个位置随意,根据自己情况更改;后台相关的代码可以删除,交由后台同学处理,因为我们现在用vue是前后台分离的,所以不需要后台代码


更改配置文件

更改ueditor.config.js,

将
var URL = window.UEDITOR_HOME_URL || getUEBasePath();
改为
var URL ="/static/lib/ueditor/"   

也就是我们的ueditor路径,如果不这么改,你可能会看到如下报错


Uncaught SyntaxError: Unexpected token <

ZeroClipboard.js:1 Uncaught SyntaxError: Unexpected token <

ueditor.all.js?9bdc:14409 Uncaught ReferenceError: ZeroClipboard is not defined

    at initZeroClipboard (ueditor.all.js?9bdc:14409)

    at eval (ueditor.all.js?9bdc:14456)

    at HTMLScriptElement.element.onload.element.onreadystatechange (ueditor.all.js?9bdc:921)


更改服务器接口地址,因为我们是前后台分离,所以之前的接口地址就不对了,要改成自己的真实接口地址

, serverUrl: process.env.API_ROOT + "/ueditor/php/controller.php"

没改正确报错

请求后台配置项http错误,上传功能将不能正常使用!


组件封装

新建一个Ueditor.vue文件,其中data中的editor为编辑器实例,为了实现v-mode功能,我们需要一个名为value属性,还需要在编辑器内容功能时,触发input事件,编辑器内容变化通过contentChange事件监听

另一个props属性为config,我们可以通过这个修改编辑器的样式等,此处给了一个默认值,高度350px

data中有两个属性

  • editor:保存编辑器对象实例

  • content:存储编辑器的内容,目的是监听value变化时,比对新value和content是否一样,一样就说明是因为编辑器内容更改,触发的更改value,此时不应该再根据这个值,重置编辑器内容,否则会造成编辑器光标错乱的尴尬情况

<template>
  <div class="ueditor">
    <script id="editor" type="text/plain"></script>
  </div>
</template>
<script>
 import  '../../../static/lib/ueditor/ueditor.config'
 import  '../../../static/lib/ueditor/ueditor.all'
 import  '../../../static/lib/ueditor/lang/zh-cn/zh-cn'
 import  '../../../static/lib/ueditor/ueditor.parse'

 export default {
    name:'Ueditor',
 data:function () {
      return{
        editor:'',
 content:''
 }
    },
 props: {
      config: {
        type: Object,
 default:function () {
          return  {
            initialFrameWidth: null,
 initialFrameHeight: 350,
 lang:"zh-cn"
 }
        }
      },
 value:String

 },
 watch:{
      value:function(new_content) {
        if(new_content!=this.content){
          this.editor.setContent(this.value);
 }
      }
    },
 mounted:function () {
      this.editor=UE.getEditor("editor",this.config)
      this.editor.addListener("ready", ()=>{
        this.editor.setContent(this.value); // 确保UE加载完成后,放入内容。

 this.editor.addListener("contentChange",()=>{
          this.content=this.editor.getContent()
          this.$emit('input', this.content)
        })
      });
 },
 methods: {
      getUEContent:function() {
        return this.editor.getContent()
      }
    },
 destroyed: function() {
      this.editor.destroy();
 },
 }
</script>
<style lang="less">

</style>

注意上面的destroyed事件是必须的,如果没有这段代码,你会发现,页面刷新的时候能够看到编辑器,此后如果通过点击返回,或者通过router跳转,再回到编辑器页面,发现编辑器消失了

在编辑页面使用Ueditor组件

<template>
  <div>
    <form class="form-horizontal form-edit">
          //其他代码       
          <ueditor v-model="content"></ueditor>
 
          //其他代码      
    </form>
  </div>
</template>

<script>
 
 import Ueditor from "../../components/mod/Ueditor"


 export default {
    name: 'NewsEdit',
 components: {Ueditor},

 data:function () {
      return {
content:'测试内容'
        
    },
 
  }
}
</script>

如果不出意外,现在你的编辑器已经可以正常工作了


图片上传

编辑器离不了图片上传,而现在图片上传还是不行的,会发现有错误提示(后台上传接口要先搞好)

提示跨域问题,图片上传这里,百度用的form表单的提交,我们把它改成通过ajax提交就好了

DOMException: Blocked a frame with origin "http://127.0.0.1:8080" from accessing a cross-origin frame.

    at HTMLIFrameElement.callback (webpack-internal:///./static/lib/ueditor/ueditor.all.js:24482:84)


此处改动较大,改动domUtils.on(input, 'change', function(){})中的内容

改动前

domUtils.on(input, 'change', function(){
    if(!input.value) return;
    var loadingId = 'loading_' + (+new Date()).toString(36);
    var params = utils.serializeParam(me.queryCommandValue('serverparam')) || '';

    var imageActionUrl = me.getActionUrl(me.getOpt('imageActionName'));
    var allowFiles = me.getOpt('imageAllowFiles');

    me.focus();
    me.execCommand('inserthtml', '<img class="loadingclass" id="' + loadingId + '" src="' + me.options.themePath + me.options.theme +'/images/spacer.gif" title="' + (me.getLang('simpleupload.loading') || '') + '" >');

    function callback(){

        try{
            var link, json, loader,
                body = (iframe.contentDocument || iframe.contentWindow.document).body,
                result = body.innerText || body.textContent || '';
            json = (new Function("return " + result))();
            link = me.options.imageUrlPrefix + json.url;
            if(json.state == 'SUCCESS' && json.url) {

              console.log("上传成功了已经")
                loader = me.document.getElementById(loadingId);
                loader.setAttribute('src', link);
                loader.setAttribute('_src', link);
                loader.setAttribute('title', json.title || '');
                loader.setAttribute('alt', json.original || '');
                loader.removeAttribute('id');
                domUtils.removeClasses(loader, 'loadingclass');
            } else {
                showErrorLoader && showErrorLoader(json.state);
            }
        }catch(er){
          console.log("上传失败了")
          console.log(er)
            showErrorLoader && showErrorLoader(me.getLang('simpleupload.loadError'));
        }
        form.reset();
        domUtils.un(iframe, 'load', callback);
    }
    function showErrorLoader(title){
        if(loadingId) {
            var loader = me.document.getElementById(loadingId);
            loader && domUtils.remove(loader);
            me.fireEvent('showmessage', {
                'id': loadingId,
                'content': title,
                'type': 'error',
                'timeout': 4000
            });
        }
    }

    /* 判断后端配置是否没有加载成功 */
    if (!me.getOpt('imageActionName')) {
        errorHandler(me.getLang('autoupload.errorLoadConfig'));
        return;
    }
    // 判断文件格式是否错误
    var filename = input.value,
        fileext = filename ? filename.substr(filename.lastIndexOf('.')):'';
    if (!fileext || (allowFiles && (allowFiles.join('') + '.').indexOf(fileext.toLowerCase() + '.') == -1)) {
        showErrorLoader(me.getLang('simpleupload.exceedTypeError'));
        return;
    }

    domUtils.on(iframe, 'load', callback);
    form.action = utils.formatUrl(imageActionUrl + (imageActionUrl.indexOf('?') == -1 ? '?':'&') + params);
    form.submit();
    
    function showErrorLoader(title){
    if(loadingId) {
      var loader = me.document.getElementById(loadingId);
      loader && domUtils.remove(loader);
      me.fireEvent('showmessage', {
        'id': loadingId,
        'content': title,
        'type': 'error',
        'timeout': 4000
      });
    }
  }
});

改动后,使用的是原生的ajax上传

domUtils.on(input, 'change', function(){

  if(!input.value) return;
  var loadingId = 'loading_' + (+new Date()).toString(36);
  var imageActionUrl = me.getActionUrl(me.getOpt('imageActionName'));
  var allowFiles = me.getOpt('imageAllowFiles');

  me.focus();
  me.execCommand('inserthtml', '<img class="loadingclass" id="' + loadingId + '" src="' + me.options.themePath + me.options.theme +'/images/spacer.gif" title="' + (me.getLang('simpleupload.loading') || '') + '" >');

  /!* 判断后端配置是否没有加载成功 *!/
  if (!me.getOpt('imageActionName')) {
    errorHandler(me.getLang('autoupload.errorLoadConfig'));
    return;
  }
  // 判断文件格式是否错误
  var filename = input.value,
    fileext = filename ? filename.substr(filename.lastIndexOf('.')):'';
  if (!fileext || (allowFiles && (allowFiles.join('') + '.').indexOf(fileext.toLowerCase() + '.') == -1)) {
    showErrorLoader(me.getLang('simpleupload.exceedTypeError'));
    return;
  }

  var params = utils.serializeParam(me.queryCommandValue('serverparam')) || '';
  var action = utils.formatUrl(imageActionUrl + (imageActionUrl.indexOf('?') == -1 ? '?' : '&') + params);
  var formData = new FormData();
  formData.append("file", form[0].files[0] );


  var xhr = null; //得到xhr对象
  if(XMLHttpRequest){
    xhr = new XMLHttpRequest();
  }else{
    xhr = new ActiveXObject("Microsoft.XMLHTTP");
  }

  xhr.open("post", action, true);//设置提交方式,url,异步提交
  xhr.onload = function ()
  {
    var data = xhr.responseText;    //得到返回值
    data=JSON.parse(data)

    var link, loader,
      body = (iframe.contentDocument || iframe.contentWindow.document).body,
      result = body.innerText || body.textContent || '';
    link = me.options.imageUrlPrefix + data.url;

    if(data.state == 'SUCCESS' && data.url) {
      loader = me.document.getElementById(loadingId);
      loader.setAttribute('src', link);
      loader.setAttribute('_src', link);
      loader.setAttribute('title', data.title || '');
      loader.setAttribute('alt', data.original || '');
      loader.removeAttribute('id');
      domUtils.removeClasses(loader, 'loadingclass');

      me.fireEvent('contentchange')
    } else {
      showErrorLoader && showErrorLoader(data.state);
    }
    form.reset();


  }
  xhr.send(formData);



  function showErrorLoader(title){
    if(loadingId) {
      var loader = me.document.getElementById(loadingId);
      loader && domUtils.remove(loader);
      me.fireEvent('showmessage', {
        'id': loadingId,
        'content': title,
        'type': 'error',
        'timeout': 4000
      });
    }
  }
});


最初,我是想用axios进行ajax操作的,在ueditor.all.js开头处加入了

import axios from 'axios'

结果发现,再次进入编辑器输入内容时,会有报错,如下,试了很多办法没用解决,搞了很长时间,虽然不影响上传,但是受不了代码有错误提示,最终换成上面的纯js的ajax上传图片,这个问题可能是因为通过import引入文件,强制改为了js严格模式

ueditor.all.js?9bdc:28636 Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them  at HTMLDocument.countFn (ueditor.all.js?9bdc:28636)


幸好,问题一个一个都被解决了,学习就是这样,会遇到很多困难,多console.log,找原因,找解决办法,一个一个测试,最终战胜困难

祝好



-END-