原生quill 富文本编辑器支持图片大小调整

268 阅读2分钟

<template>
  <div class="in-editor-wrapper">
    <div :class="this.className"></div>
    <form
      action
      method="post"
      enctype="multipart/form-data"
      id="uploadFormMulti"
    >
      <input
        style="display: none"
        :id="uniqueId"
        type="file"
        name="file"
        multiple
        accept="image/jpg, image/jpeg, image/png, image/gif"
        @change="uploadImgEditor('uploadFormMulti')"
      />
    </form>
  </div>
</template>

<script>
import { uploadImg } from "@/api/uploadImg";
// 引入原始组件
import Quill from "quill";
// 引入核心样式和主题样式
import "quill/dist/quill.core.css";
import "quill/dist/quill.snow.css";
import "./styles/editor.css";
import imageResize from "quill-blot-formatter"; //改为引入quill-blot-formatter就可以了
import QuillBetterTable from "quill-better-table";
import "quill-better-table/dist/quill-better-table.css";
Quill.register(
  {
    "modules/better-table": QuillBetterTable,
  },
  true
);
Quill.register("modules/imageResize", imageResize);
// 工具栏配置
const toolbarOptions = [
  ["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线 -----['bold', 'italic', 'underline', 'strike']
  ["blockquote", "code-block"], // 引用  代码块-----['blockquote', 'code-block']
  [{ header: 1 }, { header: 2 }], // 1、2 级标题-----[{ header: 1 }, { header: 2 }]
  [{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表-----[{ list: 'ordered' }, { list: 'bullet' }]
  [{ script: "sub" }, { script: "super" }], // 上标/下标-----[{ script: 'sub' }, { script: 'super' }]
  [{ indent: "-1" }, { indent: "+1" }], // 缩进-----[{ indent: '-1' }, { indent: '+1' }]
  [{ direction: "rtl" }], // 文本方向-----[{'direction': 'rtl'}]
  [{ size: ["small", false, "large", "huge"] }], // 字体大小-----[{ size: ['small', false, 'large', 'huge'] }]
  [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题-----[{ header: [1, 2, 3, 4, 5, 6, false] }]
  [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色-----[{ color: [] }, { background: [] }]
  [{ font: [] }], // 字体种类-----[{ font: [] }]
  [{ align: [] }], // 对齐方式-----[{ align: [] }]
  ["clean"], // 清除文本格式-----['clean']
  ["image"], // 链接、图片、视频-----['link', 'image', 'video']
  [{ table: "TD" }],
];
export default {
  name: "MEditor",
  props: {
    // 用于双向绑定
    editContent: String,
    className: {
      type: String,
      default: "in-editor",
    },
  },
  data() {
    return {
      // 待初始化的编辑器
      editor: null,
      uniqueId: this.className,
      content: this.editContent, // 富文本编辑器默认内容
      // 配置参数
      options: {
        theme: "snow",
        modules: {
          // 工具栏的具体配置
          toolbar: {
            container: toolbarOptions,
            handlers: {
              table: function () {
                this.quill.getModule("better-table").insertTable(3, 3);
              },
            },
          },
          table: false,
          "better-table": {
            operationMenu: {
              items: {
                insertColumnRight: {
                  text: "向右插入列",
                },
                insertColumnLeft: {
                  text: "向左插插列",
                },
                insertRowUp: {
                  text: "向上插入行",
                },
                insertRowDown: {
                  text: "向下插入行",
                },
                mergeCells: {
                  text: "合并单元格",
                },
                unmergeCells: {
                  text: "取消合并单元格",
                },
                deleteColumn: {
                  text: "删除列",
                },
                deleteRow: {
                  text: "删除行",
                },
                deleteTable: {
                  text: "删除表格",
                },
              },
              background: {
                color: "#333",
              },
              color: {
                colors: ["green", "red", "yellow", "blue", "white"],
                text: "背景色:",
              },
            },
          },
          imageResize: {
            displayStyles: {
              backgroundColor: "black",
              border: "none",
              color: "white",
            },
            modules: ["Resize", "DisplaySize", "Toolbar"],
          },
          clipboard: {
            // 匹配的正则表达式
            matchVisual: false,
          },

          keyboard: {
            bindings: QuillBetterTable.keyboardBindings,
          },
        },
        placeholder: "请输入内容 ...",
      },
    };
  },
  watch: {
    // 监听外部值的传入,用于将值赋予编辑器
    editContent(val) {
      // 如果编辑器没有初始化,则停止赋值
      if (!this.editor) {
        return;
      }
      // 获取编辑器当前内容
      let content = this.editor.root.innerHTML;
      // 外部传入了新值,而且与当前编辑器的内容不一致
      if (val && val !== content) {
        // 将外部传入的HTML内容转换成编辑器识别的delta对象
        let delta = this.editor.clipboard.convert({
          html: val,
        });
        console.log(delta);
        // 编辑器的内容需要接收delta对象
        this.editor.setContents(delta);
      }
    },
  },
  mounted() {
    // 初始化编辑器
    this.initEditor();
    // 添加自定义图片上传
    var imgHandler = async (image) => {
      if (image) {
        //隐藏的file文本ID
        var fileInput = document.getElementById(this.uniqueId);
        fileInput.click();
      }
    };
    this.editor.getModule("toolbar").addHandler("image", imgHandler);

    this.editor.on("text-change", () => {
      this.$emit("getContent", this.editor.root.innerHTML);
    });
  },
  methods: {
    /**
     * @descripttion: 初始化编辑器
     * @Author: 
     * @Date: 2021-07-09 15:23:02
     */
    initEditor() {
      // 获取编辑器的DOM容器
      let editorDom = this.$el.querySelector(`.${this.className}`);
      // 初始化编辑器
      this.editor = new Quill(editorDom, this.options);
      // let length = editorDom.getSelection().index
      // console.log(length)
      // 双向绑定
      this.editor.on("text-change", () => {
        this.$emit("input", this.editor.root.innerHTML);
        this.$emit("getContent", this.editor.root.innerHTML);
      });

      this.setupPasteHandler();
      // 富文本赋值
      let delta = this.editor.clipboard.convert({
        html: this.content,
      });
      this.editor.setContents(delta);
    },
    setupPasteHandler() {
      this.editor.root.addEventListener("paste", async (e) => {
        if (!e.clipboardData?.items) return;

        const items = e.clipboardData.items;
        let htmlContent = "";
        const imageFiles = [];

        // 分离文本和图片
        for (let i = 0; i < items.length; i++) {
          const item = items[i];
          console.log(item);
          if (item.type.includes("text/html")) {
            htmlContent = await new Promise((resolve) => {
              item.getAsString(resolve);
            });
          } else if (item.type.includes("image")) {
            imageFiles.push(item.getAsFile());
          }
        }
        console.log(imageFiles);
        // 若存在图片,阻止默认粘贴行为
        if (imageFiles.length > 0) {
          e.preventDefault();
          await this.processMixedContent(this.editor, htmlContent, imageFiles);
        }
      });
    },
    async processMixedContent(quill, htmlContent, imageFiles) {
      // 1. 创建临时容器解析 HTML
      const tempDiv = document.createElement("div");
      tempDiv.innerHTML = htmlContent;

      // 2. 替换图片占位符
      const images = tempDiv.querySelectorAll("img");
      for (let i = 0; i < images.length; i++) {
        if (imageFiles[i]) {
          // 上传图片并获取 URL
          let content = {
            file: imageFiles[i],
            data: { type: "image", xtype: "ysb_product", editor: true },
          };
          const imageUrl = await this.uploadImage(content);
          // 替换为有效图片地址
          images[i].src = imageUrl;
        }
      }

      // 3. 将处理后的 HTML 插入编辑器
      const delta = quill.clipboard.convert(tempDiv.innerHTML);
      quill.setContents(delta, "silent"); // 静默插入避免光标跳动
    },
    uploadImage(content) {
      try {
        //调用上传文件接口
        uploadImg(content).then((res) => {
          //返回上传文件的地址
          let url = res.data.url;
          if (url != null && url.length > 0) {
            let Range = this.editor.getSelection();
            url = url.indexOf("http") != -1 ? url : "http:" + url;
            //上传文件成功之后在富文本中回显(显示)
            this.editor.insertEmbed(
              Range != null ? Range.index : 0,
              "image",
              url
            );
          } else {
            this.$message.warning("图片上传失败");
          }
          //成功之后,将文件的文本框的value置空
          document.getElementById(this.uniqueId).value = "";
        });
      } catch ({ message: msg }) {
        document.getElementById(this.uniqueId).value = "";
        this.$message.warning(msg);
      }
    },
    /**
     * @descripttion: 富文本上传图片
     * @Author: 
     * @Date: 2021-07-06 15:48:52
     */
    uploadImgEditor: async function () {
      let content = {
        file: document.getElementById(this.uniqueId).files[0],
        data: { type: "image", xtype: "ysb_product", editor: true },
      };

      let fileArr = document.getElementById(this.uniqueId).files;

      for (const el of fileArr) {
        let content = {
          file: el,
          data: { type: "image", xtype: "ysb_product", editor: true },
        };
        this.uploadImage(content);
      }
    
    },
  },
};
</script>