1.工具栏自定义按钮

editor.ui.registry.addButton('FontPlus', {
      // text: '',
      icon: 'fontplus',
      onAction: function () {
        setfontSize(editor, 2)
      }
    })
    editor.ui.registry.addButton('FontMinus', {
      // text: 'A⁻',
      icon: 'fontminus',
      onAction: function () {
        setfontSize(editor, -2)
      }
    })

2.工具栏自定义图标

editor.ui.registry.addIcon(
      'fontplus',
      `<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
   viewBox="0 0 14 14" style="enable-background:new 0 0 14 14;" xml:space="preserve" height="17" width="17">
<rect x="9" y="3" class="st0" width="5" height="1"/>
<rect x="11" y="1" class="st0" width="1" height="5"/>
<g>
  <path class="st0" d="M11,13H9.3L8,9.7h-5L1.7,13H0L4.6,1h1.7L11,13z M3.4,8.4h4l-2-5.9L3.4,8.4z"/>
</g>
</svg>

`
    )
    editor.ui.registry.addIcon(
      'fontminus',
      `<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
   viewBox="0 0 14 14" style="enable-background:new 0 0 14 14;" xml:space="preserve"  height="17" width="17">
<rect x="9" y="3" class="st0" width="5" height="1"/>
<g>
  <path class="st0" d="M11,13H9.3L8,9.7H3L1.7,13H0L4.6,1h1.7L11,13z M3.4,8.4h4l-2-5.9L3.4,8.4z"/>
</g>
</svg>

`
    )

3.字数限制插件

;(function () {
  'use strict'

  let Tools = tinymce.util.Tools.resolve('tinymce.util.Tools')
  let global = tinymce.util.Tools.resolve('tinymce.PluginManager')

  let defaults = {
    // max: 0, // 最多可以输入多少字
    spaces: false, // 是否含空格
    isInput: !1, // 是否在超出后还可以输入
    toast: null // 自定义的提示方法, 默认用编辑器自带
  }

  class WordLimit {
    constructor(editor, options) {
      this.editor = editor
      this.options = Tools.extend(defaults, options)

      var _this = this,
        oldContent = editor.getContent(),
        WordCount = editor.plugins.wordcount,
        preCount = 0,
        _wordCount = 0

      editor.on('input paste undo redo Keyup ', function (e) {
        var content = editor.getContent() || e.content || ''

        if (!_this.options.spaces) {
          // 字数
          _wordCount = WordCount.body.getCharacterCount()
        } else {
          // 不含空格字数
          _wordCount = WordCount.body.getCharacterCountWithoutSpaces()
        }

        if (_wordCount > _this.options.max) {
          preCount = _wordCount
          // 禁止再输入
          if (_this.options.isInput == !1) {
            // 内容超出还原
            editor.setContent(oldContent)

            // 还原后重新统计
            if (!_this.options.spaces) {
              _wordCount = WordCount.body.getCharacterCount()
            } else {
              _wordCount = WordCount.body.getCharacterCountWithoutSpaces()
            }
          }

          editor.getBody().blur()
          editor.fire('wordlimit', {
            maxCount: _this.options.max,
            wordCount: _wordCount,
            preCount: preCount,
            isPaste: e.type === 'paste' || e.paste || false
          })
        }

        oldContent = editor.getContent()
      })
    }
  }

  function Plugin() {
    global.add('wordlimit', function (editor) {
      var options = editor.getParam('wordlimit', {}, 'object')

      if (!options && !options.max) {
        return !1
      }

      if (typeof options.toast !== 'function') {
        options.toast = function (message) {
          editor.notificationManager.open({
            text: message,
            type: 'error',
            timeout: 3000
          })
        }
      }

      if (!editor.plugins.wordcount) {
        options.toast('请先在tinymce的plugins配置wordlimit之前加入wordcount插件')
        return !1
      }

      editor.on('init', function (e) {
        new WordLimit(editor, options)
      })
    })
  }

  Plugin()
})()

3.获取纯文本数量

editor.plugins.wordcount.body.getCharacterCount()

4.设置字体大小

function setfontSize(editor, fontSize) {
  editor.editorCommands.commands.exec.fontsize('fontSize', false, fontSize + 'px')
}

5.完整案例

<tinymce-editor
  placeholder="请输入公司简介"
  :modelValue="companyText"
  @update:modelValue="
    (val) => {
      companyText = val
    }
  "
  @getLen="
    (val) => {
      companyTextLen = val
    }
  "
  @getRawText="
    (val) => {
      companyRawText = val
    }
  "
  :height="180"
></tinymce-editor>
<script lang="ts" setup>
import { computed, onMounted, onUnmounted } from 'vue'

import tinymce from 'tinymce/tinymce' //tinymce核心文件
import Editor from '@tinymce/tinymce-vue'

import 'tinymce/models/dom' // 引入dom模块。从 Tinymce6,开始必须有此模块导入
import 'tinymce/themes/silver' //默认主题
import 'tinymce/icons/default' //引入编辑器图标icon,不引入则不显示对应图标
import 'tinymce/plugins/wordcount' // 字数统计插件

// import '/' // 字数统计插件
import '../../../views/visualization/utils/wordlimit.js'
// import './langs/zh-Hans.js' //引入编辑器语言包

/* 引入编辑器插件
 * 位于 ./node_modules/tinymce/plugins 目录下,版本不同,插件会有所差异。根据自己版本来导入,若不存在的,不能导入,会报错。
 */
// import 'tinymce/plugins/advlist' //高级列表
// 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/fullscreen' //全屏
// import 'tinymce/plugins/help' //帮助
// 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/preview' //预览
// import 'tinymce/plugins/quickbars' //快速工具栏
// import 'tinymce/plugins/save' //保存
// import 'tinymce/plugins/searchreplace' //查找替换
// import 'tinymce/plugins/table' //表格
// import 'tinymce/plugins/template' //内容模板
// import 'tinymce/plugins/visualblocks' //显示元素范围
// import 'tinymce/plugins/visualchars' //显示不可见字符
// import 'tinymce/plugins/wordcount' //字数统计
const props = defineProps({
  modelValue: {
    type: String,
    required: true,
    default: ''
  },
  menubar: {
    type: [Boolean, String],
    default: 'file edit insert view format table tools help'
  },

  height: {
    type: Number,
    default: 600
  },
  id: {
    type: [String, Number],
    default: 'myTinymce'
  },
  disabled: {
    type: Boolean,
    default: false
  }
})

const emit = defineEmits(['update:modelValue', 'getLen'])

let contentValue = computed({
  get() {
    return props.modelValue
  },
  set(value) {
    emit('update:modelValue', value)
  }
})

const initOptions = {
  language_url: '/tinymce/langs/zh-Hans.js', //汉化
  language: 'zh-Hans',
  skin_url: '/tinymce/skins/ui/oxide', //皮肤
  content_css: '/tinymce/skins/content/default/content.css ',
  content_style:
    '::-webkit-scrollbar-thumb:vertical{background-color: #c0c0c0;-webkit-border-radius: 4px;} ::-webkit-scrollbar-track-piece { width: 8px; background-color: transparent; -webkit-border-radius: 4px; } ::-webkit-scrollbar { width: 8px; height: 8px; background-color: transparent; } body{margin:5px 12px} p{font-size:14px;margin-block-start:0;margin-block-end:0;  word-wrap: break-word;word-break: break-all;} #tinymce.mce-content-body::before{font-size:14px;color:#b2b2b2}',
  height: props.height,
  menubar: false,

  // menu: {
  // file: { title: '文件', items: 'newdocument | preview | export | deleteallconversations' },
  // edit: { title: 'Edit', items: 'undo redo restoredraft | cut copy | selectall | searchreplace' },
  // view: {
  //   title: 'View',
  //   items: 'code | visualaid visualchars visualblocks | preview fullscreen | showcomments'
  // },
  // insert: {
  //   title: 'Insert',
  //   items:
  //     'image link media addcomment pageembed template codesample inserttable | charmap emoticons | pagebreak nonbreaking anchor tableofcontents | insertdatetime'
  // },
  // format: {
  //   title: 'Format',
  //   items:
  //     'bold italic underline strikethrough superscript subscript codeformat | styles blocks fontfamily fontsize align lineheight | forecolor backcolor | language | removeformat'
  // }
  // tools: { title: 'Tools', items: 'a11ycheck code wordcount' }
  // table: {
  //   title: 'Table',
  //   items: 'inserttable | cell row column | advtablesort | tableprops deletetable'
  // }
  // help: { title: 'Help', items: 'help' }
  // },
  toolbar: 'forecolor bold FontPlus FontMinus',
  plugins: 'wordcount wordlimit',
  // 'code codesample preview searchreplace autolink directionality visualblocks visualchars fullscreen image link media template table charmap pagebreak nonbreaking anchor insertdatetime advlist lists wordcount autosave ',
  line_height_formats: '1 1.2 1.4 1.6 2', //行高
  font_size_formats: '12px 14px 16px 18px 20px 22px 24px 28px 32px 36px 48px 56px 72px', //字体大小
  // font_family_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;',
  // images_file_types: 'jpeg,jpg,png,gif,bmp',
  //images_upload_handler: editorUploadImage(blobInfo, 100),
  placeholder: '在这里输入文字',
  branding: false, //tiny技术支持信息是否显示
  statusbar: true, //最下方的元素路径和字数统计那一栏是否显示
  elementpath: false, //元素路径是否显示
  wordlimit: {
    // max: 300, // 最多可以输入多少字1
    spaces: false, // 是否含空格
    isInput: true // 是否在超出后还可以输入
    // 自定义的提示方法, 默认用编辑器自带
  },
  // custom_undo_redo_levels: 10 //撤销和重做的次数
  // draggable_modal: true //对话框允许拖拽
  setup: function (editor: any) {
    // console.log(window.document.getElementByid('tinymce'), '999')
    // ed.on('init', function () {
    //   $(ed.getBody()).on('focus', function (e) {
    //     console.log('focus')
    //   })
    // })
    editor.ui.registry.addButton('FontPlus', {
      // text: '',
      icon: 'fontplus',
      onAction: function () {
        setfontSize(editor, 2)
      }
    })
    editor.ui.registry.addButton('FontMinus', {
      // text: 'A⁻',
      icon: 'fontminus',
      onAction: function () {
        setfontSize(editor, -2)
      }
    })
    editor.ui.registry.addIcon(
      'fontplus',
      `<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
	 viewBox="0 0 14 14" style="enable-background:new 0 0 14 14;" xml:space="preserve" height="17" width="17">
<rect x="9" y="3" class="st0" width="5" height="1"/>
<rect x="11" y="1" class="st0" width="1" height="5"/>
<g>
	<path class="st0" d="M11,13H9.3L8,9.7h-5L1.7,13H0L4.6,1h1.7L11,13z M3.4,8.4h4l-2-5.9L3.4,8.4z"/>
</g>
</svg>

`
    )
    editor.ui.registry.addIcon(
      'fontminus',
      `<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
	 viewBox="0 0 14 14" style="enable-background:new 0 0 14 14;" xml:space="preserve"  height="17" width="17">
<rect x="9" y="3" class="st0" width="5" height="1"/>
<g>
	<path class="st0" d="M11,13H9.3L8,9.7H3L1.7,13H0L4.6,1h1.7L11,13z M3.4,8.4h4l-2-5.9L3.4,8.4z"/>
</g>
</svg>

`
    )
  },
  init_instance_callback: (editor) => {
    // editor.on('focus', (e) => {
    //   let sel = window.getSelection()
    //   let range = sel.getRangeAt(0)
    //   let node = range.commonAncestorContainer
    //   if (node.nodeType != 1) {
    //     node = node.parentNode
    //   }
    //   console.log(node)
    //   let font_size = window.getComputedStyle(node).getPropertyValue('font-size')
    //   console.log(font_size)
    // }),
    editorRef.value = editor
    $(editor.getContainer()).find('button.tox-statusbar__wordcount').click()
    // editor.execCommand('mceInsertContent', false, '')

    editor.on('input', (e) => {
      console.log(editor.plugins.wordcount.body.getCharacterCount())
      getText(editor)
    })
    editor.on('paste', (e) => {
      getText(editor)
    })
    editor.on('mouseup', (e) => {
      // let node = editor.selection.getNode()
      // var fontSize2 = editor.dom.getStyle(node, 'font-size')
      // console.log(fontSize2, editor.queryCommandValue('FontSize'))
      // console.log(editor.selection.getSelectedBlocks())
      // console.log(editor.selection.getSel())
      // console.log(editor.selection.getSel().extentNode.parentElement.dataset.mceStyle)
      // console.log(editor.selection.getSel())
      // console.log(editor.selection.getSel().baseNode.parentNode.dataset.mceStyle)
      // let getFontsize = editor.selection.getSel().baseNode.parentNode.dataset.mceStyle
      //   ? editor.selection.getSel().baseNode.parentNode.dataset.mceStyle.replace(/[^\d]/g, ' ')
      //   : 14 //num2 : 2021
      fontSize.value = parseFloat(editor.queryCommandValue('FontSize')) || 14
      getText(editor)
      // fontSize.value = getFontsize || 14
    })
    editor.on('wordlimit', function (e) {
      // e.maxCount   // 配置的最大输入字数
      // e.wordCount  // 已输入的字数
      // e.preCount    // 粘贴进来的内容字数,可以用来单独提示粘贴内容时超出的计算
      // e.isPaste       // 是否是粘贴输入
      // console.log('触发wordlimit')
      // ElMessage({
      //   message: '最多只能输入300字,超出部分无法保存',
      //   type: 'error'
      // })
      // return false
      // 可以吧alert换成自己的提示组件
      // alert(
      //   '最多只能输入' +
      //     e.maxCount +
      //     '个字' +
      //     (beyond > 0 ? ',已超出' + beyond + '个字,超出部分无法保存' : '。')
      // )
    })
  }
}
const editorRef = ref()
const tinyID = 'id' + Math.floor(Math.random() * (10000 - 1)) + 1
const initOption = {
  selector: tinyID,
  language: 'zh_CN',
  menubar: false,
  toolbar: 'formatting | alignment',
  toolbar_groups: {
    formatting: {
      text: '文字格式',
      tooltip: 'Formatting',
      items: 'bold italic underline | superscript subscript'
    },
    alignment: {
      icon: 'align-left',
      tooltip: 'alignment',
      items: 'alignleft aligncenter alignright alignjustify'
    }
  }
}
onMounted(async () => {
  tinymce.init({}) //初始化
})
onUnmounted(() => {
  tinymce.remove() //销毁
})

function getText(editor) {
  var cnt = editorRef.value.getContent({ format: 'text' })
  emit('getLen', editor.plugins.wordcount.body.getCharacterCount())
  emit('getRawText', cnt)
}
const fontSize = ref(12)
function setfontSize(editor, change) {
  fontSize.value = Number(fontSize.value) + change
  if (fontSize.value > 18) {
    fontSize.value = 18
  }
  if (fontSize.value < 12) {
    fontSize.value = 12
  }
  editor.editorCommands.commands.exec.fontsize('fontSize', false, fontSize.value + 'px')
}
</script>
<template>
  <div class="tinymce-box">
    <Editor v-model="contentValue" :init="initOptions" :id="props.id" :disabled="props.disabled" />
  </div>
</template>
<style scoped>
.tinymce-box {
  width: 100%;
}
:deep() .tox:not(.tox-tinymce-inline) .tox-editor-header {
  padding: 0;
}
:deep() .tox.tox-tinymce {
  border-radius: 4px;
  border-width: 1px;
  border-color: #dcdfe6;
}

:deep() .tox .tox-notifications-container .tox-notification {
  display: none;
}
:deep() .tox .tox-listboxfield .tox-listbox--select:focus,
:deep() .tox .tox-textarea:focus,
:deep() .tox .tox-textfield:focus {
  border-color: #f60 !important;
}
:deep() .tox .tox-dialog__footer .tox-button {
  font-weight: normal;
  background-color: #f60 !important;
  border-color: #f60 !important;
  color: #fff !important;
}
:deep() .tox .tox-dialog__footer .tox-button:hover:not(:disabled) {
  color: #fff;
  background-color: #ff7214 !important;
  border-color: #ff7214 !important;
}
:deep() .tox .tox-dialog__footer .tox-button:active:not(:disabled) {
  color: #fff;
  background-color: #f06000 !important;
  border-color: #f06000 !important;
}
:deep() .tox .tox-tbtn:hover {
  background: #f8f8f8;
  border: 0;
  box-shadow: none;
  color: #222f3e;
}
:deep() .tox .tox-tbtn:active,
.tox-tbtn:focus {
  background: #e8e8e8;
  border: 0;
  box-shadow: none;
  color: #222f3e;
}
:deep() .tox .tox-tbtn.tox-tbtn--enabled {
  background: #e8e8e8;
  border: 0;
  box-shadow: none;
  color: #222f3e;
}
:deep() .tox-tbtn {
  /* width: 20px !important;
  height: 20px !important; */
}
</style>

相关链接
https://juejin.cn/post/7123374361687687198open in new window

Last Updated:
Contributors: shenxin