zoukankan      html  css  js  c++  java
  • vue中引入Tinymce富文本编辑器

    中文文档地址:http://tinymce.ax-z.cn/configure/integration-and-setup.php

    最近想在项目上引入一个富文本编辑器,之前引入过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,  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="{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/tinymce-all-in-one@4.9.3/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
        },
         {
          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_ 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>

    效果:

  • 相关阅读:
    图解MySQL | [原理解析] MySQL使用固定的server_id导致数据丢失【转】
    故障分析 | 记一次 MySQL 主从双写导致的数据丢失问题【转】
    MySQL8.0之XtraBackup【转】
    使用pt-table-checksum校验MySQL主从复制【转】
    MySQL基于 GTID 的多源复制【转】
    pyenv虚拟环境管理python多版本和软件库【转】
    MySQL SQL编写笔记
    linux安装oracle客户端【转】
    spring-cloud-gateway获取post请求body参数以及响应数据
    spring-webflux中Flux<DataBuffer> 转String
  • 原文地址:https://www.cnblogs.com/fqh123/p/11442461.html
Copyright © 2011-2022 走看看