vue項目中使用tinymce富文本編輯器實現圖片上傳/粘貼格式
前言
最近因為公司項目的后臺管理端需要實現編輯器功能, 一方面滿足編輯各類文章內容需求,另一方面要自己編輯一些課程相關的介紹,于是就花了一些時間對比體驗現有的一些開源的編輯器。
編輯器之間的簡單比較
UEditor:基本滿足各種需求,依賴于jquery但是已經不再維護了,實現上傳圖片等需要修改源碼,界面不太美觀,對于老瀏覽器兼容還不錯,但是我這里采用的是VueJS來開發,所以放棄
wangEditor:比較輕量級,最最最重要的是有中文文檔上手快,UI也比較漂亮,而且還是國產的, 對于編輯器功能需求少的兄die可以考慮,但是考慮到我這項目業務比較重,所以只好放棄
Bootstrap-wysiwyg:簡潔好看,依賴于Bootstrap, jquery,選擇的Element-ui棄之
TinyMCE: 支持圖片在線處理,插件多,功能強。 嗯,就選它啦(雖然文檔是英文,但是谷歌翻譯也不錯 ?)
我們項目要解決的需求說復雜也不復雜,但是卻很煩人, 比如:
實現圖片上傳(基礎功能)
模擬手機預覽功能(基礎功能)
編輯的內容在app中顯示要適配
從135編輯器, 秀米等等編輯器拷貝過來的內容要正常顯示并且排版還要保持,還要將這些第三方圖片上傳到自己服務(怕第三方下架圖片)
引入并初始化
引入tinymace文件
項目采用vue-cli@3.x構建的, 將TinyMCE下載放在index.html同級目錄下, 并在index.html中引入TinyMCE
<script src=./tinymce4.7.5/tinymce.min.js></script>
初始化
引入文件后,在html元素上初始化TinyMCE, 由于TinyMCE允許通過CSS選擇器來標識可替換的元素,所以我們只需要將包含選擇器的對象傳遞給TinyMCE.init(),代碼如下:
<template>
<div class="tinymce-container editor-container">
<textarea :id="tinymceId" class="tinymce-textarea" />
</div>
</template>
<script>
export default {
name: 'Tinymce',
data() {
return {
tinymceId: this.id
}
},
mounted(){
this.initTinymce()
},
methods: {
initTinymce() {
window.tinymce.init({
selector: `#${this.tinymceId}`
})
}
}}
</script>
這樣就將textarea替換為TinyMCE編輯器實例, 做完了最簡單的初始化。
配置項
接下來就是添加配置項, 讓TinyMCE編輯器功能豐富起來
基礎配置
關于基礎配置, 我就不一一介紹,文檔中都有詳細的說明,如果英語和我一樣弱雞,可以借助chrome的翻譯,大概能看明白。
下面是封裝的組件的script內容, 關于一些配置直接在代碼中說明:
import plugins from '@/components/Tinymce/plugins'
import toolbar from '@/components/Tinymce/toolbar'
import {
uploadFile
} from '@/api/file/upload'
export default {
name: 'Tinymce',
props: {
tid: {
type: String,
default: 'my-tinymce-' + new Date().getTime() + parseInt(Math.random(100))
},
content: {
type: String,
default: ''
},
menubar: { // 菜單欄
type: String,
default: 'file edit insert view format table'
},
toolbar: { // 工具欄
type: Array,
required: false,
default () {
return []
}
},
height: {
type: Number,
required: false,
default: 360
}
},
data() {
return {
tinymceId: this.tid,
finishInit: false,
hasChanged: false,
config: {}
}
},
mounted() {
this.initTinymce()
},
methods: {
initTinymce() {
window.tinymce.init({
selector: `#${this.tinymceId}`,
...this.config,
content_style: 'img {max-width:100% !important }', // 初始化賦值
init_instance_callback: editor => {
if (this.content) {
editor.setContent(this.content)
}
this.finishInit = true
editor.on('NodeChange Change SetContent KeyUp', () => {
this.hasChanged = true
})
}, // 上傳圖片
images_upload_handler: (blobInfo, success, failure) => {
const formData = new FormData();
formData.append('file', blobInfo.blob());
uploadFile(formData).then(res => {
if (res.data.code == 0) {
let file = res.data.data;
success(file.filePath);
return
}
failure('上傳失敗')
}).catch(() => {
failure('上傳出錯')
})
}
})
}
}
}
組件初始化完成,編輯框如圖所示:
config內容
為了方便閱讀, 這里將config內容抽取出來單獨展示, 我也對部分配置項進行了注釋, 方便理解:
config: {
language: 'zh_CN',
height: this.height,
menubar: this.menubar, //菜單:指定應該出現哪些菜單
toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar, // 分組工具欄控件
plugins: plugins, // 插件(比如: advlist | link | image | preview等)
object_resizing: false, // 是否禁用表格圖片大小調整
end_container_on_empty_block: true, // enter鍵 分塊
powerpaste_word_import: 'merge', // 是否保留word粘貼樣式 clean | merge
code_dialog_height: 450, // 代碼框高度 、寬度
code_dialog_width: 1000,
advlist_bullet_styles: 'square' // 無序列表 有序列表
}
toolbar.js
組件中引入的toolbar.js文件存的是TinyMCE初始化時加載的工具欄控件, 設置的順序即代表著在編輯器工具欄上出現的順序
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;
plugin.js
組件中引入的plugin.js文件是設置TinyMCE初始化時加載哪些插件,默認情況下,TinyMCE不會加載任何插件:
const plugins = ["advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools importcss insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars wordcount"];
export default plugins;
上傳圖片
TinyMCE提供了圖片上傳處理函數images_upload_handler, 該函數有三個參數:blobInfo,success callback,failure callback, 分別是圖片內容, 一個成功的回調函數以及一個失敗的回調函數,具體上傳圖片代碼在上面已經寫,這里就不贅述; 需要注意的是,當向后臺上傳完圖片, 我們要調用success函數來用服務器地址替換<image>標簽的src屬性。
succuss(服務圖片地址);
本來以為上傳圖片就完成了, 圖片上傳就算完事了, 結果產品小伙伴說啦: “你這圖片不可以直接復制粘貼嗎?每次點上傳好伐呀!!”, 那繼續加復制粘貼功能唄!
拖入/粘貼圖片
其實實現圖片粘貼并不難, 之前已經加載了paste插件, 接下來只需要在初始化中插入配置項:
paste_data_images: true, // 設置為“true”將允許粘貼圖像,而將其設置為“false”將不允許粘貼圖像。
但是我卻花費了一個小時來搞這個, 因為我咋也粘貼不上, 所以不得不提一下這個坑:就因為我用的chrome開發, chrome瀏覽器直接在文件中復制粘貼圖片是無法粘貼上的, 但是可以從微信輸入框等地方粘貼上,也能拖入, 我暫時還沒有進一步去做chrome瀏覽器粘貼的兼容,后續有時間回去做.
圖片處理就告一段落~
關于預覽
TinyMCE配置了預覽插件preview, 前面在plugin.js中也加入了, 但是我們的需求是實現手機模式下的預覽, 所以還需要設置一下預覽內容的寬度以及高度
plugin_preview_width: 375, // 預覽寬度 plugin_preview_height: 668,
設置完預覽之后發現圖片大于預覽寬度, 出現了滾動,于是找到了一個content_style屬性, 可以設置css樣式,將樣式注入到編輯器中, 在初始化中設置下面的屬性:
content_style: ` * { padding:0; margin:0; } img {max-width:100% !important }`,
于是模擬手機端預覽也完事了, 但內容提交后, 手機上查看圖片仍然很大, 原因是我忽略了官方文檔說的:這些樣式不會與內容一起保存的
所以我在提交代碼時將這個style字符串拼接到內容上
content += `<style>* { padding:0; margin:0; } img {max-width:100% !important }</style>`
第三方編輯器內容拷貝
上面我也說到了第三方編輯器內容拷貝的需求, 要讓內容拷貝過來排版不變, 并且圖片內容要上傳到我們自己服務器上。
1. 對于135編輯器
135編輯器支持拷貝的是html代碼,通過直接粘貼在code中即可保持排版樣式不變,對于圖片地址處理思路如下:
為自己的服務器設置一個白名單
將頁面中非白名單內的圖片鏈接地址傳給后臺,讓后臺去把這些圖片放到自己服務器并返回給我新圖片鏈接
然后我再更新對應的圖片鏈接;
這里面主要涉及到:
找到所有圖片鏈接
更新對應的圖片鏈接
本來是打算使用正則來找到圖片, 獲得服務器返回的內容,再使用正則匹配替換, 后來發現TinyMCE提供了urlconverter_callback用于處理url替換, 它有四個參數:url,node,an_save,name,主要使用到的是要替換的url地址, 這個方法返回的是替換后的url地址;
我是這樣做的:
urlconverter_callback: (url, node, on_save, name) => { //設置白名單
const assignUrl = ['http://192.168.1.49', 'https://lms0-1251107588']
let isInnerUrl = false //默認不是內部鏈接
try {
assignUrl.forEach(item => {
if (url.indexOf(item) > -1) {
isInnerUrl = true
throw new Error('EndIterate')
}
})
} catch (e) {
if (e.message != 'EndIterate') throw e
}
if (!isInnerUrl) {
replaceUrl(url).then(result => {
if (result.data.code == 0) {
this.newImgUrl.push(result.data.data)
}
})
}
return url
},
這一步只是實現了將白名單外的圖片地址發給服務器,接收并保存返回的地址,大家可能會好奇為什么不在這里直接利用返回值替換圖片地址呢?
由于這個函數沒有沒有提供回調函數,當異步從服務器取回新地址時,renturn回去的url是不等人的, 我試了使用await來解決,但是發現它不支持異步來處理,所有只好放棄,采用這種方式變向處理,讓用戶點擊保存時再去匹配并替換內容。
if (!this.newImgUrl.length) return this.content // 匹配并替換 img中src圖片路徑
this.content = this.content.replace(/<img [^>]*src=['"]([^'"]+)[^>]*>/gi, (mactch, capture) => {
let current = ''
this.newImgUrl.forEach(item => {
if (capture == item.oriUrl) {
current = item.filePath
}
})
current = current ? current : capture
return mactch.replace(/src=[\'\"]?([^\'\"]*)[\'\"]?/i, 'src=' + current)
}) // 匹配并替換 任意html元素中 url 路徑
this.content = this.content.replace(/url\(['"](.+)['"]\)/gi, (mactch, capture) => {
let current = ''
this.newImgUrl.forEach(item => {
if (capture == item.oriUrl) {
current = item.filePath
}
})
current = current ? current : capture;
return mactch.replace(/url\((['"])(.+)(['"])\)/i, `url($1${current}$3) `)
})
return content
最后再將替換完成后的內容發送給后臺,這里對于TinyMce編輯器的使用就告一段落了,謝謝你的認真閱讀,希望對你有所幫助,后期有新的功能添加或是新內容我會再更新的。
參考文章:http://blog.ncmem.com/wordpress/2023/12/27/vue%e9%a1%b9%e7%9b%ae%e4%b8%ad%e4%bd%bf%e7%94%a8tinymce%e5%af%8c%e6%96%87%e6%9c%ac%e7%bc%96%e8%be%91%e5%99%a8%e5%ae%9e%e7%8e%b0%e5%9b%be%e7%89%87%e4%b8%8a%e4%bc%a0-%e7%b2%98%e8%b4%b4%e6%a0%bc%e5%bc%8f/
歡迎入群一起討論

浙公網安備 33010602011771號