美文网首页H5技术栈
Tinymce富文本编辑器 在 vue 项目中的封装与使用 解决

Tinymce富文本编辑器 在 vue 项目中的封装与使用 解决

作者: 范哲文 | 来源:发表于2018-11-12 13:38 被阅读4033次

    写在前面


    作者简书地址
    在我们前端开发中有时候需要对文章或者商品详情之类的进行编辑,需要图文并茂,文字排版.......普通的form表单已经不能担此重任。
    本文呢?讲的如何在vue项目中如何封装富文本编辑器 Tinymce 成组件 如何上传图片 文件
    公司采用的是阿里云oss上传 所以以这个为例
    简书首发网址

    tinymce.gif

    第三步将整个tinymce封装的代码贴出
    第四步对代码进行解析,描述一下为什么这么做
    第五步将贴出再其它组件调用tinymce的注意事项与使用方法

    具体步骤

    一 、下载tinymce 原数据包 并拷贝到 static文件夹下

    npm i tinymce -S
    

    1、从node_modules中找到tinymce数据包
    2、拷贝tinymce数据到static文件夹下


    image.png

    二、在componts文件夹下建个Tinymce组件


    image.png
    三、编写tinymce组件
    <template>
      <div>
        <input type="file" id="photoFileUpload" style="display: none" />
        <textarea :id="Id"></textarea>
      </div>
    </template>
    <script>
    import { ossUpload, uploadImg } from '@/api/public'
    import '../../../static/tinymce/tinymce'
    export default {
      name: 'mceeditor',
      props: {
        value: {
          default: '',
          type: String
        },
        config: {
          type: Object,
          default: () => {
            return {
              theme: 'modern',
              height: 600
            }
          }
        },
        url: {
          default: '',
          type: String
        },
        accept: {
          default: 'image/jpeg, image/png',
          type: String
        },
        maxSize: {
          default: 2097152,
          type: Number
        }
      },
      data() {
        const Id = Date.now()
        return {
          Id: Id,
          myEditor: null,
          DefaultConfig: {
            // GLOBAL
            language: 'zh_CN', //汉化
            height: 500, //默认高度
            theme: 'modern', //默认主题
            menubar: true,
            toolbar: `styleselect | fontselect | formatselect | fontsizeselect | forecolor backcolor | bold italic underline strikethrough | insertfile link image | table | alignleft aligncenter alignright alignjustify | outdent indent | numlist bullist | preview removeformat  hr | paste code | undo redo | fullscreen `,//需要的工具栏
            plugins: `
                paste
                importcss
                image
                code
                table
                advlist
                fullscreen
                link
                lists
                textcolor
                colorpicker
                hr
                preview
              `,
            // CONFIG
            forced_root_block: 'p',
            force_p_newlines: true,
            importcss_append: true,
            // CONFIG: ContentStyle 这块很重要, 在最后呈现的页面也要写入这个基本样式保证前后一致, `table`和`img`的问题基本就靠这个来填坑了
            content_style: `
                *                         { padding:0; margin:0; }
                html, body                { height:100%; }
                img                       { max-width:100%; display:block;height:auto; }
                a                         { text-decoration: none; }
                iframe                    { width: 100%; }
                p                         { line-height:1.6; margin: 0px; }
                table                     { word-wrap:break-word; word-break:break-all; max-width:100%; border:none; border-color:#999; }
                .mce-object-iframe        { width:100%; box-sizing:border-box; margin:0; padding:0; }
                ul,ol                     { list-style-position:inside; }
              `,
            insert_button_items: 'image link | inserttable',
            // CONFIG: Paste
            paste_retain_style_properties: 'all',
            paste_word_valid_elements: '*[*]',        // word需要它
            paste_data_images: true,                  // 粘贴的同时能把内容里的图片自动上传,非常强力的功能
            paste_convert_word_fake_lists: false,     // 插入word文档需要该属性
            paste_webkit_styles: 'all',
            paste_merge_formats: true,
            nonbreaking_force_tab: false,
            paste_auto_cleanup_on_paste: false,
            // CONFIG: Font
            fontsize_formats: '10px 11px 12px 14px 16px 18px 20px 24px',
            // CONFIG: StyleSelect
            style_formats: [
              {
                title: '首行缩进',
                block: 'p',
                styles: { 'text-indent': '2em' }
              },
              {
                title: '行高',
                items: [
                  { title: '1', styles: { 'line-height': '1' }, inline: 'span' },
                  { title: '1.5', styles: { 'line-height': '1.5' }, inline: 'span' },
                  { title: '2', styles: { 'line-height': '2' }, inline: 'span' },
                  { title: '2.5', styles: { 'line-height': '2.5' }, inline: 'span' },
                  { title: '3', styles: { 'line-height': '3' }, inline: 'span' }
                ]
              }
            ],
            // FontSelect
            font_formats: `
                微软雅黑=微软雅黑;
                宋体=宋体;
                黑体=黑体;
                仿宋=仿宋;
                楷体=楷体;
                隶书=隶书;
                幼圆=幼圆;
                Andale Mono=andale mono,times;
                Arial=arial, helvetica,
                sans-serif;
                Arial Black=arial black, avant garde;
                Book Antiqua=book antiqua,palatino;
                Comic Sans MS=comic sans ms,sans-serif;
                Courier New=courier new,courier;
                Georgia=georgia,palatino;
                Helvetica=helvetica;
                Impact=impact,chicago;
                Symbol=symbol;
                Tahoma=tahoma,arial,helvetica,sans-serif;
                Terminal=terminal,monaco;
                Times New Roman=times new roman,times;
                Trebuchet MS=trebuchet ms,geneva;
                Verdana=verdana,geneva;
                Webdings=webdings;
                Wingdings=wingdings,zapf dingbats`,
            // Tab
            tabfocus_elements: ':prev,:next',
            object_resizing: true,
            // Image
            imagetools_toolbar: 'rotateleft rotateright | flipv fliph | editimage imageoptions'
          }
        }
      },
      methods: {
        setContent(content) {
          this.myEditor.setContent(content)
        },
        getContent() {
          return this.myEditor.getContent()
        },
        init() {
          const self = this
          window.tinymce.init({
            // 默认配置
            ...this.DefaultConfig,
            // 挂载的DOM对象
            selector: `#${this.Id}`,
            file_picker_types: 'file',
            //上传文件
            file_picker_callback: function (callback, value, meta) {
              let fileUploadControl = document.getElementById("photoFileUpload")
              fileUploadControl.click()
              fileUploadControl.onchange = function () {
                if (fileUploadControl.files.length > 0) {
                  let localFile = fileUploadControl.files[0]
                  ossUpload({ type: localFile.type }).then(res => {
                    uploadImg(res.data, localFile).then(res => {
                      if (res.code == 0) {
                        callback(res.data.name, { text: localFile.name, })
                        self.$emit('on-upload-complete', res)  // 抛出 'on-upload-complete' 钩子
                      } else {
                        callback()
                        self.$emit('on-upload-complete', res)  // 抛出 'on-upload-complete' 钩子
                      }
                    })
                  })
                } else {
                  alert('请选择文件上传')
                }
              }
            },
            // 图片上传
            images_upload_handler: function (blobInfo, success, failure) {
              if (blobInfo.blob().size > self.maxSize) {
                failure('文件体积过大')
              }
              if (self.accept.indexOf(blobInfo.blob().type) >= 0) {
                uploadPic()
              } else {
                failure('图片格式错误')
              }
    
              function uploadPic() {
                ossUpload({ type: "image/png" }).then(res => {
                  uploadImg(res.data, blobInfo.blob()).then(res => {
                    if (res.code == 0) {
                      success(res.data.name)
                      self.$emit('on-upload-complete', res)  // 抛出 'on-upload-complete' 钩子
                    } else {
                      failure('上传失败: ')
                      self.$emit('on-upload-complete', res)  // 抛出 'on-upload-complete' 钩子
                    }
                  })
                })
              }
            },
            // prop内传入的的config
            ...this.config,
            setup: (editor) => {
              self.myEditor = editor
              editor.on(
                'init', () => {
                  self.loading = true
                  self.$emit('on-ready')   // 抛出 'on-ready' 事件钩子
                  editor.setContent(self.value)
                  self.loading = false
                }
              )
              // 抛出 'input' 事件钩子,同步value数据
              editor.on(
                'input change undo redo', () => {
                  self.$emit('input', editor.getContent())
                }
              )
            }
          })
        }
      },
      mounted() {
        this.init()
      },
      beforeDestroy() {
        // 销毁tinymce
        this.$emit('on-destroy')
        window.tinymce.remove(`#${this.Id}`)
      },
    }
    </script>
    
    

    四、解析代码 择重点讲
    1.dom结构 主要有两个标签,input textrea
    input: 用来做模拟文件上传的,上传后文件的地址 以a标签存在。
    官网的demo中文件(附件)上传需要有php 和 .net环境支持 需要后台配合,效果会更好,貌似还需要收费。自己可以招后端小伙伴研究
    tinymce 官网附件地址:https://www.moxiemanager.com/demos/tinymce.php
    使用input标签的方法是我绞尽脑汁想出来的唯一替代方法,也是我封装出来与其他人不同的地方,这里所需要的参数和配置在以下两个参数中:
    file_picker_types: 'file', 参数
    file_picker_callback ,上传文件的钩子函数 在里面配置的上传文件函数即可
    textarea:用来初始化tinymce的容器

    2.import 引入 static文件下的 tinymce.js

    3.prop: 组件调用时可传入的配置参数。

    • url:是留出来给上传函数封装普通ajax的路径,因为我是oss上传 所以直接改造了images_upload_handler钩子
    • accept:是点击上传图片 可选择图片类型

    4.data部分

    * DefaultConfig 注意配置tinymce的默认项 可以根据自己的需求增减
    * myEditor 用来获取tinymce的setContent 和 getContent 两个实用api,观看代码它在 tinymce的setup阶段出现
    

    5.methods
    注意暴露出 两个实用api 再将tinymce初始化
    需要自己动手的地方 ⚠️

    * file_picker_types
    * file_picker_callback
    * images_upload_handler
    

    这三个地方就是最重要的 文件上传以及图片上传,根据自己的业务需求改造上传函数即可

    6.抛出了三个钩子函数给tinymce的父组件调用

    • on-ready
    • on-upload-complete
    • on-destroy

    五、组件调用

    <template>
      <div id="home" class="pd20">
        tinyMce的使用
        <tinymce ref="richText" v-model="content" @on-upload-complete="onEditorUploadComplete"></tinymce>
        <div class="mt20">
          <el-button type="primary" class="w100" @click="get">保存</el-button>
          <el-button type="primary" class="w100" @click="set">设置</el-button>
        </div>
      </div>
    </template>
    
    <script>
    import tinymce from '@/components/Tinymce'
    window.tinymce.baseURL = '/static/tinymce' //需要调用tinymce的组件中得加入这,不然会报错
    //this.$refs.richText.setContent//getContent 两个方法 获取与设置
    export default {
      name: 'Dashboard',
      components: {
        tinymce
      },
      data() {
        return {
          content: '欢迎来到首页'
        }
      },
      watch: {
    
      },
      computed: {
      },
      methods: {
        onEditorUploadComplete(res) {
          if (res.code == 0) {
            this.$message({
              type: 'success',
              message: '上传成功'
            })
          } else {
            this.$message({
              type: 'error',
              message: res.msg
            })
          }
        },
        set() {
          this.$refs.richText.setContent('设置内容')
        },
        get() {
          console.log(this.$refs.richText.getContent())
        },
      },
      created() {
    
      },
      mounted() {
    
      }
    }
    </script>
    
    <style lang="less" scoped>
    </style>
    
    

    补个知识点:
    vue 中的ref 相当于 dom节点中id的作用 显然是更强大,this.$refs.ref,是可以拿到这个这个名为ref的实例,可以调用这个ref组件的methods方法(俗称 :父组件调用子组件的方法)

    写在后面

    总结:本文也是参考了许多其他优秀的前端开发写的博客,路数也差不多,都是写到图片上传就点到为止了,所以相似度很。但是我极少看见其他人会把tinymce文件上传的这部分写出来,我也算是另辟蹊径(歪门邪道),把这个共享给大家。希望大家会喜欢。觉得有用的小伙伴可以给个心,给个关注,有不懂的地方可以评论我和私信我,有空会一一解答。也可以加扣(214395856)
    目前本人因要踏入node开发,学习egg.js 所以前端的文章会更新的较少了。如果有想到好的题材也会分享出来。
    简书首发网址

    相关文章

      网友评论

        本文标题:Tinymce富文本编辑器 在 vue 项目中的封装与使用 解决

        本文链接:https://www.haomeiwen.com/subject/hcsqfqtx.html