<
h1
>
TinyMCE快速开始示例
</
h1
>
<
form
method
=
"post"
>
<
textarea
id
=
"mytextarea"
>
Hello, World!
</
textarea
>
</
form
>
</
body
>
</
html
>
tinymce
初始示例Demo如下:
2. 在Vue中引入TinyMCE
把上边下载好的tinymce源码放在Vue项目的直接使用的静态资源public文件夹下
<template>
<div class="home">
<textarea :id="tinymceId" class="tinymce-textarea" />
</div>
</template>
<script>
export default {
name: "TinyMCE",
data() {
return {
tinymceId: "vue-tinymce-" + +new Date() + ((Math.random() * 1000).toFixed(0) + ""),
src: "./tinymce/js/tinymce/tinymce.min.js"
mounted() {
this.dynamicLoadScript(this.src);
methods: {
dynamicLoadScript(src){
const _this = this;
const script = document.createElement("script");
script.src = src;
script.id = src;
document.body.appendChild(script);
script.onload = function() {
_this.initTinymce()
initTinymce() {
window.tinymce.init({
selector: `#${this.tinymceId}`,
</script>
<style scoped>
.tinymce-textarea {
visibility: hidden;
z-index: -1;
</style>
2.1 使用中文语言包
第一步,在语言包(俗称汉化)下载地址,选择可用的语言包下载到本地。
第二步,将语言包解压,将js文件放入tinymce根目录下的langs文件夹中(如不存在就自己新建一个),最后路径形如:XXX/tinymce/langs/zh_CN.js
第3步 配置TinyMCE实例指定语言。
tinymce.init({
selector: `#${this.tinymceId}`,
language:'zh_CN',
如要使用配置选项“language”指定语言,则必须将语言包放入langs文件夹内。如无法实现,则使用language_url指定语言包存放的URL。
3. tinymce基本配置
3.1 selector配置
selector是TinyMCE的重要配置选项,使用CSS选择器语法来确定页面上哪个元素被TinyMCE替换成编辑器。
tinymce.init({
selector: '#mytextarea',
3.2. plugins插件配置
plugins配置参数用于指定哪些插件被用在当前编辑器实例中。
TinyMCE自带丰富的插件,也可以编写自己的插件,用此选项引入。
启用插件非常简单,只需将插件名作为参数,
多个插件用空格分隔的字符串,也支持使用数组的方式。
以下为此参数的配置示例:
tinymce.init({
selector: `#${this.tinymceId}`,
plugins : 'advlist autolink link image lists preview', //字符串方式
// plugins : ['advlist','autolink','link'], //数组方式
查询全部可用插件的列表,可参考tinymce源码文件夹下的plugins文件夹,其内部包含当前所有可用的插件,文件夹是以插件名命名。
3.3 toolbar 工具栏配置
TinyMCE可配置的toolbar
工具栏及菜单如下:
lineheight(行高 V5.5新增)
newdocument(新文档)
bold(加粗)
italic(斜体)
underline(下划线)
strikethrough(删除线)
alignleft(左对齐)
aligncenter(居中对齐)
alignright(右对齐)
alignjustify(两端对齐)
styleselect(格式设置)
formatselect(段落格式)
fontselect(字体选择)
fontsizeselect(字号选择)
cut(剪切)
copy(复制)
paste(粘贴)
bullist(项目列表UL)
numlist(编号列表OL)
outdent(减少缩进)
indent(增加缩进)
blockquote(引用)
undo(撤销)
redo(重做/重复)
removeformat(清除格式)
subscript(下角标)
superscript(上角标)
3.3.1 使用toolbar
配置工具栏
使用toolbar
来配置工具栏上可用的按钮,多个控件使用空格分隔,使用“|”来创建分组。
tinymce.init({
selector: `#${this.tinymceId}`,
plugins : 'link image',
toolbar: 'bold italic | link image | undo redo',
3.3.2 完全隐藏工具栏
如要完全隐藏工具栏,则将其设为false即可。
tinymce.init({
selector: 'textarea',
toolbar: false,
3.3.3 使用数组来配置多行工具栏。
tinymce.init({
selector: 'textarea',
toolbar: [
'undo redo | bold italic ',
'alignleft alignright',
3.4 菜单栏配置——menubar和menu
与菜单相关的配置选项有两个:menubar和menu。
menubar:顶层主菜单。
menu:每个主菜单的下拉列表中显示的子菜单(其还提供创建自定义标题子菜单的方法)。
3.4.1 menubar菜单下拉列表中可配置的控件:
lineheight(行高 5.5新增)
newdocument(新文档))
undo(撤销))
redo(重做/重复)
visualaid(网格线)
cut(剪切)
copy(复制))
paste(粘贴))
selectall(全选)
bold(加粗))
italic(斜体))
underline(下划线))
strikethrough(删除线))
subscript(下角标))
superscript(上角标))
removeformat(清除格式))
formats(格式)
3.4.2 创建主菜单栏menubar
例1:下方代码将创建只含有文件和编辑的主菜单栏。
注意:每个主菜单栏(menubar)会包含默认的下拉子菜单(menu)。例如:编辑会加载默认的撤销、重做、剪切、复制、粘贴和全选
tinymce.init({
selector: 'textarea',
toolbar: false,
menubar: 'file edit'
3.4.3 自定义子菜单menu
menubar也可以传入自定义子菜单,通过在menu内配置title值可以创建自定义子菜单的名称,items可配置下拉列表的子项目,用空格分隔。
例2:以下会创建一个名为【我的菜单】的菜单项,其下拉列表中仅包含复制和粘贴
tinymce.init({
selector: 'textarea',
toolbar: false,
menubar: 'my1', // 创建主菜单
menu: {
// 配置主菜单的子菜单等信息
my1: {title: '我的菜单', items: 'copy paste formats' }
而让菜单栏消失也很简单,直接menubar: false
3.5 设置编辑器宽高
如参数只提供数字,则默认单位为像素(px),如提供了单位,TinyMCE会以css模式去理解它。单位支持px/%/em/vh/vw
width: 600,
height: 300,
3.6 设置编辑器中可编辑区域内的样式
body_class: "panel-body ",
content_style: ".panel-body{ background-color: #1e1e1e; color: #ccc}"
3.7 隐藏状态栏——Tiny版权链接
状态栏指的是编辑器最底下、左侧显示dom信息、右侧显示Tiny版权链接和调整大小的那一条。
如果不想让它显示,像下面这样设置:
statusbar: false
3.8 隐藏右上角的Tiny更新链接——Upgrade
<style lang="scss" scoped>
.tinymce-textarea {
visibility: hidden;
z-index: -1;
/deep/ .tox .tox-promotion-link {
display: none
</style>
4. TinyMCE 主题(Themes)与皮肤(Skins)
在TinyMCE中,皮肤用于更改编辑器的外观,例如颜色、边距、填充、字体、图标等等。
而主题则负责编辑器的框架构建、如编辑器上下左右,垂直水平、内外部等行为。
二者分别位于themes目录和skins目录中。skins里存放的是css,而themes里存放的是js
"Silver"
是TinyMCE的默认主题。用户可以轻松自定义菜单或工具栏,而无需编辑主题
4.1 TinyMCE使用深色版皮肤——skin: "oxide-dark"
TinyMCE v5 的默认皮肤是“oxide”,它包含浅色版本和深色版本。默认oxide是浅色版。下面的例子将使用深色版皮肤。
tinymce.init({
selector: `#${this.tinymceId}`,
language:'zh_CN',
// 编辑器深色外观,包括工具栏和菜单栏
skin: "oxide-dark",
// content_css: "dark" // 可编辑区域的样式
body_class: "panel-body ",
// 可编辑区域的样式
content_style: ".panel-body{ background-color: #222f3e; color: #fff}"
5. 其它配置说明
width: '100%',
height: '100%',
menubar: false,
branding: false,
statusbar: true,
readonly: false,
resize: false,
autosave_ask_before_unload: true,
autosave_interval: '3s',
autosave_prefix: `editor_${_this.$route.path}`,
autosave_retention: '300m',
contextmenu: 'copy paste cut link',
draggable_modal: true,
placeholder: '开始编写吧',
theme: 'silver',
skin_url: '/tinymce/skins/ui/oxide',
icons: 'custom',
icons_url: '/tinymce/icons/icons.js',
language_url: '/tinymce/langs/zh_CN.js',
language: 'zh_CN',
content_css: `/tinymce/skins/content/default`,
content_style: 'body, p{font-size: 12px}',
fontsize_formats: '12px 14px 16px 18px 20px 22px 24px 26px 36px 48px 56px',
font_formats: "微软雅黑='微软雅黑'; 宋体='宋体'; 黑体='黑体'; 仿宋='仿宋'; 楷体='楷体'; 隶书='隶书'; 幼圆='幼圆'; 方正舒体='方正舒体'; 方正姚体='方正姚体'; 等线='等线'; 华文彩云='华文彩云'; 华文仿宋='华文仿宋'; 华文行楷='华文行楷'; 华文楷体='华文楷体'; 华文隶书='华文隶书'; Andale Mono=andale mono,times; Arial=arial; Arial Black=arial black;avant garde; Book Antiqua=book antiqua;palatino; Comic Sans MS=comic sans ms; Courier New=courier new;courier; Georgia=georgia; Helvetica=helvetica; Impact=impact;chicago; Symbol=symbol; Tahoma=tahoma;arial; sans-serif; Terminal=terminal,monaco; Times New Roman=times new roman,times; Trebuchet MS=trebuchet ms; Verdana=verdana;geneva; Webdings=webdings; Wingdings=wingdings",
toolbar_sticky: true,
toolbar_mode: 'sliding',
plugins: ['autosave help textpattern lineheight'],
toolbar: 'fontselect styleselect fontsizeselect restoredraft undo redo | bold italic underline strikethrough subscript superscript removeformat forecolor backcolor lineheight align outdent indent help',
images_upload_handler: (blobInfo, success, failure) => {
success('http://pic.sc.chinaz.com/files/pic/pic9/202005/apic25209.jpg')
},
image_advtab: true,
paste_data_images: true,
paste_as_text: true,
templates: [{ title: '标题', description: '描述', content: '内容' }],
visual: false,
quickbars_selection_toolbar: 'bold italic underline strikethrough | link h2 h3 h4 blockquote',
quickbars_insert_toolbar: 'quickimage quicktable',
textpattern_patterns: [
{ start: '*', end: '*', format: 'italic' },
{ start: '**', end: '**', format: 'bold' },
{ start: '#', format: 'h1' },
{ start: '##', format: 'h2' },
{ start: '###', format: 'h3' },
{ start: '####', format: 'h4' },
{ start: '#####', format: 'h5' },
{ start: '######', format: 'h6' },
{ start: '1. ', cmd: 'InsertOrderedList' },
{ start: '* ', cmd: 'InsertUnorderedList' },
{ start: '- ', cmd: 'InsertUnorderedList' }
],
init_instance_callback: editor => {
if (_this.value) {
editor.setContent(_this.value)
_this.hasInit = true
editor.on('Input undo redo Change execCommand SetContent', (e) => {
_this.hasChange = true
_this.$emit('change', editor.getContent())
setup: (editor) => {
editor.on('keydown', (e) => {
if (e.keyCode === 9) {
if (e.shiftKey) {
editor.execCommand('Outdent')
} else {
editor.execCommand('Indent')
e.preventDefault()
e.stopPropagation()
editor.ui.registry.addButton('upload', {
text: `<i class="el-icon-upload" style="font-size: 24px"></i>`,
tooltip: '自定义上传',
onAction: () => {
_this.config.show = true
editor.ui.registry.addButton('submit', {
text: `<i class="el-icon-position" style="font-size: 18px"></i>`,
tooltip: '获取内容',
onAction: () => {
console.log(editor.getContent())
editor.ui.registry.addButton('empty', {
text: `<i class="el-icon-close" style="font-size: 18px"></i>`,
tooltip: '清空内容',
onAction: () => {
_this.content = ''
editor.setContent('')
6. tinymce插件介绍
中文文档介绍——tinymce插件
官方英文文档介绍——TinyMCE opensource
7. 封装成Vue组件
<template>
<div :class="{fullscreen:fullscreen}" class="tinymce-container" :style="{width:containerWidth ,height:containerHeight}">
<textarea :id="tinymceId" class="tinymce-textarea" />
<div v-if="isUploadImage" class="editor-custom-btn-container">
<editorImage :up-url="upUrl" :up-src="upSrc" color="#1890ff" class="editor-upload-btn" @successCBK="imageSuccessCBK" />
</div>
</div>
</template>
<script>
* 文档地址:
* https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html#tinymce
import editorImage from "./components/EditorImage";
import load from "./dynamicLoadScript";
const tinymceCDN = "/api/tinymce/js/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,
default() {
return [
"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 lineheight"
plugins: {
type: Array,
default() {
return [
"autoresize 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 lineheight"
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"
upUrl: {
type: String,
default: process.env.VUE_APP_BASE_API_YUNWX
upSrc: {
type: String,
default: "sys/moduleinfo/iconUpload"
isUploadImage: {
type: Boolean,
default: true
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[this.$store.getters.language];
containerWidth() {
const width = this.width;
if (/^[\d]+(\.[\d]+)?$/.test(width)) {
return `${width}px`;
return width;
containerHeight() {
const height = this.height;
if (/^[\d]+(\.[\d]+)?$/.test(height)) {
return `${height}px`;
return height;
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() {
load(tinymceCDN, err => {
if (err) {
this.$message.error(err.message);
return;
this.initTinymce();
initTinymce() {
const _this = this;
window.tinymce.init({
language: this.language,
convert_urls: false,
selector: `#${this.tinymceId}`,
height: this.height,
body_class: "panel-body ",
object_resizing: false,
min_height: this.height,
toolbar: this.toolbar,
menubar: this.menubar,
plugins: this.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,
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;
onPaste(event) {
const items = (event.clipboardData || window.clipboardData).items;
if (items[0].type.indexOf("image") !== -1) {
console.log("粘贴的是图片类型");
const file = items[0].getAsFile();
const formData = new FormData();
formData.append("file", file);
API.imgUpload(formData).then(res => {
console.log(res);
if (res.success) {
var src = this.upUrl + "JavaInstall/upload/imgs/" + res.data;
window.tinymce.get(this.tinymceId).insertContent(`<img class="wscnph" src="${src}" >`);
} else {
this.$message.error("图片上传失败,联系开发人员");
} else {
console.log("粘贴的不是图片,不能上传");
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) {
const _this = this;
arr.forEach(v => {
window.tinymce.get(_this.tinymceId).insertContent(`<img class="wscnph" src="${v.url}" >`);
</script>
<style scoped>
.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: 2100;
.fullscreen .editor-custom-btn-container {
z-index: 10000;
position: fixed;
.editor-upload-btn {
display: inline-block;
</style>
//dynamicLoadScript.js 动态导入tinymce.js脚本
let callbacks = []
function loadedTinymce() {
// to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2144
// check is successfully downloaded script
return 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
script.id = src
document.body.appendChild(script)
callbacks.push(cb)
const onEnd = "onload" in script ? stdOnEnd : ieOnEnd
onEnd(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 others
this.onerror = this.onload = null
for (const cb of callbacks) {
cb(null, script)
callbacks = null
script.onerror = function() {
this.onerror = this.onload = null
cb(new Error("Failed to load " + src), script)
function ieOnEnd(script) {
script.onreadystatechange = function() {
if (this.readyState !== "complete" && this.readyState !== "loaded") return
this.onreadystatechange = null
for (const cb of callbacks) {
cb(null, script)
callbacks = null
export default dynamicLoadScript
复制代码
!function(l){"use strict";var e,r,n,t,o,i,m,a,d,u,c,s,v,f,p=function(e){var r=e,n=function(){return