vue使用富文本编辑器:vue-quill-editor粘贴图片+图片上传服务器+预览图片

2023-10-26

引入vue-quill-editor
  1. 初始化vue-quill-editor
npm install vue-quill-editor --save
  1. 部分页面引入组件
  import 'quill/dist/quill.core.css'
  import 'quill/dist/quill.snow.css'
  import 'quill/dist/quill.bubble.css'
  import {quillEditor} from 'vue-quill-editor'

全局引入组件

  • main.js文件中
import  VueQuillEditor from 'vue-quill-editor'
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
Vue.use(VueQuillEditor)
根据需求自定义富文本
  • 创建QuillEditorForm.vue和toolbarOptions.js,抽象成一个新的组件
  • 需求:图片通过右键粘贴、点击上方图片上传按钮进行上传。由于组件默认将图片转为base64再放入内容中,如果图片比较大的话,富文本的内容就会很大,即使图片不大,只要图片较为多,篇幅较长,富文本的内容也变大影响数据传递。所以只能传递到服务器上,然后将url赋值给img的src。监听粘贴事件自定义上传(这个解决方案想了很久)
  • show me the code
<template>
  <!--富文本编辑器-->
  <div class="app-container">
    <div class="avatar">
      <!-- 图片上传组件辅助-->
      <el-upload
        id="quill-upload"
        action="上传的路径"
        ref="annexUpload"
        name="content"
        :limit="1"
        list-type="picture"
        :on-exceed="handleExceed"
        :on-error="handleError"
        :on-success="uploadAttachment"
        :file-list="uploadList"
        :with-credentials="true"
        :auto-upload="true">
      </el-upload>
    </div>
    <el-row v-loading="quillUpdateImg">
      <div class="edit_container">
        <quill-editor
          :disabled="disableFlag"
          v-model="detailContent"
          ref="myQuillEditor"
          :options="editorOption"
          @change="onEditorChange($event)">
        </quill-editor>
      </div>
    </el-row>
  </div>

</template>
<script>
  import 'quill/dist/quill.core.css'
  import 'quill/dist/quill.snow.css'
  import 'quill/dist/quill.bubble.css'
  import {quillEditor} from 'vue-quill-editor'
  import toolbarOptions from './toolbarOptions'

  export default {
    name: 'QuillEditorForm',
    components: {
      quillEditor
    },
    props: ['entity', 'disableFlag', 'problemDescribe'],
    data() {
      return {
        quillUpdateImg: false, // 根据图片上传状态来确定是否显示loading动画,刚开始是false,不显示
        uploadList: [],
        detailContent: '', // 富文本内容
        // 富文本编辑器配置
        editorOption: {
          placeholder: '',
          theme: 'snow',  // or 'bubble'
          modules: {
            toolbar: {
              container: toolbarOptions,  // 工具栏
              handlers: {
                'image': function(value) {
                  if (value) {
                  // 绑定上传图片按钮
                    document.querySelector('#quill-upload input').click()
                  } else {
                    this.quill.format('image', false)
                  }
                }
              }
            }
          }
        }
      }
    },
    mounted() {
      //  自定义粘贴图片功能
      let quill = this.$refs.myQuillEditor.quill
      if (!this.disableFlag) {
        toolbarOptions[0] = ['image']
      }
      this.$forceUpdate()
      quill.root.addEventListener('paste', evt => {
        if (evt.clipboardData && evt.clipboardData.files && evt.clipboardData.files.length) {
          evt.preventDefault();
          [].forEach.call(evt.clipboardData.files, file => {
            if (!file.type.match(/^image\/(gif|jpe?g|a?png|bmp)/i)) {
              return
            }
            this.uploadToServer(file, (res) => {
              // 获取光标内容
              var range = quill.getSelection()
              if (range) {
                this.uploadAttachment(res, file, null)
                //  将光标移动到图片后面
                this.$refs.myQuillEditor.quill.setSelection(range.index + 1)
              }
            })
          })
        }
      }, false)
    },
    methods: {
      uploadToServer(file, callback) {
        var xhr = new XMLHttpRequest()
        var formData = new FormData()
        formData.append('content', file)
        xhr.open('post', '服务器路径')
        xhr.withCredentials = true
        xhr.responseType = 'json'
        xhr.send(formData)
        xhr.onreadystatechange = () => {
          if (xhr.readyState === 4 && xhr.status === 200) {
            callback(xhr.response)
          }
        }
      },
      handleExceed(files, fileList) {
        this.$message.warning(`当前限制一次性上传最多 1 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`)
      },
      handleError(err, file, fileList) {
        this.$message.warning(`文件上传失败,请尝试重新上传,文件失败原因为: ${err}`)
      },
      uploadAttachment(response, file, fileList) {
        // 保存文件信息
        if (response.status.code === 0) {
          // 获取富文本组件实例
          let quill = this.$refs.myQuillEditor.quill
          // 获取光标所在位置
          let length = quill.getSelection().index
          // 插入图片  res.info为服务器返回的图片地址
          quill.insertEmbed(length, 'image', response.url)
          quill.setSelection(length + 1)
          let fileType = null
          if (file.raw && file.raw.type) {
            fileType = file.raw.type
          } else {
            fileType = file.type
          }
          let params = {}
          params = {
           // 保存文件上传的参数
          }
          workService.create(params).then((res) => {
            this.$message.success('上传成功')
          }).catch((err) => {
            this.$message.error(err)
          })
        } else {
          this.$message.error(response.status.message)
        }
        // 清空文件列表
        this.uploadList = []
      },
      onEditorChange(e) {
        this.$emit('change', e.html)
      }
    }
  }
</script>

<style scoped>

  .quill-editor {
    min-width: 600px;
    max-width: 700px;
    max-height: 250px;
    overflow: auto;
  }

  .avatar {
    display: none;
  }

  /deep/ .ql-disabled {
    background-color: #F5F7FA;
    border-color: #E4E7ED;
    color: #C0C4CC;
    cursor: not-allowed;
  }

  /*.edit_container {*/
  /*  position: relative;*/
  /*}*/
</style>

toolbarOptions.js

const toolbarOptions = [
  // ['bold', 'italic', 'underline', 'strike'],
  ['blockquote', 'code-block'],
  [{'header': 1}, {'header': 2}],
  // [{'list': 'ordered'}, {'list': 'bullet'}],
  [{'script': 'sub'}, {'script': 'super'}],
  [{'indent': '-1'}, {'indent': '+1'}],
  [{'direction': 'rtl'}],
  [{'size': ['small', false, 'large', 'huge']}],
  [{'header': [1, 2, 3, 4, 5, 6, false]}],
  // [{'color': []}, {'background': []}],
  // [{'font': []}],
  // [{'align': []}],
  // ['link', 'image', 'video'],
  ['clean']
]
export default toolbarOptions
// 只能粘贴原文本
      handleCustomMatcher(node, Delta) {
        let ops = []
        Delta.ops.forEach(op => {
          if (op.insert && typeof op.insert === 'string') {
            // 如果粘贴了图片,这里会是一个对象,所以可以这样处理
            ops.push({
              insert: op.insert
            })
          }
        })
        Delta.ops = ops
        return Delta
      },
拓展
npm install quill-image-resize-module quill-image-drop-module --save
// 引入
import { ImageDrop } from 'quill-image-drop-module'
import ImageResize from 'quill-image-resize-module'
Quill.register('modules/imageDrop', ImageDrop)
Quill.register('modules/imageResize', ImageResize)
// build/webpack.base.conf.js文件
const webpack = require('webpack')
plugins: [
   new webpack.ProvidePlugin({
       'window.Quill': 'quill/dist/quill.js',
       'Quill': 'quill/dist/quill.js'
 })
]
// 放在editorOption里面
 history: {
              delay: 1000,
              maxStack: 50,
              userOnly: false
            },
            imageDrop: true,
            imageResize: {
              displayStyles: {
                backgroundColor: 'black',
                border: 'none',
                color: 'white'
              },
              modules: [ 'Resize', 'DisplaySize', 'Toolbar' ]
            }
兼容性问题

兼容IE10以上

20211012更新富文本框

新增功能:点击预览图片

npm install v-viewer --save
<template>
  <!--富文本编辑器-->
  <div class="app-container">
    <div class="avatar">
      <!-- 图片上传组件辅助-->
      <el-upload
        id="quill-upload"
        class="avatar-uploader"
        action="/v1/file/upload"
        ref="annexUpload"
        name="content"
        :limit="1"
        list-type="picture"
        :on-exceed="handleExceed"
        :on-error="handleError"
        :on-success="uploadAttachment"
        :file-list="uploadList"
        :with-credentials="true"
        :auto-upload="true">
        <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
      </el-upload>
    </div>
    <el-row v-loading="quillUpdateImg">
      <div v-if="!disableFlag">
        <quill-editor
          v-if="loaded"
          :disabled="disableFlag"
          v-model="detailContent"
          ref="myQuillEditor"
          :options="editorOption"
          @focus="onEditorFocus($event)"
          @change="onEditorChange($event)">
        </quill-editor>
      </div>
      <div v-else>
        <div style="display: flex;">
          <span @click="showViewer = !showViewer" style="float: right;cursor: pointer;margin-right: 20px;">
            <i :class="!showViewer ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"   class="arrow"></i>
          </span>
        </div>
        <viewer v-if="showViewer" :images="[]" class="viewer" ref="viewer">
          <div v-html="detailContent"></div>
        </viewer>
      </div>
    </el-row>
  </div>

</template>
<script>
  import 'viewerjs/dist/viewer.css'
  import Viewer from 'v-viewer/src/component'
  import 'quill/dist/quill.core.css'
  import 'quill/dist/quill.snow.css'
  import 'quill/dist/quill.bubble.css'
  import Quill from 'quill'
  import {quillEditor} from 'vue-quill-editor'
  import { ImageDrop } from './ImageDrop'
  import ImageResize from 'quill-image-resize-module'
  Quill.register('modules/imageDrop', ImageDrop)
  Quill.register('modules/imageResize', ImageResize)
  export default {
    name: 'QuillEditorForm',
    components: {
      quillEditor, Viewer
    },
    props: ['entity', 'disableFlag', 'value'],
    data() {
      return {
        quillUpdateImg: false, // 根据图片上传状态来确定是否显示loading动画,刚开始是false,不显示
        serverUrl: '',  // 这里写你要上传的图片服务器地址
        header: {token: sessionStorage.token},  // 有的图片服务器要求请求头需要有token之类的参数,写在这里
        uploadList: [],
        activeNames: ['1'],
        detailContent: '', // 富文本内容
        initFlag: true,
        // 富文本编辑器配置
        editorOption: {
          placeholder: '',
          theme: 'snow',  // or 'bubble'
          modules: {
            imageDrop: true,
            imageResize: {
              displayStyles: {
                backgroundColor: 'black',
                border: 'none',
                color: 'white'
              },
              modules: [ 'Resize', 'DisplaySize', 'Toolbar' ]
            },
            toolbar: {
              container: [
                [], // 预留图片上传
                ['blockquote', 'code-block'], // 引用,代码块
                [{'list': 'ordered'}, { 'list': 'bullet' }],  // 列表
                [{'header': [1, 2, 3, false]}], // 几级标题(1~6)
                // [{'header': 1}, {'header': 2}], // 标题,键值对的形式;1、2表示字体大小
                [{'size': ['small', false, 'large']}], // 字体大小('small', false, 'large', 'huge')
                [{ 'color': [] }, { 'background': [] }],
                ['bold', 'italic', 'underline'], // 加粗,斜体,下划线,删除线('strike')
                // [{'script': 'sub'}, {'script': 'super'}],  // 上下标
                [{'indent': '-1'}, {'indent': '+1'}], // 缩进
                // [{'direction': 'rtl'}], // 文本方向
                // [{ 'font': [] }], // 字体
                // [{ 'align': [] }], // 对齐方式
                ['clean'] // 清除字体样式
              ],  // 工具栏
              handlers: {
                image: (value) => {
                  if (value) {
                    this.$refs.annexUpload.$el.querySelector('#quill-upload input').click()
                  } else {
                    this.$refs.myQuillEditor.quill.format('image', false)
                  }
                }
              }
            }
          }
        },
        quillInstance: null,
        loaded: true,
        showViewer: true
      }
    },
    watch: {
      value: {
        handler(val) {
          this.detailContent = val
          this.$forceUpdate()
        },
        deep: true,
        immediate: true
      },
      detailContent: {
        handler(val) {
          if (val && this.initFlag && this.$refs.myQuillEditor && this.$refs.myQuillEditor.quill) {
            // 初始化页面,取消focus状态
            this.$refs.myQuillEditor.quill.enable(false)
            this.detailContent = val
            this.$emit('input', val)
            setTimeout(() => {
              this.$refs.myQuillEditor.quill.enable(true)
            })
            this.initFlag = false
          } else {
            this.detailContent = val
            this.$emit('input', val)
          }
        },
        deep: true,
        immediate: true
      },
      disableFlag: {
        handler(val) {
          if (!val) {
            this.editorOption.modules.toolbar.container[0] = ['image']
          }
          this.loaded = false
          this.$nextTick(() => {
            this.loaded = true
            this.$nextTick(() => {
              // console.log('update', this, val, this.$refs.myQuillEditor)
              if (!this.$refs.myQuillEditor) {
                return
              }
              //  自定义粘贴图片功能
              this.quillInstance = this.$refs.myQuillEditor.quill
              this.quillInstance.root.removeEventListener('paste', this.handlePaste)
              this.quillInstance.root.addEventListener('paste', this.handlePaste, false)
            })
          })
        },
        immediate: true
      }
    },
    methods: {
      onEditorFocus(e) { // 用户自动获取焦点,不设置focus状态
        this.initFlag = false
      },
      handlePaste(evt) {
        if (evt.clipboardData && evt.clipboardData.files && evt.clipboardData.files.length) {
          evt.preventDefault();
          [].forEach.call(evt.clipboardData.files, file => {
            if (!file.type.match(/^image\/(gif|jpe?g|a?png|bmp)/i)) {
              return
            }
            this.uploadToServer(file, (res) => {
              // 获取光标内容
              var range = this.quillInstance.getSelection()
              if (range) {
                this.uploadAttachment(res, file, null)
                //  在当前光标位置插入图片
                // this.$refs.myQuillEditor.quill.insertEmbed(range.index, 'image', res.url)
                //  将光标移动到图片后面
                this.$refs.myQuillEditor.quill.setSelection(range.index + 1)
              }
            })
          })
        }
      },
      uploadToServer(file, callback) {
        var xhr = new XMLHttpRequest()
        var formData = new FormData()
        formData.append('content', file)
        xhr.open('post', '上传路径')
        xhr.withCredentials = true
        xhr.responseType = 'json'
        xhr.send(formData)
        xhr.onreadystatechange = () => {
          if (xhr.readyState === 4 && xhr.status === 200) {
            callback(xhr.response)
          }
        }
      },
      handleExceed(files, fileList) {
        this.$message.warning(`当前限制一次性上传最多 1 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`)
      },
      handleError(err, file, fileList) {
        this.$message.warning(`文件上传失败,请尝试重新上传,文件失败原因为: ${err}`)
      },
      uploadAttachment(response, file, fileList) {
        // 保存文件信息
        if (response.status.code === 0) {
          // 获取富文本组件实例
          let quill = this.$refs.myQuillEditor.quill
          // 获取光标所在位置
          let length = quill.getSelection().index
          // 插入图片  res.info为服务器返回的图片地址
          quill.insertEmbed(length, 'image', response.url)
          quill.setSelection(length + 1)
          let fileType = null
          if (file.raw && file.raw.type) {
            fileType = file.raw.type
          } else {
            fileType = file.type
          }
          let params = {}
          params = {
            // 保存文件上传的参数
          }
          service.createWorkOrderFile(params).then((res) => {
            this.$message.success('上传成功')
          }).catch((err) => {
            this.$message.error(err)
          })
        } else {
          this.$message.error(response.status.message)
        }
        // 清空文件列表
        this.uploadList = []
      },
      onEditorChange(e) {
        this.$emit('change', e.html)
      }
    }
  }
</script>

<style scoped>
  @import "../../assets/quill.css";
  .quill-editor {
    min-width: 657px;
    overflow: auto;
  }

  .avatar {
    display: none;
  }

  /deep/ .ql-disabled {
    background-color: #F5F7FA;
    border-color: #E4E7ED;
    color: #7f8185;
    cursor: not-allowed;
  }
  .viewer {
    border: 1px solid #ebeef5;
    padding: 12px 15px;
    min-width: 657px;
    max-width: 700px;
    max-height: 600px;
    overflow: auto;
  }
  .arrow {
    margin: 0 8px 0 auto;
    transition: transform .3s;
    font-weight: 300;
  }
</style>


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

vue使用富文本编辑器:vue-quill-editor粘贴图片+图片上传服务器+预览图片 的相关文章

  • OSQL 命令行工具

    1 什么是 Osql osql 工具是一个 Microsoft Windows 32 命令提示符工具 您可以使用它运行 Transact SQL 语句和脚本文件 osql 工具使用 ODBC 数据库应用程序编程接口 API 与服务器通讯 在

随机推荐

  • EulerOS常用命令参考

    最近在使用华为云的欧拉系统 发现命令手册比较难找 找了一下官方文档 有一份比较详细的在eSignt手册下 如下地址 需要请自行跳转 参考如下文档 EulerOS常用命令参考 eSight 20 1 维护指南 12 华为
  • 软件工程—白盒测试习题答案

    一 问题描述 为以下程序设计满足指定覆盖准则的测试用例 测试用例数量应尽可能少 其中 判定1 x gt 3 z lt 10 的真分支为b 假分支为c 判定2 x 4 y gt 5 的真分支为d 假分支为e 条件1 x gt 3 的两种取值由
  • 最近邻插值、双线性插值

    1 最近邻插值 越是简单的模型越适合用来举例子 我们就举个简单的图像 3X3 的256级灰度图 也就是高为3个象素 宽也是3个象素的图像 每个象素的取值可以是 0 255 代表该像素的亮度 255代表最亮 也就是白色 0代表最暗 即黑色 假
  • html input ng model,AngularJS ng-model用法及代码示例

    ngModel是绑定输入 选择和文本区域 并将所需的用户值存储在变量中的指令 我们可以在需要该值时使用该变量 在验证过程中也以某种形式使用它 我们可以将ngModel用于 输入文本 复选框 无线电 数 电子邮件 网址 日期 datetime
  • 人工智能研究的新前线:生成式对抗网络

    林懿伦 戴星原 李力 王晓 王飞跃 来源 科学网 摘要 生成式对抗网络 Generative adversarial networks GAN 是当前人工智能学界最为重要的研究热点之一 其突出的生成能力不仅可用于生成各类图像和自然语言数据
  • iptables 实现域名过滤

    1 需求 过滤指定的域名 网站 如www baidu com www bilibili com 2 实现方案 方案1 字符串过滤 iptables A FOWARD m string algo kmp string bilibili com
  • Dynamics 365 CRM Online 使用Ribbon Workbench 2016在view或form上添加按钮并实现CURD

    目标 Dynamics 365 CRM Online 使用Ribbon Workbench 2016在view或form上方添加按钮并根据View中所选中的记录实现CURD 内容步骤 1 安装Ribbon workbench 2016 可参
  • 『Asp.Net 组件』Asp.Net 服务器组件 内嵌CSS:将CSS封装到程序集中

    代码 using System using System Web using System Web UI using System Web UI HtmlControls using System Web UI WebControls na
  • ubuntu执行apt-get install出现E: Unable to locate package 问题

    报错原因 就是没找到你要安装的那个软件 解决方法 1 检查你要安装的软件名中 是不是吧英文的 写成了中文的 2 所要安装的那个软件已经不存在了 极有可能是改名了 所以你需要执行apt cache search 关键字 查找你所要安装的这类软
  • W800系列

    目录 续前文 调试前配置 下载调试 增加一个新的demo 目前存在的问题 待解决 win11系统提示调试错误 驱动问题解决步骤 供复现及参考分析用 调试仿真错误 T HeadDebugServer运行截图 续前文 W800系列 ST LIN
  • 用例的一些知识

    一 用例执行 说明 执行结果与用例的期望结果不一致 含义 为缺陷 提示 用例结果不通过为缺陷 需要进行缺陷管理 二 缺陷 01定义 软件中存在的各种问题 都为缺陷 简称bug 02缺陷标准 少功能 功能错误 多功能 缺少隐形功能 易用性 软
  • Android NDK 开发:实战案例

    0 前言 如果只学理论 不做实践 不踩踩坑 一般很难发现真正实践项目中的问题的 也比较难以加深对技术的理解 所以延续上篇 JNI 的实战Android NDK开发 JNI实战篇 这篇主要是一些 NDK 小项目的练习 由于这些项目网上都有 d
  • CGAL---点云处理

    CGAL是一款几何处理库 但是介绍其在点云中处理的资料比较少 现介绍一个专门介绍CGAL在点云数据中处理的网站 比包括常见的点云数据处理 功能包括 1 聚类 欧式聚类 2 配准 ICP 3 简化 格网 4 平滑 5 法向量估算 6 特征估算
  • C++ static的作用

    1 什么是static static 是C 中很常用的修饰符 它被用来控制变量的存储方式和可见性 2 为什么要引入static 函数内部定义的变量 在程序执行到它的定义处时 编译器为它在栈上分配空间 大家知道 函数在栈上分配的空间在此函数执
  • 鱼眼去锯齿

    include
  • hexo更换主题后出现问题:WARN No layout: index.html

    hexo更换主题后出现问题 WARN No layout index html hexo本地测试运行重启后页面空白 hexo g出现以上错误 错误原因 运行git clone 指令获得主题后 假设是NEXT主题 在theme主题下保存文件夹
  • 火狐不能同步的问题

    火狐不能同步的问题 解决方法 下载同版本的软件并且切换成同一个服务器类型 打开 帮助 gt 关于Firefox 查看连个软件版本号和上线时间是否一致 如果不一致 则下载最新版 同版本同上线时间 的软件 如果一致 右上角菜单 gt 选项 gt
  • 【状态估计】基于UKF法、AUKF法的电力系统三相状态估计研究(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码及数据 1 概述 基于UKF法和AUKF法的电力系统三相状
  • Unity 鼠标拖动旋转物体,并且物体不会越转越乱

    用Mathf Abs 绝对值 计算鼠标水平和竖直方向谁的位移更大 减少因为一丢丢的其他方向移动导致的物体微幅旋转影响后面物体旋转会越来越混乱 这样可以让物体旋转更好的单个方向进行旋转 代码如下 public float speed 2f v
  • vue使用富文本编辑器:vue-quill-editor粘贴图片+图片上传服务器+预览图片

    引入vue quill editor 初始化vue quill editor npm install vue quill editor save 部分页面引入组件 import quill dist quill core css impor