TinyMCE富文本編輯器在Vue中的使用方式
簡介
公司的后臺管理系統(tǒng)要做一個文檔編輯器,最開始選型是 toast-ui/editor ,但是同事用起來指出功能太少、不好用、沒有字體功能。。。
好好好,本著為人民服務(wù)的精神,我重新篩選編輯器組件,最終選定了TinyMCE這個編輯器。
一、TinyMCE文檔
在開發(fā)過程中,免不了要站在巨人的肩膀上。我參考了莫若卿大佬的TinyMCE中文文檔,大家可以參考。
順便加上官方文檔,英文的看著費(fèi)勁 TinyMCE官方文檔
二、實(shí)現(xiàn)成果
無圖無真相!這里我就把實(shí)現(xiàn)前后的效果圖先放出來,大家如果需要的話,再往后看。
1、toast-ui/editor效果

2、TinyMCE效果

對比兩個編輯器,可以發(fā)現(xiàn)TinyMCE多了很多功能,包括字體大小,字體風(fēng)格,行間距等等。
三、引用TinyMCE的方法
對比完兩個編輯器的優(yōu)劣,直接跳過toast-ui/editor,只介紹TinyMCE的實(shí)現(xiàn)。
1、引入TinyMCE
由于我是在vue中引用,跟莫若卿大佬的不太一樣,所以我介紹一下我的用法。
(1)、首先,我引用了vue-tinymce,這是提供給 vue 開發(fā)者使用的 TinyMCE 組件。
- 1.安裝組件:
npm install @packy-tang/vue-tinymce
- 2.引入:
import VueTinymce from “@packy-tang/vue-tinymce” Vue.use(VueTinymce)
- 3.使用:
<vue-tinymce ref="tinymce" v-model="content" :setting="setting" />
import tinymce from './components/tinymce/' import VueTinymce from './components/vue-tinymce' Vue.prototype.$tinymce = tinymce Vue.use(VueTinymce)
(2)、由于我改了插件中的一些方法,所以我把TinyMCE整個插件放到了代碼了,當(dāng)作組件使用。

廢話不多說,上代碼
<template>
<vue-tinymce ref="tinymce" v-model="content" :setting="setting" />
</template>
<script>
//樣式
import './skins/content/default/content.min.css'
import './skins/ui/oxide/skin.min.css'
import './skins/ui/oxide/content.min.css'
//主題
import './themes/silver'
//插件
import './plugins/link' //鏈接插件
import './plugins/image' //圖片插件
import './plugins/imagetools'
import './plugins/media' //媒體插件
import './plugins/table' //表格插件
import './plugins/lists' //列表插件
import './plugins/quickbars' //快速欄插件
import './plugins/fullscreen' //全屏插件
import './plugins/lineheight'
import './plugins/uploadimage'
import './plugins/charmap'
import './plugins/codesample'
import './plugins/upfile'
import './plugins/code'
/**
* 注:
* 5.3.x版本需要額外引進(jìn)圖標(biāo),沒有所有按鈕就會顯示not found
*/
import './icons/default/icons'
//本地化
import './plugins/zh_CN.js'
import { uploadFile } from '@/api/document/doc'
export default {
name: 'tinymceEditor',
props: {
initialValue: {
type: String,
default: ''
}
},
watch: {
initialValue (newValue) {
this.content = newValue
},
},
data(){
return {
tinymceHtml: '',
resVideo: null,
uploaded: false,
content: '',
setting: {
menubar: false,
toolbar: "undo redo | fullscreen | formatselect alignleft aligncenter alignright alignjustify | upfile link unlink code | numlist bullist lineheight | image media table | fontselect fontsizeselect forecolor backcolor | bold italic underline strikethrough charmap | indent outdent | superscript subscript | removeformat |",
toolbar_drawer: "sliding",
quickbars_selection_toolbar: "removeformat | bold italic underline strikethrough | fontsizeselect forecolor backcolor",
plugins: "code lineheight link image upfile imagetools media table lists fullscreen quickbars charmap",
language: 'zh_CN',
height: 500,
deprecation_warnings: false,
images_upload_handler: (blob, callback) => {
this.upload(blob, url => {
callback(url)
})
},
file_picker_types: 'media',
file_picker_callback: (callback, value, meta) => {
//當(dāng)點(diǎn)擊meidia圖標(biāo)上傳時,判斷meta.filetype == 'media'有必要,因?yàn)閒ile_picker_callback是media(媒體)、image(圖片)、file(文件)的共同入口
if (meta.filetype == 'media'){
let filetype = '.mp4'
//創(chuàng)建一個隱藏的type=file的文件選擇input
let input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', filetype);
let that = this;
input.onchange = function() {
let file = this.files[0];
that.uploadImg(file, url => {
callback(url)
});
}
//觸發(fā)點(diǎn)擊
input.click();
}
},
file_callback: (file, callback) => {
// 自定義處理文件操作部分
this.uploadFile(file, url => {
callback(url)
})
}
},
}
},
methods: {
upload (file, callback) {
const formData = new FormData()
if (file.size / 1024 / 1024 > 50) {
this.$message.error('圖片大小應(yīng)小于50M')
return false
}
formData.append('file', file.blob(), file.name)
uploadFile(formData).then(res => {
const imageSrc = res.data.objectUrl
callback(imageSrc)
});
},
uploadFile (file, callback) {
const fileName = file.name;
const fileSize = (file.size / 1024 / 1024).toFixed(1);
if (fileSize > 200) {
Message.error("文件大小應(yīng)小于200M");
return false;
}
const formData = new FormData();
formData.append("file", file, fileName);
var timestamp = new Date().getTime();
formData.append("batchKey", timestamp);
axios.post(`/xxx/xxx/uploadFile`, formData).then(res => {
const imageSrc = res.data.objectUrl
callback(imageSrc)
});
},
async uploadImg (file, callback) {
let loadText = document.createElement('span');
let fileName = file.name;
const fileSize = (file.size / 1024 / 1024).toFixed(1);
if (fileSize > 200) {
Message.error("文件大小應(yīng)小于200M");
return false;
}
const formData = new FormData();
formData.append("file", file, fileName);
var timestamp = new Date().getTime();
formData.append("batchKey", timestamp);
// 上傳信息顯示為位置
const supMessagePos = document.getElementsByClassName('tox-dialog__footer')[0]
const messagePos = document.getElementsByClassName('tox-dialog__footer-start')[0]
var config = {
onUploadProgress: progressEvent => {
var complete =
((progressEvent.loaded / progressEvent.total) * 100) | 0;
if (complete == 100) {
loadText.innerHTML = "加載完成,等待上傳...";
supMessagePos.insertBefore(loadText, messagePos)
} else {
loadText.innerHTML = "正在加載... " + complete + " %";
supMessagePos.insertBefore(loadText, messagePos)
}
}
};
await axios.post(`/xxx/xxx/uploadFile`, formData, config).then(res => {
if (res.data) {
loadText.innerHTML = "上傳完成!"
callback(res.data.objectKey, { title: fileName })
supMessagePos.insertBefore(loadText, messagePos)
// myWebSocket.closeSocket();
}
})
}
}
}
</script>
<style>
.tox-statusbar__branding {
display: none;
}
</style>
2、說明
我這里加上了上傳圖片、上傳文件和上傳視頻的方法,不需要的可以省略。
“setting”里的“toolbar”和"plugins"是控制編輯器中的插件功能的。toolbar代表上方引入的功能按鈕,plugins列舉需要引入的插件。
“images_upload_handler”這個屬性是上傳圖片的回調(diào),加上之后上傳圖片的組件會增加一個“上傳”按鈕,方便很多。
實(shí)現(xiàn)效果如下:

“file_picker_callback”屬性也是上傳回調(diào),與上邊那個類似,不過可以配置。
我添加了判斷 meta.filetype == 'media' ,因?yàn)?ldquo;file_picker_callback”是media(媒體)、image(圖片)、file(文件)的共同入口,我只需要上傳視頻,所以我加了判斷。
加完后的效果如下:

"file_callback"屬性比較特別,它是“upfile”插件專屬的屬性,可以添加上傳文檔的回調(diào)。我因?yàn)樾枰蟼鱬df文件,所以引入了這個插件到編輯器中。
3、其他配置項(xiàng)
(1)、在光標(biāo)位置顯示快速工具欄
quickbars_insert_toolbar([插入]快捷工具欄)
這個功能是當(dāng)你的光標(biāo)在【空的一行】時,顯示可以快速插入的工具項(xiàng)。
setting: {
quickbars_insert_toolbar: 'quickimage quicktable'
}

如果不需要,可以把這一項(xiàng)設(shè)置為空。
setting: {
quickbars_insert_toolbar: ''
}
(2)、選中文字后,顯示快速工具欄
quickbars_selection_toolbar([選擇]快捷工具欄)
這個功能是當(dāng)你選中文字后,顯示可以對文字操作的工具項(xiàng)。
setting: {
quickbars_selection_toolbar: "removeformat | bold italic underline strikethrough | fontsizeselect forecolor backcolor"
}

(3)、向編輯器粘貼圖片后,自動將圖片從base64格式轉(zhuǎn)換為url地址
urlconverter_callback(粘貼圖片后,不自動上傳,而是使用base64編碼)
前兩天同事問了我這個問題,我順便加在這里分享一下。
這個功能是為了解決向編輯器粘貼圖片的問題。正常通過插件上傳圖片時,會自動處理成url格式,通過ctrl+v粘貼的圖片則不處理。
在“setting”里加上以下這段代碼就可以解決。
setting: {
urlconverter_callback: (url, node, onSave, name) => {
if (node === 'img' && url.startsWith('blob:')) {
tinymce.activeEditor && tinymce.activeEditor.uploadImages()
}
return url
},
}
總結(jié)
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Avue實(shí)現(xiàn)動態(tài)查詢與數(shù)據(jù)展示的示例代碼
Avue是一個基于Vue.js的前端框架,它是由阿里云開發(fā)的一款企業(yè)級UI組件庫,旨在提供一套全面、易用且高性能的界面解決方案本文介紹了Avue實(shí)現(xiàn)動態(tài)查詢與數(shù)據(jù)展示的示例,需要的朋友可以參考下2024-08-08
vue動態(tài)配置模板 ''component is''代碼
這篇文章主要介紹了vue動態(tài)配置模板 'component is'代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-07-07
vue3配置代理實(shí)現(xiàn)axios請求本地接口返回PG庫數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了vue3配置代理實(shí)現(xiàn)axios請求本地接口返回PG庫數(shù)據(jù)的相關(guān)知識,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下2025-03-03
Vue.set()動態(tài)的新增與修改數(shù)據(jù),觸發(fā)視圖更新的方法
今天小編就為大家分享一篇Vue.set()動態(tài)的新增與修改數(shù)據(jù),觸發(fā)視圖更新的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-09-09
vue3中關(guān)于路由hash與History的設(shè)置
這篇文章主要介紹了vue3中關(guān)于路由hash與History的設(shè)置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04
vue3實(shí)現(xiàn)自定義導(dǎo)航菜單的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何使用vue3實(shí)現(xiàn)自定義導(dǎo)航菜單,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-11-11

