vue2 quill 视频上传 ,基于ruoyi vue,oss
包含两种上传方式,第一种点开弹新页面可选url和点击上传。本文中是第二种,自己拼的。像点击上传图片一样,直接传video文件,原创不易,纯纯踩坑;
因为现阶段能搜索到的内容,99.5%都是一样的内容,无法满足需求。
我的需求是在ruoyi-vue里面用quill富文本,但里面一部分功能没有。
但作者给了自定义插件通道。
如果根据我的代码去写,上传完视频 就拿不到光标,只能拿到最后一个字的长度,因此上传视频被提成bug。
自猜:点击完tag的上传视频 是打开个子页面,父子页面传值可能存在问题。
无奈之下只能自己手拼。看代码吧。如有问题请及时指正。
所有代码在同级目录下
贴代码
index.vue
<template><div><el-upload:action="uploadUrl":before-upload="handleBeforeUpload":http-request="uploadURL"name="file":show-file-list="false":headers="headers"style="display: none"ref="upload"v-if="this.type == 'url'"></el-upload><div class="editor" ref="editor" :style="styles"></div><!--视频上传 begin --><!-- <el-tab-pane label="添加视频链接" name="second"><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> --><!--<div><el-dialog :close-on-click-modal="false" width="50%" style="margin-top: 1px" title="视频上传" :visible.sync="videoDialog.show" append-to-body ref="dialogRef"><el-tabs v-model="videoDialog.activeName"><el-tab-pane label="本地视频上传" ><el-uploadv-loading="uploading"style="text-align: center"dragaction=""accept="video/*":http-request="uploadVideoURL":multiple="false":before-upload="videoHandleBeforeUpload"v-if="this.type == 'video'"
><i class="el-icon-upload"></i><div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div></el-upload></el-tab-pane></el-tabs></el-dialog>
</div> --><!-- :http-request="uploadVideoURL":multiple="false":before-upload="videoHandleBeforeUpload"style="display: none" -->
<el-uploadname="file":show-file-list="false":headers="headers"style="display: none"ref="uploadVideo":http-request="uploadVideoURL":before-upload="videoHandleBeforeUpload"action="uploadVideoURL":multiple="false"v-if="this.video == 'r'"
><i class="el-icon-upload"></i><div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div></el-upload><!--视频上传 end --></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";
import {client, getFileNameUUID} from '@/utils/alioss';let fontFamily = ['SimSun', 'SimHei', 'Microsoft-YaHei', 'KaiTi', 'FangSong', 'Arial', 'Times-New-Roman', 'sans-serif'];
Quill.imports['formats/font'].whitelist = fontFamily;
Quill.register(Quill.imports['formats/font']);
let fontSize = ['10px', '11px','12px', '14px', '15px', '16px', '17px', '18px', '20px', '24px', '36px']
Quill.imports['attributors/style/size'].whitelist = fontSize;
Quill.register(Quill.imports['attributors/style/size']);import Video from "./video.js";
// import Buffer from "vue-buffer";Quill.register(Video, true);// 行高
import { lineHeightStyle } from './lineHeight.js';
Quill.register({ 'formats/lineHeight': lineHeightStyle }, true);// 行间距
import { letterSpacingStyle } from './letterSpacing.js';
Quill.register({ 'formats/letterSpacing': letterSpacingStyle }, true);Quill.register({ 'formats/lineHeight': lineHeightStyle }, true);export default {name: "Editor",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,},/* 类型(base64格式、url格式) */type: {type: String,default: "url",},video: {type: String,default: "r",}},data() {return {aliyun :{},uploading : false,videoDialog: {show: false,activeName: null,videoLink:null},uploadUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址headers: {Authorization: "Bearer " + getToken()},Quill: null,currentValue: "",options: {theme: "snow",bounds: document.body,debug: "warn",modules: {// 工具栏配置toolbar: {handlers: {// lineHeight: (value) => {// if (value) {// let quill = this.$refs.myQuillEditor.quill;// quill.format("lineHeight", value);// }// },// lineHeight :['initial', '1', '1.5', '1.75', '2', '3', '4', '5'],video: (value) => {this.videoDialog.show = true;},},container : [["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线["blockquote", "code-block"], // 引用 代码块[{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表[{ indent: "-1" }, { indent: "+1" }], // 缩进// [{ size: ["small", false, "large", "huge"] }], // 字体大小[{ size:fontSize }], // 字体大小// [{ size: [ "ten",false,"eleven", "twelve", "thirteen",// "fourteen", "fifteen", "sixteen", "seventeen",// "eighteen", "nineteen", "twenty"]}],[{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题[{ color: ['aqua', 'black', 'blue', 'brown', 'cyan', 'gold', 'gray', 'green', 'indigo', 'lavender', 'lime', 'magenta', 'maroon', 'navy', 'olive', 'orange', 'pink', 'purple', 'red', 'silver', 'teal', 'violet', 'white', 'yellow'] },{ background: ['aqua', 'black', 'blue', 'brown', 'cyan', 'gold', 'gray', 'green', 'indigo', 'lavender', 'lime', 'magenta', 'maroon', 'navy', 'olive', 'orange', 'pink', 'purple', 'red', 'silver', 'teal', 'violet', 'white', 'yellow']}], // 字体颜色、字体背景颜色[{ align: [] }], // 对齐方式["clean"], // 清除文本格式["image", "video"], // 链接、图片、视频[{lineheight: ['initial', '1', '2', '3', '4', '5','20'] }],[{ letterSpacing: [ 'initial','2px','4px', '6px','8px','10px', '12px', '14px', '16px'],},], //行间距// ["link", "image", "video"] // 链接、图片、视频],}},placeholder: "请输入内容",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();},methods: {// 上传文件之前videoHandleBeforeUpload(file) {const isJpgOrPng =file.type === 'video/ogg' ||file.type === 'video/flv' ||file.type === 'video/avi' ||file.type === 'video/wmv' ||file.type === 'video/mov' ||file.type === 'video/mp4'if (!isJpgOrPng) {this.$message.error('只能上传图片/视频!')return}},uploadURL(file) {//注意哦,这里指定文件夹'image/',尝试过写在配置文件,但是各种不行,写在这里就可以var fileName = 'imgs/'+'editor' + getFileNameUUID() + "." + file.file.name.replace(/.+\./, "");//'.jpg';client().multipartUpload(fileName, file.file,{progress: function(percentage, cpt) {console.log('打印进度',percentage)}}).then((res)=>{//此处赋值,是相当于上传成功之后,手动拼接服务器地址和文件名let quill = this.Quill;let length = quill.getSelection().index;console.log("quill.getSelection().index===",length);console.log(quill.getSelection());// 插入图片 res.url为服务器返回的图片地址quill.insertEmbed(length, "image", 'https://oss-cn-beijing.aliyuncs.com/' + fileName);// 调整光标到最后quill.setSelection(length + 1);})},init() {const editor = this.$refs.editor;this.Quill = new Quill(editor, this.options);// 如果设置了上传地址则自定义图片上传事件if (this.type == 'url') {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);}});}//todo beginconsole.log("this.video",this.video);if (this.video == 'r') {let toolbar = this.Quill.getModule("toolbar");toolbar.addHandler("video", (value) => {this.uploadType = "video";console.log("value",value);if (value) {this.$refs.uploadVideo.$children[0].$refs.input.click();} else {this.quill.format("video", false);}});}//todo endthis.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);});},handleBeforeUpload(file) {const isJPEG = file.name.split('.')[1] === 'jpeg';const isJPG = file.name.split('.')[1] === 'jpg';const isPNG = file.name.split('.')[1] === 'png';const isWEBP = file.name.split('.')[1] === 'webp';const isGIF = file.name.split('.')[1] === 'gif';const isLt500K = file.size / 1024 / 1024 / 1024 / 1024 < 4;if (!isJPG && !isJPEG && !isPNG && !isWEBP && !isGIF) {this.$message.error('上传图片只能是 JPEG/JPG/PNG 格式!');}if (!isLt500K) {this.$message.error('单张图片大小不能超过 4mb!');}return (isJPEG || isJPG || isPNG || isWEBP || isGIF) && isLt500K;},uploadVideoURL(file) {//注意哦,这里指定文件夹'image/',尝试过写在配置文件,但是各种不行,写在这里就可以var fileName = 'imgs/'+'editor' + getFileNameUUID() + "." + file.file.name.replace(/.+\./, "");//'.jpg';client().multipartUpload(fileName, file.file,{progress: function(percentage, cpt) {// console.log('打印进度',percentage)}}).then((res)=>{//此处赋值,是相当于上传成功之后,手动拼接服务器地址和文件名this.videoDialog.show = false;// console.log("this.$parent.editor",this.$parent.editor);// let editor = this.$refs['editor'];// this.init();let quill = this.Quill;let lengthSelection = quill.getSelection().index;console.log("quill.getSelection().index===",lengthSelection);// console.log("this.$refs.editer.quill.selection.savedRange.index",this.$refs.editer.quill.selection.savedRange.index);console.log("quill.getLength()",quill.getLength());// 获取富文本// let range = quill.getSelection().index;// 获取光标位置:当编辑器中没有输入文本时,这里获取到的 range 为 null// let index = range ? range.index : 0// let quill = this.Quill;var length = quill.getLength();if(quill.getSelection()){length = quill.getSelection().index;// console.log("quill.getSelection().index===",length);}// console.log(quill.getSelection());let resp = 'https://oss-cn-beijing.aliyuncs.com/' + fileName;// 插入quill.insertEmbed(length, "video", resp);// 调整光标到最后quill.setSelection(length + 1);this.$forceUpdate();})},},
};</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";
}
/*todo begin*/.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='10px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='10px']::before {content: '10px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='11px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='11px']::before {content: '11px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='12px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='12px']::before {content: '12px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='14px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='14px']::before {content: '14px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='15px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='15px']::before {content: '15px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='16px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='16px']::before {content: '16px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='17px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='17px']::before {content: '17px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='18px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='18px']::before {content: '18px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='20px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='20px']::before {content: '20px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='24px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='24px']::before {content: '24px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='36px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='36px']::before {content: '36px';
}/*todo end*/
.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><style>
/* .ql-snow .ql-picker.ql-lineheight .ql-picker-label::before,
.ql-snow .ql-picker.ql-lineheight .ql-picker-item::before {content: '行高';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="行高"]::before,
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='initial']::before {content: '默认';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="1"]::before,
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='1']::before {content: '1px';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="1.5"]::before,
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='1.5']::before {content: '1.5px';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="1.75"]::before,
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='1.75']::before {content: '1.75px';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="2"]::before,
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='2']::before {content: '2px';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="3"]::before,
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='3']::before {content: '3px';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="4"]::before,
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='4']::before {content: '4px';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="5"]::before,
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='5']::before {content: '5px';
}
.ql-snow .ql-picker.ql-lineheight {width: 70px;
}*/
.ql-snow .ql-picker.ql-letterSpacing .ql-picker-label::before,
.ql-snow .ql-picker.ql-letterSpacing .ql-picker-item::before {content: '字符间距';
}
.ql-snow .ql-picker.ql-letterSpacing .ql-picker-label[data-value="字符间距"]::before,
.ql-snow .ql-picker.ql-letterSpacing .ql-picker-item[data-value='initial']::before {content: '默认';
}
.ql-snow .ql-picker.ql-letterSpacing .ql-picker-label[data-value="2px"]::before,
.ql-snow .ql-picker.ql-letterSpacing .ql-picker-item[data-value='2px']::before {content: '2px';
}
.ql-snow .ql-picker.ql-letterSpacing .ql-picker-label[data-value="4px"]::before,
.ql-snow .ql-picker.ql-letterSpacing .ql-picker-item[data-value='4px']::before {content: '4px';
}
.ql-snow .ql-picker.ql-letterSpacing .ql-picker-label[data-value="6px"]::before,
.ql-snow .ql-picker.ql-letterSpacing .ql-picker-item[data-value='6px']::before {content: '6px';
}
.ql-snow .ql-picker.ql-letterSpacing .ql-picker-label[data-value="8px"]::before,
.ql-snow .ql-picker.ql-letterSpacing .ql-picker-item[data-value='8px']::before {content: '8px';
}
.ql-snow .ql-picker.ql-letterSpacing .ql-picker-label[data-value="10px"]::before,
.ql-snow .ql-picker.ql-letterSpacing .ql-picker-item[data-value='10px']::before {content: '10px';
}
.ql-snow .ql-picker.ql-letterSpacing .ql-picker-label[data-value="12px"]::before,
.ql-snow .ql-picker.ql-letterSpacing .ql-picker-item[data-value='12px']::before {content: '12px';
}
.ql-snow .ql-picker.ql-letterSpacing .ql-picker-label[data-value="14px"]::before,
.ql-snow .ql-picker.ql-letterSpacing .ql-picker-item[data-value='14px']::before {content: '14px';
}
.ql-snow .ql-picker.ql-letterSpacing .ql-picker-label[data-value="16px"]::before,
.ql-snow .ql-picker.ql-letterSpacing .ql-picker-item[data-value='16px']::before {content: '16px';
}
.ql-snow .ql-picker.ql-letterSpacing {width: 90px;
}</style><style>
.ql-snow .ql-picker.ql-lineheight .ql-picker-label::before,
.ql-snow .ql-picker.ql-lineheight .ql-picker-item::before {content: '行高';
}.ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="1"]::before,
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value="1"]::before {content: "1";
}.ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="2"]::before,
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value="2"]::before {content: "2";
}.ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="3"]::before,
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value="3"]::before {content: "3";
}.ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="4"]::before,
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value="4"]::before {content: "4";
} .ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="5"]::before,
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value="5"]::before {content: "5";
}.ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="20"]::before,
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value="20"]::before {content: "20";
}.ql-snow .ql-picker.ql-lineheight {width: 70px;
}</style>
letterSpacing.js
import Quill from 'quill'
let Parchment = Quill.import('parchment')
// 字符间距
class letterSpacingAttributor extends Parchment.Attributor.Style {}
const letterSpacingStyle = new letterSpacingAttributor('letter-spacing','letterSpacing',{scope: Parchment.Scope.INLINE,whitelist: ['initial','2px','4px','6px','8px','10px','12px','14px','16px',],}
)
export { letterSpacingStyle }
lineHeight.js
import Quill from 'quill'
let Parchment = Quill.import('parchment')// 行高
class lineHeightAttributor extends Parchment.Attributor.Style {}
const lineHeightStyle = new lineHeightAttributor(// 'line-height', 'lineHeight',// 'lineheight', 'ql-lineheight','lineheight', 'line-height',// 'line-height','lineheight', {scope: Parchment.Scope.INLINE,whitelist: ['initial', '1', '2', '3', '4', '5','20'],
})
export { lineHeightStyle }
quill-title.js
const titleConfig = {'ql-bold': '加粗','ql-color': '颜色','ql-font': '字体','ql-code': '插入代码','ql-italic': '斜体','ql-link': '添加链接','ql-background': '背景颜色','ql-size': '字体大小','ql-strike': '删除线','ql-script': '上标/下标','ql-underline': '下划线','ql-blockquote': '引用','ql-header': '标题','ql-indent': '缩进','ql-list': '列表','ql-align': '文本对齐','ql-direction': '文本方向','ql-code-block': '代码块','ql-formula': '公式','ql-image': '图片','ql-video': '视频','ql-clean': '清除字体样式','ql-lineheight': '行高','ql-letterSpacing': '字符间距',}export function addQuillTitle() {const oToolBar = document.querySelector('.ql-toolbar'),aButton = oToolBar.querySelectorAll('button'),aSelect = oToolBar.querySelectorAll('select')aButton.forEach(function (item) {if (item.className === 'ql-script') {item.value === 'sub' ? (item.title = '下标') : (item.title = '上标')} else if (item.className === 'ql-indent') {item.value === '+1'? (item.title = '向右缩进'): (item.title = '向左缩进')} else {item.title = titleConfig[item.classList[0]]}})aSelect.forEach(function (item) {item.parentNode.title = titleConfig[item.classList[0]]})}
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);// 添加video标签所需的属性node.setAttribute("controls", "controls");node.setAttribute("type", "video/mp4");node.setAttribute("src", this.sanitize(value));//为了兼容 iOS 设备上,显示海报图(视频封面)node.setAttribute("preload", "metadata");// node.setAttribute("poster", value.poster);node.setAttribute("webkit-playsinline", "true");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");// return {// url: domNode.getAttribute('src'),// poster: domNode.getAttribute('poster')// }}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;
相关文章:
vue2 quill 视频上传 ,基于ruoyi vue,oss
包含两种上传方式,第一种点开弹新页面可选url和点击上传。本文中是第二种,自己拼的。像点击上传图片一样,直接传video文件,原创不易,纯纯踩坑; 因为现阶段能搜索到的内容,99.5%都是一样的内容&…...
YOLOv8改进实战 | 更换损失函数之MPDIOU(2023最新IOU)篇
前言 YOLOv8官方默认损失函数采用的是CIoU。本章节主要介绍如何将MPDIoU损失函数应用于目标检测YOLOv8模型。 目录 一、MPDIoU二、代码实现添加损失函数更换损失函数一、MPDIoU 论文链接:MPDIoU: A Loss for Efficient and Accurate Bounding Box Regression MPDIoU是一种基于…...
图的应用1.0-----最小生成树问题
目录 前言 生成树 1.基本概念 2.生成树的特点 3.生成树形成过程 最小生成树(Minimum Spanning Tree) 1.基本概念 2.构造方法(MST) 普里姆(Prime)算法 1.算法思想 2.代码实现 克鲁斯卡尔(Kruskal)算法 1.算法思想 2.代码…...
【计算机网络笔记】网络应用对传输服务的需求
系列文章目录 什么是计算机网络? 什么是网络协议? 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能(1)——速率、带宽、延迟 计算机网络性能(2)…...
IDEA启动报错:Command line is too long的解决办法
文章目录 1.报错现象2.解决办法验证3.最佳实践4.问题原因5.参考文献1.报错现象 在idea中启动一个spring cloud项目时,编译完成后直接报错,报错内容如下: Error running XXXApplication:Command line is too long. Shorten command line for XXXApplication or also for Sp…...
Android 中的 本地广播LocalBroadcastManager
Android 中的 本地广播LocalBroadcastManager 文章目录 Android 中的 本地广播LocalBroadcastManager一、LocalBroadcastManager 的基本作用二 、LocalBroadcastManager 的基本使用1、包的导入(1)Android 源码中bp文件的导入:(2&a…...
题目 1120: C语言训练-“水仙花数“问题2python详解)——练气三层后期
✨博主:命运之光 🦄专栏:算法修炼之练气篇(C\C版) 🍓专栏:算法修炼之筑基篇(C\C版) 🍒专栏:算法修炼之练气篇(Python版) ✨…...
sheng的学习笔记-【中】【吴恩达课后测验】Course 3 - 结构化机器学习项目 - 第二周测验
课程3_第2周_测验题 目录:目录 要解决的问题 ① 为了帮助你练习机器学习的策略,本周我们将介绍另一个场景,并询问你将如何行动。 ② 我们认为这个在机器学习项目中工作的“模拟器”将给出一个任务,即领导一个机器学习项目可能…...
基于Pytorch的驾驶员分心行为实时检测
本文使用深度学习和Pytorch(PyTorch 2.0.1\Torchvision 0.15.2)实时检测驾驶员的分心行为,并附录完整代码。 检测分心驾驶是现代汽车中最重要的功能之一。无论是自动驾驶汽车还是其它高端汽车,都配备了驾驶员监控系统,以持续跟踪驾驶员的行为。这对确保驾驶员保持目光在道路…...
【uniapp】小程序开发7:自定义组件、自动注册组件
一、自定义轮播图组件、自动注册 以首页轮播图组件为例。 1、创建组件文件src/components/my-swipper.vue 代码如下: <template><view><view class"uni-margin-wrap"><swiper class"swiper" circular :indicator-dots…...
Modbus转MQTT以太网网关MQT-802主要特点和典型应用
随着社会的快速发展,物联网已经潜移默化地深入工控行业的各个领域,其高效的资源整合和强大的数据采集能力,深受客户的喜爱。上海泗博为实现客户在云端平台接收处理世界万物的信息以及实现远程控制,精心打造一款全新物联网产品&…...
Go学习第五章——函数与包
Go学习第五章——函数与包 1 函数1.1 基本语法1.2 函数多返回值1.3 函数的可见性和包级函数1.4 函数调用机制底层原理1.5 值类型和引用类型1.6 注意事项和细节1.7 逃逸机制(补,可不看) 2 包2.1 快速入门2.2 包的使用细节 3 函数详细讲解3.1 递…...
【Python 常用脚本及命令系列 5 -- 如何使用 BeautifulSoup 解析CSDN网页表格中的数据】
文章目录 Python BeautifulSoup 介绍CSDN 网页表格解析开发问题总结 Python BeautifulSoup 介绍 BeautifulSoup是一个Python库,用于解析HTML和XML文档。它常常用于网络爬虫来提取网页中的信息。 以下是BeautifulSoup的一些主要特性: 解析HTMLÿ…...
OpenFeign实现分析、源码解析
什么是openfeign? 是springcloud全家桶的组件之一,其核心作用是为Rest API提供高效简洁的rpc调用方式。 为什么只定义接口而没有实现类? 源码解读(省略) 总结: 源码分析:如何发送http请求? …...
2023 10月最新Vmd 下载安装教程,WindowsLinux
文章目录 下载Vmdwindows版本安装LINUX版本安装 下载Vmd 谷歌搜索VMD 点击左下角download VMD 可选择对应版本 注:点击后会出现输入用户名和密码,由于我已注册,界面不见了,所以直接描述一下。 输入用户名和密码然后会出现让登记…...
Photoshop(PS)安装教程(图文教程超详细)
目录 一.简介 二.安装步骤 软件:PS版本:2023语言:简体中文大小:3.20G系统要求:Win10(1903)及以上版本,64位操作系统硬件要求:CPU2.0GHz 内存8G(或更高,不支…...
C++模版进阶
一、非类型模版参数 之前学习的模版,参数一般是某种类型,但其实非类型的参数也可以定义在模版里面,但也有一定的限制,只可以定义整形家族的参数,而且具有常量性 注意: 1. 浮点数、类对象以及字符串是不允…...
CloudCompare
CloudCompare 源码编译Windows 功能格式转换 源码编译 Windows 源码编译出来的默认基本不带几个插件,包括保存为 .las 的功能 可以直接从 https://www.danielgm.net/cc/ 下载编译好的版本,插件比较多。也有免安装版本 cmake -B build -S . -G "Vi…...
【算法小课堂】深入理解前缀和算法
前缀和是指某序列的前n项和,可以把它理解为数学上的数列的前n项和,而差分可以看成前缀和的逆运算。合理的使用前缀和与差分,可以将某些复杂的问题简单化。 我们通过一个例子来理解前缀和算法的优势: 一维前缀和: ww…...
元对象系统功能
元对象系统功能 建立工程 布局页面 布局页面 修改原件名称 建立元对象 函数作为接口 增加一些固定的属性 #------------------------------------------------- # # Project created by QtCreator 2023-10-24T21:54:44 # #----------------------------…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...
P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...
mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...
向量几何的二元性:叉乘模长与内积投影的深层联系
在数学与物理的空间世界中,向量运算构成了理解几何结构的基石。叉乘(外积)与点积(内积)作为向量代数的两大支柱,表面上呈现出截然不同的几何意义与代数形式,却在深层次上揭示了向量间相互作用的…...
Django RBAC项目后端实战 - 03 DRF权限控制实现
项目背景 在上一篇文章中,我们完成了JWT认证系统的集成。本篇文章将实现基于Redis的RBAC权限控制系统,为系统提供细粒度的权限控制。 开发目标 实现基于Redis的权限缓存机制开发DRF权限控制类实现权限管理API配置权限白名单 前置配置 在开始开发权限…...
Netty自定义协议解析
目录 自定义协议设计 实现消息解码器 实现消息编码器 自定义消息对象 配置ChannelPipeline Netty提供了强大的编解码器抽象基类,这些基类能够帮助开发者快速实现自定义协议的解析。 自定义协议设计 在实现自定义协议解析之前,需要明确协议的具体格式。例如,一个简单的…...
