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 # #----------------------------…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...
网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...
RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill
视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...
【Linux手册】探秘系统世界:从用户交互到硬件底层的全链路工作之旅
目录 前言 操作系统与驱动程序 是什么,为什么 怎么做 system call 用户操作接口 总结 前言 日常生活中,我们在使用电子设备时,我们所输入执行的每一条指令最终大多都会作用到硬件上,比如下载一款软件最终会下载到硬盘上&am…...
6个月Python学习计划 Day 16 - 面向对象编程(OOP)基础
第三周 Day 3 🎯 今日目标 理解类(class)和对象(object)的关系学会定义类的属性、方法和构造函数(init)掌握对象的创建与使用初识封装、继承和多态的基本概念(预告) &a…...
新版NANO下载烧录过程
一、序言 搭建 Jetson 系列产品烧录系统的环境需要在电脑主机上安装 Ubuntu 系统。此处使用 18.04 LTS。 二、环境搭建 1、安装库 $ sudo apt-get install qemu-user-static$ sudo apt-get install python 搭建环境的过程需要这个应用库来将某些 NVIDIA 软件组件安装到 Je…...
