【【超详细】vue项目:Tinymce富文本使用教程以及踩坑总结+功能扩展
- 引言:
- 一、 开始
- 二、快速开始
- 1、安装Tinymce
- 三、封装成Vue组件
- 1、文件结构
- 2、index.vue
- 3、dynamicLoadScript.js
- 4、plugin.js
- 5、toolbar.js
- 四、使用Tinymce组件
- 五、业务逻辑实现
- 1、添加页面只读模式,解决方案(`readonly: true`):
- - a、在组件中添加`props`
- - b、在组件初始化的时候添加该配置
- - c、使用组件时传参
- 2、数据处理:传数据给后端需要进行base64加密,但是会把标签尖括号变成中文,导致回显时错误,解决方案:
- - a、保存时转码之后再加密:
- - b、回显时解密再转码
- 3、打开页面时会出现Tinymce还未实例化的情况,页面展示空白,解决方案:
- 4、在Tinymce编辑器上方自定义按钮,打开一个弹窗,选定一个参数添加至编辑器中鼠标点击的位置
- 5、在Tinymce编辑器实现右击可以选择只粘贴文本
引言:
在Vue项目的开发过程中,经常需要使用富文本编辑器来处理用户的输入内容。Tinymce
是一个功能强大且易于使用的富文本编辑器,它支持大多数常见的文本编辑功能,并且可以通过插件进行扩展。本文将详细介绍如何在Vue项目中使用Tinymce富文本编辑器。
一、 开始
官网文档:https://www.tiny.cloud/docs/
中文文档:http://tinymce.ax-z.cn/
社区版及开发版官方最新打包地址:https://www.tiny.cloud/get-tiny/self-hosted/
汉化包:http://tinymce.ax-z.cn/static/tiny/langs/zh_CN.js
二、快速开始
1、安装Tinymce
首先,在Vue项目的根目录下打开终端
,运行以下命令来安装Tinymce
:
npm install tinymce
上述命令会下载并安装Tinymce的依赖到你的项目中。
三、封装成Vue组件
1、文件结构
2、index.vue
<template><div :class="{ fullscreen: fullscreen }" class="tinymce-container" :style="{ width: containerWidth }"><textarea :id="tinymceId" class="tinymce-textarea" /></div>
</template><script>
/*** docs:* https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html#tinymce*/
import plugins from './plugins'
import toolbar from './toolbar'
import load from './dynamicLoadScript'// why use this cdn, detail see https://github.com/PanJiaChen/tinymce-all-in-one
// http://cdn.jsdelivr.net无法访问了,将cdn.jsdelivr.net域名替换为fastly.jsdelivr.net或者gcore.jsdelivr.net
// const tinymceCDN = 'https://cdn.jsdelivr.net/npm/tinymce-all-in-one@4.9.3/tinymce.min.js'
const tinymceCDN = 'https://fastly.jsdelivr.net/npm/tinymce-all-in-one@4.9.3/tinymce.min.js'export default {name: 'Tinymce',props: {id: {type: String,default: function () {return 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')}},value: {type: String,default: ''},toolbar: {type: Array,required: false,default() {return []}},menubar: {type: String,default: 'file edit insert view format table'},height: {type: [Number, String],required: false,default: 360},width: {type: [Number, String],required: false,default: 'auto'}},data() {return {hasChange: false,hasInit: false,tinymceId: this.id,fullscreen: false,languageTypeList: {en: 'en',zh: 'zh_CN',es: 'es_MX',ja: 'ja'}}},computed: {language() {return this.languageTypeList['zh']},containerWidth() {const width = this.widthif (/^[\d]+(\.[\d]+)?$/.test(width)) {// matches `100`, `'100'`return `${width}px`}return width}},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.init()},activated() {if (window.tinymce) {this.initTinymce()}},deactivated() {this.destroyTinymce()},destroyed() {this.destroyTinymce()},methods: {init() {// dynamic load tinymce from cdnload(tinymceCDN, (err) => {if (err) {this.$message.error(err.message)return}this.initTinymce()})},initTinymce() {const _this = thiswindow.tinymce.init({language: this.language,selector: `#${this.tinymceId}`,height: this.height,body_class: 'panel-body',branding: false,object_resizing: false,toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar,menubar: this.menubar,plugins: plugins,toolbar_drawer: true,end_container_on_empty_block: true,powerpaste_word_import: 'clean',paste_data_images: true, //允许粘贴base64图片paste_enable_default_filters: false, //word文本设置code_dialog_height: 450,code_dialog_width: 1000,advlist_bullet_styles: 'default,circle,disc,square',//advlist_number_styles: 'default',imagetools_cors_hosts: ['www.tinymce.com', 'codepen.io'],default_link_target: '_blank',link_title: true,fontsize_formats: '12px 14px 16px 18px 24px 36px 48px 56px 72px',font_formats:'微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun,serif;仿宋体=FangSong,serif;黑体=SimHei,sans-serif;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;',nonbreaking_force_tab: true, // inserting nonbreaking space need Nonbreaking Space Pluginstatusbar: false,init_instance_callback: (editor) => {console.log('init_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})},// it will try to keep these URLs intact// https://www.tiny.cloud/docs-3x/reference/configuration/Configuration3x@convert_urls/// https://stackoverflow.com/questions/5196205/disable-tinymce-absolute-to-relative-url-conversionsconvert_urls: false,// 整合七牛上传// images_dataimg_filter(img) {// setTimeout(() => {// const $image = $(img);// $image.removeAttr('width');// $image.removeAttr('height');// if ($image[0].height && $image[0].width) {// $image.attr('data-wscntype', 'image');// $image.attr('data-wscnh', $image[0].height);// $image.attr('data-wscnw', $image[0].width);// $image.addClass('wscnph');// }// }, 0);// return img// },images_upload_handler(blobInfo, success, failure, progress) {// progress(0);// const token = _this.$store.getters.token;// getToken(token).then(response => {// const url = response.data.qiniu_url;// const formData = new FormData();// formData.append('token', response.data.qiniu_token);// formData.append('key', response.data.qiniu_key);// formData.append('file', blobInfo.blob(), url);// upload(formData).then(() => {// success(url);// progress(100);// })// }).catch(err => {// failure('出现未知问题,刷新页面,或者联系程序员')// console.log(err);// });const img = `data:${blobInfo.blob().type};base64,${blobInfo.base64()}`success(img)}})},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) {arr.forEach((v) => window.tinymce.get(this.tinymceId).insertContent(`<img class="wscnph" src="${v.url}" >`))}}
}
</script><style lang="less" scoped>
.tinymce-container {position: relative;line-height: normal;/deep/ * {border-color: #efefef;white-space: pre-wrap;}
}.tinymce-container {::v-deep {.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;
}
</style>
3、dynamicLoadScript.js
//dynamicLoadScript.js 动态导入tinymce.js脚本
let callbacks = []function loadedTinymce() {// to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2144// check is successfully downloaded scriptreturn window.tinymce
}const dynamicLoadScript = (src, callback) => {const existingScript = document.getElementById(src)const cb = callback || function () {}if (!existingScript) {const script = document.createElement('script')script.src = src // src url for the third-party library being loaded.script.id = srcdocument.body.appendChild(script)callbacks.push(cb)const onEnd = 'onload' in script ? stdOnEnd : ieOnEndonEnd(script)}if (existingScript && cb) {if (loadedTinymce()) {cb(null, existingScript)} else {callbacks.push(cb)}}function stdOnEnd(script) {script.onload = function () {// this.onload = null here is necessary// because even IE9 works not like othersthis.onerror = this.onload = nullfor (const cb of callbacks) {cb(null, script)}callbacks = null}script.onerror = function () {this.onerror = this.onload = nullcb(new Error('Failed to load ' + src), script)}}function ieOnEnd(script) {script.onreadystatechange = function () {if (this.readyState !== 'complete' && this.readyState !== 'loaded') returnthis.onreadystatechange = nullfor (const cb of callbacks) {cb(null, script) // there is no way to catch loading errors in IE8}callbacks = null}}
}export default dynamicLoadScript
4、plugin.js
// Any plugins you want to use has to be imported
// Detail plugins list see https://www.tinymce.com/docs/plugins/
// Custom builds see https://www.tinymce.com/download/custom-builds/const 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 visualblocks visualchars wordcount']export default plugins
5、toolbar.js
// Here is a list of the toolbar
// Detail list see https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrolsconst 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','formatselect fontselect fontsizeselect'
]export default toolbar
四、使用Tinymce组件
<template><TinyMceref="tiny"v-model="mdlValue.fullText":toolbar="toolbar"height="400px":menubar="''"></TinyMce>
</template>
<script>
import TinyMce from '../Tinymce/index'
export default {components:{TinyMce },data(){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 insertdatetime emoticons forecolor backcolor','formatselect fontselect fontsizeselect'],}
}
</script>
五、业务逻辑实现
1、添加页面只读模式,解决方案(readonly: true
):
通过查文档可以知道 readonly: true
可以配置Tinymce是否只读,然后把他封装到我们的组件里
- a、在组件中添加props
- b、在组件初始化的时候添加该配置
- c、使用组件时传参
2、数据处理:传数据给后端需要进行base64加密,但是会把标签尖括号变成中文,导致回显时错误,解决方案:
- a、保存时转码之后再加密:
this.fullText = Base64.encode(this.fullText.replace(/</g, '<').replace(/>/g,'>'))
- b、回显时解密再转码
this.fullText = Base64.decode(data.fullText).replace(/</g, '<').replace(/>/g, '>'))
3、打开页面时会出现Tinymce还未实例化的情况,页面展示空白,解决方案:
-
a、给Tinymce组件绑定**
key
** 值
-
b、在使用
Tinymce
组件的页面的mouted
去实例化