vue3富文本编辑器的二次封装开发-Tinymce

这篇具有很好参考价值的文章主要介绍了vue3富文本编辑器的二次封装开发-Tinymce。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

欢迎点击领取 -《前端开发面试题进阶秘籍》:前端登顶之巅-最全面的前端知识点梳理总结

专享链接

简介

1、安装:pnpm add tinymce @tinymce/tinymce-vue ===> Vue3 + tinymce + @tinymce/tinymce-vue
2、功能实现图片上传、基金卡片插入、收益卡片插入、源代码复用、最大长度限制、自定义表情包插入、文本内容输入、预览等功能

vue3富文本编辑器的二次封装开发-Tinymce,tinymce,富文本编辑器,vue3

代码展示

在components文件下创建TinymceEditor.vue文件作为公共组件

<template>
  <div>
    <Editor ref="EditorRefs" v-model="content" :init="myTinyInit" />
    <div class="editor_footer">
      <span v-if="wordlimit">
        <span>{{ wordLenght }}</span>
        <span> / </span>
        <span>{{ wordlimit.max }}</span> 字符
      </span>
    </div>
    <el-dialog title="自定义表情包" v-model="dialogVisible" width="45%">
      <div class="emoji">
        <div class="emoji-item" v-for="item in 40" :key="item">
          <img :src="`/src/assets/emoji/${item}.webp`" alt="" @click="chooseEmoji(item)" />
        </div>
      </div>
    </el-dialog>
    <button @click="handlePreview">预览</button>
  </div>
</template>

<script lang="ts" setup>
import './wordlimit' // 限制字符文件
import tinymce from 'tinymce/tinymce'
import Editor from '@tinymce/tinymce-vue'
import 'tinymce/icons/default/icons'
import 'tinymce/themes/silver'
import 'tinymce/models/dom/model'
import 'tinymce/plugins/table'
import 'tinymce/plugins/lists'
import 'tinymce/plugins/link'
import 'tinymce/plugins/help'
import 'tinymce/plugins/wordcount'
import 'tinymce/plugins/code'
import 'tinymce/plugins/preview'
import 'tinymce/plugins/visualblocks'
import 'tinymce/plugins/visualchars'
import 'tinymce/plugins/fullscreen'
import '/public/tinymce/plugins/image/index.js'

import { sumLetter } from '@/utils/utilTool'
import { computed, onMounted, reactive, ref, watch } from 'vue'

const props = withDefaults(
  defineProps<{
    modelValue?: string
    plugins?: string
    toolbar?: string
    wordlimit?: any
  }>(),
  {
    plugins: 'image code wordcount wordlimit preview', // 默认开启工具库
    toolbar: 'image emoji fund—icon income-icon code' // 富文本编辑器工具
  }
)

const emit = defineEmits(['input'])

const wordLenght = ref<number | string>(0)

const content = ref<string>('')

const EditorRefs = ref<any>()

const dialogVisible = ref<boolean>(false)

const myTinyInit = reactive({
  width: '100%',
  height: 600, // 默认高度
  statusbar: false,
  language_url: '/tinymce/langs/zh_CN.js', // 配置汉化-> 需下载对应汉化包引入
  language: 'zh_CN', // 语言标识
  branding: false, // 不显示右下角logo
  auto_update: false, // 不进行自动更新
  resize: true, // 可以调整大小
  menubar: false, // 关闭顶部菜单
  skin_url: '/tinymce/skins/ui/oxide', // 手动引入CSS
  content_css: '/tinymce/skins/content/default/content.css', // 手动引入CSS
  toolbar_mode: 'wrap',
  plugins: props?.plugins, // 插件
  toolbar: props?.toolbar, // 功能按钮
  wordlimit: props?.wordlimit, // 字数限制
  image_caption: false,
  paste_data_images: true,

  //粘贴图片后,自动上传
  urlconverter_callback: function (url, node, on_save, name) {
    return url
  },

  images_upload_handler: (blobInfo) =>
    new Promise((resolve, reject) => {
      console.log(blobInfo.blob())
      const formData = new FormData()
      formData.append('file', blobInfo.blob(), blobInfo.filename())
      resolve('https://szx-bucket1.oss-cn-hangzhou.aliyuncs.com/picgo/image-20230512090059968.png')
      // axios
      //   .post(`/api/backend/upload`, formData, {
      //     headers: {
      //       'Content-Type': 'multipart/form-data',
      //       Authorization: 'Bearer ' + store.state.user.accessToken,
      //     },
      //   })
      //   .then((res) => {
      //     if (res.data.code === 1) {
      //       resolve(`/image_manipulation${res.data.data.filePath}`)
      //     } else {
      //       ElNotification.warning(res.data.msg)
      //     }
      //   })
      //   .catch((error) => {
      //     reject(error)
      //   })
    }),

  setup: (editor) => { // 自定义图标内容及触发点击事件等功能
    editor.ui.registry.addIcon(
      'fund—icon',
      '<svg t="1696250970925" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="24834" width="21" height="21"><path d="M512 133.12c208.91648 0 378.88 169.96352 378.88 378.88s-169.96352 378.88-378.88 378.88-378.88-169.96352-378.88-378.88 169.96352-378.88 378.88-378.88m0-71.68c-248.83712 0-450.56 201.72288-450.56 450.56s201.72288 450.56 450.56 450.56 450.56-201.72288 450.56-450.56-201.72288-450.56-450.56-450.56z" fill="#2c2c2c" p-id="24835"></path><path d="M624.74752 263.6288a35.72224 35.72224 0 0 0-25.344 10.496L512 361.52832 424.59648 274.1248a35.73248 35.73248 0 0 0-25.344-10.496 35.84 35.84 0 0 0-25.344 61.17888L451.07712 401.9712H348.16a35.84 35.84 0 1 0 0 71.68h128v66.56H348.16a35.84 35.84 0 1 0 0 71.68h128v133.12a35.84 35.84 0 1 0 71.68 0v-133.12h128a35.84 35.84 0 1 0 0-71.68h-128v-66.56h128a35.84 35.84 0 1 0 0-71.68h-102.91712l77.16352-77.16352a35.84 35.84 0 0 0-25.33888-61.17888z" fill="#2c2c2c" p-id="24836"></path></svg>'
    )
    editor.ui.registry.addIcon(
      'income-icon',
      '<svg t="1696250530786" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15004" width="21" height="21"><path d="M920 152v720H104V152h816z m-67.2 67.2H171.2v585.6h681.6V219.2z" fill="#2c2c2c" p-id="15005"></path><path d="M32 152m7 0l946 0q7 0 7 7l0 53.2q0 7-7 7l-946 0q-7 0-7-7l0-53.2q0-7 7-7Z" fill="#2c2c2c" p-id="15006"></path><path d="M450.906 417.788l122.187 122.19 115.4-115.401 47.518 47.517-115.4 115.4-47.517 47.518-122.189-122.19-115.399 115.401-47.517-47.517 162.917-162.918z" fill="#2c2c2c" p-id="15007"></path><path d="M300.8 718.4H368v86.4h-67.2v-86.4z m120-86.4H488v172.8h-67.2V632z m120 48H608v124.8h-67.2V680z m120-67.2H728v192h-67.2v-192z" fill="#2c2c2c" p-id="15008"></path></svg>'
    )

    editor.ui.registry.addButton('emoji', {
      icon: 'emoji',
      tooltip: '自定义表情包',
      onAction: () => {
        dialogVisible.value = true
      }
    })

    editor.ui.registry.addButton('fund—icon', {
      icon: 'fund—icon',
      tooltip: '基金',
      onAction: () => {
        editor.insertContent('Hello')
      }
    })

    editor.ui.registry.addButton('income-icon', {
      icon: 'income-icon',
      tooltip: '晒收益',
      onAction: () => {
        editor.insertContent('Hello')
      }
    })
  },

  init_instance_callback: (editor: any) => {
    editor.on('input', () => getEditorWordLen())
  }
})

const initContent = computed(() => {
  return props.modelValue
})

// 选择自定义表情包
const chooseEmoji = (item) => {
  const editor = EditorRefs.value.getEditor()
  const range = editor.selection.getRng()
  const imgNode = editor.getDoc().createElement('img')
  imgNode.width = 32
  imgNode.height = 32
  imgNode.style = 'vertical-align: bottom;'
  imgNode.src = `/src/assets/emoji/${item}.webp` // 注意写你的项目相对路径
  range.insertNode(imgNode)
  dialogVisible.value = false
  editor.execCommand('seleceAll')
  editor.selection.getRng().collapse()
  editor.focus()
}

const getEditorWordLen = () => {
  const content = tinymce.activeEditor.getContent({ format: 'text' })
  const wordObj = sumLetter(content)
  wordLenght.value = wordObj?.txt?.length || 0
}

const handlePreview = () => {
  const editor = tinymce.activeEditor
  editor.on('preview', (editor) => {
    console.log(editor)
  })
}

onMounted(() => {
  tinymce.init({})
  setTimeout(() => getEditorWordLen(), 800)
})

watch(
  initContent,
  (newVal) => {
    content.value = newVal
  },
  { deep: true, immediate: true }
)

watch(
  content,
  (newVal) => {
    emit('input', newVal)
  },
  { deep: true }
)
</script>

<script lang="ts">
export default { name: 'TinymceEditor' }
</script>

<style scoped lang="scss">
.emoji {
  display: flex;
  flex-wrap: wrap;
}

.emoji-item {
  display: flex;
  justify-content: center;
  align-items: center;
  margin-left: 10px;
  margin-bottom: 8px;
  cursor: pointer;

  img {
    width: 48px;
    height: 48px;
  }
}

.editor_footer {
  margin-top: 20px;
  font-size: 13px;
}
</style>

创建wordlimit.ts文件,作为限制字符的触发条件文章来源地址https://www.toymoban.com/news/detail-728227.html

import tinymce from 'tinymce/tinymce'
import { ElMessage } from 'element-plus'
import { sumLetter } from '@/utils/utilTool'

tinymce.PluginManager.add('wordlimit', function (editor): any {
  const pluginName = '字数限制'
  const app = tinymce.util.Tools.resolve('tinymce.util.Delay')
  const Tools = tinymce.util.Tools.resolve('tinymce.util.Tools')
  const wordlimit_event = editor.getParam('ax_wordlimit_event', 'SetContent Undo Redo Keyup input paste')
  const options = editor.getParam('wordlimit', {}, 'object')
  let close = null

  const toast = function (message) {
    close && close.close()
    close = ElMessage.error(message)
    return
  }

  // 默认配置
  const defaults = {
    spaces: false, // 是否含空格
    isInput: false, // 是否在超出后还可以输入
    maxMessage: '超出最大输入字符数量!',
    changeCallback: () => {}, // 自定义的回调方法
    changeMaxCallback: () => {},
    toast // 提示弹窗
  }

  class WordLimit {
    constructor(editor, options) {
      options = Tools.extend(defaults, options)
      let preCount = 0
      let _wordCount = 0
      let oldContent = editor.getContent()
      const WordCount = editor.plugins.wordcount

      editor.on(wordlimit_event, function (e) {
        const content = editor.getContent() || e.content || ''
        if (!options.spaces) {
          _wordCount = WordCount.body.getCharacterCount()
        } else {
          _wordCount = WordCount.body.getCharacterCountWithoutSpaces()
        }
        options.changeCallback({
          ...options,
          editor,
          num: _wordCount,
          content,
          ...sumLetter(content)
        })
        if (_wordCount > options.max) {
          preCount = _wordCount
          if (options.isInput == !1) {
            editor.setContent(oldContent)
            if (!options.spaces) {
              _wordCount = WordCount.body.getCharacterCount()
            } else {
              _wordCount = WordCount.body.getCharacterCountWithoutSpaces()
            }
          }
          editor.getBody().blur()
          editor.fire('wordlimit', {
            maxCount: options.max,
            wordCount: _wordCount,
            preCount: preCount,
            isPaste: e.type === 'paste' || e.paste || false
          })
          toast('最多只能输入' + options.max + '个字')
        }
        oldContent = editor.getContent()
      })
    }
  }

  const setup = function () {
    if (!options && !options.max) return false
    if (!editor.plugins.wordcount) return toast('请先在tinymce的plugins配置wordlimit之前加入wordcount插件')
    app.setEditorTimeout(
      editor,
      function () {
        const editDom = editor.getContainer()
        const wordNum: any = editDom.querySelector('button.tox-statusbar__wordcount')
        const statusbarpath: any = editDom.querySelector('.tox-statusbar__path')
        statusbarpath ? statusbarpath.remove() : void null
        if (wordNum?.innerText?.indexOf('字符') == -1) wordNum.click()
        new WordLimit(editor, options)
      },
      300
    )
  }

  setup()

  return {
    getMetadata: function () {
      return {
        name: pluginName
      }
    }
  }
})

使用
<template>
  <div class="post_contaniner">
    <div style="width: 100%">
      <TinymceEditor v-model="content" @input="inputContent" :wordlimit="{ max: 300 }" />
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue'

const content = ref('Hello World')

const inputContent = (newVal) => {
  console.log(newVal)
  content.value = newVal
}
</script>

<style scoped lang="scss">
.post_contaniner {
  .right {
    flex: 1;
    box-shadow: 0 1px 10px 3px #dbdbdb;
    margin-right: 10px;
    padding: 10px;
    box-sizing: border-box;
  }
}
</style>

到了这里,关于vue3富文本编辑器的二次封装开发-Tinymce的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • vue3使用quill富文本编辑器,保姆级教程,富文本踩坑解决

    本文是封装成组件使用 先放效果图 先封装组件,建立如下目录 全部代码如下, 使用 本文是第二个页面使用这个富文本编辑器有可能watch监听中找不到ref,如果不能正常使用可以稍微改装下在onMounted里赋值然后在setValue里抛出就好 保姆级教程,有问题欢迎提出

    2024年02月11日
    浏览(47)
  • Vue3 代码块高亮显示并可使用富文本编辑器编辑(highlight.js + wangEditor)

    在Vue项目中实现以下功能:   功能1. 在页面中显示代码,并将其中的高亮显示。   功能2. 允许对代码块进行编辑,编辑时代码也高亮显示。   功能3. 可在编辑器中添加多个代码块,动态渲染代码高亮。   Step1: 安装所需插件(本文使用npm安装,若需

    2023年04月21日
    浏览(47)
  • vue3富文本编辑器vue-quill-editor、图片缩放ImageResize详细配置及使用教程

    官网地址:https://vueup.github.io/vue-quill/ 效果图  1、安装 2、在vue.config.js中添加配置,否则quill-image-resize-module会出现Cannot read property ‘imports‘ of undefined报错问题 3、创建quillTool.js(用于添加超链接、视频) 4、完整代码

    2024年02月04日
    浏览(53)
  • vue3+ts+tinynce富文本编辑器+htmlDocx+file-saver 配合实现word下载

    vue3 请下载html-docx-js-typescript,否则会报错类型问题 row.report效果及数据 调用

    2024年02月11日
    浏览(53)
  • 小程序Editor富文本编辑器-封装使用用法

    本文章主要讲述editor中小程序的图片上传和pc端数据不同步的问题,建议多看下代码 完整代码在最下面 1、创建个用于组件封装的editor文件夹,存放三个文件  2、editor.vue文件是主要封装代码 3、editor-icon.css文件样式 5、如果上传图片失败或者是图片裂开,和pc端数据不同步的话

    2024年02月11日
    浏览(44)
  • Tinymce富文本编辑器二次开发电子病历时解决的bug

    本文是在Tinymce富文本编辑器添加自定义toolbar,二级菜单,自定义表单,签名的基础之上进行一些bug记录,功能添加,以及模版的应用和打印 项目描述 建立电子病历模版—录入(电子病历模版和电子病历打印模版)—查看电子病历和打印病历模版 建立电子病历----添加一个电

    2024年04月10日
    浏览(58)
  • Vue3中搜索表单的二次封装

    最近使用Vue3+ElementPlus开发项目,从整体上构思组件的封装。能写成组件的内容都进行封装,方便多个地方使用。 受AntDesign的启发,在项目中有搜索表单+table+分页的地方可以封装为一个组件,只需要对组件传入table的列,组成一个配置项,通过配置可以显示搜索表单、table项的

    2024年02月11日
    浏览(49)
  • Vue3和TypeScript下基于Axios的二次封装教程

    学习如何在Vue3项目中使用TypeScript和Axios进行二次封装,实现更灵活的网络请求处理。详细教程包括拦截器设置和类型扩展。

    2024年02月05日
    浏览(136)
  • 8款 Vue 富文本编辑器

    https://www.wangeditor.com/ http://tinymce.ax-z.cn/ https://www.itxst.com/tiptap/tutorial.html https://ckeditor.com/ckeditor-5/ https://quilljs.com/ https://www.froala.com/ https://summernote.org/ 基于bootstrap,比较突出的优点就是能保持复制过来的东西的原有样式,并且比较流畅。 https://www.cnblogs.com/xxflz/p/9777075.html https

    2024年02月12日
    浏览(76)
  • Vue + 富文本编辑器:打印模板设计

    前言: 有的项目需要用到打印,如果只有少数的地方需要用到打印,一般只需要固定模板进行打印就行了,但是我们的项目总是与众不同,明明只要固定模板就可以完成需求的,非要添加一个灵活的打印模板,而且还涉及到拖拉填充文本,真是脑细胞不知道死掉了多少! ! !

    2024年02月09日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包