Tinymce相对于其他插件的优势,网上有,不赘述。本文非复制粘贴(作者也经常深受网上那些复制粘贴的文章毒害,谁让咱是小白呢,嗨~),参考的文章放到文后。说明:本文是在vue-element-admin封装好的插件上进行的修改
点击查看 vue-element-admin 富文本功能介绍
点击查看 Tinymce 中文文档
在vue-element-admi项目中:
下面就是封装好的富文本组件,你也可以 点击这里去下载 相应的文件目录,不过还是建议全部下载下来,把对应的组件拿出来用比较好。(还有很多别的组件也很好用呢,也可以“窃”<读书人的事~>来用)
本地化的的包下载:地址 :
将下载后的整个tinymce目录copy到项目底下,我是放在public目录,就是window.location.origin 所指向的目录
修改 index.vue 文件
本地化的包里是没有语言包的,在 这里 下载中文包,放到tinymce/langs底下
修改 zh_CN.js 的两处:
修改 index.vue ,
直接放( 关于上传图片和文档的中文官网说明 地址 ):
<template>
<div :class="{ fullscreen: fullscreen }" class="tinymce-container" :style="{ width: containerWidth }">
<textarea :id="tinymceId" class="tinymce-textarea" />
<div class="editor-custom-btn-container">
<!-- 暂时注释 -->
<!-- <editorImage color="#1890ff" class="editor-upload-btn" @successCBK="imageSuccessCBK" /> -->
</div>
</div>
</template>
<script>
/**
* docs:
* https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html#tinymce
*/
// 暂时注释
// import editorImage from './components/EditorImage'
import plugins from './plugins'
import toolbar from './toolbar'
import load from './dynamicLoadScript'
// 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'
const tinymceCDN = window.location.origin + '/tinymce/tinymce.min.js'
export default {
name: 'Tinymce',
// 暂时注释
// components: { editorImage },
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 {
resimg: '', // 上传img的url
resVideo: '', // 上传视频的url
uploaded: false, // 有没有上传完成
hasChange: false,
hasInit: false,
tinymceId: this.id,
fullscreen: false,
languageTypeList: {
zh: 'zh_CN',
en: 'en',
es: 'es_MX',
ja: 'ja'
}
}
},
computed: {
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 || ''))
}
}
},
mounted() {
this.init()
},
activated() {
if (window.tinymce) {
this.initTinymce()
}
},
deactivated() {
this.destroyTinymce()
},
destroyed() {
this.destroyTinymce()
},
methods: {
// 添加文件上传(图片、视频上传)方法
uploadFile(file, type) {
// console.log(token,file,type,)
let content = file
let formData = new FormData()
formData.append('files', content)
axios({
method: 'post',
//服务器上传地址
url: '',
data: formData,
headers: {
// 修改请求头
'Content-Type': 'multipart/form-data'
}
}).then(res => {
console.log(res)
if (res.data.success) {
if (type == 'image') {
this.resimg = res.data.fileUrl
} else if (type == 'media') {
this.resVideo = res.data.fileUrl
}
this.uploaded = true
}
})
},
init() {
// dynamic load tinymce from cdn
load(tinymceCDN, err => {
if (err) {
this.$message.error(err.message)
return
}
// window.tinymce.baseURL = window.location.origin + '/tinymce'
this.initTinymce()
})
},
initTinymce() {
const _this = this
window.tinymce.init({
selector: `#${this.tinymceId}`,
language: this.languageTypeList['zh'],
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,
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
})
},
// it will try to keep these URLs intact
// https://www.tiny.cloud/docs-3x/reference/configuration/Configuration3x@convert_urls/
// https://stackoverflow.com/questions/5196205/disable-tinymce-absolute-to-relative-url-conversions
convert_urls: false,
// 图片上传
images_upload_url: '/demo/upimg.php', // 指定上传图片的后端处理程序的URL
images_upload_base_path: '/demo', // 给返回的相对路径指定它所相对的基本路径
images_reuse_filename: true, // 使用文件本来的名字
// 图片上传方法(原始)
// images_upload_handler: function(blobInfo, succFun, failFun) {
// var xhr, formData
// var file = blobInfo.blob() //转化为易于理解的file对象
// xhr = new XMLHttpRequest()
// xhr.withCredentials = false
// xhr.open('POST', '/demo/upimg.php')
// xhr.onload = function() {
// var json
// if (xhr.status != 200) {
// failFun('HTTP Error: ' + xhr.status)
// return
// }
// json = JSON.parse(xhr.responseText)
// if (!json || typeof json.location != 'string') {
// failFun('Invalid JSON: ' + xhr.responseText)
// return
// }
// succFun(json.location)
// }
// formData = new FormData()
// formData.append('file', file, file.name) //此处与源文档不一样
// xhr.send(formData)
// },
// 图片上传方法(封装)
// images_upload_handler(blobInfo, success, failure, progress) {
// progress(0);
// 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('file', blobInfo.blob(), url);
// upload(formData).then(() => {
// success(url);
// progress(100);
// })
// }).catch(err => {
// failure('出现未知问题,刷新页面,或者联系程序员')
// console.log(err);
// });
// },
// 整合七牛上传
// 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
// },
// 添加文件上传(图片、视频上传)方法 start
// file_picker_types: "image media",
// file_picker_callback: function(callback, value, meta) {
// if (meta.filetype == "image") {
// let input = document.createElement("input"); //创建一个隐藏的input
// input.setAttribute("type", "file");
// // let that = this;
// input.onchange = function() {
// let file = this.files[0]; // 选取第一个文件
// // console.log(file)
// _this.uploadFile(file, "image"); // 上传视频拿到url
// if (_this.uploaded) {
// // _this.resVideo,
// console.log(_this.resimg, "true");
// callback(_this.resimg); // 将url显示在弹框输入框中
// } else {
// setTimeout(() => {
// //设置几秒后再去取数据
// console.log(_this.resimg, "false");
// callback(_this.resimg);
// }, 5000);
// }
// };
// //触发点击
// input.click();
// }
// if (meta.filetype == "media") {
// let input = document.createElement("input"); //创建一个隐藏的input
// input.setAttribute("type", "file");
// // let that = this;
// input.onchange = function() {
// let file = this.files[0]; //选取第一个文件
// // console.log(file)
// _this.uploadFile(file, "media"); // 上传视频拿到url
// if (_this.uploaded) {
// // _this.resVideo,
// console.log(_this.resVideo, "true");
// callback(_this.resVideo); //将url显示在弹框输入框中
// } else {
// setTimeout(() => {
// //设置几秒后再去取数据
// console.log(_this.resVideo, "false");
// callback(_this.resVideo);
// }, 5000);
// }
// };
// //触发点击
// input.click();
// }
// },
// 添加文件上传(图片、视频上传)方法 end
})
},
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()
},
imageSuccessCBK(arr) {
arr.forEach(v => window.tinymce.get(this.tinymceId).insertContent(`<img class="wscnph" src="${v.url}" >`))
}
}
}
</script>
<style lang="scss" scoped>
.tinymce-container {
position: relative;
line-height: normal;
}
.tinymce-container {
::v-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>
去掉 plugins.js 的三个(其实是四个,因为colorpicker有两个)已内置的插件: contextmenu、colorpicker、textcolor