vue 中quill富文本視頻標簽為video且自定義寬高

原始視頻添加
改版后
成功添加視頻
1.我們要先修改quill源碼中視頻<iframe> 標簽為<video>和寬高設置,新建js為quill-video.js
import Quill from "quill";

// 源碼中是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) {
    let node = super.create()
    //添加
    node.setAttribute('src', value.url)
    node.setAttribute('controls', value.controls)
    node.setAttribute('width', value.width)
    node.setAttribute('height', value.height)
    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) {
    // 設置值包含寬高恳蹲,為了達到自定義效果
    //寬高為空的話鞠值,就是按100%算
    return {
      url: domNode.getAttribute('src'),
      controls: domNode.getAttribute('controls'),
      width: domNode.getAttribute('width'),
      height: domNode.getAttribute('height')
    }
  }


  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
2.封裝quill組件
<template>
  <div>
    <!-- 圖片上傳 -->
    <el-upload
      :action="uploadUrl"
      :on-success="handleUploadSuccess"
      :on-error="handleUploadError"
      name="file"
      :show-file-list="false"
      :headers="headers"
      style="display: none"
      ref="upload"
      v-if="this.uploadUrl"
    >
    </el-upload>
    <!-- 視頻彈窗 -->
    <el-dialog
      width="650px"
      id="videoUpload"
      title="視頻上傳"
      :visible.sync="videoUploadTag"
      append-to-body
      :close-on-click-modal="false"
    >
      <el-form ref="videoObj" :model="videoObj" label-width="80px">
        <el-form-item label="視頻鏈接">
          <el-input
            v-model="videoObj.videoLink"
            placeholder="請輸入視頻鏈接"
            clearable
          ></el-input>
        </el-form-item>
        <el-row>
          <el-col :span="10">
            <el-form-item label="寬">
              <el-input-number
                :min="200"
                controls-position="right"
                v-model="videoObj.videoWidth"
                placeholder="請輸入自定義寬"
                clearable
              ></el-input-number>
            </el-form-item>
          </el-col>
          <el-col :span="10" :offset="3">
            <el-form-item label="高">
              <el-input-number
                :min="200"
                controls-position="right"
                v-model="videoObj.videoHeight"
                placeholder="請輸入自定義高"
                clearable
              ></el-input-number> </el-form-item
          ></el-col>
        </el-row>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="addVideoLink()">添加</el-button>
      </div>
    </el-dialog>
    <div class="editor" ref="editor" :style="styles"></div>
  </div>
</template>

<script>
import Quill from "quill";
import "quill/dist/quill.core.css";
import "quill/dist/quill.snow.css";
import "quill/dist/quill.bubble.css";
import { getToken } from "@/utils/auth";
// 注冊自定義Video
import Video from "@/utils/quill-video";
Quill.register(Video, true);

export default {
  name: "Editor",
  props: {
    /* 編輯器的內(nèi)容 */
    value: {
      type: String,
      default: ""
    },
    /* 高度 */
    height: {
      type: Number,
      default: null
    },
    /* 最小高度 */
    minHeight: {
      type: Number,
      default: null
    },
    /* 只讀 */
    readOnly: {
      type: Boolean,
      default: false
    },
    /* 上傳地址 */
    uploadUrl: {
      type: String,
      default: ""
    }
  },
  data() {
    var than = this;
    return {
      //顯示插入視頻鏈接彈框的標識
      videoUploadTag: false,
      //彈框插入的視頻鏈接記錄
      videoObj: {
        videoLink: "",
        videoWidth: "",
        videoHeight: ""
      },
      headers: {
        Authorization: "Bearer " + getToken()
      },
      Quill: null,
      currentValue: "",
      options: {
        theme: "snow",
        bounds: document.body,
        debug: "warn",
        modules: {
          // 工具欄配置
          toolbar: {
            container: [
              ["bold", "italic", "underline", "strike"], // 加粗 斜體 下劃線 刪除線
              ["blockquote", "code-block"], // 引用  代碼塊
              [{ list: "ordered" }, { list: "bullet" }], // 有序两入、無序列表
              [{ indent: "-1" }, { indent: "+1" }], // 縮進
              [{ size: ["small", false, "large", "huge"] }], // 字體大小
              [{ header: [1, 2, 3, 4, 5, 6, false] }], // 標題
              [{ color: [] }, { background: [] }], // 字體顏色裳仆、字體背景顏色
              [{ align: [] }], // 對齊方式
              ["clean"], // 清除文本格式
              ["link", "image", "video"] // 鏈接溉箕、圖片
            ],
            handlers: {
              video: value => {
                // 點擊視頻圖標晦墙,彈出窗
                than.videoObj = {
                  videoLink: "",
                  wdith: "",
                  height: ""
                };
                than.videoUploadTag = true;
              }
            }
          }
        },
        placeholder: "請輸入內(nèi)容",
        readOnly: this.readOnly
      }
    };
  },
  computed: {
    styles() {
      let style = {};
      if (this.minHeight) {
        style.minHeight = `${this.minHeight}px`;
      }
      if (this.height) {
        style.height = `${this.height}px`;
      }
      return style;
    }
  },
  watch: {
    value: {
      handler(val) {
        if (val !== this.currentValue) {
          this.currentValue = val === null ? "" : val;
          if (this.Quill) {
            this.Quill.pasteHTML(this.currentValue);
          }
        }
      },
      immediate: true
    }
  },
  mounted() {
    this.init();
  },
  beforeDestroy() {
    this.Quill = null;
  },
  methods: {
    init() {
      const editor = this.$refs.editor;
      this.Quill = new Quill(editor, this.options);
      // 如果設置了上傳地址則自定義圖片上傳事件
      if (this.uploadUrl) {
        let toolbar = this.Quill.getModule("toolbar");
        toolbar.addHandler("image", value => {
          this.uploadType = "image";
          if (value) {
            this.$refs.upload.$children[0].$refs.input.click();
          } else {
            this.quill.format("image", false);
          }
        });
        toolbar.addHandler("video", value => {
          this.uploadType = "video";
          if (value) {
            this.$refs.upload.$children[0].$refs.input.click();
          } else {
            this.quill.format("video", false);
          }
        });
      }
      this.Quill.pasteHTML(this.currentValue);
      this.Quill.on("text-change", (delta, oldDelta, source) => {
        const html = this.$refs.editor.children[0].innerHTML;
        const text = this.Quill.getText();
        const quill = this.Quill;
        this.currentValue = html;
        this.$emit("input", html);
        this.$emit("on-change", { html, text, quill });
      });
      this.Quill.on("text-change", (delta, oldDelta, source) => {
        this.$emit("on-text-change", delta, oldDelta, source);
      });
      this.Quill.on("selection-change", (range, oldRange, source) => {
        this.$emit("on-selection-change", range, oldRange, source);
      });
      this.Quill.on("editor-change", (eventName, ...args) => {
        this.$emit("on-editor-change", eventName, ...args);
      });
    },
    handleUploadSuccess(res, file) {
      // 獲取富文本組件實例
      let quill = this.Quill;
      // 如果上傳成功
      if (res.code == 200) {
        // 獲取光標所在位置
        let length = quill.getSelection().index;
        // 插入圖片  res.url為服務器返回的圖片地址
        quill.insertEmbed(length, "image", res.url);
        // 調(diào)整光標到最后
        quill.setSelection(length + 1);
      } else {
        this.$message.error("圖片插入失敗");
      }
    },
    handleUploadError() {
      this.$message.error("圖片插入失敗");
    },
    // 添加視頻鏈接
    addVideoLink() {
      if (this.videoObj.videoLink.length == 0) {
        return this.$message.error("請輸入視頻鏈接");
      }
      //視頻插入在富文本中的位置
      // 當編輯器中沒有輸入文本時,這里獲取到的 newRange 為 null
      let newRange = this.Quill.selection.savedRange;
      let index = 0;
      if (newRange == null) {
        index = 0;
      } else {
        index = newRange.index;
      }
      this.Quill.insertEmbed(index, "video", {
        url: this.videoObj.videoLink,
        controls: "controls",
        width: this.videoObj.videoWidth,
        height: this.videoObj.videoHeight
      });
      //隱藏彈框
      this.videoUploadTag = false;
      this.Quill.setSelection(1 + index);
    }
  }
};
</script>

<style>
/* 富文本框樣式肴茄,一定要加 */
.editor,
.ql-toolbar {
  white-space: pre-wrap !important;
  line-height: normal !important;
}
.quill-img {
  display: none;
}
.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: "等寬字體";
}

.ql-snow .ql-tooltip[data-mode="video"] {
  left: 15px !important;
}
/* .ql-video {
  width: 80%;
  height: 400px;
} */
</style>

3.使用
<template>
  <div>
    <editor :value="content" v-model="content" :height="480" />
  </div>
</template>

<script>
import Editor from "@/components/Editor";

export default {
  components: {
    Editor
  },
  data() {
    return {
      content: ""
    };
  }
};
</script>

ps:更多關于quill的修改可以看node_modules\quill文件夾
可正常下載觀看視頻鏈接:https://media.w3.org/2010/05/sintel/trailer.mp4
參考地址:https://biglead.blog.csdn.net/article/details/104468690
https://www.cnblogs.com/1175429393wljblog/p/12748681.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末晌畅,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子寡痰,更是在濱河造成了極大的恐慌抗楔,老刑警劉巖棋凳,帶你破解...
    沈念sama閱讀 222,807評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異连躏,居然都是意外死亡剩岳,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評論 3 399
  • 文/潘曉璐 我一進店門入热,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拍棕,“玉大人,你說我怎么就攤上這事勺良〈虏ィ” “怎么了?”我有些...
    開封第一講書人閱讀 169,589評論 0 363
  • 文/不壞的土叔 我叫張陵尚困,是天一觀的道長蠢箩。 經(jīng)常有香客問我,道長事甜,這世上最難降的妖魔是什么谬泌? 我笑而不...
    開封第一講書人閱讀 60,188評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮逻谦,結(jié)果婚禮上呵萨,老公的妹妹穿的比我還像新娘。我一直安慰自己跨跨,他們只是感情好潮峦,可當我...
    茶點故事閱讀 69,185評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著勇婴,像睡著了一般忱嘹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上耕渴,一...
    開封第一講書人閱讀 52,785評論 1 314
  • 那天拘悦,我揣著相機與錄音,去河邊找鬼橱脸。 笑死础米,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的添诉。 我是一名探鬼主播屁桑,決...
    沈念sama閱讀 41,220評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼栏赴!你這毒婦竟也來了蘑斧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,167評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎竖瘾,沒想到半個月后沟突,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,698評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡捕传,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,767評論 3 343
  • 正文 我和宋清朗相戀三年惠拭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片庸论。...
    茶點故事閱讀 40,912評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡求橄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出葡公,到底是詐尸還是另有隱情罐农,我是刑警寧澤,帶...
    沈念sama閱讀 36,572評論 5 351
  • 正文 年R本政府宣布催什,位于F島的核電站涵亏,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蒲凶。R本人自食惡果不足惜气筋,卻給世界環(huán)境...
    茶點故事閱讀 42,254評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望旋圆。 院中可真熱鬧宠默,春花似錦、人聲如沸灵巧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刻肄。三九已至瓤球,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間敏弃,已是汗流浹背卦羡。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留麦到,地道東北人绿饵。 一個月前我還...
    沈念sama閱讀 49,359評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像瓶颠,于是被迫代替她去往敵國和親拟赊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,922評論 2 361

推薦閱讀更多精彩內(nèi)容