1.安装vue-ueditor-wrap
npm install vue-ueditor-wrap
2.下载最新编译的 UEditor放到public文件, 我没有下载直接在gitee上粘贴过去的
3.在src>components 创建vue-ueditor-wrap.vue
<template>
<div>
<div ref="script" :name="name" type="text/plain"></div>
</div>
</template>
<script>
import LoadEvent from '../utils/Event.js';
import Debounce from '../utils/Debounce.js';
export default {
name: 'VueUeditorWrap',
data () {
return {
status: 0,
initValue: '',
defaultConfig: {
// VUE CLI 3 会添加 process.env.BASE_URL 的环境变量,而 VUE CLI 2 没有,所以借此设置 UEDITOR_HOME_URL,能涵盖大部分 Vue 开发者的使用场景
UEDITOR_HOME_URL: process.env.BASE_URL ? process.env.BASE_URL + 'UEditor/' : '/static/UEditor/',
enableAutoSave: false
}
};
},
props: {
// v-model 实现方式
mode: {
type: String,
default: 'observer',
validator: function (value) {
// 1. observer 借助 MutationObserver API https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver
// 2. listener 借助 UEditor 的 contentChange 事件 https://ueditor.baidu.com/doc/#UE.Editor:contentChange
return ['observer', 'listener'].indexOf(value) !== -1;
}
},
value: {
type: String,
default: ''
},
config: {
type: Object,
default: function () {
return {};
}
},
init: {
type: Function,
default: function () {
return () => {};
}
},
destroy: {
type: Boolean,
default: false
},
name: {
type: String,
default: ''
},
observerDebounceTime: {
type: Number,
default: 50,
validator: function (value) {
return value >= 20;
}
},
observerOptions: {
type: Object,
default: function () {
// https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit
return {
attributes: true, // 是否监听 DOM 元素的属性变化
attributeFilter: ['src', 'style', 'type', 'name'], // 只有在该数组中的属性值的变化才会监听
characterData: true, // 是否监听文本节点
childList: true, // 是否监听子节点
subtree: true // 是否监听后代元素
};
}
},
// 本组件提供对普通 Vue 项目和 Nuxt 项目开箱即用的支持,但如果是自己搭建的 Vue SSR 项目,可能需要自行区分是客户端还是服务端环境并跳过环境检测,直接初始化
forceInit: {
type: Boolean,
default: false
}
},
computed: {
mixedConfig () {
return Object.assign({}, this.defaultConfig, this.config);
}
},
methods: {
// 添加自定义按钮(自定义按钮,自定义弹窗等操作从 2.2.0 版本开始不再考虑直接集成,这会使得组件和 UEditor 过度耦合,但为了兼容一些老版用户的写法,这个方法依然保留)
registerButton ({ name, icon, tip, handler, index, UE = window.UE }) {
UE.registerUI(name, (editor, name) => {
editor.registerCommand(name, {
execCommand: () => {
handler(editor, name);
}
});
const btn = new UE.ui.Button({
name,
title: tip,
cssRules: `background-image: url(${icon}) !important;background-size: cover;`,
onclick () {
editor.execCommand(name);
}
});
editor.addListener('selectionchange', () => {
const state = editor.queryCommandState(name);
if (state === -1) {
btn.setDisabled(true);
btn.setChecked(false);
} else {
btn.setDisabled(false);
btn.setChecked(state);
}
});
return btn;
}, index, this.id);
},
// 实例化编辑器
_initEditor () {
this.$refs.script.id = this.id = 'editor_' + Math.random().toString(16).slice(-6); // 这么做是为了支持 Vue SSR,因为如果把 id 属性放在 data 里会导致服务端和客户端分别计算该属性的值,而造成 id 不匹配无法初始化的 BUG
this.init();
this.$emit('before-init', this.id, this.mixedConfig);
this.$emit('beforeInit', this.id, this.mixedConfig); // 虽然这个驼峰的写法会导致使用 DOM 模版时出现监听事件自动转小写的 BUG,但如果经过编译的话并不会有这个问题,为了兼容历史版本,不做删除,参考 https://vuejs.org/v2/guide/components-custom-events.html#Event-Names
this.editor = window.UE.getEditor(this.id, this.mixedConfig);
this.editor.addListener('ready', () => {
if (this.status === 2) { // 使用 keep-alive 组件会出现这种情况
this.editor.setContent(this.value);
} else {
this.status = 2;
this.$emit('ready', this.editor);
if (this.initValue) {
this.editor.setContent(this.initValue);
}
}
if (this.mode === 'observer' && window.MutationObserver) {
this._observerChangeListener();
} else {
this._normalChangeListener();
}
});
},
// 检测依赖,确保 UEditor 资源文件已加载完毕
_checkDependencies () {
return new Promise((resolve, reject) => {
// 判断ueditor.config.js和ueditor.all.js是否均已加载(仅加载完ueditor.config.js时UE对象和UEDITOR_CONFIG对象存在,仅加载完ueditor.all.js时UEDITOR_CONFIG对象存在,但为空对象)
let scriptsLoaded = !!window.UE && !!window.UEDITOR_CONFIG && Object.keys(window.UEDITOR_CONFIG).length !== 0 && !!window.UE.getEditor;
if (scriptsLoaded) {
resolve();
} else if (window['$loadEnv']) { // 利用订阅发布,确保同时渲染多个组件时,不会重复创建script标签
window['$loadEnv'].on('scriptsLoaded', () => {
resolve();
});
} else {
window['$loadEnv'] = new LoadEvent();
// 如果在其他地方只引用ueditor.all.min.js,在加载ueditor.config.js之后仍需要重新加载ueditor.all.min.js,所以必须确保ueditor.config.js已加载
this._loadConfig().then(() => this._loadCore()).then(() => {
resolve();
window['$loadEnv'].emit('scriptsLoaded');
});
}
});
},
_loadConfig () {
return new Promise((resolve, reject) => {
if (window.UE && window.UEDITOR_CONFIG && Object.keys(window.UEDITOR_CONFIG).length !== 0) {
resolve();
return;
}
let configScript = document.createElement('script');
configScript.type = 'text/javascript';
configScript.src = this.mixedConfig.UEDITOR_HOME_URL + 'ueditor.config.js';
document.getElementsByTagName('head')[0].appendChild(configScript);
configScript.onload = function () {
if (window.UE && window.UEDITOR_CONFIG && Object.keys(window.UEDITOR_CONFIG).length !== 0) {
resolve();
} else {
console.error('加载ueditor.config.js失败,请检查您的配置地址UEDITOR_HOME_URL填写是否正确!\n', configScript.src);
}
};
});
},
_loadCore () {
return new Promise((resolve, reject) => {
if (window.UE && window.UE.getEditor) {
resolve();
return;
}
let coreScript = document.createElement('script');
coreScript.type = 'text/javascript';
coreScript.src = this.mixedConfig.UEDITOR_HOME_URL + 'ueditor.all.min.js';
document.getElementsByTagName('head')[0].appendChild(coreScript);
coreScript.onload = function () {
if (window.UE && window.UE.getEditor) {
resolve();
} else {
console.error('加载ueditor.all.min.js失败,请检查您的配置地址UEDITOR_HOME_URL填写是否正确!\n', coreScript.src);
}
};
});
},
// 设置内容
_setContent (value) {
value === this.editor.getContent() || this.editor.setContent(value);
},
contentChangeHandler () {
this.$emit('input', this.editor.getContent());
},
// 基于 UEditor 的 contentChange 事件
_normalChangeListener () {
this.editor.addListener('contentChange', this.contentChangeHandler);
},
// 基于 MutationObserver API
_observerChangeListener () {
const changeHandle = (mutationsList) => {
if (this.editor.document.getElementById('baidu_pastebin')) {
return;
}
this.$emit('input', this.editor.getContent());
};
// 函数防抖
this.observer = new MutationObserver(Debounce(changeHandle, this.observerDebounceTime));
this.observer.observe(this.editor.body, this.observerOptions);
}
},
deactivated () {
this.editor && this.editor.removeListener('contentChange', this.contentChangeHandler);
this.observer && this.observer.disconnect();
},
beforeDestroy () {
if (this.destroy && this.editor && this.editor.destroy) {
this.editor.destroy();
}
if (this.observer && this.observer.disconnect) {
this.observer.disconnect();
}
},
// v-model语法糖实现
watch: {
value: {
handler (value) {
// 修复值为空无法双向绑定的问题
if (value === null) {
value = '';
}
// 0: 尚未初始化 1: 开始初始化但尚未ready 2 初始化完成并已ready
switch (this.status) {
case 0:
this.status = 1;
this.initValue = value;
// 判断执行环境是服务端还是客户端,这里的 process.client 是 Nuxt 添加的环境变量
(this.forceInit || (typeof process !== 'undefined' && process.client) || typeof window !== 'undefined') && this._checkDependencies().then(() => {
this.$refs.script ? this._initEditor() : this.$nextTick(() => this._initEditor());
});
break;
case 1:
this.initValue = value;
break;
case 2:
this._setContent(value);
break;
default:
break;
}
},
immediate: true
}
}
};
</script>
4.在src>utils创建Event.js
// 一个简单的事件订阅发布的实现,取代原生Event对象,提升IE下的兼容性
// 富文本
function LoadEvent() {
this.listeners = {};
this.on = function(eventName, callback) {
if (this.listeners[eventName] === undefined) {
this.listeners[eventName] = [];
}
this.listeners[eventName].push(callback);
};
this.emit = function(eventName) {
this.listeners[eventName] && this.listeners[eventName].forEach(callback => callback());
};
}
export default LoadEvent;
5.在src>utils创建Debounce.js
/**
* 一个简单的函数防抖
* 富文本
* @param {Function} fun 需要限制执行频率的函数
* @param {Number} delay 延迟时间,这段时间过后,才可触发第二次
*/
export default function(fun, delay) {
var timer = null;
var debounced = function() {
var ctx = this;
var args = arguments;
if (timer) clearTimeout(timer);
timer = setTimeout(function() {
fun.apply(ctx, args);
}, delay);
};
return debounced;
}
6.调用插件
<template>
<div class="contact">
<vue-ueditor-wrap v-model="msg" :config="myConfig" @before-init="addCustomButtom"></vue-ueditor-wrap>
</div>
</template>
<script>
import VueUeditorWrap from "vue-ueditor-wrap"; // ES6 Module
export default {
data() {
return {
// 富文本
msg: '111',
myConfig: {
autoHeightEnabled: false,
initialFrameHeight: 200,
initialFrameWidth: '100%',
UEDITOR_HOME_URL: '/UEditor/',
serverUrl: 'http://35.201.165.105:8000/controller.php',
},
}
},
// 富文本
// 添加自定义按钮
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 是哪个编辑器实例上的,默认是页面上所有的编辑器都会添加这个按钮 */
);
},
}
</script>
//和element样式冲突
<style>
.edui-default .edui-toolbar {
/* // 原样式不变,增加: */
line-height: initial !important;
}
.edui-default .edui-toolbar .edui-combox .edui-combox-body {
/* // 原样式不变,增加: */
line-height: initial !important;
}
</style>
摘取自https://gitee.com/CrazyHat/vue-ueditor-wrap.git
如有侵权请私信,马上删除