vue使用quill编辑器自定义附件上传方法,并根据上传附件名称生成链接

news2024/9/28 7:16:04

1、附件上传
需求:
在编辑器中上传word,pdf,excel等附件后,能根据上传附件的名称生成link链接,在展示页面能实现点击链接下载或预览附件,效果图如下:
在这里插入图片描述
实现方法:
quill编辑器自身带有link,但不满足需求,于是我修改了原有的link方法,使其适配目前的需求
1.增加el-upload上传组件

 <el-upload
      :action="uploadUrl"
      :before-upload="handleBeforeUploadFile"
      :on-success="handleUploadSuccessFile"
      :on-error="handleUploadErrorFile"
      name="file"
      :show-file-list="false"
      :headers="headers"
      style="display: none"
      ref="uploadFile"
      v-if="this.type == 'url'"
    >
</el-upload>

2.重写link方法
在这里插入图片描述

// 源码中是import直接倒入,这里要用Quill.import引入
const Link = Quill.import("formats/link");
// 自定义a链接
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.href = value.href
      node.innerText = value.innerText
      // node.setAttribute('download', value.innerText);  // 左键点击即下载
    }
    return node;
  }
}
FileBlot.blotName = "link" // 这里不用改,如果需要也可以保留原来的,这里用个新的blot
FileBlot.tagName = "A"
Quill.register(FileBlot) // 注册link

3.给link生成的超链接按钮添加点击事件
在这里插入图片描述

	toolbar.addHandler("link", (value) => {
          if (value) {
            debugger
            console.log('this.$refs.upload',this.$refs.upload)
            this.$refs.uploadFile.$children[0].$refs.input.click();
          } else {
            this.quill.format("link", false);
          }
    });

4.添加upload的on-success、on-error方法
在这里插入图片描述

 handleUploadSuccessFile(res, file) {
  // 如果上传成功
  if (res.code == 200) {
      // 获取富文本组件实例
       let quill = this.Quill;
       // 获取光标所在位置
       let length = quill.getSelection().index;
       // 插入文件  res.url为服务器返回的图片地址
        quill.insertEmbed(length, "link", 
        {
          href: process.env.VUE_APP_BASE_API + res.fileName, 
          innerText: res.originalFilename
       },
      );
     // 调整光标到最后
     quill.setSelection(length + 1);
  } else {
    this.$message.error("文件插入失败");
  }
},
handleUploadErrorFile() {
      this.$message.error("文件插入失败");
},

完整代码

<template>
  <div>
    <el-upload
      :action="uploadUrl"
      :before-upload="handleBeforeUpload"
      :on-success="handleUploadSuccess"
      :on-error="handleUploadError"
      name="file"
      :show-file-list="false"
      :headers="headers"
      style="display: none"
      ref="upload"
      v-if="this.type == 'url'"
    >
    </el-upload>
    <el-upload
      :action="uploadUrl"
      :before-upload="handleBeforeUploadVideo"
      :on-success="handleUploadSuccessVideo"
      :on-error="handleUploadErrorVideo"
      name="file"
      :show-file-list="false"
      :headers="headers"
      style="display: none"
      ref="uploadVideo"
      v-if="this.type == 'url'"
    >
    </el-upload>
    <el-upload
      :action="uploadUrl"
      :before-upload="handleBeforeUploadFile"
      :on-success="handleUploadSuccessFile"
      :on-error="handleUploadErrorFile"
      name="file"
      :show-file-list="false"
      :headers="headers"
      style="display: none"
      ref="uploadFile"
      v-if="this.type == 'url'"
    >
    </el-upload>
    <div class="editor" ref="editor" :style="styles"></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 { getToken } from "@/utils/auth";

// 源码中是import直接倒入,这里要用Quill.import引入
const Link = Quill.import("formats/link");
// 自定义a链接
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.href = value.href
      node.innerText = value.innerText
      // node.setAttribute('download', value.innerText);  // 左键点击即下载
    }
    return node;
  }
}
FileBlot.blotName = "link" // 这里不用改,如果需要也可以保留原来的,这里用个新的blot
FileBlot.tagName = "A"
Quill.register(FileBlot) // 注册link

export default {
  name: "Editor",
  props: {
    /* 编辑器的内容 */
    value: {
      type: String,
      default: "",
    },
    /* 高度 */
    height: {
      type: Number,
      default: null,
    },
    /* 最小高度 */
    minHeight: {
      type: Number,
      default: null,
    },
    /* 只读 */
    readOnly: {
      type: Boolean,
      default: false,
    },
    /* 上传文件大小限制(MB) */
    fileSize: {
      type: Number,
      default: 50,
    },
    /* 上传图片大小限制(MB) */
    imageSize: {
      type: Number,
      default: 5,
    },
    videoSize: {
      type: Number,
      default: 500,
    },
    /* 类型(base64格式、url格式) */
    type: {
      type: String,
      default: "url",
    }
  },
  data() {
    return {
      uploadUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址
      headers: {
        Authorization: "Bearer " + getToken()
      },
      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: ["20px","14px","16px", "large", "huge"] }],   // 字体大小
            [{ header: [1, 2, 3, 4, 5, 6, false] }],         // 标题
            [{ color: [] }, { background: [] }],             // 字体颜色、字体背景颜色
            [{ align: [] }],                                 // 对齐方式
            ["clean"],                                       // 清除文本格式
            ["link", "image", "video"]                       // 链接、图片、视频
          ],
        },
        placeholder: "请输入内容",
        readOnly: this.readOnly,
      },
    };
  },
  computed: {
    styles() {
      let style = {};
      if (this.minHeight) {
        style.minHeight = `${this.minHeight}px`;
      }
      if (this.height) {
        style.height = `${this.height}px`;
      }
      return style;
    },
  },
  watch: {
    value: {
      handler(val) {
        if (val !== this.currentValue) {
          this.currentValue = val === null ? "" : val;
          if (this.Quill) {
            this.Quill.pasteHTML(this.currentValue);
          }
        }
      },
      immediate: true,
    },
  },
  mounted() {
    this.init();
  },
  beforeDestroy() {
    this.Quill = null;
  },
  methods: {
    init() {
      const editor = this.$refs.editor;
      this.Quill = new Quill(editor, this.options);
      var Size = Quill.import("formats/size");
      Size.whitelist = ["14px","16px", "large","20px", "huge"];
      // 如果设置了上传地址则自定义图片上传事件
      if (this.type == 'url') {
        let toolbar = this.Quill.getModule("toolbar");
        toolbar.addHandler("image", (value) => {
          if (value) {
            this.$refs.upload.$children[0].$refs.input.click();
          } else {
            this.quill.format("image", false);
          }
        });
        toolbar.addHandler("video", (value) => {
          if (value) {
            this.$refs.uploadVideo.$children[0].$refs.input.click();
          } else {
            this.quill.format("video", false);
          }
        });
        toolbar.addHandler("link", (value) => {
          if (value) {
            debugger
            console.log('this.$refs.upload',this.$refs.upload)
            this.$refs.uploadFile.$children[0].$refs.input.click();
          } else {
            this.quill.format("link", false);
          }
        });
      }
      this.Quill.pasteHTML(this.currentValue);
      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);
      });
    },
    // 上传前校检格式和大小
    handleBeforeUpload(file) {
      const type = ["image/jpeg", "image/jpg", "image/png", "image/svg"];
      const isJPG = type.includes(file.type);
      // 检验文件格式
      if (!isJPG) {
        this.$message.error(`图片格式错误!`);
        return false;
      }
      // 校检文件大小
      if (this.imageSize) {
        const isLt = file.size / 1024 / 1024 < this.imageSize;
        if (!isLt) {
          this.$message.error(`上传文件大小不能超过 ${this.imageSize} MB!`);
          return false;
        }
      }
      return true;
    },
    handleBeforeUploadVideo(file) {
      const type = ["video/mp4"];
      const isVideo = type.includes(file.type);
      // 检验文件格式
      if (!isVideo) {
        this.$message.error(`视频格式错误!`);
        return false;
      }
      // 校检文件大小
      if (this.videoSize) {
        const isLt = file.size / 1024 / 1024 < this.videoSize;
        if (!isLt) {
          this.$message.error(`上传文件大小不能超过 ${this.videoSize} MB!`);
          return false;
        }
      }
      return true;
    },
    // 上传前校检格式和大小
    handleBeforeUploadFile(file) {
      // 校检文件大小
      if (this.fileSize) {
        const isLt = file.size / 1024 / 1024 < this.fileSize;
        if (!isLt) {
          this.$message.error(`上传文件大小不能超过 ${this.fileSize} MB!`);
          return false;
        }
      }
      return true;
    },
    handleUploadSuccess(res, file) {
      // 如果上传成功
      if (res.code == 200) {
        // 获取富文本组件实例
        let quill = this.Quill;
        // 获取光标所在位置
        let length = quill.getSelection().index;
        // 插入图片  res.url为服务器返回的图片地址
        quill.insertEmbed(length, "image", process.env.VUE_APP_BASE_API + res.fileName);
        // 调整光标到最后
        quill.setSelection(length + 1);
      } else {
        this.$message.error("图片插入失败");
      }
    },
    handleUploadSuccessVideo(res, file) {
      // 如果上传成功
      if (res.code == 200) {
        // 获取富文本组件实例
        let quill = this.Quill;
        // 获取光标所在位置
        let length = quill.getSelection().index;
        // 插入图片  res.url为服务器返回的图片地址
        quill.insertEmbed(length, "video", process.env.VUE_APP_BASE_API + res.fileName);
        // 调整光标到最后
        quill.setSelection(length + 1);
      } else {
        this.$message.error("视频插入失败");
      }
    },
    handleUploadSuccessFile(res, file) {
      // 如果上传成功
      if (res.code == 200) {
        // 获取富文本组件实例
        let quill = this.Quill;
        // 获取光标所在位置
        let length = quill.getSelection().index;
        // 插入文件  res.url为服务器返回的图片地址
        quill.insertEmbed(length, "link", 
          {
            href: process.env.VUE_APP_BASE_API + res.fileName, 
            innerText: res.originalFilename
          },
        );
        // 调整光标到最后
        quill.setSelection(length + 1);
      } else {
        this.$message.error("文件插入失败");
      }
    },
    handleUploadError() {
      this.$message.error("图片插入失败");
    },
    handleUploadErrorVideo() {
      this.$message.error("视频插入失败");
    },
    handleUploadErrorFile() {
      this.$message.error("文件插入失败");
    },
  },
};
</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="14px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="14px"]::before {
  content: "14px";
}
.ql-size-14px {
  font-size: 14px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="16px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="16px"]::before {
  content: "16px";
}
.ql-size-16px {
  font-size: 16px;
}

.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="20px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="20px"]::before {
  content: "20px";
}
.ql-size-20px {
  font-size: 20px;
}
.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-video {
  width: 100%;
  height: 425px;
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1919814.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

探索【Python面向对象】编程:新时代的高级编程范式详解

目录 1. 面向对象编程概念&#xff08;OOP&#xff09; 1.1 什么是类和对象&#xff1f; 1.2 类的定义 1.3 类和对象的关系 1.4 小李的理解 2. 抽象 2.1 抽象的概念 2.2 抽象类和方法 2.3 小李的理解 3. 类和实例 3.1 类的定义和实例化 3.2 类的属性和方法 3.3 小…

[Linux][Shell][Shell逻辑控制]详细讲解

目录 1.if 判断1.if-then2.if-then-else3.elif4.case5.实际上手 2.条件测试0.事前说明1.test 命令2.[]3.双括号1.(())2.[[]] 4.实际上手 3.循环1.for2.while3.until命令4.控制循环1.break2.continue 5.处理循环的输出 1.if 判断 1.if-then 语法&#xff1a;if command thenco…

Java技术栈总结:容器集合篇

一、List 1、ArrayList &#xff08;1&#xff09;底层数据结构 底层数据结构为数组。数组是一种用连续的内存空间存储相同数据类型数据的线性数据结构。 Q&#xff1a;为什么数组索引下标从0开始&#xff1f; A&#xff1a;从0开始&#xff0c;对应寻址公式&#xff1a;a[i]…

FLStudio21.3.12中文破解版本安装包win+mac电脑安装包下载

&#x1f3a4; FL Studio 21中文版&#xff1a;音乐制作新宠&#xff0c;让你的创作起飞&#xff01; 嗨&#xff0c;亲爱的音乐创作者们&#xff01;&#x1f44b;今天要和大家分享一个让我超级兴奋的宝藏软件——FL Studio 21中文版&#xff01;这不仅仅是一款音乐制作软件&…

科研绘图系列:R语言金字塔图(pyramid plot)

介绍 金字塔图(Pyramid chart)是一种用于展示人口统计数据的图表,特别是用于展示不同年龄段的人口数量。这种图表通常用于展示人口结构,比如性别和年龄的分布。 特点: 年龄分层:金字塔图按年龄分层,每一层代表一个年龄组。性别区分:通常,男性和女性的数据会被分别展…

Linux命令-grep/wc/管道符

1、Linux命令-grep/wc/管道符 2、echo/tail/重定向符 3、vi/vim 编辑器

有哪些好用的考勤管理系统?

&#x1f308; 对于企业而言&#xff0c;考勤管理不仅仅是支持员工工资计算&#xff0c;还会对实际的运营产生很大影响。一个好用的考勤管理系统能够实现考勤数据的实时采集和管理&#xff0c;保证考勤数据的稳定运行&#xff0c;从而实现复杂的工作安排&#xff0c;有效降低人…

uniapp上架到appstore遇到的问题

1、appstore在美国审核&#xff0c;需要把服务器接口的国外访问权限放开 2、登陆部分 a、审核时只能有密码登陆&#xff0c;可以通过接口响应参数将其他登陆方式暂时隐藏&#xff0c;审核成功后放开即可 b、需要有账号注销功能 3、使用照相机和相册功能时需要写清楚描述文案

具有 0.5V 超低输入电压的 3A 升压转换器TPS61021

1 特性 输入电压范围&#xff1a;0.5V 至 4.4V 启动时的最小输入电压为 0.9V 可设置的输出电压范围&#xff1a;1.8V 到 4.0V 效率高达 91%&#xff08;VIN 2.4V、VOUT 3.3V 且 IOUT 1.5A 时&#xff09; 2.0MHz 开关频率 IOUT > 1.5A&#xff0c;VOUT 3.3V&#xff08;V…

OSINT 项目:以太坊可视化工具

KennBro &#xff0c; iKy的开发者&#xff0c;正在构建一个令人兴奋的新工具。 他使用来自Etherscan区块浏览器的信息为以太坊创建了一个可视化浏览器。 使用免费的 API 密钥和此工具&#xff0c;您可以直观地了解交易和钱包。 我还没有时间自己安装它来测试它&#xff0c;…

Simscape物理建模步骤

为了介绍构建和仿真物理模型的步骤&#xff0c;这里以simulink自带示例模型Mass-Spring-Damper with Controller为例&#xff0c;下图为建立好的模型。 详细物理建模和仿真分析步骤如下&#xff1a; 步骤 1&#xff1a;使用 ssc_new 创建新模型 使用 ssc_new 是开始构建 Sims…

linux系统操作/基本命令/vim/权限修改/用户建立

Linux的目录结构&#xff1a; 一&#xff1a;在Linux系统中&#xff0c;路径之间的层级关系&#xff0c;使用:/来表示 注意:1、开头的/表示根目录 2、后面的/表示层级关系 二&#xff1a;在windows系统中&#xff0c;路径之间的层级关系&#xff0c;使用:\来表示 注意:1、D:表示…

职业本科计算机网络实训室

一、职业本科计算机网络实训室建设的背景 随着数字化时代的深入发展&#xff0c;计算机网络技术已经渗透到社会的每一个角落&#xff0c;成为推动社会进步的重要力量。在《中华人民共和国国民经济和社会发展第十四个五年规划和2035年远景目标纲要》中&#xff0c;建设数字中国…

2972.力扣每日一题7/11 Java(击败100%)

博客主页&#xff1a;音符犹如代码系列专栏&#xff1a;算法练习关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 目录 解题思路 解题方法 时间复杂度 空间复杂度 Code 解题思路 该问…

vscode编译环境配置-c++

1. 支持跳转 安装c/c扩展 安装后即可支持跳转

Elasticsearch:介绍 retrievers - 搜索一切事物

作者&#xff1a;来自 Elastic Jeff Vestal, Jack Conradson 在 8.14 中&#xff0c;Elastic 在 Elasticsearch 中引入了一项名为 “retrievers - 检索器” 的新搜索功能。继续阅读以了解它们的简单性和效率&#xff0c;以及它们如何增强你的搜索操作。 检索器是 Elasticsearc…

还不懂 OOM ?详解内存溢出与内存泄漏区别!

内存溢出与内存泄漏 1. 内存溢出&#xff08;Out Of Memory&#xff0c;OOM&#xff09; 概念&#xff1a; 内存溢出是指程序在运行过程中&#xff0c;尝试申请的内存超过了系统所能提供的最大内存限制&#xff0c;并且垃圾收集器也无法提供更多的内存&#xff0c;导致程序无…

深入浅出Ansiable

目录 Ansible的起源 Ansible的发展史 Ansible的功能 Ansible的特性 Ansible的架构 Ansible的注意事项 Ansible入门 Ansible的安装 Ansible配置文件 配置文件解析 inventory主机配置清单 Ansible相关工具 Ansible的常用模块 Command模块 shell模块 Script模块 C…

Windows 电脑查看 WiFi 密码的方法都有哪些?

从设置面板中查看 当你使用的是笔记本电脑并且连接 WiFi 之后可以在设置面板中查看 WiFi 密码&#xff0c;首先打开设置界面&#xff0c;然后点击网络和 Internet&#xff0c;找到 WiFi 之后点击进入&#xff0c;然后点击管理已知网络。 然后点击已经连接好的无线网络。 进入之…

快速导入mysql百万用户数据

1. 前言 随着互联网的发展,大数据已经是很普遍的一个现象,已不再是零几年时的神话。数据资源意为着就是信息资源,很好的使用起来说是财富资源一点也不夸张.所以无论当下是小的还是大的互联网公司都会遇到大数的情况。只不过不同的业务逻辑需求与使用情况不一样罢了。 大数据我…