记录vue3+elementplus的el-upload的二次封装和自定义上传,以及触发上传成功失败的事件

web端的压缩函数

/*
 * 压缩图片
 * param  file: 接受的文件对象
 * param  option: 图片压缩的参数  具体可以看一下 canvas的官网介绍 :https://www.canvasapi.cn/HTMLCanvasElement/toDataURL
 */
export function compressPic(
    file: any,
    option?: {
        maxWidth?: number
        maxHeight?: number
        minSize?: number
        mimeType?: string
        quality?: number
    }
): any {
    const maxWidth = option?.maxWidth || 1600 // 最大宽度
    const maxHeight = option?.maxHeight || 1600 // 最大高度
    const minSize = option?.minSize ?? 300 * 1024 // 最小压缩文件大小 300kb
    const mimeType = option?.mimeType || 'image/jpeg' // 默认图片类型
    let qualitys: number = option?.quality || 0.8 // 默认压缩阙值0.75
    // 根据文件大小设置不同的默认压缩质量
    if (parseInt((file.size / 1024).toFixed(2)) < 1024) {
        qualitys = 0.85
    }
    if (5 * 1024 < parseInt((file.size / 1024).toFixed(2))) {
        qualitys = 0.92
    }

    // 如果上传的是多个文件,递归处理每个文件
    if (file[0]) {
        return Promise.all(Array.from(file).map((e: any) => compressPic(e, option)))
    } else {
        return new Promise((resolve) => {
            // 这里我注释了是因为我们没有这个需求,如果有这个需求的可以将这个注释放开就可以了
            // // 如果图片大小小于300KB,直接返回原始图片数据
            // if (file.size < minSize) {
            //   resolve({
            //     file: file,
            //   });
            // } else {
            // 创建FileReader对象,异步读取存储在客户端上的文件内容
            const reader: FileReader = new FileReader()
            // 读取操作完成时触发该事件,使用格式(必须将接收到的数据从onload发送到其他函数):reader.onload = e => {}
            reader.onload = ({ target }: ProgressEvent<FileReader>) => {
                // console.log("target----", target);

                //创建img元素
                const image = new Image() as any
                // 图片加载完成后异步执行,当image的src发生改变,浏览器就会跑去加载这个src里的资源,这个操作是异步的。
                image.onload = async () => {
                    // 创建一个新的画布元素和上下文,用于绘制压缩后的图片
                    const canvas = document.createElement('canvas')
                    const context = canvas.getContext('2d') as any
                    // 计算目标图片的宽度和高度,以适应最大宽度和高度的要求
                    let targetWidth = image.width
                    let targetHeight = image.height

                    // 缩放图片尺寸以适应最大宽度和高度
                    if (targetWidth > maxWidth || targetHeight > maxHeight) {
                        const scaleFactor = Math.min(maxWidth / targetWidth, maxHeight / targetHeight)
                        targetWidth *= scaleFactor
                        targetHeight *= scaleFactor
                    }
                    // 设置画布的尺寸
                    canvas.width = targetWidth
                    canvas.height = targetHeight
                    // 清空画布并在画布上绘制压缩后的图片
                    context.clearRect(0, 0, targetWidth, targetHeight)
                    context.drawImage(image, 0, 0, targetWidth, targetHeight)
                    // 将压缩后的图片数据转换为 data URI。可以使用 type 参数其类型,默认为 PNG 格式。qualitys越小,文件体积越小
                    const canvasURL = canvas.toDataURL(mimeType, qualitys)
                    // 解码 data URI,获取图片的二进制数据。atob:是ascii to binary,用于将ascii码解析成binary数据,即Base64的解码过程。
                    const buffer = atob(canvasURL.split(',')[1])
                    let length = buffer.length
                    //创建一个 Uint8Array 类型的向量,用于存储图片的二进制数据
                    const bufferArray = new Uint8Array(new ArrayBuffer(length))
                    while (length--) {
                        bufferArray[length] = buffer.charCodeAt(length)
                    }
                    // 创建一个压缩后的文件对象
                    const miniFile = new File([bufferArray], file.name, {
                        type: mimeType,
                    })

                    // 解析压缩后的文件对象
                    resolve({
                        uid: file.uid,
                        raw: miniFile,
                        origin: file,
                        beforeSrc: target?.result,
                        afterSrc: canvasURL,
                        beforeKB: Number((file.size / 1024).toFixed(2)),
                        afterKB: Number((miniFile.size / 1024).toFixed(2)),
                    })
                }
                // 设置图片的 src,触发图片加载
                image.src = target?.result
            }
            // 读取文件内容,并在读取完成后触发 onload 事件
            console.log('压缩函数运行时的file',file)
            reader.readAsDataURL(file)
            // }
        })
    }
}

二次封装el-upload,实现对el-upload的属性,方法,事件,插槽的全面兼容,并重写上传方法,同时完成onsuccess,onerror等等事件,暂时未作onprogress的处理,就是滚动条那个函数。

<template>
    <el-upload v-bind="$attrs" ref="eluploadRef" :http-request="myupload">
        <template v-for="(value, name) in slots" #[name]="scope">
            <slot :name="name" v-bind="scope || {}"></slot>
        </template>
    </el-upload>
    <!-- <canvas ref="canvasRef" style="display: none;"></canvas> -->
</template>

<script setup>
import { onMounted, ref, useSlots, defineExpose, defineProps } from 'vue'

import {compressPic} from '/@/utils/compressPic'
import { mypicUpload } from '/@/api/common'


defineProps({
    // picChange: {
    //     type: Function,
    //     default:compressPicAndUpload
    // },
})

const slots = useSlots()

const expose = {}
const eluploadRef = ref(null)
// const canvasRef=ref(null)

const myupload = async (option) => {
    console.log('上传时的file', option)
    const newfile = await compressPic(option.file)
    console.log('压缩后的图片', newfile)

    // 设置formdata
    let fd = new FormData()
    fd.append('file', newfile.raw)
  

    // 调用上传函数
    mypicUpload(fd)
        .then((res) => {
            if (res.code == 1) {
                option.onSuccess(res)
              
            } else {
                console.log('上传成功的res',res)
                option.onSuccess(res)
            }
        })
        .catch((err) => {
            option.onError(err)
           
        })
        .finally(() => {
            // state.uploading--
            // onChange(file, files)
        })
}


onMounted(() => {
    const entries = Object.entries(eluploadRef.value)
    for (const [method, fn] of entries) {
        expose[method] = fn
    }
})
defineExpose(expose)
</script>

<style scoped>
/* ... */
</style>

vue3全局引入组件

在mian.ts中

import compressUpload from '/@/views/common/component/compressUpload/index.vue'
******
******
const app = createApp(App)
 // 封装el-upload,主要是自定义上传函数,在上传时提前压缩
app.component('compressUpload', compressUpload)

具体页面中的使用

直接把
<el-upload></el-upload>
改为
<compressUpload></compressUpload>

posted @ 2024-08-02 15:51  风意不止  阅读(541)  评论(0编辑  收藏  举报