富文本编辑器取消自动聚焦
先说说,我的ruoyi项目使用富文本场景;在列表中新增/和列表中每项数据的编辑,都会使用到富文本Quill。
取消自动聚焦方法:在创建挂载富文本Quill对象的事件函数中,加入代码
(下图:第一个红框是在页面中使用富文本;第二个红框是富文本组件中的init方法)
-
只是在这里加上代码,会有一个问题:第一次点击编辑的时候,能够实现取消自动聚焦问题;第二次点击的时候,效果没有实现
-
解决方法:父组件代码中,在Editor组件上加上一个判断,v-if=”dislog”。
-
加上一个判断v-if=”dislog”,目的是:使其富文本跟随弹框一起显示/隐藏。显示的时候:富文本就会重走Editor组件中写的所有生命周期以及函数事件(主要是让富文本重新走“取消自动聚焦”的代码 - 也就是init方法)
-
贴上富文本组件代码
<template>
<div class="system-editor">
<div class="editor" ref="editor" :style="styles">
<input v-show="false"
ref="editorFile"
id="file"
type="file"
accept="image/png, image/gif, image/jpeg, image/bmp, image/x-icon"
@change="onFileChange"
/>
<input
v-show="false"
ref="videoFile"
id="videoFile"
type="file"
accept="video/*"
@change="onFileChange"
/>
</div>
<!-- 下面代码作用:在富文本右下角展示字数计数功能 -->
<div class="se-fontcount" v-if="maxCount > 0">
<span :style="{color: inputCount > maxCount ? '#ff0000' : '#888'}">{{ inputCount }}</span>
<span style="margin:0 2px">/</span>
<span>{{ maxCount }}</span>
</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 { uploadFile } from '@/api/tool/fileUpload'
export default {
name: "Editor",
props: {
/* 编辑器的内容 */
value: {
type: String,
default: "",
},
/* 高度 */
height: {
type: Number,
default: null,
},
/* 最小高度 */
minHeight: {
type: Number,
default: null,
},
/* 只读 */
readOnly: {
type: Boolean,
default: false,
},
width: {
type: String,
default: "680px"
},
/** 禁用插件链接图片视频 */
noMedia: {
type: Boolean,
default: false
},
/** 限制字数 默认0 不限制*/
maxCount: {
type: Number,
default: 0
},
/** 默认显示提示语 */
placeholder: {
type: String,
default: "请输入内容"
},
/** 限制图片数量 默认0 不限制 */
maxImg: {
type: Number,
default: 0
},
/** 禁用上传视频 */
noVideo: {
type: Boolean,
default: false
},
},
data() {
return {
Quill: null,
currentValue: "",
options: {
theme: "snow",
bounds: document.body,
debug: "warn",
modules: {
// 工具栏配置
toolbar: [
["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"] // 链接、图片、视频 "link", "image", "video"
]
},
placeholder: this.placeholder,
readOnly: this.readOnly,
},
// 插入图片计数
imageAmount: 0,
};
},
computed: {
styles() {
let style = {};
if (this.minHeight) {
style.minHeight = `${this.minHeight}px`;
}
if (this.height) {
style.height = `${this.height}px`;
}
style.width = "100%";
style.overflow = "auto"
return style;
},
inputCount() {
let msg = this.value;
let info = msg.replace(/<[^<^>]*>/g, '').replace(eval('/ /g'), "");
return info.trim().length
}
},
watch: {
value: {
handler(val) {
// 当前富文本插入图片数量
this.imageAmount = (val.length-val.replaceAll("<img","").length)/4
if (val !== this.currentValue) {
this.currentValue = val === null ? "" : val;
if (this.Quill) {
this.Quill.pasteHTML(this.currentValue);
}
}
},
immediate: true,
},
},
mounted() {
if (this.noMedia) {
this.options.modules.toolbar = this.options.modules.toolbar.splice(0,9)
}
if (this.noVideo) {
let customToolbar = this.options.modules.toolbar.splice(0,9)
customToolbar.push(["link", "image"])
this.options.modules.toolbar = customToolbar
}
this.init();
},
beforeDestroy() {
this.Quill = null;
},
methods: {
init() {
console.log('++++++++++++++++++++++++++s')
const editor = this.$refs.editor;
this.Quill = new Quill(editor, this.options);
// 取消自动聚焦 start
this.Quill.enable(false);
this.$nextTick(()=>{
this.Quill.enable(true);
this.Quill.blur();
});
// 取消自动聚焦 end
this.Quill.pasteHTML(this.currentValue);
const Link = Quill.import('formats/link');
class FileBlot extends Link { // 继承Link Blot
static create(value) {
let node = undefined
if (value&&!value.href){ // 适应原本的Link Blot
node = super.create(value);
}
else{ // 自定义Link Blot
node = super.create(value.href);
node.innerText = value.innerText;
}
return node;
}
}
FileBlot.blotName = 'link';
FileBlot.tagName = 'A';
Quill.register(FileBlot);
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);
});
const toolBar = this.Quill.getModule("toolbar");
toolBar.addHandler('image', () => {
this.$refs['editorFile'].click();
});
toolBar.addHandler('link', val => {
if (val) {
let href = prompt('请输入链接地址');
// let length = this.Quill.getSelection().index;
// this.Quill.insertEmbed(length, 'a', href);
if (href) {
const range = this.Quill.getSelection();
this.Quill.insertEmbed(range.index, 'link', { href: href, innerText: href }, "api")
this.Quill.setSelection(href.length);
}
} else {
this.Quill.format('link', false);
}
})
toolBar.addHandler('video', () => {
this.$refs['videoFile'].click();
})
this.Quill.root.addEventListener('paste', evt => {
// 不启用上传图片视频时,增加提示
if (this.noMedia && evt.clipboardData.types[0] == 'Files') {
this.msgError('不能粘贴图片,请清除!')
evt.clipboardData.types = []
evt.clipboardData.types.push('text/plain')
return
}
if (evt.clipboardData && evt.clipboardData.files && evt.clipboardData.files.length) {
evt.preventDefault();
[].forEach.call(evt.clipboardData.files, file => {
if (!file.type.match(/^image\/(gif|jpe?g|a?png|bmp)/i)) {
return
}
uploadFile(file).then(res => {
let url = process.env.VUE_APP_BASE_FILEURL + res.msg
let range = this.Quill.getSelection()
if (range) {
let length = this.Quill.getSelection().index;
this.Quill.insertEmbed(length, 'image', url);
this.Quill.setSelection(length + 1);
this.Quill.setSelection(range.index + 1)
}
});
})
}
}, false)
},
onFileChange(e) {
let file = e.target.files[0];
let index= file.name.lastIndexOf(".");
let ext = file.name.substr(index + 1);
let accept = ['jpg', 'png', 'jpeg', 'git', 'mp4', 'avi', 'mov', 'flv'];
if (accept.indexOf(ext) >= 0) {
uploadFile(file).then(res => {
let url = process.env.VUE_APP_BASE_FILEURL + res.msg
let selection = this.Quill.getSelection(true);
//调用insertEmbed 将图片插入到编辑器
if (ext === 'mp4' || ext === 'avi' || ext === 'mov' || ext === 'flv') {
this.Quill.insertEmbed(selection.index, "video", url);
} else {
this.imageAmount++
if(this.maxImg > 0){
// 限制图片上传数量
this.imageAmount <= this.maxImg? this.Quill.insertEmbed(selection.index, "image", url)
: this.$message({
message: '插入图片最多15张',
type: 'warning'
});
}else{
// 不限制图片上传数量
this.Quill.insertEmbed(selection.index, "image", url);
}
}
}).catch(() => {
this.msgError("文件上传失败");
});
} else {
this.msgError("只支持jpg、png、jpeg、git格式图片,mp4、avi、mov、flv格式的视频");
}
e.target.value = "";
},
},
};
</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-container.ql-snow, .ql-toolbar.ql-snow {
border-color: #dcdfe6;
border-radius: 4px;
}
.ql-toolbar.ql-snow {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.ql-container.ql-snow {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.ql-editor.ql-blank::before {
font-style: normal;
color: #c0c4cc;
}
.system-editor {
position: relative;
}
.system-editor .ql-editor {
padding-bottom: 20px;
}
.system-editor .se-fontcount {
font-size: 12px;
position: absolute;
right: 1px;
bottom: 1px;
line-height: 22px;
padding: 0 10px;
background: #fff;
}
</style>