Vue 中使用 Tinymce 富文本编辑器
生活随笔
收集整理的這篇文章主要介紹了
Vue 中使用 Tinymce 富文本编辑器
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
參考鏈接:https://www.cnblogs.com/wisewrong/p/8985471.html
Tinymce : 從 word 粘貼過(guò)來(lái)還能保持絕大部分格式的編輯器
一. 下載
?
npm install tinymce -S?安裝之后,在 node_modules 中找到 tinymce/skins 目錄,然后將 skins 目錄拷貝到 public 目錄下
(如果是使用 vue-cli 2.x 構(gòu)建的 typescript 項(xiàng)目,就放到 static 目錄下)
tinymce 默認(rèn)是英文界面,所以還需要下載一個(gè)中文 語(yǔ)言包
將這個(gè)語(yǔ)言包放到 public 目錄下,為了結(jié)構(gòu)清晰,我包了一層 tinymce 目錄
二. 初始化
import tinymce from 'tinymce/tinymce' // 初始化發(fā)現(xiàn)編輯器不顯示,報(bào)“theme.js:1 Uncaught SyntaxError: Unexpected token <”這個(gè)錯(cuò) // 需要手動(dòng)引入tinymce主題,在init({})方法里加theme: 'silver',沒(méi)用。 import 'tinymce/themes/silver/theme' cnpm install --save tinymce/theme三. 使用示例
<template><div name='tinymce'><div class='title'><input placeholder="請(qǐng)輸入文章標(biāo)題" v-model="title"/></div><div :class="{fullscreen:fullscreen}" class="tinymce-container editor-container"><textarea :id="tinymceId" class="tinymce-textarea"/><div class="editor-custom-btn-container"><editorImage color="var(--main-color)" class="editor-upload-btn" @successCBK="imageSuccessCBK" /></div></div><div class="publish"><span @click="submission">提交</span><span class="cancel">取消</span></div></div> </template><script> import plainBtn from '@/components/Buttons/plainBtn'import tinymce from 'tinymce/tinymce' // import 'tinymce/themes/mobile/theme' // import 'tinymce/themes/modern/theme' // 按示例初始化發(fā)現(xiàn)編輯器不顯示,報(bào)“theme.js:1 Uncaught SyntaxError: Unexpected token <”這個(gè)錯(cuò) // 需要手動(dòng)引入tinymce主題,在init({})方法里加theme: 'silver',沒(méi)用。 import 'tinymce/themes/silver/theme'import editorImage from './components/editorImage' import plugins from './plugins' import toolbar from './toolbar'export default {name: 'Tinymce',components: { editorImage,plainBtn },props: {id: {type: String,default: function() {return 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')}},value: {type: String,default: 'Editor me ...'},toolbar: {type: Array,required: false,default() {return []}},menubar: {type: String,default: 'file edit insert view format table'},height: {type: Number,required: false,default: 360}},data() {return {hasChange: false,hasInit: false,tinymceId: this.id,fullscreen: false,languageTypeList: {'zh': 'zh_CN'},title: ''}},computed: {language() {return this.languageTypeList[this.$store.getters.language]}},watch: {value(val) {if (!this.hasChange && this.hasInit) {this.$nextTick(() =>window.tinymce.get(this.tinymceId).setContent(val || ''))}},language() {this.destroyTinymce()this.$nextTick(() => this.initTinymce())}},mounted() {// 注: 在此需要傳入一個(gè)空對(duì)象this.initTinymce({})},activated() {this.initTinymce()},deactivated() {this.destroyTinymce()},destroyed() {this.destroyTinymce()},methods: {initTinymce() {const _this = this// window.tinymce.baseURL = '/tinymces/tinymce'window.tinymce.init({language: this.language,selector: `#${this.tinymceId}`,// 安裝之后,在 node_modules 中找到 tinymce/skins 目錄,然后將 skins 目錄拷貝到 public 的 tinymce 目錄下// 必須加 skin_url 否則會(huì)報(bào)錯(cuò)skin_url: '/tinymce/skins/ui/oxide',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 Plugininit_instance_callback: editor => {if (_this.value) {editor.setContent(_this.value)}_this.hasInit = trueeditor.on('NodeChange Change KeyUp SetContent', () => {this.hasChange = truethis.$emit('input', editor.getContent())})},setup(editor) {editor.on('FullscreenStateChanged', (e) => {_this.fullscreen = e.state})}})},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) {console.log(arr)// 處理圖片上傳const _this = thisarr.forEach(v => {window.tinymce.get(_this.tinymceId).insertContent(`<img class="wscnph" src="${v}" >`)// tinyMCE.editors[0].setContent(`<img class="wscnph" src="${v}" >`)})},// 點(diǎn)擊 提交submission() {if(this.title.trim() === ''){this.$message({message: '請(qǐng)輸入文章標(biāo)題',type: 'warning'})return}else if(tinyMCE.activeEditor.getContent().trim() === ''){this.$message({message: '請(qǐng)輸入文章內(nèi)容',type: 'warning'})return}// console.log(tinyMCE.activeEditor.getContent())console.log(tinyMCE.editors[0].getContent())}} } </script><style scoped lang='scss'> .tinymce-container {position: relative;line-height: normal; } .tinymce-container>>>.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; }// 標(biāo)題樣式 .title{& input {margin: 20px 0;height: 36px;line-height: 36px;width: calc(100% - 10px);outline: none;border-radius: 2px;border: 1px solid #D2D2D2;font-size: 16px;padding-left: 10px;}& input:hover {border: 1px solid var(--main-color);} } // 提交,取消按鈕 .publish{margin: 20px 0px; } .publish span{display: inline-block;height: 30px;line-height: 30px;padding: 0 10px;border:1px solid var(--main-color);color: var(--main-color);cursor: pointer;border-radius: 2px;background: #ffffff;margin-right: 20px; } .publish span:hover{background: var(--main-color);color:#ffffff; } .publish .cancel{border:1px solid var(--main-font-color);color: var(--main-font-color); } .publish .cancel:hover{background: var(--main-font-color);color:#ffffff; } </style> // plugins.jsconst plugins = ['advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars wordcount']export default plugins // toolbar.js// 加粗 斜體 下劃線 刪除線 bold italic underline strikethroug // 居左 居中 居右 兩端對(duì)齊 alignleft aligncenter alignright alignjustify // 清除 格式選擇下拉框(縮進(jìn)、行高) 段落選擇下拉框(段落、標(biāo)題) 字體選擇下拉框 字號(hào)選擇下拉框 alignnone styleselect formatselect fontselect fontsizeselect // 剪切 復(fù)制 粘貼 cut copy paste // 減少縮進(jìn) 增加縮進(jìn) outdent indent // 引用 撤銷 恢復(fù) 清除格式 blockquote undo redo removeformat // 下標(biāo) 上標(biāo) 網(wǎng)格線 插入的集合按鈕 水平線 無(wú)序列表 有序列表 subscript superscript visualaid insert hr bullist numlist // 添加和修改鏈接 去除鏈接格式 打開(kāi)選中鏈接 添加和修改圖片 特殊符號(hào) 粘貼純文本 link unlink openlink image charmap pastetext // 打印 預(yù)覽 作者 print preview anchor // 分頁(yè)符 拼寫(xiě)檢查 搜索 pagebreak spellchecker searchreplace // 代碼 全屏 插入時(shí)間 插入/編輯表格 刪除表格 單元格屬性 合并單元格 拆分單元格 在當(dāng)前行之前插入一個(gè)新行 在當(dāng)前行之后插入一個(gè)新行 刪除當(dāng)前行 行屬性 剪切選定行 復(fù)制選定行 在當(dāng)前行之前粘貼行 在當(dāng)前行之后粘貼行 在當(dāng)前列之前插入一個(gè)列 在當(dāng)前列之后插入一個(gè)列 刪除當(dāng)前列 code fullscreen insertdatetime table tabledelete tablecellprops tablemergecells tablesplitcells tableinsertrowbefore tabledeleterow tablerowprops tablecutrow tablecopyrow tablepasterowbefore tablepasterowafter tableinsertcolbefore tableinsertcolafter tabledeletecol // 在當(dāng)前行之前插入一個(gè)新行 const toolbar = ['bold italic underline strikethrough | alignleft aligncenter alignright alignjustify | alignnone styleselect formatselect fontselect fontsizeselect | cut copy paste | outdent indent | blockquote undo redo removeformat | subscript superscript visualaid insert hr bullist numlist | link unlink openlink image charmap pastetext | print preview anchor | pagebreak spellchecker searchreplace | code fullscreen insertdatetime table tabledelete tablecellprops tablemergecells tablesplitcells tableinsertrowbefore tabledeleterow tablerowprops tablecutrow tablecopyrow tablepasterowbefore tablepasterowafter tableinsertcolbefore tableinsertcolafter tabledeletecol ','hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen']export default toolbar四. 實(shí)現(xiàn)上傳圖片到七云牛
1.下載
cnpm install qiniu-js var qiniu = require('qiniu-js')// orimport * as qiniu from 'qiniu-js'2. upToken的生成
一般都是后端給的,但是前端也可以實(shí)現(xiàn),我們就在這里以前端的方法實(shí)現(xiàn)它
@/utils/quillToken.js
import CryptoJS from 'crypto-js'const utf16to8 = function (str) {/** Interfaces:* utf8 = utf16to8(utf16)* utf16 = utf8to16(utf8)*/var out, i, len, cout = ''len = str.lengthfor (i = 0 ; i < len; i++) {c = str.charCodeAt(i)if ((c >= 0x0001) && (c <= 0x007F)) {out += str.charAt(i)} else if (c > 0x07FF) {out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F))out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F))out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F))} else {out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F))out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F))}}return out }const base64encode = function (str) {/** Interfaces:* b64 = base64encode(data)* data = base64decode(b64)*/var out, i, lenvar c1, c2, c3len = str.lengthi = 0out = ''var base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';while (i < len) {c1 = str.charCodeAt(i++) & 0xffif (i == len) {out += base64EncodeChars.charAt(c1 >> 2)out += base64EncodeChars.charAt((c1 & 0x3) << 4)out += '=='break}c2 = str.charCodeAt(i++)if (i == len) {out += base64EncodeChars.charAt(c1 >> 2)out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4))out += base64EncodeChars.charAt((c2 & 0xF) << 2)out += '='break}c3 = str.charCodeAt(i++)out += base64EncodeChars.charAt(c1 >> 2)out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4))out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6))out += base64EncodeChars.charAt(c3 & 0x3F)}return out }const base64decode = function (str) {var c1, c2, c3, c4var i, len, outlen = str.lengthi = 0out = ''var base64DecodeChars = new Array(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1)while (i < len) {/* c1 */do {c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff]} while (i < len && c1 == -1)if (c1 == -1) break/* c2 */do {c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff]} while (i < len && c2 == -1)if (c2 == -1) breakout += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4))/* c3 */do {c3 = str.charCodeAt(i++) & 0xffif (c3 == 61) return outc3 = base64DecodeChars[c3]} while (i < len && c3 == -1)if (c3 == -1) breakout += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2))/* c4 */do {c4 = str.charCodeAt(i++) & 0xffif (c4 == 61) return outc4 = base64DecodeChars[c4]} while (i < len && c4 == -1)if (c4 == -1) breakout += String.fromCharCode(((c3 & 0x03) << 6) | c4)}return out }const safe64 = function (base64) {base64 = base64.replace(/\+/g, '-')base64 = base64.replace(/\//g, '_')return base64 }const genUpToken = function () {// 參數(shù) accessKey,secretKey,putPolicyvar accessKey = 'q5Oqby268SGSsWEBrkwGW9oQ20qzi2-fXl6Xm1zL'var secretKey = 'B7IQmIhh38gIHXEDccW8YN4Yath8vHpwf_aifeDW'var putPolicy = {'scope':'lyajuan','deadline':Math.round(new Date().getTime() / 1000) + 3600}// SETP 2var putPolicy1 = JSON.stringify(putPolicy)// SETP 3var encoded = base64encode(utf16to8(putPolicy1))// SETP 4var hash = CryptoJS.HmacSHA1(encoded, secretKey)var encodedSigned = hash.toString(CryptoJS.enc.Base64)// SETP 5var uploadToken = accessKey + ':' + safe64(encodedSigned) + ':' + encodedreturn uploadToken } export {utf16to8,base64encode,base64decode,safe64 ,genUpToken }在需要生成 Token 的 .vue 文件中引入
import {genUpToken} from '@/utils/qiniuToken.js'安裝:crypto-js 加密: https://www.jianshu.com/p/a47477e8126a
cnpm install crypto-js --save <template><div class="chooseImage" @click="choose"><svg-icon icon-class='upload'/>選擇圖片<input type="file" class="pickFile" @change="uploadFile" ref='chooseFile' title="上傳文件" multiple :style="{background:color,borderColor:color}"/></div> </template><script> // 引入七云牛js文件 import * as qiniu from 'qiniu-js' // 生成token的文件 import {genUpToken} from '@/utils/qiniuToken.js' import store from '@/store'export default {name: 'chooseImage',props: {color: {type: String,default: 'var(--main-color)'}},data() {return {fileList: []}},methods: {choose() {this.$refs.chooseFile.click()},// 上傳文件uploadFile($event) {store.dispatch('SetLoading',true)const file = $event.target.filesfor(var i=0;i<file.length; i++) {// 限制上傳文件的大小為200M// console.log(file[i])if (file[i].size > 209715200) {const cur_size = Math.floor(file.size * 100 / 1024 / 1024) / 100this.$notify.info({title: '消息',message: '上傳文件大小不得超過(guò)200M 當(dāng)前文件' + cur_size + 'M '})return false}// this.showProgress = trueconst token = genUpToken();const fileName = file[i].nameconst suffix = fileName.substring(fileName.lastIndexOf('.')) // 后綴名const prefix = fileName.substring(0, fileName.lastIndexOf('.'))const key = prefix + token + suffix // 上傳文件名const observer = {next: response => {// 上傳進(jìn)度'+Math.floor(response.total.percent)+'%'// total.loaded: number,已上傳大小,單位為字節(jié)。// total.total: number,本次上傳的總量控制信息,單位為字節(jié),注意這里的 total 跟文件大小并不一致。// total.percent: number,當(dāng)前上傳進(jìn)度,范圍:0~100。this.uploadProgress = Math.floor(response.total.percent)if(this.uploadProgress == 100){this.$message('上傳成功!')}},error: err => {// 上傳失敗觸發(fā)this.$message.error('上傳失敗' + err.message)console.log(err)},complete: response => {this.uploadProgress = 0this.showProgress = falsethis.fileList.push('http://poxcqlozi.bkt.clouddn.com/' + response.key)}}// 可通過(guò) subscription.unsubscribe() 停止當(dāng)前文件上傳 const putExtra = {// 文件原文件名fname: '',// 用來(lái)放置自定義變量params: {},// 用來(lái)限制上傳文件類型,為 null 時(shí)表示不對(duì)文件類型限制// 限制類型放到數(shù)組里,如 mimeType: mimeType: ['image/png', 'image/jpeg', 'image/gif']}const config = {// 是否使用 cdn 加速域名,默認(rèn)falseuseCdnDomain: true,// 上傳域名區(qū)域,當(dāng)為 null 或 undefined 時(shí),自動(dòng)分析上傳域名區(qū)域region: qiniu.region.z1}/*file: Blob 對(duì)象,上傳的文件key: 文件資源名token: 上傳驗(yàn)證信息,前端通過(guò)接口請(qǐng)求后端獲得config: object*/// 關(guān)鍵代碼let options = {quality: 0.92,noCompressIfLarger: true,maxWidth: 800,maxHeight: 618}qiniu.compressImage(file[i], options).then(data => {// data : {// dist: 壓縮后輸出的 blob 對(duì)象,或原始的 file,具體看下面的 options 配置// width: 壓縮后的圖片寬度// height: 壓縮后的圖片高度// }var observable = qiniu.upload(data.dist, key, token, putExtra, config)var subscription = observable.subscribe(observer) // 上傳開(kāi)始});}this.$emit('successCBK', this.fileList)this.fileList = []}} } </script><style lang="scss" scoped> .chooseImage{text-align: center;background: var(--main-color);color: #ffffff;border-radius: 5px;padding: 5px 10px;& input{display: none;}& .svg-icon{margin-right: 10px;} } </style>?
總結(jié)
以上是生活随笔為你收集整理的Vue 中使用 Tinymce 富文本编辑器的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 树莓派--搭建nextcloud私有云
- 下一篇: PDF的文件大小怎么压缩,两款高效的PD