[vue-quill-editor]同一页面有多个编辑器时的冲突处理&B站链接插入问题

环境描述/前提

脚手架:GitHub - PanJiaChen/vue-admin-template: a vue2.0 minimal admin template

全局注册quill-editor:vue使用富文本编辑器vue-quill-editor的操作指南和注意事项_vue.js_脚本之家

问题描述

1. 当一个页面有多个编辑器时,在文本中间插入视频,视频总是插入到文本开头。原因在于quill的视频按钮没法传参,导致无法正确识别到具体的一个编辑器。

2. 直接插入B站链接,富文本会显示B站页面。

3. 对于b站视频链接,用iframe标签显示;对于上传的视频,用video标签显示。

解决方案

1. 通过闭包的方式获取编辑器的id,并将其与该页面的data变量联系起来。

2. 先正则匹配获取链接中的bv号,然后调取b站api获取aid,通过aid展示纯视频。

3. 额外写一个quill自定义组件进行配置。

代码

主页面的html部分:

<template>
  <div>

    <div>
      <quill-editor
        class="ql-editor"
        v-model="editModel.content1"
        id="myQuillEditor1"
        ref="myQuillEditor1"
        :options="editorOption"
        @blur="onEditorBlur($event)"
        @focus="onEditorFocus($event)"
        @change="onEditorChange($event)"
      >
      </quill-editor>
    </div>

    <div>
      <quill-editor
        class="ql-editor"
        v-model="editModel.content2"
        id="myQuillEditor2"
        ref="myQuillEditor2"
        :options="editorOption"
        @blur="onEditorBlur($event)"
        @focus="onEditorFocus($event)"
        @change="onEditorChange($event)"
      >
      </quill-editor>
    </div>

    <div>
      <quill-editor
        class="ql-editor"
        v-model="editModel.content3"
        id="myQuillEditor3"
        ref="myQuillEditor3"
        :options="editorOption"
        @blur="onEditorBlur($event)"
        @focus="onEditorFocus($event)"
        @change="onEditorChange($event)"
      >
      </quill-editor>
    </div>


    <!--视频上传弹窗-->
    <div>
      <el-dialog
        :close-on-click-modal="false"
        width="50%"
        style="margin-top: 1px"
        title="视频上传"
        :visible.sync="videoForm.show"
        append-to-body
      >
        <el-tabs v-model="videoForm.activeName">
          <el-tab-pane label="添加视频链接" name="first">
            <el-input
              v-model="videoForm.videoLink"
              placeholder="请输入视频链接"
              clearable
            ></el-input>
            <el-button
              type="primary"
              size="small"
              style="margin: 20px 0px 0px 0px"
              @click="insertVideoLink(videoForm.videoLink, videoForm.ref)"
              >确认
            </el-button>
          </el-tab-pane>
          <el-tab-pane label="本地视频上传" name="second">
            <el-upload
              ref="uploadVideoForm"
              style="text-align: center"
              action=""
              drag
              :http-request="uploadVideo"
              accept="video/*"
              :file-list="fileList"
              :on-change="handleChange"
            >
              <i class="el-icon-upload"></i>
              <div class="el-upload__text">
                将文件拖到此处,或<em>点击上传</em>
              </div>
              <div class="el-upload__tip" slot="tip">
                只能上传视频文件,且不超过{{ uploadVideoConfig.maxSize }}M
              </div>
            </el-upload>
          </el-tab-pane>
        </el-tabs>
      </el-dialog>
    </div>

  </div>
</template>

主页面的js部分:

<script>
import biliApi from "@/api/biliApi";
import Quill from "quill"; // 引入编辑器
import Video from "@/quill/quill-video";
Quill.register(Video, true);

// 工具栏配置
var _EditorOption_ = function (page_this) {
  return {
    modules: {
      toolbar: {
        container: [
          ["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" }], // 文本方向
          [{ header: [1, 2, 3, 4, 5, 6] }], // 标题
          [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
          // [{ font: ['songti'] }], // 字体种类
          [{ align: [] }], // 对齐方式
          ["clean"], // 清除文本格式
          ["image", "video"], // 链接、图片、视频
        ],
        handlers: {
          video: function (value) {
            // 使用闭包可获取id
            if (value) {
              page_this.videoForm.show = true;
              page_this.videoForm.ref = this.container.parentElement.id;
            }
          },
        },
      },
    },
    placeholder: "请输入正文",
  };
};

export default {
  name: "MyEdit",
  props: ["id"],

  data() {
    return {
      editorOption: _EditorOption_(this), //富文本配置

      // 视频上传变量
      videoForm: {
        show: false, // 显示插入视频链接弹框的标识
        videoLink: "",
        activeName: "first",
        ref: "",
      },

      fileList: [],
      videoFile: null,

      // 视频上传配置
      uploadVideoConfig: {
        type: null,
        uploadUrl: "api/case/uploadPicture", // 上传地址
        maxSize: 1024, // 上传大小限制,默认不超过2M
        name: "file", // 上传字段
      },

    };
  },

  methods: {
    // 失去焦点事件
    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);
    },

    // 插入视频链接
    async insertVideoLink(link, editor) {
      var videoLink = link;
      if (!videoLink) return this.$message.error("视频地址不能为空!");
      this.videoForm.show = false;

      var iframeTag = false; // 站外用iframe标签,站内用video标签

      await this.handleInput(videoLink).then((res) => {
        videoLink = res;
        if (videoLink !== link) {
          iframeTag = true;
        }
      });

      console.log("vlink: " + videoLink);
      console.log("editor: " + editor);

      let quill = this.$refs[editor].quill;

      // 获取富文本
      let range = quill.getSelection(true);
      console.log("range: " + range);
      // 获取光标位置:当编辑器中没有输入文本时,这里获取到的 range 为 null
      let index = range ? range.index : 0;

      // 在光标所在位置 插入视频
      quill.insertEmbed(index, "video", videoLink);

      // 调整光标到最后
      quill.setSelection(index + 1);
    },

    // bilibili视频处理 将bv改为aid(纯视频)
    async handleInput(content) {
      var url = content;
      if (content.includes("BV")) {
        // 使用正则表达式匹配 BV 号
        const match = content.match(/video\/(BV[^\/?&]+)[\/?&]?/);
        var bv = "";
        if (match && match[1]) {
          bv = match[1]; // 返回匹配到的 BV 号, 下标0是整个正则匹配,1是第一个捕获组
        }

        await biliApi
          .getAidByBv(bv)
          .then((res) => {
            const aid = res.data.aid;
            url = `https://player.bilibili.com/player.html?aid=${aid}`;
          })
          .catch((err) => {
            console.error("Failed to fetch aid from Bilibili API: " + err);
          });
      };
      return url;
    },

    // 富文本视频上传
    uploadVideo() {
      const file = this.videoFile;
      const formData = new FormData();
      formData.append("file", file);

      caseApi.uploadPicture(formData).then((res) => {
        this.$message({
          message: "上传成功",
          type: "success",
        });
        this.videoForm.videoLink = res.data.fileURL;
        this.clearFiles("uploadVideoForm");
        this.insertVideoLink(this.videoForm.videoLink, this.videoForm.ref);
      });
    },
    handleChange(file) {
      const fileName = file.name; // 文件名字
      const fileType = fileName.substring(fileName.lastIndexOf(".") + 1);
      this.videoFile = file.raw;
    },
    // 清空列表方法
    clearFiles(ref) {
      this.$refs[ref].clearFiles();
    },

  },
}
</script>

src/api/biliApi.js

import request from '@/utils/request'


export default {
    getAidByBv(bv) {
        return request({
          url: `/bili/x/web-interface/view?bvid=${bv}`,
          method: 'get',
        });
    },
}

src/quill/quill-video.js

import { Quill } from "vue-quill-editor";
const BlockEmbed = Quill.import('blots/block/embed');
const Link = Quill.import('formats/link');

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

class Video extends BlockEmbed {
  static create(value) {
    let node;
    // 检查URL是否为外部链接
    if (value.includes('bilibili')) {
      // 使用iframe标签
      node = document.createElement('iframe');
      node.setAttribute('frameborder', '0');
      node.setAttribute('allowfullscreen', true);
      node.setAttribute('src', this.sanitize(value));
    } else {
      // 使用video标签
      node = document.createElement('video');
      node.setAttribute('controls', 'controls');
      node.setAttribute('type', 'video/mp4');
      node.setAttribute('src', this.sanitize(value));
    }
    node.setAttribute('width', '500px');
    node.setAttribute('height', '300px');
    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); // 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';
Video.className = 'ql-video';
// 根据URL类型动态设置tagName
Video.tagName = 'video'; // 默认使用

export default Video;

注意:代码为本人参与项目代码的节选,如有需要请自行修改。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值