UEditor + 秀米编辑器的集成 + 编辑器富文本图片的转存

场景&需求:也是源于一个需求。本来我们的后台系统的编辑器用的是tinymce。后来由于运营那边都是用别的第三方编辑器去编辑各种版式的文章,然后再复制过来后台系统这边发布,由于发布文章也是展示在移动端上的,然后就会有不少问题。

(好像这里展示不了代码,看代码的话可以打开我的原文链接:《UEditor + 秀米编辑器的集成 + 编辑器富文本图片的转存》

**1:文章的排版错误,发布出去的内容排版和秀米编辑器上面的相差太远**

**2:由于在秀米编辑器那边发布的文章,上传的图片都是存在他们的服务器,然后我们后台发布后就出现了防盗链显示不出来的情况,就更是加剧了排版错乱**

**解决方案:让在秀米编辑器复制过来的文档在我们的后台系统发布时候排版正常,已经,处理防盗链的问题,就是将第三方的图片转存到我们自己的服务器或者oss**



然后发现,秀米官方有提供给第三方的对接方案。我们这边系统需要有自己的编辑器,毕竟有时候也是会有不需要花里胡哨排版的时候的,所以,就采用了秀米官方提供的第二个方案,但是有个前提,必须是UEditor的内核。那就是说,我们就要采用UEditor的编辑器了。




 一、引入UEditor

1、因为我们后台用的技术栈是vue,就采用了大佬封装好的[vue-ueditor-wrap](https://github.com/HaoChuan9421/vue-ueditor-wrap/tree/2.x)。然后按照它的说明来安装,npm安装好包后,按照说明去[UEditor 官网](http://fex.baidu.com/ueditor/#start-start)下载UEditor文件夹,放在项目的静态资源文件夹下就行。


2、引入组件 & 注册

....... 这里略过了~这里的配置就按照github上面一步步配置就行了,最后最重要是编辑器配置的UEDITOR_HOME_URL 和 serverUrl两个选项。具体可以看UEditor文档[UEditor文档](http://fex.baidu.com/ueditor/#start-config)

-----------

二、引入秀米,集成进UEditor

这里是官网的引入实例,[秀米图文排版UEditor插件示例](https://ent.xiumi.us/ue/)

按照他的文档引入就行了,这里没有什么坑。有个小坑后面在图片转存的时候才需要去搞。

这一步引入没啥问题的话,编辑器的工具栏就会展示出来一个秀米的小图标在最后面了。

![image.png](/upload/2022/01/image-0167cd1c6cf74d39ba52a4707318f6b0.png)

------------

## 三、图片转存,这里的就都是代码了


UEditor是有提供了一个图片转存的选项,只需要在他的代码里面做一下转存的更改就可以了。

然后就是去到它的源码文件那里,大概是在23200行左右,可能每个版本不一样,找到这个地方,里面的监听catchRemoteImage函数里面


我这边是重写了它原来的逻辑,因为我们这边的转存是前端获取到图片地址,然后请求到后端,处理完返回转存后的oss地址,我这边再去做替换原来的地址,就是替换掉原来秀米的oss地址。




```javascript

me.addListener("catchRemoteImage", function () {

        var catcherLocalDomain = me.getOpt('catcherLocalDomain') || [],

            catcherActionUrl = me.getActionUrl(me.getOpt('catcherActionName')),

            catcherUrlPrefix = me.getOpt('catcherUrlPrefix'),

            catcherFieldName = me.getOpt('catcherFieldName');

            try {

              catcherLocalDomain.push('k-mmh.com')  //插入不需要过滤的域名白名单  2022年1月19日  --kapok  ***************************************

            } catch (error) {


            }

            console.log('白名单',catcherLocalDomain)

            // 重写xhr请求  不改动原来ueditor的了,防止出别的问题            --kapok

            var kapokHttp = {};

            kapokHttp.quest = function (option, callback) {

              var url = option.url;

              var method = option.method;

              var data = option.data;

              var timeout = option.timeout || 0;

              var xhr = new XMLHttpRequest();

              (timeout > 0) && (xhr.timeout = timeout);

              xhr.onreadystatechange = function () {

                if (xhr.readyState == 4) {

                  if (xhr.status >= 200 && xhr.status < 400) {

                    var result = xhr.responseText;

                    try { result = JSON.parse(xhr.responseText); } catch (e) { }

                    callback && callback(null, result);

                  } else {

                    callback && callback('status: ' + xhr.status);

                  }

                }

              }.bind(this);

              xhr.open(method, url, true);

              if (typeof data === 'object') {

                try {

                  data = JSON.stringify(data);

                } catch (e) { }

              }

              // 获取token

              let getTheCookie = function(c_name){

                if(document.cookie.length > 0) {

                  let c_start = document.cookie.indexOf(c_name + "=");//获取字符串的起点

                  if(c_start != -1) {

                    c_start = c_start + c_name.length + 1;//获取值的起点

                    let c_end = document.cookie.indexOf(";", c_start);//获取结尾处

                    if(c_end == -1) c_end = document.cookie.length;//如果是最后一个,结尾就是cookie字符串的结尾

                    return decodeURI(document.cookie.substring(c_start, c_end));//截取字符串返回

                  }

                }


              }

              // 这里由于上传文件转存地址接口需要用到token,也是直接写死  ---- 可以增加判断是否需要

              xhr.setRequestHeader('UserKey', getTheCookie('vue_admin_template_token'));

              // 这里接口是post,直接写死Content-Type,如果有新的接口可以修改判断去配置

              xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');


              xhr.send(data);

              xhr.ontimeout = function () {

                callback && callback('timeout');

                console.log('%c连%c接%c超%c时', 'color:red', 'color:orange', 'color:purple', 'color:green');

              };

            };

            // get用不到,注释了 ----------------------------------

            // kapokHttp.get = function (url, callback) {

            //  var option = url.url ? url : { url: url };

            //  option.method = 'get';

            //  this.quest(option, callback);

            // };

            kapokHttp.post = function (option, callback) {

              option.method = 'post';

              this.quest(option, callback);

            };

            // 尝试下重写原来editor的图片替换  ----- start ----------------------------------------

            var remoteImages = [],


            //获取富文本里style里带url的元素,以及img元素

            imgs = me.document.querySelectorAll('[style*="url"],img'),


            //判断图片链接是否在白名单内的方法    --白名单要在上面的白名单配置,配置我们的oss地址前缀,不然触发这个方法的时候会无限次去换新图

            test = function(src, urls) {

              if (src.indexOf(location.host) != -1 || /(^\.)|(^\/)/.test(src)) {

                return true;

              }

              if (urls) {

                for (var j = 0, url; (url = urls[j++]); ) {

                  if (src.indexOf(url) !== -1) {

                    return true;

                  }

                }

              }

              return false;

            };

            for (var i = 0, ci; i<imgs.length;i++) {

              ci = imgs[i]

              //ci为current item,当前元素

              // 这里临时存一个新的变量

              let cc = ci

              //如果有word_img这个属性的话,就略过-------------------------

              if (cc.getAttribute("word_img")) {

                continue;

              }

              //如果该元素是一img元素

              if(cc.nodeName == "IMG"){

              //获取图片元素的src的值,估计会有“”空字符串,因为有的图片确实是没有写src链接的

                var src = cc.getAttribute("_src") || cc.src || "";

                //判断头部是否含有https、http或者ftp字样,并且不在白名单里面的

                if (/^(https?|ftp):/i.test(src) && !test(src, catcherLocalDomain)) {

                  // 把图片存起来、、、、不过这个可能暂时不用了,预留后面的奇葩需求,放着...........

                  remoteImages.push(src); 

                  // 接口取到

                  let ajaxUrl = `${process.env.VUE_APP_BASE_API}/api/admin/v1/file/uploadFileByUrl`

                  kapokHttp.post({ url: ajaxUrl, data: `fileUrl=${src.split('?')[0]}`, timeout: 60000 }, function (err, res) {

                      // 这里对结果进行处理,替换

                      if(res.status === 'success'){

                        domUtils.setAttributes(cc, {

                          class: "newUrlClass",

                          "src": res.result,

                          "_src": res.result

                        })

                      }

                  });

                }

              // 这里不是图片就是背景图了

              } else {

                // 获取背景图片url

                var backgroundImageurl = cc.style.cssText.replace(/.*\s?url\([\'"]?/, '').replace(/[\'"]?\).*/, '');

                //跟上面的img差不多的判断

                if (/^(https?|ftp):/i.test(backgroundImageurl) && !test(backgroundImageurl, catcherLocalDomain)) {

                  // 跟上面一样,把图片存起来、、、、不过这个可能暂时不用了,预留后面的奇葩需求,放着...........

                  remoteImages.push(backgroundImageurl);

                  // 接口地址

                  let ajaxUrl = `${process.env.VUE_APP_BASE_API}/api/admin/v1/file/uploadFileByUrl`

                  let newRequestUrl = backgroundImageurl

                  kapokHttp.post({ url: ajaxUrl, data: `fileUrl=${newRequestUrl.split('?')[0]}`, timeout: 60000 }, function (err, res) {

                      // 这里对结果进行处理

                      if(res.status === 'success'){

                        cc.style.cssText = cc.style.cssText.replace(backgroundImageurl, res.result);

                        domUtils.setAttributes(cc, {

                          "data-background": res.resul

                        })

                      }

                  });

                }

              }

          }

}

```

然后最后还有一个一个秀米的坑,在他们官网下载的这个xiumi-ue-dialog-v5.html文件,在完成编辑点导出的时候是没有触发UEditor的这个远程图片抓取函数的,需要增加上一句代码,因为这个远程图片抓取是UEditor那边粘贴事件触发的。

```language

editor.fireEvent('catchRemoteImage');

```


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,176评论 5 469
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,190评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,232评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,953评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,879评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,177评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,626评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,295评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,436评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,365评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,414评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,096评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,685评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,771评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,987评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,438评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,032评论 2 341

推荐阅读更多精彩内容