参考文档: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