目录
Vue項目集成Tinymce
组件功能支持情况
- 获取内容
- 设置内容
- 插入内容
- 光标放最后
- 销毁编辑器
- 内容双向绑定
- 自定义图片上传
- 文章末尾有Tinymce源码链接,推荐直接下载本地部署使用(npm方法使用需要秘钥,而且限制每天的打开次数),下载后配合示例代码嘎嘎好用,已支持简体中文)
更完整的案例见:https://blog.csdn.net/m0_62332650/article/details/139579289?spm=1001.2014.3001.5501
vue组件方式使用示例
属性介绍
- content:编辑器内容;可利用sync指令实现双向绑定
- show:控制编辑器显示隐藏
<Tinymce
ref="Tinymce"
:content.sync="content"
:show="show"
/>
组件完整代码
<template>
<textarea
:id="tinymceID"
:class="hasTinymceInstance ? 'show' : 'hide'"
></textarea>
</template>
<script>
import { getFpToekn } from '@/api/fp'
import { fpUrl } from '@/config'
import axios from 'axios'
export default {
props: {
content: {
type: null,
default: '',
},
show: {
type: Boolean,
default: () => false,
},
},
data() {
return {
tinymceID: 'tinymceEditor',
hasTinymceInstance: undefined, // 标记是否存在编辑器实例
}
},
watch: {
show: {
handler(newData, oldData) {
// console.log('show', 'newData:', newData, ',', 'oldData:', oldData)
// console.log('show content', this.content)
// console.log('this.hasTinymceInstance', this.hasTinymceInstance)
switch (newData) {
case true:
this.showTinymce().then(() => {
this.setContent(this.content || '')
})
break
case false:
this.hideTinymce()
break
default:
break
}
},
immediate: true,
},
content(newData, oldData) {
// console.log('content', 'newData:', newData, ',', 'oldData:', oldData)
// console.log('this.hasTinymceInstance', this.hasTinymceInstance)
if (this.hasTinymceInstance) {
this.showTinymce().then(() => {
this.setContent(newData || '')
})
}
},
},
mounted() {},
beforeDestroy() {
console.log('beforeDestroy')
if (this.hasTinymceInstance) {
this.destroyTinymce()
}
},
methods: {
// 显示
async showTinymce() {
return new Promise((resolve) => {
if (this.hasTinymceInstance) {
window.tinyMCE.editors[this.tinymceID].show()
resolve()
} else {
this.initTinymceEditor(resolve)
}
})
},
// 隐藏
hideTinymce() {
this.hasTinymceInstance && window.tinyMCE.editors[this.tinymceID].hide()
},
// 创建
initTinymceEditor(resolve) {
const init = () => {
window.tinymce.init({
selector: `#${this.tinymceID}`,
language: 'zh_CN',
// 注册插件
plugins:
' preview searchreplace autolink directionality visualblocks visualchars fullscreen image link template code codesample table charmap hr pagebreak nonbreaking anchor insertdatetime advlist lists wordcount imagetools textpattern help emoticons autosave bdmap indent2em autoresize formatpainter axupimgs',
// 工具栏配置。| 为分组,单行超出会折叠;不加|不会折叠,超出自动换行
toolbar:
'code undo redo restoredraft | cut copy paste pastetext | forecolor backcolor bold italic underline strikethrough link anchor | alignleft aligncenter alignright alignjustify outdent indent | \
styleselect formatselect fontselect fontsizeselect | bullist numlist | blockquote subscript superscript removeformat | \
table image charmap emoticons hr pagebreak insertdatetime preview | fullscreen | bdmap indent2em lineheight formatpainter axupimgs',
/*content_css: [ //可设置编辑区内容展示的css,谨慎使用
'/static/reset.css',
'/static/ax.css',
'/static/css.css',
],*/
// link_list: [
// { title: '预置链接1', value: 'http://www.tinymce.com' },
// { title: '预置链接2', value: 'http://tinymce.ax-z.cn' }
// ],
// image_list: [
// { title: '预置图片1', value: 'https://www.tiny.cloud/images/glyph-tinymce@2x.png' },
// { title: '预置图片2', value: 'https://www.baidu.com/img/bd_logo1.png' }
// ],
// image_class_list: [
// { title: 'None', value: '' },
// { title: 'Some class', value: 'class-name' }
// ],
toolbar_sticky: true,
toolbar_drawer: 'floating',
min_height: window.screen.height - 380,
max_height: window.screen.height - 380,
fontsize_formats:
'12px 14px 15px 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;Times New Roman=times new roman,times,serif',
importcss_append: true,
// images_upload_url: '/demo/upimg.php', // 指定一个接受上传文件的后端处理程序地址
// 自定义图片上传逻辑
images_upload_handler: async (blobInfo, succFun, failFun) => {
try {
let file = blobInfo.blob()
let formData = new FormData()
const res = await getFpToekn({
mimeLimit: ['image/*'],
fsizeLimit: 10 * 1024 * 1024,
})
formData.append('Authorization', res.data['token'])
formData.append('file', file)
formData.append('fpfile', file)
const response = await axios.post(fpUrl, formData, {
withCredentials: false,
})
succFun(response.data.url)
} catch (e) {
console.error(e)
failFun('图片上传失败,请重试')
}
},
// file_picker_types: 'file image media', // 指定允许上传的类型
//自定义文件选择器的回调内容(不配置此配置时不会显示上传按钮)
// file_picker_callback: function (callback, value, meta) {
// console.log('file_picker_callback', callback, '---', value, '---', meta);
// if (meta.filetype === 'file') {
// callback('https://www.baidu.com/img/bd_logo1.png', { text: 'My text' });
// }
// if (meta.filetype === 'image') {
// callback('https://www.baidu.com/img/bd_logo1.png', { alt: 'My alt text' });
// }
// if (meta.filetype === 'media') {
// callback('movie.mp4', { source2: 'alt.ogg', poster: 'https://www.baidu.com/img/bd_logo1.png' });
// }
// },
autosave_ask_before_unload: true, // 当关闭或跳转URL时,弹出提示框提醒用户仍未保存变更内容。默认开启提示。
autosave_interval: '30s', // 自动存稿的时间间隔
autosave_restore_when_empty: false, // 当编辑器初始化时内容区为空时,Tinymce是否应自动还原存储在本地存储中的草稿。
statusbar: true, // 隐藏状态栏 默认true显示,false隐藏
setup: (editor) => {
console.log('ID为: ' + editor.id + ' 的编辑器即将初始化.')
editor.on('blur', () => {
// console.log('blur', editor.getContent())
this.$emit('update:content', editor.getContent())
})
},
init_instance_callback: (editor) => {
console.log('ID为: ' + editor.id + ' 的编辑器已初始化完成.')
this.hasTinymceInstance = true
resolve()
},
})
}
if (!window.tinymce) {
const script = document.createElement('script')
script.src = './static/tinymce/js/tinymce/tinymce.min.js'
script.onload = init
document.body.appendChild(script)
} else {
init()
}
},
// 获取内容
getContent() {
const cnt = window.tinyMCE.editors[this.tinymceID].getContent()
console.log(cnt)
},
// 设置内容
setContent(cnt) {
if (this.hasTinymceInstance) {
// console.log('setContent', cnt, window.tinyMCE.editors[this.tinymceID])
window.tinyMCE.editors[this.tinymceID].show() // 需要先显示编辑器才能设置内容
window.tinyMCE.editors[this.tinymceID].setContent(cnt)
// this.goEnd() // 执行setContent后,光标默认在最左侧,手动设置光标放最后
}
},
// 插入内容
insertContent(cnt) {
this.hasTinymceInstance &&
window.tinyMCE.editors[this.tinymceID].insertContent(cnt)
console.log(cnt)
},
// 光标放最后
goEnd() {
const editor = window.tinyMCE.editors[this.tinymceID]
editor.execCommand('selectAll')
editor.selection.getRng().collapse(false)
editor.focus()
},
// 销毁编辑器
destroyTinymce() {
if (this.hasTinymceInstance) {
console.log('销毁编辑器')
window.tinyMCE.editors[this.tinymceID].off()
window.tinyMCE.editors[this.tinymceID].destroy()
this.hasTinymceInstance = false
}
},
},
}
</script>
<style scoped lang="scss">
.show {
visibility: visible;
width: 1000px;
height: 400px;
}
.hide {
visibility: hidden;
width: 1000px;
height: 400px;
}
</style>
实现[选择快捷工具栏]和[插入快捷工具栏]
在编辑器内容区,光标插入(回车)或选择时,在光标位置出现的快捷工具栏。
可使用任何在工具栏(toolbar)中可用的项目。
使用该选项必须先启用quickbars插件。
tinymce.init({
selector: '#textarea1',
plugins: 'quickbars',
quickbars_insert_toolbar: 'quickimage quicktable',
quickbars_selection_toolbar: 'bold italic | quicklink h2 h3 blockquote',
});
实现格式刷功能
插件源码地址:https://gitee.com/wgmgitee/tinymce5/tree/master/external_plugins/formatpainter
配置:
把文件放到下图所示的目录里:
Tinymce初始化时会自动加载插件。
实现粘贴时清除格式
tinymce.init({
// 其他配置项...
plugins: "paste",
paste_as_text: true
});
实现粘贴时清除格式和标签
tinymce.init({
// 其他配置项...
plugins: "paste",
paste_as_text: true, // 清除格式
paste_preprocess: function (plugin, args) {
args.content = `<div>${args.content.replace(
/<[^>]+>/g,
''
)}</div>` // 移除所有 HTML 标签
},
});
实现输入文本时默认使用div标签
TinyMCE 默认情况下会将输入的文本包装在p标签中,这是因为它的默认配置是使用p作为块级元素。要使 TinyMCE 输入文本时默认使用div标签,可以通过以下方法实现:
tinymce.init({
// 其他配置项...
forced_root_block: 'div'
});
在菜单栏自定义一个单级菜单,点击直接执行“清除格式”
tinymce.init({
selector: 'textarea',
menubar: 'ycustommenu',
setup: function(editor) {
editor.addMenu('mycustommenu', {
title: '清除格式',
onclick: function() {
editor.execCommand('removeFormat');
}
});
}
});
在工具栏自定义下拉选项功能
tinymce.init({
selector: 'textarea',
toolbar: 'customStyles', // 将菜单项添加到工具栏上显示
setup: (editor) => {
// 自定义菜单项
editor.ui.registry.addMenuButton('customStyles', {
text: '预设样式',
fetch: function (callback) {
const items = [
{
type: 'menuitem',
text: '段落标题',
onAction: function () {
editor.selection.setContent(
'<p style="margin:0; padding:0;color: #1B1A1A; line-height: 2; font-size: 16px; font-weight: 600">' +
editor.selection.getContent({ format: 'text' }) +
'</p>'
)
},
},
{
type: 'menuitem',
text: '正文内容',
onAction: function () {
editor.selection.setContent(
'<p style="margin:0; padding:0;color: #696B70; line-height: 1.5; font-size: 15px; font-weight: 400">' +
editor.selection.getContent({ format: 'text' }) +
'</p>'
)
},
},
{
type: 'menuitem',
text: '强调',
onAction: function () {
editor.selection.setContent(
'<p style="margin:0; padding:0;color: #FF9500; line-height: 1.5; font-size: 15px; font-weight: 400">' +
editor.selection.getContent({ format: 'text' }) +
'</p>'
)
},
},
{
type: 'menuitem',
text: '超强调',
onAction: function () {
editor.selection.setContent(
'<p style="margin:0; padding:0;color: #EB4B4B; line-height: 1.5; font-size: 15px; font-weight: 400">' +
editor.selection.getContent({ format: 'text' }) +
'</p>'
)
},
},
{
type: 'menuitem',
text: '链接',
onAction: function () {
editor.selection.setContent(
'<a style="color: #0075FF; line-height: 1.5; font-size: 15px; font-weight: 400; text-decoration: underline; cursor: pointer">' +
editor.selection.getContent() +
'</a>'
)
},
},
]
callback(items)
},
})
},
})
在工具栏自定义普通按钮功能
tinymce.init({
selector: 'textarea',
toolbar: 'customRemoveFormat', // 将菜单项添加到工具栏上显示
setup: (editor) => {
editor.ui.registry.addButton('customRemoveFormat', {
text: '清除格式',
onAction: () => {
editor.execCommand('removeFormat', false)
},
})
},
})
execCommand 执行命令
可用命令可在此查看:https://www.tiny.cloud/docs/tinymce/latest/editor-command-identifiers/
Tinymce 自定义插件
/*
Note: We have included the plugin in the same JavaScript file as the TinyMCE
instance for display purposes only. Tiny recommends not maintaining the plugin
with the TinyMCE instance and using the `external_plugins` option.
*/
tinymce.PluginManager.add('example', (editor, url) => {
const openDialog = () => editor.windowManager.open({
title: 'Example plugin',
body: {
type: 'panel',
items: [
{
type: 'input',
name: 'title',
label: 'Title'
}
]
},
buttons: [
{
type: 'cancel',
text: 'Close'
},
{
type: 'submit',
text: 'Save',
buttonType: 'primary'
}
],
onSubmit: (api) => {
const data = api.getData();
/* Insert content when the window form is submitted */
editor.insertContent('Title: ' + data.title);
api.close();
}
});
/* Add a button that opens a window */
editor.ui.registry.addButton('example', {
text: 'My button',
onAction: () => {
/* Open window */
openDialog();
}
});
/* Adds a menu item, which can then be included in any menu via the menu/menubar configuration */
editor.ui.registry.addMenuItem('example', {
text: 'Example plugin',
onAction: () => {
/* Open window */
openDialog();
}
});
/* Return the metadata for the help plugin */
return {
getMetadata: () => ({
name: 'Example plugin',
url: 'http://exampleplugindocsurl.com'
})
};
});
/*
The following is an example of how to use the new plugin and the new
toolbar button.
*/
tinymce.init({
selector: 'textarea#custom-plugin',
plugins: 'example',
toolbar: 'example'
});
Tinymce 注册并应用自定义格式
注意:不能在setup里注册,要在init_instance_callback里注册
tinymce.init({
init_instance_callback: function (editor) {
editor.formatter.register('mycustomformat', {
block: 'h1', // 指定块级元素的类型,例如 "div"。block和inline二选一,不能同时配置
inline: 'span', // 指定内联元素的类型,例如 "span"。
styles: { color: '#ff0000', margin: 0, padding: 0 }, // 指定要应用到格式化文本的样式属性和值的对象。
classes: 'my-custom-format', // 指定要应用到格式化文本的 CSS 类。
attributes: { // 指定要应用到格式化文本的 HTML 属性的对象
'data-custom': 'example',
'title': 'Custom Format'
}
});
}
});
使用
editor.formatter.apply('mycustomformat');
Tinymce 自定义样式的5种方法
- 注册自定义格式,参考上面
- formats
该选项可用于覆盖编辑器默认格式,添加自定义格式
配置:
block_formats: '标题1=h1;标题2=h2;标题3=h3;标题4=h4;标题5=h5;标题6=h6;段落=p;Div=div;自定义=test', // formatselect 的配置
formats:{
h1: { block: 'h1', styles: { margin: '0px', padding: '0px', textAlign: 'justify' }},
h2: { block: 'h2', styles: { margin: '0px', padding: '0px', textAlign: 'justify' }},
h3: { block: 'h3', styles: { margin: '0px', padding: '0px', textAlign: 'justify' }},
h4: { block: 'h4', styles: { margin: '0px', padding: '0px', textAlign: 'justify' }},
h5: { block: 'h5', styles: { margin: '0px', padding: '0px', textAlign: 'justify' }},
h6: { block: 'h6', styles: { margin: '0px', padding: '0px', textAlign: 'justify' }},
p: { block: 'p', styles: { margin: '0px', padding: '0px', textAlign: 'justify' }},
test: { inline: 'span', classes: 'class1' }
}, // 该选项可用于覆盖编辑器默认格式,添加自定义格式
效果:
源码:
- setContent
editor.selection.setContent(
'<p style="margin:0; padding:0;">' +
'<span style="color: #1B1A1A; line-height: 2; font-size: 16px; font-weight: 600">' +
editor.selection.getContent({ format: 'text' }) +
'</span>' +
'</p>'
)
- execCommand命令
- setAttribute
const selectedNode = editor.selection.getNode()
selectedNode.setAttribute(
'style',
'color: #0075FF; line-height: 1.5; font-size: 15px; font-weight: 400; text-decoration: underline; cursor: pointer'
)
editor.formatter 对象
文档:https://www.tiny.cloud/docs/tinymce/latest/apis/tinymce.formatter/
TinyMCE 5 中的 editor.formatter 对象具有以下方法:
- match(name: string): boolean - 检查指定的格式是否已应用于当前选区的内容。
例:执行过了editor.execCommand('bold')
执行editor.formatter.match('bold')会返回true
- toggle(name: string, state?: boolean): void - 切换指定格式的状态。如果 state 参数为 true,则应用格式;如果为 false,则移除格式。
- apply(name: string, vars?: Record<string, any>): void - 应用指定的格式,并可以传递额外的参数。
- remove(name: string, vars?: Record<string, any>): void - 移除指定的格式,并可以传递额外的参数。
开启插件支持粘贴上传图片
配置 paste_data_images: true,images_upload_handler自定义上传逻辑
Tinymce 自定义粘贴图片上传
tinymce.init({
setup: (editor) => {
editor.on('paste', async (event) => {
try {
const clipboardItems = event.clipboardData.items
for (const clipboardItem of clipboardItems) {
const kind = clipboardItem.kind
const type = clipboardItem.type
if (kind === 'file' && type.startsWith('image/')) {
const file = clipboardItem.getAsFile()
if (file) {
const formData = new FormData()
formData.append('image', file)
const res = await getFpToekn({
mimeLimit: ['image/*'],
fsizeLimit: 10 * 1024 * 1024,
})
formData.append('Authorization', res.data['token'])
formData.append('file', file)
formData.append('fpfile', file)
this.$emit('update:loading', true)
const response = await axios.post(fpUrl, formData, {
withCredentials: false,
})
const imgSrc = response.data.url
this.insertContent(`<img src="${imgSrc}" alt="" />`)
}
}
}
} catch (error) {
console.error('Clipboard error:', error)
}
})
},
});
自定义粘贴逻辑【支持粘贴纯文本、图片(自动上传)、富文本】
https://blog.csdn.net/m0_62332650/article/details/141197149
源码
https://gitee.com/wgmgitee/tinymce5
常见问题
1,与ElementUI的Dialog组件搭配使用时,会出现工具栏层级样式错乱问题,解决办法:把z-index设置得更大
2. 自定义插件报错
原因:使用了箭头函数
解决:用function关键字声明函数代替箭头函数
- 使用了自定义样式再使用格式刷会报错
原码:
editor.formatter.register('custom-h2', {
block: 'h2',
styles: {
margin: 0,
padding: 0,
color: '#1B1A1A',
lineHeight: 2,
fontSize: 16,
fontWeight: 600,
},
})
原因:没有margin、padding会报错;margin、padding单位为数字也报错
解决:加上margin、padding,单位为字符串
editor.formatter.register('custom-h2', {
block: 'h2',
styles: {
margin: '0',
padding: '0',
color: '#1B1A1A',
lineHeight: 2,
fontSize: 16,
fontWeight: 600,
},
})
- 格式刷不能刷自定义样式
原因:自定义样式定义在块级标签里,格式刷默认只能刷行内样式
相关资料
中文文档:http://tinymce.ax-z.cn/
官方文档:https://www.tiny.cloud/