1.安装
npm install tinymce -S npm --save @tinymce/tinymce-vue
2.在public下新建tinymce文件夹,将node_moudles里面tinymce里的skins文件复制到该文件下(我这里是把node_moudles下的整个tinymce文件拷贝到了public下
3.下载中文包
链接:https://www.tiny.cloud/get-tiny/language-packages/
把下载的语言包放到之前新建的tinymce文件夹里
4.新建一个tinymec组件,在组件中引入
import tinymce from 'tinymce/tinymce'
import Editor from '@tinymce/tinymce-vue'
5.在组件中引入自己需要的工具栏
import 'tinymce/themes/silver/theme'
import 'tinymce/plugins/image'
import 'tinymce/plugins/table'
import 'tinymce/plugins/lists'
import 'tinymce/plugins/wordcount'
import 'tinymce/plugins/colorpicker'
import 'tinymce/plugins/code'
import 'tinymce/plugins/paste'
自定义按钮
init: {
...
setup: function (editor) { /*自定义上传图片按钮*/
editor.ui.registry.addButton('uploadimg', {
icon: "image",
tooltip: "上传图片",
onAction: function (_) {
document.querySelector("#upload_img input").click();
}
});
},
},
props
props: {
//传入一个value,使组件支持v-model绑定
value: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
},
plugins: {
type: [String, Array],
default: 'lists table wordcount code image'
},
toolbar1: {
type: [String, Array],
default: 'undo redo | code | fontsizeselect | fontselect | styleselect | bold italic underline | bullist numlist outdent indent | uploadimg table'
},
toolbar2: {
type: [String, Array],
default: 'alignleft aligncenter alignright alignjustify | forecolor backcolor | removeformat'
},
},
在data里初始化配置
init: {
language_url: '/tinymce/langs/zh_CN.js', /*vue3.x 的路径/tinymce/langs/zh_CN.js ,vue2.x的路径/static/tinymce/langs/zh_CN.js*/
language: 'zh_CN',
skin_url: '/tinymce/skins/ui/oxide',
height: 600,
width:810,
plugins: this.plugins,
toolbar1: this.toolbar1,
toolbar2: this.toolbar2,
branding: false,
menubar: false,
statusbar: false, // 隐藏编辑器底部的状态栏
// image_description: false,
// image_dimensions: false,
// image_title: false,
// image_uploadtab: false,本地上传图片
paste_data_images: true,/*是否允许粘贴图片*/
setup: function (editor) { /*自定义上传图片按钮*/
editor.ui.registry.addButton('uploadimg', {
icon: "image",
tooltip: "上传图片",
onAction: function (_) {
document.querySelector("#upload_img input").click();
}
});
},
//如需ajax上传可参考https://www.tiny.cloud/docs/configure/file-image-upload/#images_upload_handler
/*上传到oss*/
images_upload_handler: (blobInfo, success, failure) => { /*没有自定义上传按钮,用他本身的时候用这里的上传*/
let time = Date.parse(new Date())
let that = this
if(blobInfo.blob().size / 1024 / 1024 > 1 ){
failure('上传图片不能超过1M!');
}
if(blobInfo.blob().type !== "image/jpeg"){
failure('上传图片只能是 JPG 格式!');
}
if(that.imgNum >= that.totalImg){
failure('图片不能超过30张!')
}
let img = new Image();
img.src = "data:"+blobInfo.blob().type+";base64,"+blobInfo.base64();
img.onload = function(){
// console.log(img.width, img.height);
if (img.width < 500 || img.height < 500) failure('上传图片分辨率不能低于500*500!');
that.imgNum +=1
uploadImg().then(res => {
const form = new FormData()
form.append('policy', _get(res, 'data.policy'))
form.append('OSSAccessKeyId', _get(res, 'data.accessid'))
form.append('signature', _get(res, 'data.signature'))
form.append('key', `${_get(res, 'data.dir')}${_get(res, 'data.key')}${'_'}${time}${'.jpg'}`)
form.append('success_action_status', 200)
form.append('file', blobInfo.blob(), blobInfo.filename())
that.updateImg = (process.env.OSS_URL + '/' + `${_get(res, 'data.dir')}${_get(res, 'data.key')}${'_'}${time}${'.jpg'}`)
that.$http.post(process.env.OSS_URL, form).then(resp => {
success(that.updateImg)
}).catch(err => {
failure('网络错误,请稍后再试!')
console.log(err);
})
}).catch(err => {
failure('网络错误,请稍后再试!')
console.log(err);
})
}
/*const img = 'data:image/jpeg;base64,' + blobInfo.base64()
success(img)
console.log(success);*/
}
},
组件全部代码(结合element-ui)上传图片到阿里云
<template>
<div>
<el-upload
style="display: none"
class="fwb-avatar-uploader"
id="upload_img"
action=""
name="img"
:auto-upload="false"
:show-file-list="false"
:before-upload="fbeforeUpload"
:on-change="fhandleAvatarChange"
:on-success="fuploadSuccess"
></el-upload>
<div class="tinymce-editor" style="position: relative;">
<em class="num-loading" v-show="Imgloading"></em>
<editor v-model="myValue"
:init="init"
:disabled="disabled"
@onKeyUp="onImgQuery"
@onPaste="onImgQuery"
@onClick="onClick">
</editor>
<div class="rlPopup" v-if="imgPovp">
<div class="rl-inner">
<div class="g-c-33 g-fz-16 pop-txt">
<p>图片不能超过30张</p>
</div>
<div class="pop-btn">
<a href="javascript:;" class="pop-a edit-btn g-c-33" @click="imgPovp = false">返回</a>
<a href="javascript:;" class="pop-a cancel-btn" @click="imgPovp = false">我知道了</a>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
// import { uploadImg,uploadOOS} from '@/api/single'
// import _get from 'lodash.get'
import tinymce from 'tinymce/tinymce'
import Editor from '@tinymce/tinymce-vue'
import 'tinymce/themes/silver/theme'
import 'tinymce/plugins/image'
import 'tinymce/plugins/table'
import 'tinymce/plugins/lists'
import 'tinymce/plugins/wordcount'
import 'tinymce/plugins/colorpicker'
import 'tinymce/plugins/code'
import 'tinymce/plugins/paste'
export default {
components: {
Editor
},
props: {
//传入一个value,使组件支持v-model绑定
value: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
},
plugins: {
type: [String, Array],
default: 'lists table wordcount code image'
},
toolbar1: {
type: [String, Array],
default: 'undo redo | code | fontsizeselect | fontselect | styleselect | bold italic underline | bullist numlist outdent indent | uploadimg table'
},
toolbar2: {
type: [String, Array],
default: 'alignleft aligncenter alignright alignjustify | forecolor backcolor | removeformat'
},
},
data() {
return {
updateImg:null,
Imgloading:false,
imgPovp:false,
//初始化配置
init: {
language_url: '/tinymce/langs/zh_CN.js', /*vue3.x 的路径/tinymce/langs/zh_CN.js ,vue2.x的路径/static/tinymce/langs/zh_CN.js*/
language: 'zh_CN',
skin_url: '/tinymce/skins/ui/oxide',
height: 600,
width:810,
plugins: this.plugins,
toolbar1: this.toolbar1,
toolbar2: this.toolbar2,
branding: false,
menubar: false,
statusbar: false, // 隐藏编辑器底部的状态栏
// image_description: false,
// image_dimensions: false,
// image_title: false,
// image_uploadtab: false,本地上传图片
paste_data_images: true,/*是否允许粘贴图片*/
setup: function (editor) { /*自定义上传图片按钮*/
editor.ui.registry.addButton('uploadimg', {
icon: "image",
tooltip: "上传图片",
onAction: function (_) {
document.querySelector("#upload_img input").click();
}
});
},
//如需ajax上传可参考https://www.tiny.cloud/docs/configure/file-image-upload/#images_upload_handler
/*上传到oss*/
images_upload_handler: (blobInfo, success, failure) => { /*没有自定义上传按钮,用他本身的时候用这里的上传*/
let time = Date.parse(new Date())
let that = this
if(blobInfo.blob().size / 1024 / 1024 > 1 ){
failure('上传图片不能超过1M!');
}
if(blobInfo.blob().type !== "image/jpeg"){
failure('上传图片只能是 JPG 格式!');
}
if(that.imgNum >= that.totalImg){
failure('图片不能超过30张!')
}
let img = new Image();
img.src = "data:"+blobInfo.blob().type+";base64,"+blobInfo.base64();
img.onload = function(){
// console.log(img.width, img.height);
if (img.width < 500 || img.height < 500) failure('上传图片分辨率不能低于500*500!');
that.imgNum +=1
uploadImg().then(res => {
const form = new FormData()
form.append('policy', _get(res, 'data.policy'))
form.append('OSSAccessKeyId', _get(res, 'data.accessid'))
form.append('signature', _get(res, 'data.signature'))
form.append('key', `${_get(res, 'data.dir')}${_get(res, 'data.key')}${'_'}${time}${'.jpg'}`)
form.append('success_action_status', 200)
form.append('file', blobInfo.blob(), blobInfo.filename())
that.updateImg = (process.env.OSS_URL + '/' + `${_get(res, 'data.dir')}${_get(res, 'data.key')}${'_'}${time}${'.jpg'}`)
that.$http.post(process.env.OSS_URL, form).then(resp => {
success(that.updateImg)
}).catch(err => {
failure('网络错误,请稍后再试!')
console.log(err);
})
}).catch(err => {
failure('网络错误,请稍后再试!')
console.log(err);
})
}
/*const img = 'data:image/jpeg;base64,' + blobInfo.base64()
success(img)
console.log(success);*/
}
},
myValue: this.value,
totalImg:30,
imgNum:0,
uploadImgList:[],
isUpload: false,
}
},
mounted() {
tinymce.init({})
},
methods: {
//添加相关的事件,可用的事件参照文档=> https://github.com/tinymce/tinymce-vue => All available events
//需要什么事件可以自己增加
onClick(e) {
this.$emit('onClick', e, tinymce)
},
//可以添加一些自己的自定义事件,如清空内容
clear() {
this.myValue = ''
},
onImgQuery(){
/*获取所有的img*/
let imgList = document.getElementsByTagName('iframe')[0].contentWindow.document.querySelectorAll("#tinymce img")
if(this.imgNum > imgList.length) this.imgNum = imgList.length //针对用户删除图片
if(this.isUpload) return
//console.log(this.imgNum)
let that = this
let isErr = false
if(imgList.length > 0){
for(const i in imgList){
if(imgList[i].src&&(imgList[i].src.indexOf("taoic") == -1 )){
that.imgNum += 1
//console.log(that.imgNum)
if(that.imgNum > that.totalImg){
imgList[i].remove()
that.imgNum -= 1
isErr = true
} else {
that.uploadImgList.push(imgList[i].src)
}
}
}
if (that.uploadImgList.length >0){
that.UploadOOSImg()
}
if (isErr) that.imgPovp = true;
}
},
UploadOOSImg(){
let that = this
that.isUpload = true
uploadOOS(that.uploadImgList.join("-")).then(res => {
if(res.code == 1){
let imgList = document.getElementsByTagName('iframe')[0].contentWindow.document.querySelectorAll("#tinymce img")
let uploadImgList = that.uploadImgList
for(const i in imgList){
for(const j in uploadImgList){
if(imgList[i].src == uploadImgList[j]){ //对应上
imgList[i].src = res.Data[j]
}
}
}
}
that.isUpload = false
that.uploadImgList = [];
}).catch(err => {
that.isUpload = false
that.uploadImgList = [];
})
},
async fbeforeUpload(file) {
// console.log(file);
},
fuploadSuccess(res, file) {
// console.log("成功" + res);
},
/*上传阿里云*/
async fhandleAvatarChange(file) {
let that = this
if (!file) {
return
}
if(this.imgNum >= this.totalImg){
that.imgPovp = true;
return false;
}
// this.imgNum +=1
const isJPG = 'image/jpeg';
const isLt1M = file.raw.size / 1024 / 1024 < 1;
if(file.raw.type !== isJPG){
this.$message.error('上传图片只能是 JPG 格式!');
return false;
}
if(!isLt1M){
this.$message.error('上传图片不能超过1M!');
return false;
}
that.Imgloading = true
// let img = new Image();
// img.src = URL.createObjectURL(file.raw);
// img.onload = function() {
// that.Imgloading = true
// console.log(img.width);
// console.log(img.width, img.height);
/* if (img.width < 500 || img.height < 500) {
that.$message.error('上传图片分辨率不能低于500*500!');
that.Imgloading = false
return false
}*/
uploadImg().then(resp => {
if(resp.code == 1){
let time = Date.parse(new Date())
const form = new FormData()
form.append('policy', _get(resp, 'data.policy'))
form.append('OSSAccessKeyId', _get(resp, 'data.accessid'))
form.append('signature', _get(resp, 'data.signature'))
form.append('key', `${_get(resp, 'data.dir')}${_get(resp, 'data.key')}${'_'}${time}${'.jpg'}`)
form.append('success_action_status', 200)
form.append('file', file.raw)
that.$http.post(process.env.OSS_URL, form).then(res => {
if(res.status == 200){
that.imgNum += 1
that.Imgloading = false
that.updateImg = (process.env.OSS_URL + '/' + `${_get(resp, 'data.dir')}${_get(resp, 'data.key')}${'_'}${time}${'.jpg'}`)
// 插入图片 res.url为服务器返回的图片地址
let dom = tinymce.activeEditor.dom;
tinyMCE.execCommand("mceInsertContent",false,dom.createHTML("img", {src: that.updateImg}));
}else {
that.$message.error('网络错误,请稍后再试!');
}
}).catch(err => {
console.log(err);
})
}else {
that.$message.error('网络错误,请稍后再试!');
}
}).catch(err => {
console.log(err);
})
// }
},
},
watch: {
value(newValue) {
this.myValue = newValue
},
myValue(newValue) {
this.$emit('input', newValue)
}
}
}
</script>
<style scoped>
.num-loading{
height: 24px;
width: 24px;
background: #fff;
border-radius: 50%;
border: 1px solid #1D9AF2;
border-top: 2px solid #fff;
animation: spinner1 600ms linear infinite;
-moz-animation: spinner1 600ms linear infinite;
-webkit-animation: spinner1 600ms linear infinite;
display: inline-block;
position: absolute;
top: 50%;
left: 50%;
z-index: 1000;
}
@keyframes spinner1 {
to {
transform: rotate(360deg);
-moz-transform: rotate(360deg);
-webkit-transform: rotate(360deg);
}
}
.g-c-300{
color: #ff7300;
}
.notic-box{
color: #ff7300;
margin-top: 5px;
}
.rlPopup{
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 999;
}
.rl-inner{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
-webkit-transform: translate(-50%,-50%);
width: 320px;
height: 154px;
background-color: #ffffff;
border-radius: 10px;
overflow: hidden;
}
.pop-txt{
height: 114px;
padding: 32px 24px 0 24px;
border-bottom: 1px solid #f9f9f9;
}
.pop-btn{
height: 40px;
}
.pop-a{
width: 160px;
height: 40px;
display: block;
text-align: center;
line-height: 40px;
float: left;
}
.cancel-btn{
background-color: #409efe;
color: #fff;
/*border-radius: 0 0 10px 0;*/
}
.cancel-btn:hover{
background-color: #3996f6;
}
</style>
在页面中使用
import TinymceEditor from '@/views/tinymce.vue'
components: {TinymceEditor},
<tinymce-editor v-model="msg" :disabled="disabled" ref="editor"></tinymce-editor>
<div>{{ msg }}</div>
效果图
如果报这种错,说明路径不对