Vue 中使用 Tinymce 富文本编辑器
生活随笔
收集整理的这篇文章主要介绍了
Vue 中使用 Tinymce 富文本编辑器
小编觉得挺不错的,现在分享给大家,帮大家做个参考.
参考链接:https://www.cnblogs.com/wisewrong/p/8985471.html
Tinymce : 从 word 粘贴过来还能保持绝大部分格式的编辑器
一. 下载
npm install tinymce -S
安装之后,在 node_modules 中找到 tinymce/skins 目录,然后将 skins 目录拷贝到 public 目录下
(如果是使用 vue-cli 2.x 构建的 typescript 项目,就放到 static 目录下)
tinymce 默认是英文界面,所以还需要下载一个中文 语言包
将这个语言包放到 public 目录下,为了结构清晰,我包了一层 tinymce 目录
二. 初始化
import tinymce from 'tinymce/tinymce' // 初始化发现编辑器不显示,报“theme.js:1 Uncaught SyntaxError: Unexpected token <”这个错 // 需要手动引入tinymce主题,在init({})方法里加theme: 'silver',没用。 import 'tinymce/themes/silver/theme' cnpm install --save tinymce/theme三. 使用示例
<template><div name='tinymce'><div class='title'><input placeholder="请输入文章标题" 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' // 按示例初始化发现编辑器不显示,报“theme.js:1 Uncaught SyntaxError: Unexpected token <”这个错 // 需要手动引入tinymce主题,在init({})方法里加theme: 'silver',没用。 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() {// 注: 在此需要传入一个空对象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 否则会报错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}" >`)})},// 点击 提交submission() {if(this.title.trim() === ''){this.$message({message: '请输入文章标题',type: 'warning'})return}else if(tinyMCE.activeEditor.getContent().trim() === ''){this.$message({message: '请输入文章内容',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; }// 标题样式 .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 // 居左 居中 居右 两端对齐 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 // 在当前行之前插入一个新行 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四. 实现上传图片到七云牛
1.下载
cnpm install qiniu-js var qiniu = require('qiniu-js')// orimport * as qiniu from 'qiniu-js'2. upToken的生成
一般都是后端给的,但是前端也可以实现,我们就在这里以前端的方法实现它
@/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 () {// 参数 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: '上传文件大小不得超过200M 当前文件' + 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 => {// 上传进度'+Math.floor(response.total.percent)+'%'// total.loaded: number,已上传大小,单位为字节。// total.total: number,本次上传的总量控制信息,单位为字节,注意这里的 total 跟文件大小并不一致。// total.percent: number,当前上传进度,范围:0~100。this.uploadProgress = Math.floor(response.total.percent)if(this.uploadProgress == 100){this.$message('上传成功!')}},error: err => {// 上传失败触发this.$message.error('上传失败' + err.message)console.log(err)},complete: response => {this.uploadProgress = 0this.showProgress = falsethis.fileList.push('http://poxcqlozi.bkt.clouddn.com/' + response.key)}}// 可通过 subscription.unsubscribe() 停止当前文件上传 const putExtra = {// 文件原文件名fname: '',// 用来放置自定义变量params: {},// 用来限制上传文件类型,为 null 时表示不对文件类型限制// 限制类型放到数组里,如 mimeType: mimeType: ['image/png', 'image/jpeg', 'image/gif']}const config = {// 是否使用 cdn 加速域名,默认falseuseCdnDomain: true,// 上传域名区域,当为 null 或 undefined 时,自动分析上传域名区域region: qiniu.region.z1}/*file: Blob 对象,上传的文件key: 文件资源名token: 上传验证信息,前端通过接口请求后端获得config: object*/// 关键代码let options = {quality: 0.92,noCompressIfLarger: true,maxWidth: 800,maxHeight: 618}qiniu.compressImage(file[i], options).then(data => {// data : {// dist: 压缩后输出的 blob 对象,或原始的 file,具体看下面的 options 配置// width: 压缩后的图片宽度// height: 压缩后的图片高度// }var observable = qiniu.upload(data.dist, key, token, putExtra, config)var subscription = observable.subscribe(observer) // 上传开始});}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>
总结
以上是生活随笔为你收集整理的Vue 中使用 Tinymce 富文本编辑器的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: 树莓派--搭建nextcloud私有云
- 下一篇: 基于Vue的淘宝SKU组合算法