# vue-ueditor-wrap

Chatroom Version Downloads jsdelivr size Issues License

> Vue + UEditor + v-model 双向绑定。之所以有这个 `repo` 的原因是:
 1、UEditor 依然是国内使用频率极高的所见即所得编辑器而 Vue 又有着广泛的使用,所以将两者结合使用,是很多 Vue 项目开发者的切实需求。
 2、目前没有发现满足这种需求,而使用又很方便的 `repo`、有的可能也只是简单的暴露一个 `UEditor` 的实例,仍然需要开发者手动去调用 `getContent`,`setContent`,而通过 `v-model` 绑定数据也是很多人期待的方式。于是自己在写公司项目时就手动撸了一个,周末整理一下分享出来,希望能帮助到有同样需求的小伙伴。 [点击预览](https://haochuan9421.github.io/vue-ueditor-wrap-demo/) [加入聊天室](https://gitter.im/haochuan9421/vue-ueditor-wrap/) ## Installation ```bash npm i vue-ueditor-wrap # 或者 yarn add vue-ueditor-wrap ``` ## Quick Start > [基于 vue-cli 2.x 的完整 DEMO](https://github.com/HaoChuan9421/vue-ueditor-wrap-demo)
[基于 Nuxt 的服务端渲染 DEMO](https://github.com/HaoChuan9421/vue-ueditor-wrap-nuxt)。 1. ~~下载 [UEditor](http://ueditor.baidu.com/website/download.html)~~ > 下载[最新编译的 UEditor](https://github.com/HaoChuan9421/vue-ueditor-wrap/tree/master/assets/downloads)。官网目前最新的版本是`1.4.3.3`,存在诸多 BUG,例如 [Issue1](https://github.com/HaoChuan9421/vue-ueditor-wrap/issues/1),且官方不再积极维护。为了世界的和平,针对一些常见 BUG,我进行了[修复](https://github.com/HaoChuan9421/ueditor/commits/dev-1.4.3.3),并把编译好的文件放在了本仓库的 `assets/downloads` 目录下,你可以放心[下载](https://github.com/HaoChuan9421/vue-ueditor-wrap/tree/master/assets/downloads),当然你也可以自己 `clone` [官方源码](https://github.com/fex-team/ueditor)并[编译](http://fex.baidu.com/ueditor/#dev-bale_width_grunt)。 将下载的压缩包解压并重命名为 `UEditor`(只需要选择一个你需要的版本,比如 `utf8-php`),放入你项目的 `static` 目录下。 > 如果你使用的是 [vue-cli 3.x](https://cli.vuejs.org/zh/guide/),可以把 `UEditor` 文件夹放入项目的 `public` 目录下。 2. 引入`VueUeditorWrap`组件 ```js import VueUeditorWrap from 'vue-ueditor-wrap' // ES6 Module // 或者 const VueUeditorWrap = require('vue-ueditor-wrap') // CommonJS ``` > 你也可以通过直接引入 `CDN` 链接的方式来使用,它会暴露一个全局的 `VueUeditorWrap` 变量(具体如何使用你可以阅读我的这篇[博客](https://juejin.im/post/5b97b84ee51d450e6c7492f6)或参考这个[仓库](https://github.com/HaoChuan9421/vue-optimization/tree/cdn))。 ```html ``` 3. 注册组件 ```js components: { VueUeditorWrap } // 或者在 main.js 里将它注册为全局组件 Vue.component('vue-ueditor-wrap', VueUeditorWrap) ``` 4. `v-model`绑定数据 ```html ``` ```js data () { return { msg: '

Vue + UEditor + v-model双向绑定

' } } ``` > 至此你已经可以在页面中看到一个初始化之后的 `UEditor` 了,并且它已经成功和数据绑定了!👏👏👏 5. 根据项目需求修改配置,完整配置选项查看 [ueditor.config.js](https://github.com/HaoChuan9421/vue-ueditor-wrap/blob/master/public/UEditor/ueditor.config.js) 源码或 [官方文档](http://fex.baidu.com/ueditor/) ```html ``` ```js data () { return { msg: '

Vue + UEditor + v-model双向绑定

', 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 1. 如何获取 `UEditor` 实例? ```html ``` ```js methods: { ready (editorInstance) { console.log(`编辑器实例${editorInstance.key}: `, editorInstance) } } ``` 2. 设置是否在组件的 `beforeDestroy` 钩子里销毁 `UEditor` 实例 ```html ``` 3. 选取 `v-model` 的实现方式。双向绑定的实现依赖对编辑器内容变化的监听,由于监听方式的不同,会带来监听效果的差异性,你可以自行选择,但建议使用开箱即用的默认值。 ```html ``` 可选值:`observer`,`listener` 默认值:`observer` 参数说明: 1. `observer` 模式借助 [MutationObserver API](https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver)。优点在于监听的准确性,缺点在于它会带来一点额外的性能开销。你可以通过 `observerDebounceTime` 属性设置触发间隔,还可以通过 `observerOptions` 属性有选择的设置 [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit) 的监听行为。该 API 只兼容到 IE11+,但 `vue-ueditor-wrap` 会在不支持的浏览器中自动启用 `listener` 模式。 ```html ``` 2. `listener` 模式借助 UEditor 的 [contentChange 事件](https://ueditor.baidu.com/doc/#UE.Editor:contentChange),优点在于依赖官方提供的事件 API,无需额外的性能消耗,兼容性更好,但缺点在于监听的准确性并不高,存在如下方 [常见问题 5] 中的提到的 BUG。 4. 是否支持 `Vue SSR`? 自 `2.4.0` 版本开始支持服务端渲染!本组件提供对 `Nuxt` 项目开箱即用的支持。但如果你是自己搭建的 `Vue SSR` 项目,你可能需要自行区分服务端和客户端环境并结合 `forceInit` 属性强制初始化编辑器,但大概率你用不到该属性,即使是自己搭建的 SSR 项目,更多问题欢迎提交 ISSUE。 5. 如何进行二次开发(添加自定义按钮、弹窗等)? 本组件提供了 `beforeInit` 钩子,它会在 `UEditor` 的 scripts 加载完毕之后、编辑器初始化之前触发,你可以在此时机,通过操作 window.UE 对象,来进行诸如添加自定义按钮、弹窗等的二次开发。`beforeInit` 的触发函数以 编辑器 id 和 配置参数 作为入参。下面提供了一个简单的自定义按钮和自定义弹窗的示例,[DEMO](https://github.com/HaoChuan9421/vue-ueditor-wrap-demo) 仓库中也提供了自定义“表格居中”按钮的示例,如果有更多二次开发的需求,你可以参考[官方 API](https://ueditor.baidu.com/doc/) 或者 [UEditor 源码](https://github.com/HaoChuan9421/ueditor/tree/dev-1.4.3.3/_examples) 中的示例。
自定义按钮 Demo ```html ``` ```js addCustomButtom (editorId) { window.UE.registerUI('test-button', function (editor, uiName) { // 注册按钮执行时的 command 命令,使用命令默认就会带有回退操作 editor.registerCommand(uiName, { execCommand: function () { editor.execCommand('inserthtml', `这是一段由自定义按钮添加的文字`) } }) // 创建一个 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 ```html ``` ```js 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` ```html Title

hello vue-ueditor-wrap

```
## Features 1. `v-model` 双向数据绑定!你不需要考虑实例化,也不需要考虑何时 `getContent`,何时`setContent`,简单到像使用 `input` 框一样! 2. 完全遵从官方 `API`,所有的配置参数和实例方法与官方完全一致。通过给 `vue-ueditor-wrap` 组件的 `config` 属性传递一个对象,你就可以得到一个完全独立配置的 `UEditor` 编辑器。通过监听 `ready` 事件你就可以得到初始化后的 `UEditor` 实例并执行实例上的各种方法。 3. 自动添加依赖文件。你不需要自己在 `index.html` 或 `main.js` 里引入 `UEditor` 的 JS 文件。更重要的是即使你在一个页面里同时使用多个 `vue-ueditor-wrap` 组件,它所依赖的 JS 文件也只会加载一次。这么做的原因在于你不需要当用户一打开项目就先加载大量 `UEditor` 相关的资源,所有的资源文件只会在 `vue-ueditor-wrap` 组件第一次被激活时才加载。当然,如果你在 `index.html` 或 `main.js` 里引入了相关资源,`vue-ueditor-wrap` 也会准确判断,你不用担心它会重复加载。 4. 每个 `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` 的临时接口,你可以用于测试,**但切忌在生产环境使用!!!** 关于如何搭建上传接口,可以参考[官方文档](http://fex.baidu.com/ueditor/#server-deploy)。 4. 单图片跨域上传失败! `UEditor` 的单图上传是通过 Form 表单 + iframe 的方式实现的,但由于同源策略的限制,父页面无法访问跨域 iframe 的文档内容,所以会出现单图片跨域上传失败的问题。我通过 XHR 重构了单图上传的方式,[下载最新编译的 UEditor](https://github.com/HaoChuan9421/vue-ueditor-wrap/tree/master/assets/downloads) 资源文件即可在 `IE10+` 的浏览器中实现单图跨域上传了。具体细节,[点此查看](https://github.com/HaoChuan9421/ueditor/commit/31f9207142d21a406041da0bd97968b466530c76)。当然你也可以通过配置 `toolbars` 参数来隐藏单图片上传按钮,并结合上面介绍的“自定义按钮”,曲线救国,以下代码仅供参考。 ```js 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](https://github.com/fex-team/ueditor)。 6. 单图片上传后 `v-model` 绑定的是 `loading` 小图标。 这个也是 `UEditor` 的 `BUG`。我最新编辑的版本,修复了官方的这个 `BUG`,如果你使用的是官网下载的资源文件,请替换资源文件或参考 [Issue1](https://github.com/HaoChuan9421/vue-ueditor-wrap/issues/1)。 > 更多问题,欢迎提交 [ISSUE](https://github.com/HaoChuan9421/vue-ueditor-wrap/issues) 或者去 [聊天室](https://gitter.im/haochuan9421/vue-ueditor-wrap/) 提问。但由于这是一个个人维护的项目,我平时也有自己的工作,所以并不能保证及时解决你们的所有问题,如果小伙伴们有好的建议或更炫酷的操作,也欢迎 `PR`,如果你觉得这个组件给你的开发带来了实实在在的方便,也非常感谢你的`Star`,当然还有咖啡: > 代码修改请遵循指定的 `ESLint` 规则,`PR` 之前请先执行 `npm run lint` 进行代码风格检测,大部分语法细节可以通过 `npm run fix` 修正,构建之后,记得修改 `package.json` 里的版本号,方便我 `Review` 通过后麻溜溜的发布到 `npm`。 ## License [MIT](https://github.com/HaoChuan9421/vue-ueditor-wrap/blob/master/LICENSE)