vue-quill-editor 富文本编辑器(可上传视频图片),组件挂载的方式实现

news2025/2/23 2:45:24

1.安装

npm install vue-quill-editor --save
npm install quill-image-drop-module --save     
npm install quill-image-resize-module --save

2.在组件下面新增组件 QlEditor

在这里插入图片描述

(1)index.vue

<template>
  <div>
    <div id='quillEditorQiniu'>
      <!-- 基于elementUi的上传组件 el-upload begin-->
      <el-upload
        class="avatar-uploader"
        :action="uploadImgUrl"
        :accept="'image/*,video/*'"
        :show-file-list="false"
        :on-success="uploadEditorSuccess"
        :on-error="uploadEditorError"
        :before-upload="beforeEditorUpload"
        :headers="headers">
      </el-upload>
      <!-- 基于elementUi的上传组件 el-upload end-->
      <quill-editor
        class="editor"
        v-model="content"
        ref="customQuillEditor"
        :options="editorOption"
        @blur="onEditorBlur($event)"
        @focus="onEditorFocus($event)"
        @change="onEditorChange($event)"
      >
      </quill-editor>
    </div>
  </div>
</template>

<script>
import Vue from 'vue'
//引入quill-editor编辑器
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)

//实现quill-editor编辑器拖拽上传图片
import Quill from 'quill'
import { ImageDrop } from 'quill-image-drop-module'
Quill.register('modules/imageDrop', ImageDrop)

//实现quill-editor编辑器调整图片尺寸
import ImageResize from 'quill-image-resize-module'
Quill.register('modules/imageResize', ImageResize)

// 这里引入修改过的video模块并注册
import Video from './video'
Quill.register(Video, true)

//获取登录token,引入文件,如果只是简单测试,没有上传文件是否登录的限制的话,
//这个token可以不用获取,文件可以不引入,把上面对应的上传文件携带请求头  :headers="headers" 这个代码删掉即可
import {getToken} from "@/utils/auth";

const toolbarOptions = [
  ['bold', 'italic', 'underline', 'strike'],        // toggled buttons
  ['blockquote', 'code-block'],
  [{'header': 1}, {'header': 2}],               // custom button values
  [{'list': 'ordered'}, {'list': 'bullet'}],
  [{'script': 'sub'}, {'script': 'super'}],      // superscript/subscript
  [{'indent': '-1'}, {'indent': '+1'}],          // outdent/indent
  [{'direction': 'rtl'}],                         // text direction
  [{'size': ['small', false, 'large', 'huge']}],  // custom dropdown
  [{'header': [1, 2, 3, 4, 5, 6, false]}],
  [{'color': []}, {'background': []}],          // dropdown with defaults from theme
  [{'font': []}],
  [{'align': []}],
  ['link', 'image', 'video'],
  ['clean']                                         // remove formatting button
];
export default {
  name:"QlEditor",
  props: {
    /* 编辑器的内容 */
    value: {
      type: String,
      default: "",
    },
    /* 高度 */
    height: {
      type: Number,
      default: null,
    },
    /* 最小高度 */
    minHeight: {
      type: Number,
      default: null,
    },
    /* 只读 */
    readOnly: {
      type: Boolean,
      default: false,
    },
    /* 上传文件大小限制(MB) */
    fileSize: {
      type: Number,
      default: 5,
    },
  },
  model: {
    //v-model本质上是一个语法糖 这一步在父组件中可使用v-model
    prop: "value",
    event: "change",
  },
  data(){
    return {
      headers: {
        Authorization: "Bearer " + getToken(),
      },
      uploadImgUrl:process.env.VUE_APP_BASE_API + "/common/upload",
      uploadUrlPath:"没有文件上传",
      quillUpdateImg:false,
      content:'',    //最终保存的内容
      editorOption:{
        placeholder:'你想说什么?',
        modules: {
          imageResize: {
            displayStyles: {
              backgroundColor: 'black',
              border: 'none',
              color: 'white'
            },
            modules: [ 'Resize', 'DisplaySize', 'Toolbar' ]
          },
          toolbar: {
            container: toolbarOptions,  // 工具栏
            handlers: {
              'image': function (value) {
                if (value) {
                  document.querySelector('#quillEditorQiniu .avatar-uploader input').click()
                } else {
                  this.quill.format('image', false);
                }
              },
              'video': function (value) {
                if (value) {
                  document.querySelector('#quillEditorQiniu .avatar-uploader input').click()
                } else {
                  this.quill.format('video', false);
                }
              },
            }
          }
        }
      },
    }
  },
  methods:{
    //上传图片之前async
    beforeEditorUpload(res, file){
      //显示上传动画
      this.quillUpdateImg = true;
      //  const res1 = await uploadImage()
      // console.log(res1,'=====');
      // this.$emit('before',res, file)
      console.log(res);
      console.log(file);
    },
    // 上传图片成功
    uploadEditorSuccess(res, file) {

      //拼接出上传的图片在服务器的完整地址
      let url=res.url;
      let type=url.substring(url.lastIndexOf(".")+1);
      // 获取富文本组件实例
      let quill = this.$refs.customQuillEditor.quill;
      // 获取光标所在位置
      let length = quill.getSelection().index;
      // 插入图片||视频  res.info为服务器返回的图片地址
      if(type=='mp4' || type=='MP4'){
        window.jsValue=url;
        quill.insertEmbed(length, 'video', url)
      }else{
        quill.insertEmbed(length, 'image', url)
      }
      // 调整光标到最后
      quill.setSelection(length + 1)
      //取消上传动画
      this.quillUpdateImg = false;
    },
    // 上传(文件)图片失败
    uploadEditorError(res, file) {
      //页面提示
      this.$message.error('上传图片失败')
      //取消上传动画
      this.quillUpdateImg = false;
    },
    //上传组件返回的结果
    uploadResult:function (res){
      this.uploadUrlPath=res;
    },

    // 失去焦点事件
    onEditorBlur(quill) {
      console.log('editor blur!', quill)
    },
    // 获得焦点事件
    onEditorFocus(quill) {
      console.log('editor focus!', quill)
    },
    // 准备富文本编辑器
    onEditorReady(quill) {
      console.log('editor ready!', quill)
    },
    // 内容改变事件
    onEditorChange({ quill, html, text }) {
      console.log('editor change!', quill, html, text)
      this.content = html
    }

  },
  created () {
  },
  //只执行一次,加载执行
  mounted () {
    console.log("开始加载")
    // 初始给编辑器设置title
  },
  watch: {
    value: {
      handler(val) {
        if (val !== this.content) {
          this.content = val === null ? "" : val;
        }
      },
      immediate: true,
    },
  },
}
</script>

<style>
.editor {
  line-height: normal !important;
  height: 400px;
  margin-bottom: 50px;
}
.ql-snow .ql-tooltip[data-mode="link"]::before {
  content: "请输入链接地址:";
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
  border-right: 0px;
  content: "保存";
  padding-right: 0px;
}

.ql-snow .ql-tooltip[data-mode="video"]::before {
  content: "请输入视频地址:";
}

.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
  content: "14px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {
  content: "10px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {
  content: "18px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {
  content: "32px";
}

.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
  content: "文本";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
  content: "标题1";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
  content: "标题2";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
  content: "标题3";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
  content: "标题4";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
  content: "标题5";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
  content: "标题6";
}

.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
  content: "标准字体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
  content: "衬线字体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
  content: "等宽字体";
}
</style>

(2)video.js

import  { Quill }  from 'vue-quill-editor'

// 源码中是import直接倒入,这里要用Quill.import引入
const BlockEmbed = Quill.import('blots/block/embed')
// const Link = Quill.import('formats/link')

const ATTRIBUTES = ['height', 'width']

class Video extends BlockEmbed {
  static create (value) {
    const node = super.create(value)
    // console.log("js文件"+ window.jsValue)
    // 添加video标签所需的属性
    node.setAttribute('controls', 'controls') // 控制播放器
    //删除原生video的控制条的下载或者全屏按钮的方法
    //<video controls controlsList='nofullscreen nodownload noremote footbar' ></video>
    //不用哪个在下面加上哪个
    node.setAttribute('controlsList', 'nofullscreen') // 控制删除
    node.setAttribute('type', 'video/mp4')
    node.setAttribute('style', 'object-fit:fill;width: 100%;')
    node.setAttribute('preload', 'auto')    // auto - 当页面加载后载入整个视频  meta - 当页面加载后只载入元数据  none - 当页面加载后不载入视频
    node.setAttribute('playsinline', 'true')
    node.setAttribute('x-webkit-airplay', 'allow')
    // node.setAttribute('x5-video-player-type', 'h5') // 启用H5播放器,是wechat安卓版特性
    node.setAttribute('x5-video-orientation', 'portraint') // 竖屏播放 声明了h5才能使用  播放器支付的方向,landscape横屏,portraint竖屏,默认值为竖屏
    node.setAttribute('x5-playsinline', 'true') // 兼容安卓 不全屏播放
    node.setAttribute('x5-video-player-fullscreen', 'true')    // 全屏设置,设置为 true 是防止横屏
    node.setAttribute('src', window.jsValue)
    return node
  }

  static formats (domNode) {
    return ATTRIBUTES.reduce((formats, attribute) => {
      if (domNode.hasAttribute(attribute)) {
        formats[attribute] = domNode.getAttribute(attribute)
      }
      return formats
    }, {})
  }

  // static sanitize (url) {
  //
  //      // eslint-disable-line import/no-named-as-default-member
  // }

  static value (domNode) {
    return domNode.getAttribute('src')
  }

  format (name, value) {
    if (ATTRIBUTES.indexOf(name) > -1) {
      if (value) {
        this.domNode.setAttribute(name, value)
      } else {
        this.domNode.removeAttribute(name)
      }
    } else {
      super.format(name, value)
    }
  }

  html () {
    const { video } = this.value()
    return `<a href="${video}" rel="external nofollow" >${video}</a>`
  }
}
Video.blotName = 'video' // 这里不用改,楼主不用iframe,直接替换掉原来,如果需要也可以保留原来的,这里用个新的blot
Video.className = 'ql-video'
Video.tagName = 'video' // 用video标签替换iframe

export default Video

3.main.js

import Vue from 'vue'
import QlEditor from "@/components/QlEditor"
// 全局组件挂载
Vue.component('QlEditor', QlEditor)

4.vue.config.js中加入

const webpack = require('webpack');
new webpack.ProvidePlugin({
  'window.Quill': 'quill/dist/quill.js',
  'Quill': 'quill/dist/quill.js'
}),

加入位置如图
在这里插入图片描述
参考:https://www.cnblogs.com/wihimi/p/14246911.html

5.页面使用

<el-form-item label="内容">
  <QlEditor v-model="form.content" ref="qlEditorRef" :min-height="192"/>
</el-form-item>

<script>
/** 提交按钮 */
submitForm() {
  this.$refs["form"].validate(valid => {
    this.form.content=this.$refs.qlEditorRef.content;
  });
},
</script>

6.上传效果图

在这里插入图片描述
参考:https://www.jb51.net/article/264842.htm

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1565775.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

邀请函 | 第89届CMEF即将开幕,我们上海见!

2024年4月11日~14日&#xff0c;第89届中国国际医疗器械&#xff08;春季&#xff09;博览会&#xff08;简称&#xff1a;CMEF&#xff09;将在上海国家会展中心举行&#xff0c;届时飞凌嵌入式将隆重亮相&#xff0c;展出适用于IVD、医疗影像、生命体征监测等医疗设备的嵌入式…

划重点!实物黄金和现货黄金的区别

有人说虽然现货黄金不是实物黄金&#xff0c;但却胜于实物黄金&#xff0c;我们认为如果从投资的便利性&#xff0c;以及潜的获利空间这两个主要的方面来说&#xff0c;上述的观点是相当正确的。但投资者在正式参与之前&#xff0c;最好还是认真了解一下实物黄金和现货黄金的主…

上位机图像处理和嵌入式模块部署(qmacvisual亮度检测)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们说过&#xff0c;在机器视觉当中&#xff0c;对于光源的处理要非常小心。这里面不仅包括了选择什么样的光源&#xff0c;还取决于怎样使用…

软件设计不是CRUD(17):低耦合模块设计理论——总结

本文将已经介绍的低耦合设计理论梳理成一张思维导图&#xff0c;便于各位读者下载浏览。感兴趣但未阅读VIP文章的读者&#xff0c;可以解锁相关详细内容。&#xff08;第9篇——第16篇&#xff09; &#xff08;可直接保存到本地后&#xff0c;放大阅读&#xff09; 从后文开…

UE4_破碎插件的蓝图节点_Apply Radius Damage

一、知识点 Apply Radius Damage:破碎组件所带的蓝图节点。 二、使用方法&#xff1a; 1、设置——插件&#xff0c;搜索destruction&#xff0c;找到 Apex Destruction&#xff0c;勾选已启用。重启虚幻编辑器。 2、这样右键操作就有创建可破坏的网格体菜单&#xff0c;将do…

C++初学者:优雅创建第一个窗口

我想学习C做一些实用的程序&#xff0c;但是我不想在软件界面上花太多的时间&#xff0c;可是每每就是界面影响我的思绪。 今天学习C类的包装知识&#xff0c;终于整出了一个我的界面类&#xff0c;虽然封装水平很弱&#xff0c; 这次就用这个类&#xff0c;写了自己工作上常用…

资源优化题

资源平滑类资源优化题 不改变关键路径的情况下&#xff0c;如何避免资源扎堆使用。 破解思路: 1、能串行尽量串行。 2、一定要并行&#xff0c;尽量躲开跟人数多的活动并行。 3、如何躲开&#xff0c;就靠时差来实现。 例题1&#xff1a;某工程包括A、B、C、D、E、F、G七项工作…

Windows搭建Lychee图片管理系统结合内网穿透实现公网访问本地图床

文章目录 1.前言2. Lychee网站搭建2.1. Lychee下载和安装2.2 Lychee网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 1.前言 图床作为图片集中存放的服务网站&#xff0c;可以看做是云存储的一部分&#xff0c;既可…

Java就近原则和this关键字

Java 中的就近原则和 this 关键字有着密切的关系&#xff0c;特别是在处理成员变量与方法参数同名的情况下。就近原则指的是在同一作用域下&#xff0c;优先使用最近声明的变量或参数。 在 Java 中&#xff0c;如果一个方法的参数与类的成员变量同名&#xff0c;为了明确指示要…

E5071C是德科技E5071C网络分析仪

181/2461/8938产品概述&#xff1a; E5071C ENA 矢量网络分析仪&#xff0c;9 kHz 至 20 GHz&#xff0c;配有增强型 TDR 测量选件。E5071C 网络分析仪具有较高的射频性能和较快的速度&#xff0c;并具有宽频率范围和全面的功能。它是制造和研发工程师们测试频率范围在 20 GHz…

Redis 全景图(2)---- 关于 Redis 的三“高”

前言 我们继续写第一篇文章没写完的。其实我也不想将我写的一篇 Redis 文章分成几篇中短文来写&#xff0c;但是没办法&#xff0c;我一次写个1万字&#xff0c;会限流&#xff0c;所以将就一下吧。 上篇文章我用了 Redis 的6大模块这个思路来描绘我脑子中的 Redis。其实这6大…

生女儿一定要把她打扮成自己喜欢的样子

不仅女儿喜欢我也很喜欢 一套上身满满的美式学院风 有蓝、杏两种颜色 条纹短袖衬衫搭配短款百褶裙 简约百搭&#xff0c;时尚单品 全棉面料&#xff0c;舒适透气

2024年第三期丨全国高校大数据与人工智能师资研修班邀请函

2024年第三期 杭州线下班 数据采集与机器学习实战&#xff08;Python&#xff09; 线上班 八大专题 大模型技术与应用实战 数据采集与处理实战&#xff08;Python&八爪鱼&#xff09; 大数据分析与机器学习实战&#xff08;Python&#xff09; 商务数据分析实战&…

windows无法使用hadoop报错:系统找不到路径

在windows下安装hadoop-3.1.4,进行环境变量配置后&#xff0c;打开window命令行窗口测试hadoop命令&#xff0c;报错&#xff0c;如图所示&#xff1a; 方案&#xff1a;由于JAVA_HOME路径有空格导致&#xff0c;可修改hadoop下\etc\hadoop\hadoop_env.cmd文档中set JAVA_HOME以…

提效提速的快捷回复工具

在数字化交流日益增长的今天&#xff0c;客服工作显得尤为重要。为了提升对话质量和回复速度&#xff0c;同时减少重复劳动&#xff0c;我同事给我介绍了一款快捷回复工具&#xff0c;叫做客服宝聊天助手。我用了几天真心觉得好好用&#xff0c;今天特地分享这个软件给你们&…

Kubernetes(k8s):如何进行 Kubernetes 集群健康检查?

Kubernetes&#xff08;k8s&#xff09;&#xff1a;如何进行 Kubernetes 集群健康检查&#xff1f; 一、节点健康检查1、使用 kubectl 查看节点状态2、查看节点详细信息3、检查节点资源使用情况 2、Pod 健康检查2.1、 使用 kubectl 查看 Pod 状态2.2、 查看特定 Pod 的详细信息…

弱电工程有哪些系统?一站式解决方案

随着科技的不断进步&#xff0c;现代建筑不仅仅是砖石和水泥的堆砌&#xff0c;它们已经转化为拥有高度智能化的复杂结构。在这些建筑的核心&#xff0c;弱电工程扮演着至关重要的角色。今天&#xff0c;我们将深入探讨弱电工程的各个组成部分以提供的解决方案。 弱电工程涵盖…

【软路由】iStoreOS全量备份或数据迁移思路

背景&#xff1a;之前是在我的i3小主机上面搭建了iStoreOS&#xff0c;因为有段时间爱折腾&#xff0c;于是乎不知道什么情况就造成首页无法登录&#xff0c;改了的东西无法回滚&#xff0c;好在使用“万能重启”法又可以登录了&#xff0c;于是我就在想把这玩意定期备份一下。…

PCB的电气/物理特性检查项目需思考的问题

在PCB设计、制造和装配过程中&#xff0c;为确保产品性能和质量&#xff0c;电子工程师必须进行电气特性和物理特性检查&#xff0c;然而对很多新人来说如何高效进行检查是个难题&#xff0c;所以下面将分别探讨这些检查时需要考虑的问题。 1、PCB电气特性检查项目①导线参数分…

小学生古诗文大会往届真题测一测和独家详细解析(1-4期)

最近&#xff0c;古诗文大会主办方已经通过官微发布往期真题测一测&#xff0c;为2024年的小学生古诗文大会预热。主办方发布在官微的往期真题测一测形式是每期发布四道题目&#xff0c;两道单选题、两道填空题&#xff0c;孩子们可以在留言区回复答案。不过主办方并没有公布答…