根据Uview官方文档所说,若是小程序和app项目,上传使用压缩图片,可以配置u-upload组件的sizeType属性为'compress',但这对H5是无效的。在实际开发中,图片压缩上传经常是一个必要的需求,所以本篇文章主要讲的是在H5项目中如何使用Uview的upload组件进行压缩图片

tool.js

/**
 * 图片压缩
 * imgSrc 地址
 * scale 压缩质量 0-1
 * type 文件类型
 */
const compressImg = (imgSrc, scale, type, callback) => {
    // uni.$u.toast('压缩中')
    var img = new Image();
    img.src = imgSrc;
    img.onload = function() {
        var that = this;
        var h = (img.height * scale).toFixed(0); // 默认按质量比例压缩
        var w = (img.width * scale).toFixed(0);
        var canvas = document.createElement('canvas');
        var ctx = canvas.getContext('2d');
        var width = document.createAttribute("width");
        width.nodeValue = w;
        var height = document.createAttribute("height");
        height.nodeValue = h;
        canvas.setAttributeNode(width);
        canvas.setAttributeNode(height);
        ctx.drawImage(that, 0, 0, w, h);
        var base64 = canvas.toDataURL('image/jpeg', scale); //压缩比例
        canvas = null;
        if (type == 'base64') {
            let data = {
                size: getBase64Size(base64),
                type: type,
                source: base64
            }
            callback(base64);
        } else {
            let blob = base64ToBlob(base64);
            console.log('压缩后的大小', blob.size);
            const blobUrl = window.URL.createObjectURL(blob); //blob地址                    
            blob.source = blobUrl
            callback(blob);
        }
    }
};
/**base转Blob */
const base64ToBlob = (base64) => {
    var arr = base64.split(','),
        mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]),
        n = bstr.length,
        u8arr = new Uint8Array(n);
    while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
    }
    return new Blob([u8arr], {
        type: mime
    });
};
/**获取base64的文件大小 */
const getBase64Size = () => {
    let size = 0;
    if (base64Str) { // 获取base64图片byte大小
        const equalIndex = base64Str.indexOf('='); // 获取=号下标
        if (equalIndex > 0) {
            const str = base64Str.substring(0, equalIndex); // 去除=号
            const strLength = str.length;
            const fileLength = strLength - (strLength / 8) * 2; // 真实的图片byte大小
            size = Math.floor(fileLength); // 向下取整
        } else {
            const strLength = base64Str.length;
            const fileLength = strLength - (strLength / 8) * 2;
            size = Math.floor(fileLength); // 向下取整
        }
    } else {
        size = null;
    }
    return size
};

在upload-image.vue中使用

/** 压缩图片*/
            compressImg(source, compressionRatio) {
                let that = this;
                return new Promise((resolve, reject) => {
                    that.$.compressImg(source.url, compressionRatio, source.type, compressRes => {
                        resolve(compressRes);
                    })
                }).then((res) => {
                    source.size = res.size
                    // window.URL.revokeObjectURL(source.url) // 删除被压缩的缓存文件,这里注意,如果是相册选择上传,可能会删除相册的图片
                    source.url = res.source
                    source.thumb = res.source                    
                    return source
                }).catch(err => {
                    console.log('图片压缩失败', err)
                })
            },

完整示例代码,以及我碰到的问题

<template>
    <view class="actual-download">
        <top-bar title="回单影像上传"></top-bar>
        <view class="main page-content">
            <view class="scanCode" v-if="showScan">
                <!-- <mumu-get-qrcode @close="showScan=false" @success='getScan' @error="err"></mumu-get-qrcode> -->
                <mumu-one-code @close="showScan=false" @success='getScan' definition :readers='["code_128_reader"]'></mumu-one-code>
            </view>

            <view class="main-top">
                <u--input placeholder="请输入内容" border="surround" v-model="value"></u--input>
                <uni-icons @click="showScan=true" size="28" type="scan" color="#6BA8F8"></uni-icons>
            </view>
            <view>
                <u-upload :fileList="fileList1" @afterRead="afterRead" @delete="deletePic" name="1" multiple
                    :maxCount="10"></u-upload>
            </view>
        </view>
        <bottom-bar rightBtnText="完成" @clickRight="rightBtnClick" :showLeftBtn="false"></bottom-bar>
    </view>
</template>

<script>
    import {
        receiptPic
    } from '@/common/api.js'
    import compressImg from '@/util/compressImg.js'

    export default {
        data() {
            return {
                fileList1: [],
                showScan: false,
                value: '',
                fileMaxSize: 2 * 1024 * 1024, // 超出2M开启压缩
                maxSize:20 * 1024 * 1024, //图片最大不能超过20M
                fileMinSize: 5 * 1024 // 最小为5KB
            };
        },
        methods: {
            err() {

            },
            getScan(val) {
                this.value = val
                this.showScan = false
            },


            // 删除图片
            deletePic(event) {
                this[`fileList${event.name}`].splice(event.index, 1)
            },

            /** 压缩图片*/
            compressImg(source, compressionRatio) {
                console.log('压缩图片', source)
                return new Promise((resolve, reject) => {
                    compressImg(source.url, compressionRatio, source.type, source.name, compressRes => {
                        resolve(compressRes);
                    })
                }).then((res) => {
                    source.size = res.blob.size
                    // window.URL.revokeObjectURL(source.url) // 删除被压缩的缓存文件,这里注意,如果是相册选择上传,可能会删除相册的图片
                    source.url = res.blob.source
                    source.thumb = res.blob.source
                    source.file = res.file
                    return source
                }).catch(err => {
                    console.log('图片压缩失败', err)
                })
            },
            /**获取文件大小倍数,生成质量比*/
            getCompressionRatio(fileSize) {
                const multiple = (fileSize / this.fileMaxSize).toFixed(2);
                let compressionRatio = 1;
                if (multiple > 5) {
                    compressionRatio = 0.5
                } else if (multiple > 4) {
                    compressionRatio = 0.6
                } else if (multiple > 3) {
                    compressionRatio = 0.7
                } else if (multiple > 2) {
                    compressionRatio = 0.8
                } else if (multiple > 1) {
                    compressionRatio = 0.9
                } else {
                    compressionRatio = 2
                }
                return compressionRatio;
            },
            // 新增图片
            async afterRead(event) {
                // 当设置 multiple 为 true 时, file 为数组格式,否则为对象格式
                let lists = [].concat(event.file)
                let fileListLen = this[`fileList${event.name}`].length
                for (let index in lists) {
                    const item = lists[index];
                    const fileSize = item.size;
                    const fileName = item.name ?? '';
                    console.log('文件大小', fileSize);
                    if (fileSize > this.fileMaxSize) {
                        const compressionRatio = this.getCompressionRatio(fileSize);
                        if (compressionRatio > 1) {
                            uni.$u.toast('文件' + fileName + '大于2M')
                            return false
                        }
                        // 超出1M自动压缩图片
                        await this.compressImg(item, compressionRatio)
                        if (item.size > this.maxSize) {
                            uni.$u.toast('文件' + fileName + '压缩后超出20M')
                            return false
                        }
                    }
                    if (item.size < this.fileMinSize) {
                        uni.$u.toast('文件' + fileName + '不能小于5KB')
                        return false
                    }
                }
                lists.map((item) => {
                    this[`fileList${event.name}`].push({
                        ...item,
                        status: 'uploading',
                        message: '上传中'
                    })
                })
                for (let i = 0; i < lists.length; i++) {
                    const flag = lists[i]?.file ? 'file' : 'filePath'
                    const result = await this.uploadFilePromise(flag, flag === 'file' ? lists[i].file : lists[i].url)

                    let item = this[`fileList${event.name}`][fileListLen]
                    this[`fileList${event.name}`].splice(fileListLen, 1, Object.assign(item, {
                        status: 'success',
                        message: '',
                        url: lists[i].url,
                        res: result
                    }))
                    fileListLen++
                }
            },
            uploadFilePromise(flag,url) {
                console.log(uni.getStorageSync('lifeData'))
        let token = uni.getStorageSync('lifeData')
                return new Promise((resolve, reject) => {
                    let a = uni.uploadFile({
                        url: `/mp/file/notice/upload.do?token=${token.token}`,
                        [flag]: url,
                        // filePath:url
                        // file:url,
                        name: 'uploadfile',
                        formData: {},
                        success: (res) => {
                            setTimeout(() => {
                                let obj = JSON.parse(res.data)
                                obj = obj.path
                                obj = obj[0]
                                obj = obj[0]
                                obj = obj.split('|')
                                obj = obj[1]
                                // obj = window.location.protocol + window.location.hostname + obj
                                resolve(obj)
                            }, 1000)
                        }
                    });
                })
            },
            async rightBtnClick() {
                if (!this.value) {
                    uni.$u.toast('请填写运单号')
                    return
                }
                for (let i = 0; i < this.fileList1.length; i++) {
                    if (this.fileList1[i].status != 'success') {
                        uni.$u.toast('图片上传中,请稍后')
                        return
                    }
                }
                let arr = []
                for (let i = 0; i < this.fileList1.length; i++) {
                    arr.push({
                        picName: this.fileList1[i].name,
                        picAddress: this.fileList1[i].res
                    })
                }
                const res = await receiptPic({
                    shippingCode: this.value,
                    picRecordDTOList: arr
                })
                if (res?.result == '1') {
                    uni.$u.toast(res.msg);
                    this.fileList1=[]
                    this.value=''
                }
            }
        }
    }
</script>

问题是大于2M后的图片上传后,服务器识别不了文件类型.一开始我在uni.uploadFile使用的是filePath,后面改成大于2M使用file上传,优化了下tools.js的代码.返回多了个file

const compressImg = (imgSrc, scale, type,fileName,callback) => {
    // uni.$u.toast('压缩中')
    var img = new Image();
    img.src = imgSrc;
    img.onload = function() {
        var that = this;
        var h = (img.height * scale).toFixed(0); // 默认按质量比例压缩
        var w = (img.width * scale).toFixed(0);
        var canvas = document.createElement('canvas');
        var ctx = canvas.getContext('2d');
        var width = document.createAttribute("width");
        width.nodeValue = w;
        var height = document.createAttribute("height");
        height.nodeValue = h;
        canvas.setAttributeNode(width);
        canvas.setAttributeNode(height);
        ctx.drawImage(that, 0, 0, w, h);
        var base64 = canvas.toDataURL('image/jpeg', scale); //压缩比例
        canvas = null;
        if (type == 'base64') {
            let data = {
                size: getBase64Size(base64),
                type: type,
                source: base64
            }
            callback(base64);
        } else {
            let blob = base64ToBlob(base64);
            console.log('压缩后的大小', blob.size);
            const blobUrl = window.URL.createObjectURL(blob); //blob地址                    
            blob.source = blobUrl
            const file = new File([blob], fileName, { type });
      callback({ blob, file })
        }
    }
};

发表评论