场景&需求:也是源于一个需求。本来我们的后台系统的编辑器用的是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');
```