vue中引入Tinymce富文本编辑器

最近想在项目上引入一个富文本编辑器,之前引入过summernote,感觉并不太适合vue使用,

然后在网上查了查,vue中使用Tinymce比较适合,

首先,我们在vue项目的components文件夹中加入如下几个文件

首先看一下Tinymce/dynamicLoadScript.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 // src url for the third-party library being loaded.
    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) // there is no way to catch loading errors in IE8
      }
      callbacks = null
    }
  }
}

export default dynamicLoadScript

再来看一下Tinymce/components/EditorImage.vue中的内容

<template>
  <div class="upload-container">
    <el-button :style="{background:color,borderColor:color}" icon="el-icon-upload" size="mini" type="primary" @click=" dialogVisible=true">
      upload
    </el-button>
    <el-dialog :visible.sync="dialogVisible">
      <el-upload
        :multiple="true"
        :file-list="fileList"
        :show-file-list="true"
        :on-remove="handleRemove"
        :on-success="handleSuccess"
        :before-upload="beforeUpload"
        class="editor-slide-upload"
        action="https://httpbin.org/post"
        list-type="picture-card"
      >
        <el-button size="small" type="primary">
          Click upload
        </el-button>
      </el-upload>
      <el-button @click="dialogVisible = false">
        Cancel
      </el-button>
      <el-button type="primary" @click="handleSubmit">
        Confirm
      </el-button>
    </el-dialog>
  </div>
</template>

<script>
// import { getToken } from ‘api/qiniu‘

export default {
  name: ‘EditorSlideUpload‘,
  props: {
    color: {
      type: String,
      default: ‘#1890ff‘
    }
  },
  data() {
    return {
      dialogVisible: false,
      listObj: {},
      fileList: []
    }
  },
  methods: {
    checkAllSuccess() {
      return Object.keys(this.listObj).every(item => this.listObj[item].hasSuccess)
    },
    handleSubmit() {
      const arr = Object.keys(this.listObj).map(v => this.listObj[v])
      if (!this.checkAllSuccess()) {
        this.$message(‘Please wait for all images to be uploaded successfully. If there is a network problem, please refresh the page and upload again!‘)
        return
      }
      this.$emit(‘successCBK‘, arr)
      this.listObj = {}
      this.fileList = []
      this.dialogVisible = false
    },
    handleSuccess(response, file) {
      const uid = file.uid
      const objKeyArr = Object.keys(this.listObj)
      for (let i = 0, len = objKeyArr.length; i < len; i++) {
        if (this.listObj[objKeyArr[i]].uid === uid) {
          this.listObj[objKeyArr[i]].url = response.files.file
          this.listObj[objKeyArr[i]].hasSuccess = true
          return
        }
      }
    },
    handleRemove(file) {
      const uid = file.uid
      const objKeyArr = Object.keys(this.listObj)
      for (let i = 0, len = objKeyArr.length; i < len; i++) {
        if (this.listObj[objKeyArr[i]].uid === uid) {
          delete this.listObj[objKeyArr[i]]
          return
        }
      }
    },
    beforeUpload(file) {
      const _self = this
      const _URL = window.URL || window.webkitURL
      const fileName = file.uid
      this.listObj[fileName] = {}
      return new Promise((resolve, reject) => {
        const img = new Image()
        img.src = _URL.createObjectURL(file)
        img.onload = function() {
          _self.listObj[fileName] = { hasSuccess: false, uid: file.uid, width: this.width, height: this.height }
        }
        resolve(true)
      })
    }
  }
}
</script>

<style lang="scss" scoped>
.editor-slide-upload {
  margin-bottom: 20px;
  /deep/ .el-upload--picture-card {
    width: 100%;
  }
}
</style>

Tinymce/plugins.js中的内容:

// Any plugins you want to use has to be imported
// Detail plugins list see https://www.tinymce.com/docs/plugins/
// Custom builds see https://www.tinymce.com/download/custom-builds/

const plugins = [‘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‘]

export default plugins

Tinymce/toolbar.js的内容:

// Here is a list of the toolbar
// Detail list see https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols

const toolbar = [‘searchreplace bold fontsizeselect 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‘]

export default toolbar

在看看Tinymce/index.vue中的内容:

<template>
  <div :class="{fullscreen:fullscreen}" class="tinymce-container" :style="{width:containerWidth}">
    <textarea :id="tinymceId" class="tinymce-textarea" />
    <!-- <div class="editor-custom-btn-container">
      <editorImage color="#1890ff" class="editor-upload-btn" @successCBK="imageSuccessCBK" />
    </div> -->
  </div>
</template>

<script>
/**
 * docs:
 * https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html#tinymce
 */
import editorImage from ‘./components/EditorImage‘
import plugins from ‘./plugins‘
import toolbar from ‘./toolbar‘
import load from ‘./dynamicLoadScript‘

// why use this cdn, detail see https://github.com/PanJiaChen/tinymce-all-in-one
const tinymceCDN = ‘https://cdn.jsdelivr.net/npm/[email protected]/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,
      required: false,
      default() {
        return []
      }
    },
    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‘
    }
  },
  data() {
    return {
      hasChange: false,
      hasInit: false,
      tinymceId: this.id,
      fullscreen: false,
      languageTypeList: {
        ‘en‘: ‘en‘,
        ‘zh‘: ‘zh_CN‘,
        ‘es‘: ‘es_MX‘,
        ‘ja‘: ‘ja‘
      }
    }
  },
  computed: {
    containerWidth() {
      const width = this.width
      if (/^[\d]+(\.[\d]+)?$/.test(width)) { // matches `100`, `‘100‘`
        return `${width}px`
      }
      return width
    }
  },
  watch: {
    value(val) {
      if (!this.hasChange && this.hasInit) {
        this.$nextTick(() =>
          window.tinymce.get(this.tinymceId).setContent(val || ‘‘))
      }
    }
  },
  mounted() {
    this.init()
  },
  activated() {
    if (window.tinymce) {
      this.initTinymce()
    }
  },
  deactivated() {
    this.destroyTinymce()
  },
  destroyed() {
    this.destroyTinymce()
  },
  methods: {
    init() {
      // dynamic load tinymce from cdn
      load(tinymceCDN, (err) => {
        if (err) {
          this.$message.error(err.message)
          return
        }
        this.initTinymce()
      })
    },
    initTinymce() {
      const _this = this
      window.tinymce.init({
        selector: `#${this.tinymceId}`,
        language: this.languageTypeList[‘zh‘],
        height: this.height,
        body_class: ‘panel-body ‘,
        object_resizing: false,
        toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar,
        menubar: this.menubar,
        plugins: 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, // inserting nonbreaking space &nbsp; need Nonbreaking Space Plugin
        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
          })
        }
        // 整合七牛上传
        // images_dataimg_filter(img) {
        //   setTimeout(() => {
        //     const $image = $(img);
        //     $image.removeAttr(‘width‘);
        //     $image.removeAttr(‘height‘);
        //     if ($image[0].height && $image[0].width) {
        //       $image.attr(‘data-wscntype‘, ‘image‘);
        //       $image.attr(‘data-wscnh‘, $image[0].height);
        //       $image.attr(‘data-wscnw‘, $image[0].width);
        //       $image.addClass(‘wscnph‘);
        //     }
        //   }, 0);
        //   return img
        // },
        // images_upload_handler(blobInfo, success, failure, progress) {
        //   progress(0);
        //   const token = _this.$store.getters.token;
        //   getToken(token).then(response => {
        //     const url = response.data.qiniu_url;
        //     const formData = new FormData();
        //     formData.append(‘token‘, response.data.qiniu_token);
        //     formData.append(‘key‘, response.data.qiniu_key);
        //     formData.append(‘file‘, blobInfo.blob(), url);
        //     upload(formData).then(() => {
        //       success(url);
        //       progress(100);
        //     })
        //   }).catch(err => {
        //     failure(‘出现未知问题,刷新页面,或者联系程序员‘)
        //     console.log(err);
        //   });
        // },
      })
    },
    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: 2005;*/
}
.fullscreen .editor-custom-btn-container {
  z-index: 10000;
  position: fixed;
}
.editor-upload-btn {
  display: inline-block;
}
</style>

最后,在需要引入的页面内引入;

<template>
  <div >
    <tinymce v-model="content" :height="300" />

  </div>

</template>

<script>
import Tinymce from ‘@/components/Tinymce‘

export default {
  components:{
    Tinymce
  }
</script>

效果:

原文地址:https://www.cnblogs.com/fqh123/p/11442461.html

时间: 2024-12-14 04:55:26

vue中引入Tinymce富文本编辑器的相关文章

在 Vue 项目中引入 tinymce 富文本编辑器

项目中原本使用的富文本编辑器是 wangEditor,这是一个很轻量.简洁编辑器 但是公司的业务升级,想要一个功能更全面的编辑器,我找了好久,目前常见的编辑器有这些: UEditor:百度前端的开源项目,功能强大,基于 jQuery,但已经没有再维护,而且限定了后端代码,修改起来比较费劲 bootstrap-wysiwyg:微型,易用,小而美,只是 Bootstrap + jQuery... kindEditor:功能强大,代码简洁,需要配置后台,而且好久没见更新了 wangEditor:轻量.

在vue中使用tinymce富文本编辑器,解决tinymce在dialog对话框中层级太低的问题

1.安装 npm install tinymce -S 2.把node_modules\tinymce里面的文件 包括tinymce文件夹 全部复制到static文件夹下面,如下图 3.tinymce默认是英文界面,还需要下载一个中文语言包zh_CN.js https://www.tiny.cloud/get-tiny/language-packages/ 在tinymce文件夹下新建langs文件夹,将下载好的语言包放到langs文件夹下面如图  4.在main.js中引入tinymce  5

Vue 中使用UEditor富文本编辑器-亲测可用-vue-ueditor-wrap

其中UEditor中也存在不少错误,再引用过程中. 但是UEditor相对还是比较好用的一个富文本编辑器. vue-ueditor-wrap说明 Vue + UEditor + v-model 双向绑定.之所以有这个 repo 的原因是: ?1.UEditor 依然是国内使用频率极高的所见即所得编辑器而 Vue 又有着广泛的使用,所以将两者结合使用,是很多 Vue 项目开发者的切实需求. ?2.目前没有发现满足这种需求,而使用又很方便的 repo.有的可能也只是简单的暴露一个 UEditor 的

vue中使用kindeditor富文本编辑器2

第一步,下载依赖 yarn add kindeditor 第二步,建立kindeditor.vue组件 <template> <div class="kindeditor"> <textarea :id="id" name="content" v-model="outContent"></textarea> </div> </template> <s

粗暴将tinymce富文本编辑器整合到vue项目

之前我使用的tinymce富文本编辑器,版本时4版本的,一些新的功能使用不上,现在想把5版本的tinymce整合进项目,之前tinymce是通过cdn的形式引入的,不会占用项目体积, 但是又想使用tinymce的新功能,比如加入第三方插件什么的,地图插件,设置行高,多图片上传等,都是很吸引人的功能,于是打算先不顾文件体积,将tinymce整个文件拷贝到项目里,这样方便插入第三方插件, 中文官网:http://tinymce.ax-z.cn/configure/integration-and-se

Vue项目增加TinyMec富文本编辑器组件

TinyMec富文本编辑器开源,而且开发人员对他的支持比较好,所以选用了它 https://liubing.me/vue-tinymce-5.html 基于Vue CLI 3脚手架搭建的项目整合tinymce 5富文本编辑器,vue cli 2版本及tinymce 4版本参考:https://blog.csdn.net/liub37/article/details/83310879做了些小修改,详情见github Github:https://github.com/liub1934/vue-us

vue集成百度UEditor富文本编辑器

在前端开发的项目中.难免会遇到需要在页面上集成一个富文本编辑器.那么.如果你有这个需求.希望可以帮助到你 vue是前端开发者所追捧的框架,简单易上手,但是基于vue的富文本编辑器大多数太过于精简.于是我将百度富文本编辑器放到vue项目中使用.效果图如下 废话不多说. 1.使用vue-cli构建一个vue项目.然后下载UEditor源码.地址:http://ueditor.baidu.com/website/ 把项目复制到vue项目的static文件下.目的是让服务可以访问到里面的文件,打开UEd

在网站中使用UEditor富文本编辑器

UEditor是由百度WEB前端研发部开发的所见即所得的开源富文本编辑器,具有轻量.可定制.用户体验优秀等特点. 官网链接 进入到下载页面,选择相应的版本下载 这里我们使用ASP.NET开发,所以选择.NET版本. 将文件解压后,目录结构如下: 将外部js引入到页面中 <script src="Assets/js/ueditor/ueditor.config.js" type="text/javascript"></script> <s

vue2.0项目中使用Ueditor富文本编辑器示例

最近在vue项目中需要使用富文本编辑器,于是将Ueditor集成进来,作为公共组件.在线预览:https://suweiteng.github.io/vue2-management-platform/#/editor项目地址:https://github.com/suweiteng/vue2-management-platform 记得在右上角点个赞哦~ 1.放入静态资源并配置 首先把官网下载的Ueditor资源,放入静态资源src/static中.修改ueditor.config.js中的wi