ElementPlus Upload组件使用compressorjs压缩图片上传

需求

Compressor.js 是一个用于在客户端(即在浏览器中)对图片进行压缩的 JavaScript 库。使用它有以下几个优点和意义:

  1. 减少文件大小: 图片通常是网页中占用大量带宽的资源之一。通过使用 Compressor.js 对图片进行压缩,可以显著减少图片文件的大小,从而减少页面加载时间,提高网页性能。

  2. 节省带宽: 在移动设备上访问网页时,特别是在使用移动数据连接(如4G或5G)时,大文件大小的图片会消耗大量的数据流量。通过压缩图片,可以节省用户的数据流量,降低用户的数据费用。

  3. 提高用户体验: 加载速度快的网页可以提供更好的用户体验。通过减少图片大小,可以加快网页的加载速度,使用户能够更快地访问和浏览网页内容。

  4. 支持移动端开发: 在移动端开发中,特别是在开发需要上传图片的应用时,通常需要在客户端对上传的图片进行压缩以减少上传时间和数据传输量。Compressor.js 提供了一个方便的方式来在移动设备上对图片进行压缩,使得开发人员能够轻松地实现这一功能。

  5. 保持图片质量: Compressor.js 在压缩图片的同时,尽可能地保持图片的质量,避免出现明显的视觉损失。尤其对于相机直拍的照片,有很好的加速作用,试想当服务器网络质量差,带宽只有数十K的时候,上传一张5M的高清图至少需要一分钟以上。而通过压缩操作后,5M照片可以压缩至200~300K且不损失质量,上传至需要几秒即可完成,体验立马上去一个档次。

而Element-UI的上传组件,默认就是使用FormData进行的直传,如果可以和压缩功能结合,当图片较大时自动处理。则在移动端上传直拍照片可以获得很好的体验效果。

集成方案

按照comppressor.js的文档(compressorjs - npm)。我们可以新建一个Compressor,在其success回调里进行上传。其入口和回调返回都是File结构,这样我们便可以处理文件流的压缩

针对element-ui的el-upload组件,有一个扩展点,即http-request可以传入一个UploadRequestHandler结构:

export declare type UploadRequestHandler = (options: UploadRequestOptions) => XMLHttpRequest | Promise<unknown>;

返回一个Promise进行对应的上传处理,这样我们即可绕过el-upload自带的上传逻辑,建立我们自己的上传及处理返回值逻辑

我们定义处理器如下:

// 文件上传切面,支持图片压缩和适配权限,默认激活策略是:图片大小大于300k(jpg)或500k(png),比例缩放到1334大小
export function optImgHttpRequest(options: UploadRequestOptions) {
  // el-upload的图片压缩适配
  const SIZE_LIMIT = 1334
  const JPG_KB_START = 300 * 1024
  const PNG_KB_START = 500 * 1024

  const uploadProcess = (resolve: Function, reject: Function, file: File, thumbWidth: string, thumbHeight: string) => {
    const formData = new FormData()
    formData.append(
      'file',
      file,
      options.file.name.toLowerCase().endsWith('.png') && options.file.size > 5000000
        ? options.file.name.replace(/(\.PNG|\.png)$/i, '.jpg') //超过5M的png会转化为jpg,文件名改变
        : file.name
    )
    if (thumbHeight.length > 0) {
      formData.append('thumbHeight', thumbHeight)
    }
    if (thumbWidth.length > 0) {
      formData.append('thumbWidth', thumbWidth)
    }
    return API.adminTools.upload
      .request({}, formData, {
        headers: {
          'Content-Type': 'multipart/form-data' // 覆盖类型为form-data上传
        }
      })
      .then((res: defs.FileInfo) => {
        resolve(res)
      })
      .catch((err) => {
        reject(err)
      })
  }

  if (
    // jpg和png要大于尺寸才开启压缩
    ((options.file.name.toLowerCase().endsWith('.jpg') || options.file.name.toLowerCase().endsWith('.jpeg')) &&
      options.file.size > JPG_KB_START) ||
    (options.file.name.toLowerCase().endsWith('.png') && options.file.size > PNG_KB_START)
  ) {
    return new Promise((resolve, reject) => {
      new Compressor(options.file, {
        quality: 0.8,
        maxWidth: SIZE_LIMIT,
        maxHeight: SIZE_LIMIT,
        success(result: File) {
          uploadProcess(
            resolve,
            reject,
            result,
            (options.data['thumbWidth'] || '') as string,
            (options.data['thumbHeight'] || '') as string
          )
        }
      })
    })
  } else {
    // 否则直接上传
    return new Promise((resolve, reject) => {
      uploadProcess(
        resolve,
        reject,
        options.file,
        (options.data['thumbWidth'] || '') as string,
        (options.data['thumbHeight'] || '') as string
      )
    })
  }
}

上传组件

这里是ccframe的一个input风格的上传组件,支持预览。按照定制的错误规则展示信息,以及当5M以上大小PNG自动更换后缀名(compressorjs默认5M以上PNG会压缩为JPG格式)等功能的支持

<!--
  Element Plus Upload 组件兼容Input布局的组件,支持预览
  @Author Jim 24/03/25
 -->
<template>
  <el-input
    :placeholder="placeholder"
    v-model="fileInfo.filename"
    :size="size"
    :readonly="true"
    class="cc-upload-field"
  >
    <template #suffix>
      <div class="flex items-center" style="height: 36px">
        <svg-icon name="upload" class="toolIcon" @click="dialogVisible = true" title="上传" />
        <svg-icon name="preview" class="toolIcon" @click="showUploadFile" style="padding-left: 2px" title="预览" />
        <svg-icon name="delete" class="toolIcon delIcon" @click="doDelete" title="清除" />
      </div>
      <el-dialog
        title="文件上传"
        :append-to-body="true"
        v-model="dialogVisible"
        width="400px"
        :before-close="checkUploading"
      >
        <el-upload
          ref="upload"
          style="width: 360px"
          action="/admin/fileInf/upload.json"
          :accept="accept"
          drag
          :limit="1"
          :on-error="showFail"
          :on-success="updateAndClose"
          :before-upload="beforeUpload"
          :http-request="optImgHttpRequest"
        >
          <i class="el-icon-upload" />
          <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
          <template #tip>
            <div class="el-upload__tip">上传文件不能超过10M</div>
          </template>
        </el-upload>
      </el-dialog>
    </template>
  </el-input>
  <transition name="fade">
    <el-overlay
      v-if="showPreview"
      class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-20px z-99999"
      @click="showPreview = false"
    >
      <img class="preview-img" :src="fileInfo.url" />
    </el-overlay>
  </transition>
</template>

<script lang="ts" setup>
import {
  watch,
  ref,
  onMounted,
  reactive,
  defineModel,
  withDefaults,
  defineProps,
  nextTick,
  getCurrentInstance
} from 'vue'
import { UPDATE_MODEL_EVENT } from 'element-plus/es/constants/event.mjs'
import { ElMessageBox, ElMessage } from 'element-plus'
import { UploadFile } from 'element-plus'
import { UploadAjaxError } from 'element-plus/es/components/upload/src/ajax'

import { tools, optImgHttpRequest } from '@/main'
import '../../services'
import { reset } from 'numeral'

const props = withDefaults(
  defineProps<{
    modelValue: string | null
    accept?: string
    checkType?: string // 如果要检查图片,使用 'image/gif,image/jpeg,image/png'
    placeholder: string
    thumbHeight?: number
    thumbWidth?: number
    size: 'default' | 'small' | 'large'
  }>(),
  {
    modelValue: null,
    accept: '.gif,.jpg,.png,image/gif,image/jpeg,image/png', // 注意,默认是上传图片
    checkType: 'image/gif,image/jpeg,image/png', // 默认检查图片
    placeholder: '请选择文件上传',
    size: 'large'
  }
)

const emit = defineEmits([UPDATE_MODEL_EVENT, 'change'])

// dom
const upload = ref<any>()
// data
const fileInfo = reactive<defs.FileInfo>({
  url: undefined, // 回显图片地址
  filename: '', // 图片名称
  ext: '', // 扩展名类型
  path: '' //图片路径
})

const uploading = ref<boolean>(false)
const dialogVisible = ref<boolean>(false)
const showPreview = ref<boolean>(false)

watch(
  // modelValue重新赋值时,根据值解析path和filename、ext、url
  () => props.modelValue,
  (val) => {
    if (val) {
      // eslint-disable-next-line no-control-regex
      const extract = /((\w+\/)*)([^\0-\x1F\\/:*?"<>|]+\.([^.]+))$/.exec(val)
      if (extract) {
        fileInfo.filename = extract[3]
        fileInfo.ext = extract[4]
        fileInfo.path = extract[1]
        fileInfo.url = import.meta.env.VITE_UPLOAD_PERFIX + fileInfo.path + fileInfo.filename
      }
    } else {
      // 清空
      fileInfo.filename = ''
      fileInfo.ext = ''
      fileInfo.url = ''
      fileInfo.path = ''
    }
  }
)

// methods
const getFileUrl: () => string = () => {
  return fileInfo.url || ''
}

const beforeUpload: (file: File) => boolean = (file) => {
  if (props.checkType && !props.checkType.includes(file.type)) {
    tools.alert(`文件格式不正确 ${file.type}`)
    return false
  }
  const isLt10M = file.size / 1024 / 1024 < 10
  if (!isLt10M) {
    tools.alert('上传文件大小不能超过 10MB!')
  } else {
    uploading.value = true
  }
  return isLt10M
}

const checkUploading: (done: any) => void = (done) => {
  if (uploading.value) {
    tools
      .confirm('上传进度未结束,您确定要终止上传吗?')
      .then(() => {
        uploading.value = false
        upload.value.abort()
        done()
      })
      .catch(() => {
        // NOOP
      })
  } else {
    done()
  }
}

const updateAndClose: (fileInfo: defs.FileInfo, file: UploadFile, fileList: UploadFile[]) => void = (res) => {
  // 只有在整合optImgHttpRequest后,才会返回FileInfo
  uploading.value = false
  dialogVisible.value = false
  Object.assign(fileInfo, res)
  const fileVal = '' + fileInfo.path + fileInfo.filename
  upload.value.clearFiles() //手动清理队列

  emit(UPDATE_MODEL_EVENT, fileVal === '' ? null : fileVal)
  emit('change', fileVal === '' ? null : fileVal)
}

const doDelete: () => void = () => {
  emit(UPDATE_MODEL_EVENT, null)
  emit('change', null)
}

const showUploadFile: () => void = () => {
  if (!props.modelValue) {
    tools.toast('您还未上传文件,无法预览', 'error')
  } else {
    if (['jpg', 'gif', 'png'].includes((fileInfo.ext || '').toLowerCase())) {
      // 图片展示
      showPreview.value = true
    } else {
      // 非图片下载
      const aTag = document.createElement('a')
      aTag.download = fileInfo.filename || '' // 下载的文件名
      aTag.href = fileInfo.url || ''
      aTag.click()
    }
  }
}

const showFail: (err: Error, file: UploadFile, fileList: UploadFile[]) => void = (err, file, fileList) => {
  let errorText = '网络请求失败'
  if (err instanceof UploadAjaxError) {
    // 需要验证,会不会走这里
    if (err.status) {
      switch (err.status) {
        case 500: {
          errorText = '上传失败' // 正常需要在组件限制文件大小,如果未限制会失败。默认最大的文件为10M
          break
        }
        case 504:
        case 404: {
          errorText = '上传URL错误'
          break
        }
      }
    }
  } else {
    errorText = err.message
  }
  tools.toast(errorText, 'error')
  uploading.value = false
}
</script>

<style scoped lang="scss">
.cc-upload-field .el-input__inner {
  padding-right: 95px !important;
}

.toolIcon {
  font-family: iconfont;
  user-select: none;
  width: 24px;
  height: 24px;
  line-height: 24px;
  cursor: pointer;
  margin-left: 7px;
  color: #409eff;
  :hover {
    color: #66b1ff;
  }
  :active {
    color: #3a8ee6;
  }
}

.delIcon {
  color: #f56c6c;
  :hover {
    color: #f78989;
  }
  :active {
    color: #dd6161;
  }
}

.x-screen-box {
  position: fixed;
  top: 70px;
  bottom: 70px;
  left: 70px;
  right: 70px;
  z-index: 99999;
}
.preview-img {
  object-fit: scale-down;
  width: 100%;
  height: 100%;
}
</style>

最终效果

点击上传按钮

上传完毕

点击预览按钮

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

力扣面试150 直线上最多的点数 数学 直线斜率 欧几里得求最大公约数

Problem: 149. 直线上最多的点数 思路 &#x1f468;‍&#x1f3eb; 参考题解 &#x1f496; 枚举直线 枚举统计 时间复杂度: O ( n 3 ) O(n^3) O(n3) 空间复杂度: O ( 1 ) O(1) O(1) class Solution {public int maxPoints(int[][] points){int n points.length;int…

数据库之备份与恢复

MySQL完全备份与恢复 数据备份的重要性 在生产环境中&#xff0c;数据的安全性至关重要任何数据的丢失都可能产生严重的后果造成数据丢失的原因 程序错误 人为操作错误 运算错误 磁盘故障 灾难(如火灾、地震)和盗窃 数据库备份的分类 物理角度 物理备份:对数据库操作系统的…

【网络爬虫】(1) 网络请求,urllib库介绍

各位同学好&#xff0c;今天开始和各位分享一下python网络爬虫技巧&#xff0c;从基本的函数开始&#xff0c;到项目实战。那我们开始吧。 1. 基本概念 这里简单介绍一下后续学习中需要掌握的概念。 &#xff08;1&#xff09;http 和 https 协议。http是超文本传输&#xf…

Visual Studio项目编译和运行依赖第三方库的项目

1.创建项目&#xff0c;这里创建的项目是依赖于.sln的项目&#xff0c;非CMake项目 2.添加第三方库依赖的头文件和库文件路劲 3.添加第三方依赖库文件 4.项目配置有2个&#xff0c;一个是Debug&#xff0c;一个是Release&#xff0c;如果你只配置了Debug&#xff0c;编译和运行…

厨余垃圾处理设备工业监控PLC连接APP小程序智能软硬件开发之功能原理篇

接着上一篇《厨余垃圾处理设备工业监控PLC连接APP小程序智能软硬件开发之功能结构篇》继续总结一下厨余垃圾处理设备智能软硬件统的原理。所有的软硬件系统全是自己一人独自开发&#xff0c;看法和角度难免有局限性。希望抛砖引玉&#xff0c;将该智能软硬件系统分享给更多有类…

java的封装

封装概述 java中的封装指的是将一系列有关的事物的共同属性和行为提取出来放到一个类中&#xff0c;隐藏对象的实行和现实细节&#xff0c;仅对外提供公共的访问方式的操作。这样说起来感觉很抽象&#xff0c;也不好理解&#xff0c;这里不妨举一个例子。将配置电脑这个动作看成…

伪装目标检测之注意力CBAM:《Convolutional Block Attention Module》

论文地址&#xff1a;link 代码&#xff1a;link 摘要 我们提出了卷积块注意力模块&#xff08;CBAM&#xff09;&#xff0c;这是一种简单而有效的用于前馈卷积神经网络的注意力模块。给定一个中间特征图&#xff0c;我们的模块依次推断沿着两个独立维度的注意力图&#xff…

Qt实现简易的多线程TCP服务器(支持多个客户端连接)附源码

目录 一.UI界面的设计 二.服务器的启动 三.实现自定义的TcpServer类 1.在widget中声明自定义TcpServer类的成员变量 2.在TcpServer的构造函数中对于我们声明的m_widget进行初始化&#xff0c;m_widget我们用于后续的显示消息等&#xff0c;说白了就是主界面的更新显示等 …

为何ChatGPT日耗电超50万度?

看新闻说&#xff0c;ChatGPT每天的耗电量是50万度&#xff0c;国内每个家庭日均的耗电量不到10度&#xff0c;ChatGPT耗电相当于国内5万个家庭用量。 网上流传&#xff0c;英伟达创始人黄仁勋说&#xff1a;“AI的尽头是光伏和储能”&#xff0c;大佬的眼光就是毒辣&#xff…

使用LLaVA模型实现以文搜图和以图搜图

本文将会详细介绍如何使用多模态模型——LLaVA模型来实现以文搜图和以图搜图的功能。本文仅为示例Demo&#xff0c;并不能代表实际的以文搜图和以图搜图的技术实现方案。 1、实现原理 使用多模态模型获取图片的标题和详细描述以文搜图功能&#xff1a;使用ES实现查询匹配&…

深入了解 Linux 中的 MTD 设备:/dev/mtd* 与 /dev/mtdblock*

目录 前言一、什么是MTD子系统&#xff1f;二、 /dev/mtd* 设备文件用途注意事项 三、/dev/mtdblock* 设备文件用途注意事项 三、这两种设备文件的关系四、关norflash的一些小知识 前言 在嵌入式Linux系统的世界里&#xff0c;非易失性存储技术扮演着至关重要的角色。MTD&#…

面试知识汇总——垃圾回收器(分代收集算法)

分代收集算法 根据对象的存活周期&#xff0c;把内存分成多个区域&#xff0c;不同区域使用不同的回收算法回收对象。 对象在创建的时候&#xff0c;会先存放到伊甸园。当伊甸园满了之后&#xff0c;就会触发垃圾回收。 这个回收的过程是&#xff1a;把伊甸园中的对象拷贝到F…

初识redis(一)

前言 引用的是这本书的原话 Redis[1]是一种基于键值对&#xff08;key-value&#xff09;的NoSQL数据库&#xff0c;与很多键值对数据库不同的是&#xff0c;Redis中的值可以是由string&#xff08;字符串&#xff09;、hash&#xff08;哈希&#xff09;、list&#xff08;列…

Android15功能和 API 概览

Android 15 面向开发者引入了一些出色的新功能和 API。以下部分总结了这些功能&#xff0c;以帮助您开始使用相关 API。 如需查看新增、修改和移除的 API 的详细列表&#xff0c;请参阅 API 差异报告。如需详细了解新的 API&#xff0c;请访问 Android API 参考文档&#xff0…

Selenium 自动化 —— 定位页面元素

更多内容请关注我的 Selenium 自动化 专栏&#xff1a; 入门和 Hello World 实例使用WebDriverManager自动下载驱动Selenium IDE录制、回放、导出Java源码浏览器窗口操作切换浏览器窗口 使用 Selenium 做自动化&#xff0c;我们不仅仅是打开一个网页&#xff0c;这只是万里长…

Stable Diffusion 进阶教程 - 二次开发(制作您的文生图应用)

目录 1. 引言 2. 基于Rest API 开发 2.1 前置条件 2.2 代码实现 2.3 效果演示 2.4 常见错误 3. 总结 1. 引言 Stable Diffusion作为一种强大的文本到图像生成模型&#xff0c;已经在艺术、设计和创意领域引起了广泛的关注和应用。然而&#xff0c;对于许多开发者来说&#xff…

时序预测 | Matlab实现SSA-BP麻雀算法优化BP神经网络时间序列预测

时序预测 | Matlab实现SSA-BP麻雀算法优化BP神经网络时间序列预测 目录 时序预测 | Matlab实现SSA-BP麻雀算法优化BP神经网络时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现SSA-BP麻雀算法优化BP神经网络时间序列预测&#xff08;完整源码和数据…

DRC检查及丝印的调整

DRC检查及丝印的调整 综述&#xff1a;本文主要讲述AD软件中DRC检查、丝印的调整以及logo的添加的相关步骤&#xff0c;附加logo添加的脚本链接和大量操作图片&#xff0c;使步骤详细直观。 1. 点击“工具”→“设计规则检查”→“运行DRC”。&#xff08;一开始可以只开启电…

利用云手机技术,开拓海外社交市场

近年来&#xff0c;随着科技的不断进步&#xff0c;云手机技术逐渐在海外社交营销领域崭露头角。其灵活性、成本效益和全球性特征使其成为海外社交营销的利器。那么&#xff0c;究竟云手机在海外社交营销中扮演了怎样的角色呢&#xff1f; 首先&#xff0c;云手机技术能够消除地…

LLM - 大语言模型的指令微调(Instruction Tuning) 概述

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/137009993 大语言模型的指令微调(Instruction Tuning)是一种优化技术&#xff0c;通过在特定的数据集上进一步训练大型语言模型(LLMs)&a…