[vue-quill-editor]同一页面有多个编辑器时的冲突处理&B站链接插入问题
1. 当一个页面有多个编辑器时,在文本中间插入视频,视频总是插入到文本开头。原因在于quill的视频按钮没法传参,导致无法正确识别到具体的一个编辑器。2. 先正则匹配获取链接中的bv号,然后调取b站api获取aid,通过aid展示纯视频。3. 对于b站视频链接,用iframe标签显示;对于上传的视频,用video标签显示。1. 通过闭包的方式获取编辑器的id,并将其与该页面的data变量联系起来。
·
环境描述/前提
脚手架: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;
注意:代码为本人参与项目代码的节选,如有需要请自行修改。
更多推荐
已为社区贡献1条内容
所有评论(0)