ruoyi 富文本编辑器Quill-取消自动聚焦

富文本编辑器取消自动聚焦

先说说,我的ruoyi项目使用富文本场景;在列表中新增/和列表中每项数据的编辑,都会使用到富文本Quill。

取消自动聚焦方法:在创建挂载富文本Quill对象的事件函数中,加入代码

(下图:第一个红框是在页面中使用富文本;第二个红框是富文本组件中的init方法)
在这里插入图片描述

  1. 只是在这里加上代码,会有一个问题:第一次点击编辑的时候,能够实现取消自动聚焦问题;第二次点击的时候,效果没有实现

  2. 解决方法:父组件代码中,在Editor组件上加上一个判断,v-if=”dislog”。
    在这里插入图片描述

  3. 加上一个判断v-if=”dislog”,目的是:使其富文本跟随弹框一起显示/隐藏。显示的时候:富文本就会重走Editor组件中写的所有生命周期以及函数事件(主要是让富文本重新走“取消自动聚焦”的代码 - 也就是init方法)

  4. 贴上富文本组件代码

<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('/&nbsp;/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>

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

F2E_zeke

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值