富文本编辑器组件
<template>
<div ref="tinymceBox" class="tinymce-box">
<Editor id="myEditor" v-model="contentValue" :init="init" :disabled="disabled" @blur="inputBlur" @click="onClick" />
</div>
</template>
<script>
const api = import.meta.env.VITE_APP_API_FILE
import { post } from '@/services/http'
// 引入tinymce编辑器
import Editor from '@tinymce/tinymce-vue'
// 引入node_modules里的tinymce相关文件文件
import tinymce from 'tinymce/tinymce' // tinymce默认hidden,不引入则不显示编辑器
import 'tinymce/themes/silver' // 编辑器主题,不引入则报错
import 'tinymce/icons/default' // 引入编辑器图标icon,不引入则不显示对应图标
// 引入编辑器插件(基本免费插件都在这儿了)
// import 'tinymce/plugins' //高级列表
// import 'tinymce/plugins/anchor' //锚点
// import 'tinymce/plugins/autolink' //自动链接
// import 'tinymce/plugins/autoresize' //编辑器高度自适应,注:plugins里引入此插件时,Init里设置的height将失效
// import 'tinymce/plugins/autosave' //自动存稿
// import 'tinymce/plugins/charmap' //特殊字符
import 'tinymce/plugins/code' // 编辑源码
import 'tinymce/plugins/codesample' // 代码示例
import 'tinymce/plugins/directionality' // 文字方向
// import 'tinymce/plugins/emoticons' //表情
// import 'tinymce/plugins/fullpage' //文档属性
import 'tinymce/plugins/fullscreen' // 全屏
// import 'tinymce/plugins/help' //帮助
import 'tinymce/plugins/hr' // 水平分割线
import 'tinymce/plugins/image' // 插入编辑图片
// import 'tinymce/plugins/importcss' //引入css
// import 'tinymce/plugins/insertdatetime' //插入日期时间
import 'tinymce/plugins/link' // 超链接
import 'tinymce/plugins/lists' // 列表插件
import 'tinymce/plugins/media' // 插入编辑媒体
// import 'tinymce/plugins/nonbreaking' //插入不间断空格
// import 'tinymce/plugins/pagebreak' //插入分页符
// import 'tinymce/plugins/paste' //粘贴插件
import 'tinymce/plugins/preview' // 预览
import 'tinymce/plugins/print' // 打印
// import 'tinymce/plugins/quickbars' //快速工具栏
// import 'tinymce/plugins/save' //保存
// import 'tinymce/plugins/searchreplace' //查找替换
// import 'tinymce/plugins/spellchecker' //拼写检查,暂未加入汉化,不建议使用
// import 'tinymce/plugins/tabfocus' //切入切出,按tab键切出编辑器,切入页面其他输入框中
import 'tinymce/plugins/table' // 表格
// import 'tinymce/plugins/template' //内容模板
// import 'tinymce/plugins/textcolor' //文字颜色
// import 'tinymce/plugins/textpattern' //快速排版
// import 'tinymce/plugins/toc' //目录生成器
import 'tinymce/plugins/visualblocks' // 显示元素范围
// import 'tinymce/plugins/visualchars' //显示不可见字符
import 'tinymce/plugins/wordcount' // 字数统计
export default {
name: 'TEditor',
components: {
Editor
},
props: {
value: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
},
plugins: {
type: [String, Array],
// powerpaste
default:
'print code codesample preview directionality visualblocks fullscreen image link media table hr lists wordcount indent2em wordlimit'
},
toolbar: {
type: [String, Array],
// undo redo media visualblocks fontselect 隐藏工具
default:
'fullscreen code codesample restoredraft forecolor backcolor bold italic underline strikethrough link | formatselect fontsizeselect lineheight | alignleft aligncenter alignright alignjustify | indent2em outdent indent | bullist numlist | blockquote removeformat | table image charmap hr print preview | '
},
fileType: {
type: String,
default: ''
},
supportBase64: {
type: Boolean,
default: true
},
maxLimit: {
type: Number,
default: 500
},
placeholder: {
type: String,
default: ''
}
},
data() {
return {
dom: null,
init: {
language_url: '/tinymce/langs/zh_CN.js', // 引入语言包文件
language: 'zh_CN', // 语言类型
skin_url: '/tinymce/skins/ui/oxide', // 皮肤:浅色
// skin_url: '/tinymce/skins/ui/oxide-dark',//皮肤:暗色
plugins: this.plugins, // 插件配置
toolbar: this.toolbar, // 工具栏配置,设为false则隐藏
menubar: false, // 菜单栏配置,设为false则隐藏
external_plugins: {
// powerpaste: `/tinymce/powerpaste/plugin.min.js`,
indent2em: `/tinymce/indent2em/plugin.min.js`,
wordlimit: `/tinymce/wordlimit/plugin.min.js`
},
fontsize_formats: '12px 14px 16px 18px 20px 22px 24px 28px 32px 36px 48px 56px 72px', // 字体大小
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: '0.5 0.8 1 1.2 1.5 1.75 2 2.5 3 4 5', // 行高配置,也可配置成"12px 14px 16px 20px"这种形式
// height: 400, //注:引入autoresize插件时,此属性失效
min_height: 300,
max_height: 500,
toolbar_mode: 'sliding',
placeholder: this.placeholder || '在这里输入描述',
branding: false, // tiny技术支持信息是否显示
resize: true, // 编辑器宽高是否可变,false-否,true-高可变,'both'-宽高均可,注意引号
// statusbar: false, //最下方的元素路径和字数统计那一栏是否显示
elementpath: false, // 元素路径是否显示
content_style: 'img {max-width:100%;} .mce-item-anchor {display:none !important}', // 直接自定义可编辑区域的css样式
content_css: 'tinymce/skins/content/default/content.css', // 以css文件方式自定义可编辑区域的css样式,css文件需自己创建并引入
// images_upload_url: '/apib/api-upload/uploadimg', //后端处理程序的url,建议直接自定义上传函数image_upload_handler,这个就可以不用了
// images_upload_base_path: '/demo', //相对基本路径--关于图片上传建议查看--http://tinymce.ax-z.cn/general/upload-images.php
wordlimit: {
max: this.maxLimit, // 最多可以输入多少字
spaces: false, // 是否含空格
isInput: false, // 是否在超出后还可以输入
maxMessage: `超出最大输入字符数量,最多允许${this.maxLimit}个字符!`
},
setup(editor) {
// 设置默认字体样式
editor.on('init', function (e) {
this.getBody().style.lineHeight = '2'
this.getBody().style.fontFamily = '微软雅黑'
})
},
urlconverter_callback: (url, node, onSave, name) => {
if (node === 'img' && url.startsWith('blob:')) {
tinymce.activeEditor && tinymce.activeEditor.uploadImages()
}
return url
},
// paste_preprocess: (editor, args) => {
// if (this.disabled) {
// args.content = ''
// }
// },
end_container_on_empty_block: true,
powerpaste_html_import: 'merge',
powerpaste_word_import: 'merge',
powerpaste_allow_local_images: true,
paste_data_images: true, // 图片是否可粘贴
images_upload_handler: (blobInfo, success, failure) => {
let file = null
const fileSize = 3
const fileType = ['jpg', 'png']
if (blobInfo.blob() instanceof Blob) {
file = new File([blobInfo.blob()], blobInfo.blob().name || 'image.png', { type: 'image/png' })
} else {
file = blobInfo.blob()
}
const suffix = file.name.substring(file.name.lastIndexOf('.') + 1).toLocaleLowerCase()
const allowType = fileType.map(v => v.toLocaleLowerCase()).includes(suffix)
const allowSize = file.size / 1024 / 1024 < fileSize
if (!allowType) {
failure('上传失败,仅支持上传JPG、PNG格式的图片')
} else if (!allowSize) {
failure(`上传失败,图片大小请控制在 ${fileSize}M 以内`)
} else {
const params = new FormData()
params.append('file', file)
params.append('type', this.fileType || 'product')
const config = {
headers: {
'Content-Type': 'multipart/form-data',
'Client-Type': 0
}
}
const AuthorizationInfo = localStorage.getItem('AuthorizationInfo')
if (AuthorizationInfo) {
const { tokenHeader, tokenHead, token } = JSON.parse(AuthorizationInfo)
config.headers[tokenHeader] = `${tokenHead} ${token}`
}
post(api, params, config, '/')
.then(res => {
success(res.url) // 上传成功,在成功函数里填入图片路径
})
.catch(() => {
// 是否支持上传图片为base64格式
if (!this.supportBase64) {
const imgReg = /<img.*?(?:>|\/>)/gi // 匹配图片中的img标签
this.contentValue = this.contentValue.replace(imgReg, (match, capture) => (match.indexOf('base64') == -1 ? match : ''))
}
failure('上传出错,服务器开小差了呢')
})
}
}
},
contentValue: this.value
}
},
watch: {
value(newValue) {
this.contentValue = newValue
},
contentValue(newValue) {
this.$emit('input', newValue)
}
},
mounted() {
tinymce.init({})
},
methods: {
// 添加相关的事件,可用的事件参照文档=> https://github.com/tinymce/tinymce-vue => All available events
onClick(e) {
this.$emit('onClick', e, tinymce)
},
// 清空内容
clear() {
this.contentValue = ''
},
// 失去焦点
inputBlur() {
this.$emit('blur')
}
}
}
</script>
<style lang="less" scoped>
.tinymce-box {
min-height: 300px;
}
</style>
项目中引用
注意:要给组件加上id
<TEditor
id="myEditor"
ref="tEditorRef"
fileType="supply/hr/report"
v-model:value="formState.emailContent"
@input="e => (formState.emailContent = e)"
:maxLimit="10000"
placeholder="请输入邮件内容"
/>
// 点击标签
const changeLabel = item => {
const editor = tinymce.get('myEditor')
if (editor) {
editor.focus()
editor.selection.setContent(`<div>【${item}】</div>`)
}
state.labelCount++
if (state.labelCount <= 2) {
formState.emailSubject += `【${item}】`
}
}