首页 > 其他分享 >vue2+element+vue-quill-editor实现富文本框组件(使用链接引入视频+上传本地视频+上传本地图片)

vue2+element+vue-quill-editor实现富文本框组件(使用链接引入视频+上传本地视频+上传本地图片)

时间:2023-11-20 11:46:46浏览次数:44  
标签:picker 视频 snow value deep 本地 上传 ql before

参考文档:https://www.duidaima.com/Group/Topic/Vue/12272

前提不赘述,npm引入插件并全局导入

 

components文件夹下创建ArticleEditor.vue:

<template>
  <div class="">
    <!-- 富文本框 -->
    <quill-editor
      ref="myQuillEditor"
      v-bind:value="value"
      :placeholder="placeholder"
      @input="inputFun"
      class="editor"
      :options="editorOption"
    />
    <!-- 富文本编辑器中的上传图片控件 -->
    <el-upload
      class="avatar-uploader-img"
      :action="action"
      :show-file-list="false"
      :on-success="uploadImgSuccess"
      :before-upload="beforeUploadImg"
      :on-error="uploadImgError"
      :data="{ game: 'ppjt' }"
      :headers="headers"
    />
    <!-- <el-upload
      class="avatar-uploader-video"
      :action="action"
      :show-file-list="false"
      :on-success="uploadVideoSuccess"
      :before-upload="beforeUploadVideo"
      :on-error="uploadVideoError"
      :data="{ game: 'ppjt' }"
      :headers="headers"
    /> -->
    <div>
      <el-dialog
        :close-on-click-modal="false"
        width="800px"
        style="margin-top: 1px"
        title="视频上传"
        :visible.sync="videoDialog.show"
        append-to-body
        class="avatar-uploader-dialog"
        ref="dialog"
      >
        <el-tabs v-model="videoDialog.activeName">
          <el-tab-pane label="添加视频链接" name="first">
            <el-input
              v-model="videoDialog.videoLink"
              placeholder="请输入视频链接"
              clearable
            ></el-input>
            <el-button
              type="primary"
              size="small"
              style="margin: 20px 0px 0px 0px"
              @click="addVideoLink(videoDialog.videoLink)"
              >添加
            </el-button>
          </el-tab-pane>
          <el-tab-pane label="本地视频上传" name="second">
            <el-upload
              drag
              :action="action"
              accept="video/*"
              :show-file-list="false"
              :data="{ game: 'ppjt' }"
              :on-success="uploadVideoSuccess"
              :before-upload="beforeUploadVideo"
              :on-error="uploadVideoError"
              :multiple="false"
              :headers="headers"
            >
              <i class="el-icon-upload"></i>
              <div class="el-upload__text">点击上传</div>
            </el-upload>
          </el-tab-pane>
        </el-tabs>
      </el-dialog>
    </div>
  </div>
</template>
<script>
import Video from "../utils/video";
import { getToken } from "@/utils/auth";
import { Quill } from "vue-quill-editor";
Quill.register(Video, true);
// 自定义字体大小
// const Size = Quill.import("attributors/style/size");
// Size.whitelist = [false, "14px", "16px", "18px", "20px", "32px"];
// Quill.register(Size, true);
// 工具栏配置
const toolbarOptions = [
  ["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线
  ["blockquote", "code-block"], // 引用 代码块
  [{ header: 1 }, { header: 2 }], // 1、2 级标题
  [{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表
  [{ script: "sub" }, { script: "super" }], // 上标/下标
  [{ indent: "-1" }, { indent: "+1" }], // 缩进
  // [{'direction': 'rtl'}], // 文本方向
  [{ size: ["small", "normal", "large", "huge"] }], // 字体大小
  // [{ size: Size.whitelist }], // 字体大小
  [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
  [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
  [{ font: [] }], // 字体种类
  [{ align: [] }], // 对齐方式
  ["clean"], // 清除文本格式
  ["link", "image", "video"], // 链接、图片、视频
];
export default {
  name: "ArticleEditor",
  model: {
    prop: "value",
    event: "inputFun",
  },
  props: {
    value: {
      type: String,
      require: false,
      default: "",
    },
    placeholder: {
      type: String,
      require: false,
      default: "请输入",
    },
  },
  data() {
    var self = this;
    return {
      headers: {
        "Access-Control-Allow-Origin": "*",
        "Admin-Token": getToken() || sessionStorage.getItem("token"),
      },
      action:
        process.env.NODE_ENV === "production"
          ? `${process.env.VUE_APP_BASE_API}/admin_api/uploadFile.php`
          : "http://xxxx:xxxx/admin_api/uploadFile.php",
      videoDialog: {
        show: false,
        activeName: "first",
        videoLink: "",
      },
      editorOption: {
        // 编辑框操作事件
        theme: "snow", // or 'bubble'
        placeholder: "请输入想发布的内容",
        imageDrop: true,
        modules: {
          toolbar: {
            container: toolbarOptions,
            handlers: {
              image: function (value) {
                // 上传图片
                if (value) {
                  document.querySelector(".avatar-uploader-img input").click(); // 触发input框选择文件
                } else {
                  this.quill.format("image", false);
                }
              },
              link: function (value) {
                // 添加链接
                if (value) {
                  var href = prompt("请输入url");
                  this.quill.format("link", href);
                } else {
                  this.quill.format("link", false);
                }
              },
              video: function (value) {
                // 上传视频
                if (value) {
                  self.videoDialog.show = true;
                  // document
                  //   .querySelector(".avatar-uploader-video input")
                  //   .click(); // 触发input框选择文件
                } else {
                  this.quill.format("video", false);
                }
              },
            },
          },
        },
      },
    };
  },
  watch: {
    value: {
      handler(newVal) {
        if (newVal) {
          this.fileUrl = newVal;
        }
      },
      immediate: true,
    },
  },
  methods: {
    //富文本图片上传前
    beforeUploadImg(file) {
      const isJPG =
        file.type === "image/jpeg" ||
        file.type === "image/png" ||
        file.type === "image/gif" ||
        file.type === "image/webp";
      if (!isJPG) {
        this.$message.error("上传图片只能是 JPG,PNG, GIF 格式!");
      } else {
        // 显示loading动画
        this.quillUpdate = true;
      }
      return isJPG;
    },
    // 富文本视频上传前
    beforeUploadVideo(file) {
      const fileSize = file.size / 1024 / 1024 < 500;
      if (
        [
          "video/mp4",
          "video/ogg",
          "video/flv",
          "video/avi",
          "video/wmv",
          "video/rmvb",
          "video/mov",
        ].indexOf(file.type) == -1
      ) {
        this.$message.error("请上传正确的视频格式");
        return false;
      }
      if (!fileSize) {
        this.$message.error("视频大小不能超过500MB");
        return false;
      }
      // 富文本框视频上传限制最小宽高均为480px
      this.isShowUploadVideo = false;
      // const isVideo = file.type === "video/mp4";
      // if (!isVideo) {
      //   this.$message.error("上传视频只能是 mp4 格式!");
      // } else {
      //   // 显示loading动画
      //   this.quillUpdate = true;
      // }
      // return isVideo;
    },
    uploadImgSuccess(res) {
      //富文本图片上传成功
      // res为图片服务器返回的数据
      // 获取富文本组件实例

      const quill = this.$refs.myQuillEditor.quill;

      // 这里需要注意自己文件上传接口返回内容,code=0表示上传成功,返回的文件地址:res.data.src
      if (res.code !== 0) {
        this.$message.error("图片插入失败");
      } else {
        // 获取光标所在位置
        const length = quill.getSelection(true).index;
        // // 插入图片
        quill.insertEmbed(length, "image", res.data.url);
        // // 调整光标到最后
        quill.setSelection(length + 1);
      }
      // loading动画消失
      this.quillUpdate = false;
    },
    uploadImgError() {
      //富文本图片上传失败
      // loading动画消失
      this.quillUpdate = false;
      this.$message.error("图片插入失败!");
    },
    addVideoLink(videoLink) {
      if (!videoLink) return this.$message.error("请输入视频地址");
      this.videoDialog.show = false;
      const quill = this.$refs.myQuillEditor.quill;
      const length = quill.getSelection(true).index;
      quill.insertEmbed(length, "video", videoLink);
      quill.setSelection(length + 1);
    },
    uploadVideoSuccess(res) {
      // res为图片服务器返回的数据
      // 获取富文本组件实例
      const quill = this.$refs.myQuillEditor.quill;
      // 如果上传成功
      if (res.code == 0 && res.data.url != null) {
        this.videoDialog.show = false;
        // 获取光标所在位置
        const length = quill.getSelection(true).index;
        // 插入图片  res.info为服务器返回的图片地址
        quill.insertEmbed(length, "video", res.data.url);
        // 调整光标到最后

        quill.setSelection(length + 1);
      } else {
        this.$message.error("视频插入失败");
      }
      // loading动画消失
      this.quillUpdate = false;
    },
    uploadVideoError() {
      // loading动画消失
      this.quillUpdate = false;
      this.$message.error("视频插入失败");
    },
    inputFun(e) {
      this.$emit("inputFun", e);
    },
  },
};
</script>

<!-- 富文本编辑器 -->
<style lang="scss" scoped>
.editor {
  line-height: normal !important;
  height: 730px;
  margin-bottom: 30px;
}
.ql-container {
  height: 700px !important;
}
.avatar-uploader-img {
  height: 0;
}
.avatar-uploader-video {
  height: 0;
}

::v-deep .ql-snow .ql-tooltip[data-mode="link"]::before {
  content: "请输入链接地址:";
}
::v-deep .ql-snow .ql-tooltip.ql-editing a.ql-action::after {
  border-right: 0px;
  content: "保存";
  padding-right: 0px;
}
::v-deep .ql-snow .ql-tooltip[data-mode="video"]::before {
  content: "请输入视频地址:";
}
::v-deep .ql-snow .ql-picker.ql-size .ql-picker-label::before,
::v-deep .ql-snow .ql-picker.ql-size .ql-picker-item::before {
  content: "14px";
}
::v-deep
  .ql-snow
  .ql-picker.ql-size
  .ql-picker-label[data-value="small"]::before,
::v-deep
  .ql-snow
  .ql-picker.ql-size
  .ql-picker-item[data-value="small"]::before {
  content: "10px";
}
::v-deep
  .ql-snow
  .ql-picker.ql-size
  .ql-picker-label[data-value="large"]::before,
::v-deep
  .ql-snow
  .ql-picker.ql-size
  .ql-picker-item[data-value="large"]::before {
  content: "18px";
}
::v-deep
  .ql-snow
  .ql-picker.ql-size
  .ql-picker-label[data-value="huge"]::before,
::v-deep
  .ql-snow
  .ql-picker.ql-size
  .ql-picker-item[data-value="huge"]::before {
  content: "32px";
}
::v-deep .ql-snow .ql-picker.ql-header .ql-picker-label::before,
::v-deep .ql-snow .ql-picker.ql-header .ql-picker-item::before {
  content: "文本";
}
::v-deep .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
::v-deep .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
  content: "标题1";
}
::v-deep .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
::v-deep .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
  content: "标题2";
}
::v-deep .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
::v-deep .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
  content: "标题3";
}
::v-deep .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
::v-deep .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
  content: "标题4";
}
::v-deep .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
::v-deep .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
  content: "标题5";
}
::v-deep .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
::v-deep .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
  content: "标题6";
}
::v-deep .ql-snow .ql-picker.ql-font .ql-picker-label::before,
::v-deep .ql-snow .ql-picker.ql-font .ql-picker-item::before {
  content: "标准字体";
}
::v-deep
  .ql-snow
  .ql-picker.ql-font
  .ql-picker-label[data-value="serif"]::before,
::v-deep
  .ql-snow
  .ql-picker.ql-font
  .ql-picker-item[data-value="serif"]::before {
  content: "衬线字体";
}
::v-deep
  .ql-snow
  .ql-picker.ql-font
  .ql-picker-label[data-value="monospace"]::before,
::v-deep
  .ql-snow
  .ql-picker.ql-font
  .ql-picker-item[data-value="monospace"]::before {
  content: "等宽字体";
}
</style>

  

utils文件夹下创建video.js文件,用来解决quill-editor导入视频后使用iframe标签包裹的问题:

import { Quill } from "vue-quill-editor";
// 堆代码 duidaima.com
// 源码中是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);
    // 添加video标签所需的属性
    node.setAttribute("controls", "controls");
    node.setAttribute("type", "video/mp4");
    node.setAttribute("src", this.sanitize(value));
    //为了兼容 iOS 设备上,显示海报图(视频封面)
    node.setAttribute("preload", "metadata");
    return node;
  }
  static formats(domNode) {
    return ATTRIBUTES.reduce((formats, attribute) => {
      if (domNode.hasAttribute(attribute)) {
        formats[attribute] = domNode.getAttribute(attribute);
      }
      return formats;
    }, {});
  }
  static sanitize(url) {
    return Link.sanitize(url);
  }
  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}">${video}</a>`;
  }
}
Video.blotName = "video";
Video.className = "ql-video";
Video.tagName = "video"; // 用video标签替换iframe
export default Video;

  

动态表单里使用富文本框组件:

<!-- 富文本框 -->
          <ArticleEditor
            v-if="item.type === 'content'"
            v-model="form[item.model]"
          />

  

import ArticleEditor from "./ArticleEditor.vue";

  

  components: {
    ArticleEditor,
  },

  

页面展示文章使用富文本框组件:

<div class="detailBody">
          <div class="ql-container ql-snow">
            <div class="ql-editor">
              <div class="detailArticle" v-html="change(detailInfo)"></div>
            </div>
          </div>
        </div>

  

补充一个video标签替换iframe的代码(因为一开始的实现方案没有在组件里用video替代iframe):

  methods: {
    change(content) {
      let t = content
        .replaceAll(
          "<iframe",
          `<video style="width:100%;outline:none;" controls="" autoplay=""`
        )
        .replaceAll("</iframe>", "</video>");
      return t;
    },
  },

  

效果:

 

 

 

标签:picker,视频,snow,value,deep,本地,上传,ql,before
From: https://www.cnblogs.com/nangras/p/17843586.html

相关文章

  • 关于EasyDarwin互联网视频云服务平台的功能简介
    近期。我们开发了EasyDarwin平台,EasyDarwin互联网视频云服务以其高效的视频管理功能著称,支持一站式的上传、转码、直播、回放、嵌入和分享等功能。此外,它还具备多屏播放、自由组合和丰富的接口等特点。该系统能提供稳定流畅的直播、点播、时移和回看服务。广泛应用于互联网教学、......
  • 【漏洞复现】金蝶OA-EAS系统 uploadLogo.action 任意文件上传漏洞(0day)
    阅读须知    此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等(包括但不限于)进行检测或维护参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失,均由使用者本人负责。本文所提供的工具仅......
  • 本地Stackedit Markdown编辑器设置远程访问
    StackEdit是一个受欢迎的Markdown编辑器,在GitHub上拥有20.7kStar!,它支持将Markdown笔记保存到多个仓库,包括Gitee、GitHub和Gitea。此在线笔记工具还提供了一些便捷功能,如拖拽或粘贴上传图片、文件搜索功能,以及可切换为炫酷的暗黑主题,这些功能特别适合那些喜欢使用Markdown来记录笔......
  • Avalonia 实现跨平台的IM即时通讯、语音视频通话(源码,支持信创国产OS,统信、银河麒麟)
       在Avalonia如火如荼的现在,之前使用CPF实现的简单IM,非常有必要基于Avalonia来实现了。Avalonia在跨平台上的表现非常出色,对信创国产操作系统(像银河麒麟、统信UOS、Deepin等)也很不错。   现在,我们就来使用Avalonia实现一个跨平台的简单IM,除了文字聊天外,还可......
  • local-ses 方便本地测试aws ses 邮件服务的工具
    local-ses是基于nodejs开发的可以让我们本地就能测试awsses邮件服务的工具,官方提供了docker镜像可以快速使用说明云环境的本地测试很多时候还是比较重要的,一个是可以方便测试,还有就是节省成本localstack也是aws本地测试一个很不错的平台工具,act是一个githubaction本地......
  • 本地设备名已在使用中。此连接尚未还原。
    本地设备名已在使用中。此连接尚未还原。 在出现的问题的WindowsXP客户端上使用\\ip(*.*.*.*)的方式访问共享文件夹,测试是否能够正常。 我本机是删除了密码,用户名与NAS机上的用户名相同。用\\IP方式重新访问时,重新输入NAS机的用户名和密码后,解决。......
  • 谷歌浏览器视频调速
    谷歌浏览器视频调速方法  1、打开想要播放的视频  2、按F12进入开发者模式,在弹出窗口中选择console,在下方输入命令,然后回车执行命令,即可实现突破2倍速    document.querySelector('video').playbackRate=3       ......
  • 抖音快手陌陌自动刷视频养号脚本,24小时稳定运行,按键精灵开源版
    原理非常简单,就是自动刷视频,然后时间都是随机的,比如看视频或者看动态时间都是随机的,而且还能24小时运行,实现了一个非常不错的一个养号效果,支持抖音快手陌陌三个平台,客户定制的,现在代码留着没用,分享出来。UI界面:  代码:=============================================='创......
  • 函数(4)本地变量(局部变量)及函数细节补充
    <1>本地变量(1)本地变量的定义函数每一次运行都会产生一个独立的变量空间,在这个变量空间中的变量是函数此次运行中独有的变量,称为本地变量;定义在函数内部的变量就是本地变量;参数也是本地变量;(2)变量的生存期和作用域生存期:从变量开始出现到变量消亡的时间;作用域:在代码的......
  • 抖音自动关注点赞评论脚本,可批量导入视频连接,易语言精易VIP模块开发
    这个软件也是客户定制的,然后调用的是精易论坛的一个VIP模块,一个月收费10块钱,但是功能很强大,它可以直接调用你电脑上安装的谷歌浏览器,然后在浏览器里面直接执行数据功能,比如数据包截取,COOKIE的导入导出,还有JS网页端直接,填表操作等功能,所以用这个模块开发了一个抖音私信的功能,可以直......