vue2集成tinymce富文本编辑器,添加本地资源解决加载慢问题
组件 | 源码 | 版本 | license |
---|---|---|---|
tinymce | 地址 | 4.9.3 | MIT |
示例项目一
源码地址:gitee
1.封装组件
<template> <div :class="{fullscreen:fullscreen}" class="tinymce-container" :style="{width:containerWidth}"> <textarea :id="tinymceId" class="tinymce-textarea" /> <div class="editor-custom-btn-container"> <!-- <editorImage color="#1890ff" class="editor-upload-btn" @successCBK="imageSuccessCBK" /> --> </div> </div> </template> <script> /** * tinymce版本:^4.9.3 * docs: * https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html#tinymce */ import editorImage from './components/EditorImage' import plugins from './plugins' import toolbar from './toolbar' import load from './dynamicLoadScript' import axios from 'axios' import { getToken } from '@/utils/auth' // why use this cdn, detail see https://github.com/PanJiaChen/tinymce-all-in-one // const tinymceCDN = 'https://cdn.jsdelivr.net/npm/tinymce-all-in-one@4.9.3/tinymce.min.js' const tinymceCDN = window.location.origin + '/Tinymce/tinymce.min.js' export default { name: 'Tinymce', components: { editorImage }, 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' }, disabled: { type: [Boolean], required: false, default: false } }, data() { return { hasChange: false, hasInit: false, tinymceId: this.id, fullscreen: false, languageTypeList: { 'en': 'en', 'zh': 'zh_CN', 'es': 'es_MX', 'ja': 'ja' } } }, computed: { containerWidth() { const width = this.width if (/^[\d]+(\.[\d]+)?$/.test(width)) { // matches `100`, `'100'` return `${width}px` } return width } }, watch: { value(val) { if (val !== null) { if (!this.hasChange && this.hasInit) { this.$nextTick(() => window.tinymce.get(this.tinymceId).setContent(val || '')) } } } }, mounted() { this.init() }, activated() { if (window.tinymce) { this.initTinymce() } }, deactivated() { this.destroyTinymce() }, destroyed() { this.destroyTinymce() }, methods: { init() { // dynamic load tinymce from cdn load(tinymceCDN, (err) => { if (err) { this.$message.error(err.message) return } // window.tinymce.baseURL = window.location.origin + '/tinymce' this.initTinymce() }) }, initTinymce() { const _this = this window.tinymce.init({ readonly: this.disabled, branding: false, // 去掉商标 selector: `#${this.tinymceId}`, language: this.languageTypeList['zh'], images_upload_handler: (blobInfo, success, failure) => { if(blobInfo.blob().size/1024/1024>2){ failure("上传失败,图片大小请控制在 2M 以内") }else{ let params=new FormData() params.append('file',blobInfo.blob()) let config={ headers:{ "Content-Type":"multipart/form-data", 'Authorization': "Bearer " + getToken() } } axios.post(process.env.VUE_APP_BASE_API + `/common/upload`,params,config).then(res=>{ console.log(res) success(process.env.VUE_APP_BASE_API + res.data.fileName) }).catch(()=>{ failure("上传出错,服务器开小差了呢") }) } }, // toolbar: 'fontselect', font_formats: 'Andale Mono=andale mono,times; Arial=arial,helvetica,sans-serif; Arial Black=arial black,avant garde; Book Antiqua=book antiqua,palatino; Comic Sans MS=comic sans ms,sans-serif; Courier New=courier new,courier; Georgia=georgia,palatino; Helvetica=helvetica; Impact=impact,chicago; Symbol=symbol; Tahoma=tahoma,arial,helvetica,sans-serif; Terminal=terminal,monaco; Times New Roman=times new roman,times; Trebuchet MS=trebuchet ms,geneva; Verdana=verdana,geneva; Webdings=webdings; Wingdings=wingdings,zapf dingbats', height: this.height, body_class: 'panel-body ', object_resizing: false, toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar, menubar: this.menubar, fontsize_formats: '8pt 10pt 12pt 14pt 16pt 18pt 24pt 36pt 48pt', // eslint-disable-next-line no-dupe-keys font_formats: "微软雅黑='微软雅黑';宋体='宋体';黑体='黑体';仿宋='仿宋';楷体='楷体';隶书='隶书';幼圆='幼圆';Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings", lineheight_formats: '1 1.1 1.2 1.3 1.4 1.5 2', 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 Plugin init_instance_callback: editor => { if (_this.value) { editor.setContent(_this.value) } _this.hasInit = true editor.on('NodeChange Change KeyUp SetContent', () => { this.hasChange = true this.$emit('input', editor.getContent()) }) }, setup(editor) { editor.on('FullscreenStateChanged', (e) => { _this.fullscreen = e.state }) }, templates: [ {title: 'Some title 1', description: 'Some desc 1', content: '这是模板内容。'} ], // 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-conversions convert_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('出现未知问题,刷新页面,或者联系程序员') // }); // }, }) }, 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="scss" scoped> .tinymce-container { position: relative; line-height: normal; } .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; } .v-modal{ z-index: 0; } </style>
2.样式截图
示例项目二
项目地址:gitee
1.组件结构
2.添加静态文件
ruo-yi-vue-docHub / ruoyi-ui / public / Tinymce
3.组件使用方式
<Tinymce :height='500' v-model='form.content'></Tinymce> import Tinymce from '@/components/Tinymce' export default { components: { Tinymce, }, }
4.样式截图
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术