前言
封面是UEditor的 百度指数 折线图。虽然今天已经是 2018 年,且优秀的富文本编辑器层出不穷(包括移动端),但从图中可以看出UEditor仍然维持着较高的搜索热度。而不少公司和个人也仍然在项目中使用UEditor。目前,UEditor官网的最后一次版本更新是 1.4.3.3,这已经是 2016 年的事情了,而今天的前端开发,很多小伙伴都在使用Vue,React 这种组件化的前端框架。这就导致在这些“现代”框架中集成UEditor变得很不平滑。所以才会有下图这些大量介绍如何在Vue项目中集成UEditor的博客:


为了提高代码的可复用性,也为了尽可能的不在业务代码中参杂UEditor的相关操作,我在几个月前,公司项目的开发中撸了一个组件,可以通过v-model双向绑定的方式来使用UEditor,简单到就像使用input框一样。当我撸完,感觉非常的Vue范儿。而且看了不少博客和GitHub项目,都没有类似的实现。于是我决定发布到 npm 上,帮助一众还在思考如何把UEditor集成到Vue项目中的小伙伴。几个月下来,基本已经稳定,所以,今天通过这篇博客,分享给大家。

先看效果图:


点击预览    仓库地址

Installation
npm i vue-ueditor-wrap
# 或者
yarn add vue-ueditor-wrap

Quick Start
基于 vue-cli 2.x 的完整 DEMO
基于 Nuxt 的服务端渲染 DEMO。

下载 UEditor

下载最新编译的 UEditor。官网目前最新的版本是1.4.3.3,存在诸多 BUG,例如 Issue1,且官方不再积极维护。为了世界的和平,针对一些常见 BUG,我进行了修复,并把编译好的文件放在了本仓库的 assets/downloads 目录下,你可以放心下载,当然你也可以自己 clone 官方源码并编译。


将下载的压缩包解压并重命名为 UEditor(只需要选择一个你需要的版本,比如 utf8-php),放入你项目的 static 目录下。


如果你使用的是 vue-cli 3.x,可以把 UEditor 文件夹放入项目的 public 目录下。

引入VueUeditorWrap组件

import VueUeditorWrap from 'vue-ueditor-wrap' // ES6 Module
// 或者
const VueUeditorWrap = require('vue-ueditor-wrap') // CommonJS

你也可以通过直接引入 CDN 链接的方式来使用,它会暴露一个全局的 VueUeditorWrap 变量(具体如何使用你可以阅读我的这篇博客或参考这个仓库)。

<script src="https://cdn.jsdelivr.net/npm/vue-ueditor-wrap@latest/lib/vue-ueditor-wrap.min.js"></script>

注册组件

components: {
  VueUeditorWrap
}
// 或者在 main.js 里将它注册为全局组件
Vue.component('vue-ueditor-wrap', VueUeditorWrap)

v-model绑定数据

<vue-ueditor-wrap v-model="msg"></vue-ueditor-wrap>
data () {
  return {
    msg: '<h2><img src="http://img.baidu.com/hi/jx2/j_0003.gif"/>Vue + UEditor + v-model双向绑定</h2>'
  }
}

至此你已经可以在页面中看到一个初始化之后的 UEditor 了,并且它已经成功和数据绑定了!???

根据项目需求修改配置,完整配置选项查看 ueditor.config.js 源码或 官方文档

<vue-ueditor-wrap v-model="msg" :config="myConfig"></vue-ueditor-wrap>

data () {
  return {
    msg: '<h2><img src="http://img.baidu.com/hi/jx2/j_0003.gif"/>Vue + UEditor + v-model双向绑定</h2>',
    myConfig: {
      // 编辑器不自动被内容撑高
      autoHeightEnabled: false,
      // 初始容器高度
      initialFrameHeight: 240,
      // 初始容器宽度
      initialFrameWidth: '100%',
      // 上传文件接口(这个地址是我为了方便各位体验文件上传功能搭建的临时接口,请勿在生产环境使用!!!)
      serverUrl: 'http://35.201.165.105:8000/controller.php',
      // UEditor 资源文件的存放路径,如果你使用的是 vue-cli 生成的项目,通常不需要设置该选项,vue-ueditor-wrap 会自动处理常见的情况,如果需要特殊配置,参考下方的常见问题2
      UEDITOR_HOME_URL: '/static/UEditor/'
    }
  }
}

Advanced
如何获取 UEditor 实例?

<vue-ueditor-wrap @ready="ready"></vue-ueditor-wrap>

methods: {
  ready (editorInstance) {
    console.log(`编辑器实例${editorInstance.key}: `, editorInstance)
  }
}

设置是否在组件的 beforeDestroy 钩子里销毁 UEditor 实例

<vue-ueditor-wrap :destroy="true"></vue-ueditor-wrap>

选取 v-model 的实现方式。双向绑定的实现依赖对编辑器内容变化的监听,由于监听方式的不同,会带来监听效果的差异性,你可以自行选择,但建议使用开箱即用的默认值。

<vue-ueditor-wrap mode="listener"></vue-ueditor-wrap>

可选值:observer,listener

默认值:observer

参数说明:

observer 模式借助 MutationObserver API。优点在于监听的准确性,缺点在于它会带来一点额外的性能开销。你可以通过 observerDebounceTime 属性设置触发间隔,还可以通过 observerOptions 属性有选择的设置 MutationObserver 的监听行为。该 API 只兼容到 IE11+,但 vue-ueditor-wrap 会在不支持的浏览器中自动启用 listener 模式。

<vue-ueditor-wrap
  mode="observer"
  :observerDebounceTime="100"
  :observerOptions="{ attributes: true, characterData: true, childList: true, subtree: true }"
  >
</vue-ueditor-wrap>

listener 模式借助 UEditor 的 contentChange 事件,优点在于依赖官方提供的事件 API,无需额外的性能消耗,兼容性更好,但缺点在于监听的准确性并不高,存在如下方 [常见问题 5] 中的提到的 BUG。

是否支持 Vue SSR?

自 2.4.0 版本开始支持服务端渲染!本组件提供对 Nuxt 项目开箱即用的支持。但如果你是自己搭建的 Vue SSR 项目,你可能需要自行区分服务端和客户端环境并结合 forceInit 属性强制初始化编辑器,但大概率你用不到该属性,即使是自己搭建的 SSR 项目,更多问题欢迎提交 ISSUE。

如何进行二次开发(添加自定义按钮、弹窗等)?

本组件提供了 beforeInit 钩子,它会在 UEditor 的 scripts 加载完毕之后、编辑器初始化之前触发,你可以在此时机,通过操作 window.UE 对象,来进行诸如添加自定义按钮、弹窗等的二次开发。beforeInit 的触发函数以 编辑器 id 和 配置参数 作为入参。下面提供了一个简单的自定义按钮和自定义弹窗的示例,DEMO 仓库中也提供了自定义“表格居中”按钮的示例,如果有更多二次开发的需求,你可以参考官方 API 或者 UEditor 源码 中的示例。

自定义按钮 Demo
<vue-ueditor-wrap v-model="msg" @beforeInit="addCustomButtom"></vue-ueditor-wrap>

addCustomButtom (editorId) {
  window.UE.registerUI('test-button', function (editor, uiName) {
    // 注册按钮执行时的 command 命令,使用命令默认就会带有回退操作
    editor.registerCommand(uiName, {
      execCommand: function () {
        editor.execCommand('inserthtml', `<span>这是一段由自定义按钮添加的文字</span>`)
      }
    })

// 创建一个 button
    var btn = new window.UE.ui.Button({
      // 按钮的名字
      name: uiName,
      // 提示
      title: '鼠标悬停时的提示文字',
      // 需要添加的额外样式,可指定 icon 图标,图标路径参考常见问题 2
      cssRules: "background-image: url('/test-button.png') !important;background-size: cover;",
      // 点击时执行的命令
      onclick: function () {
        // 这里可以不用执行命令,做你自己的操作也可
        editor.execCommand(uiName)
      }
    })

// 当点到编辑内容上时,按钮要做的状态反射
    editor.addListener('selectionchange', function () {
      var state = editor.queryCommandState(uiName)
      if (state === -1) {
        btn.setDisabled(true)
        btn.setChecked(false)
      } else {
        btn.setDisabled(false)
        btn.setChecked(state)
      }
    })

// 因为你是添加 button,所以需要返回这个 button
    return btn
  }, 0 /* 指定添加到工具栏上的哪个位置,默认时追加到最后 */, editorId /* 指定这个 UI 是哪个编辑器实例上的,默认是页面上所有的编辑器都会添加这个按钮 */)
}

自定义弹窗 Demo
<vue-ueditor-wrap v-model="msg" @beforeInit="addCustomDialog"></vue-ueditor-wrap>

addCustomDialog (editorId) {
  window.UE.registerUI('test-dialog', function (editor, uiName) {
    // 创建 dialog
    var dialog = new window.UE.ui.Dialog({
      // 指定弹出层中页面的路径,这里只能支持页面,路径参考常见问题 2
      iframeUrl: '/customizeDialogPage.html',
      // 需要指定当前的编辑器实例
      editor: editor,
      // 指定 dialog 的名字
      name: uiName,
      // dialog 的标题
      title: '这是一个自定义的 Dialog 浮层',
      // 指定 dialog 的外围样式
      cssRules: 'width:600px;height:300px;',
      // 如果给出了 buttons 就代表 dialog 有确定和取消
      buttons: [
        {
          className: 'edui-okbutton',
          label: '确定',
          onclick: function () {
            dialog.close(true)
          }
        },
        {
          className: 'edui-cancelbutton',
          label: '取消',
          onclick: function () {
            dialog.close(false)
          }
        }
      ]
    })

// 参考上面的自定义按钮
    var btn = new window.UE.ui.Button({
      name: 'dialog-button',
      title: '鼠标悬停时的提示文字',
      cssRules: `background-image: url('/test-dialog.png') !important;background-size: cover;`,
      onclick: function () {
        // 渲染dialog
        dialog.render()
        dialog.open()
      }
    })

return btn
  }, 0 /* 指定添加到工具栏上的那个位置,默认时追加到最后 */, editorId /* 指定这个UI是哪个编辑器实例上的,默认是页面上所有的编辑器都会添加这个按钮 */)
}

弹出层中的 HTML 页面 customizeDialogPage.html

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="renderer" content="webkit">
  <!--页面中一定要引入internal.js为了能直接使用当前打开dialog的实例变量-->
  <!--internal.js默认是放到 UEditor/dialogs 目录下的-->
  <script type="text/javascript" src="./UEditor/dialogs/internal.js"></script>
</head>

<body>
  <h1>hello vue-ueditor-wrap</h1>
  <script>
    //可以直接使用以下全局变量
    //当前打开dialog的实例变量
    console.log('editor: ' + editor);
    //一些常用工具
    console.log('domUtils: ' + domUtils);
    console.log('utils: ' + utils);
    console.log('browser: ' + browser);
    dialog.onok = function() {
      editor.execCommand('inserthtml', '<span>我点击了确定</span>');
    };
    dialog.oncancel = function() {
      editor.execCommand('inserthtml', '<span>我点击了取消</span>');
    };
  </script>
</body>

</html>

Features
v-model 双向数据绑定!你不需要考虑实例化,也不需要考虑何时 getContent,何时setContent,简单到像使用 input 框一样!

完全遵从官方 API,所有的配置参数和实例方法与官方完全一致。通过给 vue-ueditor-wrap 组件的 config 属性传递一个对象,你就可以得到一个完全独立配置的 UEditor 编辑器。通过监听 ready 事件你就可以得到初始化后的 UEditor 实例并执行实例上的各种方法。

自动添加依赖文件。你不需要自己在 index.html 或 main.js 里引入 UEditor 的 JS 文件。更重要的是即使你在一个页面里同时使用多个 vue-ueditor-wrap 组件,它所依赖的 JS 文件也只会加载一次。这么做的原因在于你不需要当用户一打开项目就先加载大量 UEditor 相关的资源,所有的资源文件只会在 vue-ueditor-wrap 组件第一次被激活时才加载。当然,如果你在 index.html 或 main.js 里引入了相关资源,vue-ueditor-wrap 也会准确判断,你不用担心它会重复加载。

每个 vue-ueditor-wrap 组件是完全独立的。你甚至可以在上面使用 v-for 指令一次渲染 99个 兔斯基(不要忘记添加 key 值)。

FAQ(常见问题)
1.是否支持 IE 等低版本浏览器?

与 Vue 相同,整体支持到 IE9+???


2.为什么我会看到这个报错?


这是 UEDITOR_HOME_URL 参数配置错误导致的。在 vue cli 2.x 生成的项目中使用本组件,默认值是 '/static/UEditor/',在 vue cli 3.x 生成的项目中,默认值是 process.env.BASE_URL + 'UEditor/' 。但这并不能满足所有情况。例如你的项目不是部署在网站根目录下,如"http://www.example.com/my-app/",你可能需要设置为"/my-app/static/UEditor/"。是否使用了相对路径、路由是否使用 history 模式、服务器配置是否正确等等都有可能会产生影响。总而言之:无论本地开发和部署到服务器,你所指定的 UEditor 资源文件是需要真实存在的,vue-ueditor-wrap 也会在 JS 加载失败时通过 console 输出它试图去加载的资源文件的完整路径,你可以借此分析如何填写。当需要区分环境时,你可以通过判断 process.env.NODE_ENV 来分别设置。

3.我该如何上传图片和文件?为什么我会看到后台配置项返回格式出错?


上传图片、文件等功能是需要与后台配合的,而你没有给 config 属性传递正确的 serverUrl ,我提供了http://35.201.165.105:8000/controller.php 的临时接口,你可以用于测试,但切忌在生产环境使用!!! 关于如何搭建上传接口,可以参考官方文档。

4.单图片跨域上传失败!

UEditor 的单图上传是通过 Form 表单 + iframe 的方式实现的,但由于同源策略的限制,父页面无法访问跨域 iframe 的文档内容,所以会出现单图片跨域上传失败的问题。我通过 XHR 重构了单图上传的方式,下载最新编译的 UEditor 资源文件即可在 IE10+ 的浏览器中实现单图跨域上传了。具体细节,点此查看。当然你也可以通过配置 toolbars 参数来隐藏单图片上传按钮,并结合上面介绍的“自定义按钮”,曲线救国,以下代码仅供参考。

var input = document.createElement('input')
input.type = "file"
input.style.display = 'none'
document.body.appendChild(input)
input.click()
input.addEventListener('change',(e)=>{
    // 利用 AJAX 上传,上传成功之后销毁 DOM
    console.log(e.target.files)
})

5.为什么我输入的"? ! $ #" 这些特殊字符,没有成功绑定?

当你使用 listener 模式时,由于 v-model 的实现是基于对 UEditor 实例上 contentChange 事件的监听,而你输入这些特殊字符时通常是按住 shift 键的,UEditor 本身的 contentChange 在 shift 键按住时不会触发,你也可以尝试同时按下多个键,你会发现 contentChange 只触发一次。你可以使用 observer 模式或移步 UEditor。

6.单图片上传后 v-model 绑定的是 loading 小图标。

这个也是 UEditor 的 BUG。我最新编辑的版本,修复了官方的这个 BUG,如果你使用的是官网下载的资源文件,请替换资源文件或参考 Issue1。

更多问题,欢迎提交 ISSUE 或者去 聊天室 提问。但由于这是一个个人维护的项目,我平时也有自己的工作,所以并不能保证及时解决你们的所有问题,如果小伙伴们有好的建议或更炫酷的操作,也欢迎 PR,如果你觉得这个组件给你的开发带来了实实在在的方便,也非常感谢你的Star,当然还有咖啡:

代码修改请遵循指定的 ESLint 规则,PR 之前请先执行 npm run lint 进行代码风格检测,大部分语法细节可以通过 npm run fix 修正,构建之后,记得修改 package.json 里的版本号,方便我 Review 通过后麻溜溜的发布到 npm。

总结
虽然这是一次很小的创新,UEditor也可能是一个过气的富文本编辑器。但是在维护这个项目以及帮助一众小伙伴解决ISSUE的过程中,我成长了很多。最令我感动的是不少小伙伴还给我邮箱发了感谢信,而且我还发现确实已经有一些人开始在项目中用了。这种被他人认可,以及帮助别人的快乐真的只有体会过的人才知道。也就在前不久,我决定开始在掘金写博客,虽然一些东西写的不那么好,或者自己认知有错误,但总有一群热心且优秀的小伙伴,会在评论区指正以及给出宝贵的意见。分享是快乐的!所以,我的这篇文章也权当抛砖引玉,如果小伙伴们有好的建议或更炫酷的操作,也欢迎PR,不过PR之前请先执行npm run lint进行代码风格检测,大部分语法细节也可以通过npm run fix修正,也要记得修改package.json的版本号version,方便我直接发布到npm。当然如果你有好用的富文本编辑器,也可以在评论区推荐。
--------------------- 
作者:郑昊川 
来源:CSDN 
原文:https://blog.csdn.net/haochuan9421/article/details/81975966 
版权声明:本文为博主原创文章,转载请附上博文链接!

Vue项目中最简单的使用集成百度UEditor方式,含图片上传相关推荐

  1. Vue项目中最简单的使用集成UEditor方式,含图片上传

    Vue 3 项目参考这里 前言 封面是UEditor的 百度指数 折线图.虽然今天已经是 2018 年,且优秀的富文本编辑器层出不穷(包括移动端),但从图中可以看出UEditor仍然维持着较高的搜索热 ...

  2. vue3开发1:在vue3项目中集成ckeditor5编辑器,自定义图片上传,图片编辑排坑(一)

    最近尝试用了vue3进行开发,没想到在使用element-plus框架的时候出现了bug(一个星期后官方修复了),所以我对在项目中集成富文本插件ckeditor5比较忐忑,而且也没有vue3中集成ck ...

  3. vue中使用wangeditor富文本编辑器(含图片上传和回显)

    最近在写vue的项目中,遇到一个需求,点击编辑,显示弹框,在弹框中的富文本编辑器中编辑自定义文本样式,可以上传图片并回显.编辑完成确定后显示在页面上. 首先先写一个editor.vue的页面.(建议单 ...

  4. Springboot集成七牛云,实现图片上传功能

    七牛云的使用 1.进入官网,注册一个账号 2.找到对象存储 3.新建存储空间 4.管理存储空间 5.到此基本操作就完成了,接下来我们要在项目中使用 方式一:官方的帮助文档,很详细: 步骤一:找到开发者 ...

  5. SpringBoot 中的验证码、二维码、缩略图、图片上传、定时器调度

    1.验证码生成 pom.xml 文件中添加依赖: <dependency><groupId>com.github.penggle</groupId><arti ...

  6. 「Vue To Words」 - 教你在Vue项目中导出Word文档(包含表格合并,图片修改大小)

    前言:最近在项目中遇到了,vue页面需要导出word文档,今日就进行记录一下,如有不对的地方,还请见谅 ^^ 简单说一下需求:需要导出的页面是表格页面,因为包含表格的嵌套和合并,其实最主要的就是导出页 ...

  7. 在vue项目中实现 将数据导入Excel 三种方式(.xlsx , .csv)

    方法一 Vue 将页面中表格数据导出excel 一.需要安装三个依赖: npm install -S file-saver xlsxnpm install -D script-loader 二.项目中 ...

  8. Vue项目中前端请求后端数据的两种方式

    1.JS方式,使用fetch函数,较底层 //JS方式请求分页数据 fetch("http://localhost:9090/user/page?pageNum=" +this.p ...

  9. Vue项目中mockjs简单应用

    1.安装引入: 需要安装mockjs和axios, npm install mockjs --save npm install axios --save 在main.js文件中引入mockjs req ...

最新文章

  1. Controller上使用@CrossOrigin注解
  2. ES5-拓展 原型链、继承、类
  3. Java学习笔记9-2——JavaWeb
  4. c语言autoi函数如何使用,C++的auto声明、memset函数
  5. Intel® Nehalem/Westmere架构/微架构/流水线 (2) - 流水线概述
  6. 海量数据库解决方案2011050301
  7. python 最小二乘回归 高斯核_[数值计算] 数据拟合——非线性最小二乘法
  8. X.509,RSA,PKCS 普及
  9. 48小时备考TOGAF经验分享
  10. ESP32驱动AD7705
  11. Spring学习记录01
  12. android service设置persistent,Persistent service
  13. 使用费马小定理和欧拉定理计算余数
  14. 2D Conforming Triangulations
  15. movidius 神经计算棒 ncsdk windows 平台 支持 配置 教程
  16. java读取pdf总结
  17. c语言spawning c1.exe,在VC++6.0中,总是出现一个叫error spawning c1.exe的错误,怎么回事?...
  18. 计算思维与数据科学 K-Means聚类模型信用卡用户风险评估 python连接数据库来处理数据
  19. 如何搭建视频通信服务器架构
  20. 基于PHP语言Laravel+Layui后台代码生成工具

热门文章

  1. 小米战略入股TCL集团,不只是为了0.48%的股份
  2. maven仓库报错 MavenResportException: Error while generating Javadoc:
  3. 金九银十!字节跳动三轮面试惨败,总结面试经验,下次再战!
  4. 为什么LPDDR不能完全代替DDR?
  5. Android Studio安装步骤
  6. 字符串<string.h>头文件
  7. 钢筋连接力矩值A钢筋套筒连接不同钢筋直径不同力矩值
  8. 多个pdf文件合并为一个pdf文件
  9. 电子名片输出格式通用规范
  10. Echarts动态饼状图,柱形图,关系图绘制