之前图快,直接使用cdn方式引用的tinymce文件,但是在使用过程中发现极易受网络影响,索性改成本地引用的方式,本地引用方式很多,可以npm下载,也可以向我这样官网下载,然后放在public文件夹内使用。
包括汉化、自定义图标、上传图片视频、解决在el-dialog中tinymce z-index 太小而被遮挡问题、解决打包后找不到文件的相关配置等
效果图
实现步骤
1、先下载tinymce文件包,我下载的是5.5版本的
下载地址:https://www.tiny.cloud/get-tiny/self-hosted/
2、将下载的文件解压缩,复制js目录里面tinymce文件夹到项目public目录下
tinymce文件夹内文件如上图蓝色框中所示
3、下载汉化包,将下载的汉化包放到langs文件夹下
4、设置自定义图标,比对icons目录下default文件夹中的js文件编写即可
在icons文件夹下创建custom目录,新建icons.js文件,在这个文件里面添加需要修改的图标即可
形式:
tinymce.IconManager.add('custom', {
icons: {}
})
注:图标要使用svg代码,可以到阿里图标上复制相应的svg代码
icon对照表:https://blog.csdn.net/qq_34114082/article/details/90024080
5、准备就绪后,就是创建相应的组件
Tinymce组件的代码主要来自git的开源项目【https://github.com/PanJiaChen/vue-element-admin】,在其基础上进行了一些修改,实现图片视频上传,解决项目打包后找不到文件等问题,这里就不做赘述,直接贴代码了(index.vue),需要完整的自行到git开源项目下载
<!--
* @描述: 富文本编辑器组件:
* 引用public目录下文件在路径前面添加 process.env.BASE_URL,可避免打包后找不到静态文件的问题
* process.env.BASE_URL 为 vue.config.js里面配置的 publicPath
-->
<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'
import {
uploadImg
} from '@/api/upload'
// 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 = 'http://lib.baomitu.com/tinymce/4.9.2/tinymce.min.js'
const tinymceCDN = process.env.BASE_URL + '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 {
hasChange: false,
hasInit: false,
tinymceId: this.id,
fullscreen: false,
languageTypeList: {
'en': 'en',
'zh': 'zh_CN',
'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: {
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({
selector: `#${this.tinymceId}`,
// icons_url: '/tinymce/icons/custom/icons.js', // 如果需要自定义图标,到这个文件里面添加就行
// icons: 'custom',
language_url: process.env.BASE_URL + 'tinymce/langs/zh.js',
language: '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,
fontsize_formats: '12px 14px 16px 18px 24px 36px 48px 56px 72px',
font_formats: '微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun,serif;仿宋体=FangSong,serif;黑体=SimHei,sans-serif;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;',
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_upload_handler: function(blobInfo, succFun, failFun) {
var formData
formData = new FormData()
var file = blobInfo.blob() // 转化为易于理解的file对象
formData.append('file', file, file.name)
uploadImg(formData).then(res => {
succFun(res.filenames)
}).catch(err => {
failFun('出现未知问题,刷新页面,或者联系程序员: ' + err)
})
},
file_picker_types: 'media',
file_picker_callback: function(cb, value, meta) {
// 当点击meidia图标上传时,判断meta.filetype == 'media'有必要,因为file_picker_callback是media(媒体)、image(图片)、file(文件)的共同入口
if (meta.filetype === 'media') {
// 创建一个隐藏的type=file的文件选择input
const input = document.createElement('input')
input.setAttribute('type', 'file')
input.onchange = function() {
const file = this.files[0] // 只选取第一个文件。如果要选取全部,后面注意做修改
var formData
formData = new FormData()
formData.append('file', file)
uploadImg(formData).then(res => {
// 接口返回的文件保存地址
const mediaLocation = res.filenames
// cb()回调函数,将mediaLocation显示在弹框输入框中
cb(mediaLocation, {
title: file.name
})
}).catch(err => {
console.log(err)
})
}
// 触发点击
input.click()
}
},
// 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
})
},
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>
<style>
/* 在el-dialog中tinymce z-index 被太小而被遮挡时要加这两句 */
.tox-tinymce-aux{z-index:99999 !important;}
.tinymce.ui.FloatPanel{z-Index: 99;}
</style>
图片视频上传具体实现可以看我另一篇文章,直接复制上面的文件可能没用,需要修改相应的接口调用:
https://blog.csdn.net/qq_43467284/article/details/130971345?spm=1001.2014.3001.5501
想省事的话也可以直接下载我处理好的tinymce文件,但是不推荐,自己写一遍也很快,而且印象也能深刻点,还能省积分
tinymce包下载链接:https://download.csdn.net/download/qq_43467284/88701525?spm=1001.2014.3001.5501
tinymce组件下载链接:https://download.csdn.net/download/qq_43467284/88701734?spm=1001.2014.3001.5503
上面的链接要是失效了就自己写吧,我不知道这个资源审核会不会通过