效果图
该编辑器引用了tinymce.min.js库
一.在components目录下定义Tinymce组件
1. plugins.js
// Any plugins you want to use has to be imported
// Detail plugins list see https://www.tinymce.com/docs/plugins/
// Custom builds see https://www.tinymce.com/download/custom-builds/
const plugins = ['advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars wordcount']
export default plugins
2. toolbar.js
// Here is a list of the toolbar
// Detail list see https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols
const toolbar = ['searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample', 'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen']
export default toolbar
3. dynamicLoadScript.js
let callbacks = []
function loadedTinymce() {
// to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2144
// check is successfully downloaded script
return window.tinymce
}
const dynamicLoadScript = (src, callback) => {
const existingScript = document.getElementById(src)
const cb = callback || function() {}
if (!existingScript) {
const script = document.createElement('script')
script.src = src // src url for the third-party library being loaded.
script.id = src
document.body.appendChild(script)
callbacks.push(cb)
const onEnd = 'onload' in script ? stdOnEnd : ieOnEnd
onEnd(script)
}
if (existingScript && cb) {
if (loadedTinymce()) {
cb(null, existingScript)
} else {
callbacks.push(cb)
}
}
function stdOnEnd(script) {
script.onload = function() {
// this.onload = null here is necessary
// because even IE9 works not like others
this.onerror = this.onload = null
for (const cb of callbacks) {
cb(null, script)
}
callbacks = null
}
script.onerror = function() {
this.onerror = this.onload = null
cb(new Error('Failed to load ' + src), script)
}
}
function ieOnEnd(script) {
script.onreadystatechange = function() {
if (this.readyState !== 'complete' && this.readyState !== 'loaded') return
this.onreadystatechange = null
for (const cb of callbacks) {
cb(null, script) // there is no way to catch loading errors in IE8
}
callbacks = null
}
}
}
export default dynamicLoadScript
4. index.vue
<template>
<div :class="{ fullscreen: fullscreen }" class="tinymce-container" :style="{ width: containerWidth }">
<textarea :id="tinymceId" class="tinymce-textarea" />
</div>
</template>
<script>
/**
* docs:
* https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html#tinymce
*/
import plugins from "./plugins.js";
import toolbar from "./toolbar.js";
import load from "./dynamicLoadScript.js";
// why use this cdn, detail see https://github.com/PanJiaChen/tinymce-all-in-one
const tinymceCDN =
"https://cdn.jsdelivr.net/npm/tinymce-all-in-one@4.9.3/tinymce.min.js";
export default {
name: "Tinymce",
props: {
id: {
type: String,
default: function () {
return (
"vue-tinymce-" +
+new Date() +
((Math.random() * 1000).toFixed(0) + "")
);
},
},
value: {
type: String,
default: "",
},
toolbar: {
type: Array,
required: false,
default() {
return [];
},
},
menubar: {
type: String,
default: "file edit insert view format table",
},
height: {
type: [Number, String],
required: false,
default: 360,
},
width: {
type: [Number, String],
required: false,
default: "auto",
},
},
data() {
return {
hasChange: false,
hasInit: false,
tinymceId: this.id,
fullscreen: false,
languageTypeList: {
//'en': 'en',
zh: "zh_CN",
// 'es': 'es_MX',
// 'ja': 'ja'
},
};
},
computed: {
language() {
//return this.languageTypeList[this.$store.getters.language]
return "zh_CN";
},
containerWidth() {
const width = this.width;
if (/^[\d]+(\.[\d]+)?$/.test(width)) {
// matches `100`, `'100'`
return `${width}px`;
}
return width;
},
},
watch: {
value(val) {
if (!this.hasChange && this.hasInit) {
this.$nextTick(() =>
window.tinymce.get(this.tinymceId).setContent(val || "")
);
}
},
language() {
this.destroyTinymce();
this.$nextTick(() => this.initTinymce());
},
},
mounted() {
this.init();
},
activated() {
if (window.tinymce) {
this.initTinymce();
}
},
deactivated() {
this.destroyTinymce();
},
destroyed() {
this.destroyTinymce();
},
methods: {
init() {
// dynamic load tinymce from cdn
load(tinymceCDN, (err) => {
if (err) {
this.$message.error(err.message);
return;
}
this.initTinymce();
});
},
initTinymce() {
const _this = this;
window.tinymce.init({
language: this.language,
selector: `#${this.tinymceId}`,
height: this.height,
body_class: "panel-body ",
object_resizing: false,
toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar,
menubar: this.menubar,
plugins: plugins,
end_container_on_empty_block: true,
// powerpaste_word_import: "clean",
code_dialog_height: 450,
code_dialog_width: 1000,
powerpaste_word_import: 'merge',
paste_data_images: true,
advlist_bullet_styles: "square",
advlist_number_styles: "default",
imagetools_cors_hosts: ["www.tinymce.com", "codepen.io"],
default_link_target: "_blank",
link_title: false,
nonbreaking_force_tab: true, // inserting nonbreaking space need Nonbreaking Space Plugin
init_instance_callback: (editor) => {
if (_this.value) {
editor.setContent(_this.value);
}
_this.hasInit = true;
editor.on("NodeChange Change KeyUp SetContent", () => {
this.hasChange = true;
this.$emit("input", editor.getContent());
});
},
setup(editor) {
editor.on("FullscreenStateChanged", (e) => {
_this.fullscreen = e.state;
});
},
// 整合七牛上传
images_dataimg_filter(img) {
setTimeout(() => {
const $image = $(img);
$image.removeAttr('width');
$image.removeAttr('height');
if ($image[0].height && $image[0].width) {
$image.attr('data-wscntype', 'image');
$image.attr('data-wscnh', $image[0].height);
$image.attr('data-wscnw', $image[0].width);
$image.addClass('wscnph');
}
}, 0);
return img
},
images_upload_handler(blobInfo, success, failure, progress) {
progress(0);
let formData = new FormData();
formData.append('files', blobInfo.blob());
_this.$http.upload('/api/file/uploads', formData).then(res => {
success(process.env.VUE_APP_IMG + res.data.imgPath);
progress(100);
})
return
const token = _this.$store.getters.token;
getToken(token).then(response => {
const url = response.data.qiniu_url;
const formData = new FormData();
formData.append('token', response.data.qiniu_token);
formData.append('key', response.data.qiniu_key);
formData.append('files', blobInfo.blob(), url);
upload(formData).then(() => {
success(url);
progress(100);
})
}).catch(err => {
failure('出现未知问题,刷新页面,或者联系程序员')
console.log(err);
});
},
});
},
destroyTinymce() {
const tinymce = window.tinymce.get(this.tinymceId);
if (this.fullscreen) {
tinymce.execCommand("mceFullScreen");
}
if (tinymce) {
tinymce.destroy();
}
},
setContent(value) {
window.tinymce.get(this.tinymceId).setContent(value);
},
getContent() {
window.tinymce.get(this.tinymceId).getContent();
},
},
};
</script>
<style scoped>
.tinymce-container {
position: relative;
line-height: normal;
box-shadow: 0 2px 4px 0 rgb(0 0 0 / 12%), 0 0 6px 0 rgb(0 0 0 / 4%);
}
.tinymce-container :deep(.mce-fullscreen) {
z-index: 10000;
}
.tinymce-textarea {
visibility: hidden;
z-index: -1;
}
.editor-custom-btn-container {
position: absolute;
right: 4px;
top: 4px;
/*z-index: 2005;*/
}
.fullscreen .editor-custom-btn-container {
z-index: 10000;
position: fixed;
}
.editor-upload-btn {
display: inline-block;
}
</style>
二.在页面文件引入组件使用
1.在script脚本里面引入、注册组件
import Tinymce from '/@/components/Tinymce/index.vue';
components: { Tinymce },
2.在template模板使用组件
<tinymce :value="ruleForm.content"></tinymce>