uniapp+vue2开发微信小程序实现图片压缩上传

最近公司的项目有个上传图片且要图片压缩到200KB以内的需求,经过一番曲折,终是勉强开发出了符合要求的功能,以下是分享,亲测有效:

1.组件结构
<template>
    <view class="upload-container">
        <!-- 上传按钮 -->
        <view class="upload-btn" :style="{width: size + 'rpx',height: size + 'rpx'}" @tap="handleChoose">
            <uni-icons type="camera" :size="size * (28 / 148)" color="#ccc" />
            <text class="label" :style="{fontSize: size * (18 / 148) + 'rpx'}">{{placeholder}}</text>
        </view>
        <!-- 已上传列表 -->
        <view class="item-image" v-for="(item,index) in fileList" :key="item.picUrl"
            @tap.stop="handlePrevImage(index, fileList)">
            <image :src="item.picUrl" mode="aspectFill" :style="{width: size + 'rpx',height: size + 'rpx'}" />
            <view class="toolbars" @tap.stop="toDelete(item,index)">
                <uni-icons type="closeempty" size="14" color="#FFF" class="icon" />
            </view>
        </view>
    </view>
</template>
1.script部分
<script>
    // 导入配置文件(根据实际情况导入)
    import indexConfig from '@/config/baseUrl'

    // 导入模块API(根据实际情况导入)
    import API from '@/api/safetyStickers'

    export default {
        name: "uploadFile",

        props: {
            // 双向绑定值
            value: {
                type: Array,
                default: () => {
                    return []
                }
            },

            // 控件尺寸,单位:rpx
            size: {
                type: [String, Number],
                default: 148
            },

            // 占位符
            placeholder: {
                type: String,
                default: '上传图片'
            }
        },

        data() {
            return {
                fileList: []  //已上传图片列表
            }
        },

        watch: {
            value: {
                handler(n) {
                    if (Array.isArray(n)) {
                        if (n.length) {
                            // 监听value的变化赋值给文件列表
                            this.fileList = n
                        }
                    }
                },
                immediate: true
            }
        },

        methods: {
            /**
             * 选择图片或调起相机
             */
            handleChoose() {
                const vm = this
                uni.chooseImage({
                    // 只允许选择压缩图,巧用微信的这个特性,相当于第一重压缩,微信帮我们实现,选择图片或者相机都试用(这一步很重要,能省去很多操作,这是本次分享的核心)
                    sizeType: ['compressed'],
                    success(res) {
                        if (res) {
                            // 获取临时路径数组
                            const lists = res.tempFilePaths
                            // 已上传到服务器的图片列表
                            const uploadPromises = lists.map(filepath => vm.uploadPromise(filepath))
                            // 运用Promise.all保证所有图片都完成上传再回传结果
                            Promise.all(uploadPromises).then(results => {
                                // 将上传结果进行响应式更新
                                if (results.length) {
                                    //赋值给图片列表
                                    const fileList = [
                                        ...vm.fileList,
                                        ...results.map(el => {
                                            return {
                                                id: '',
                                                picUrl: el.data
                                            }
                                        })
                                    ]
                                    //触发双向绑定机制
                                    vm.$emit('input', fileList)
                                }
                                // 提示上传结果
                                uni.showToast({
                                    icon: results.length ? 'success' : 'error',
                                    title: results.length ? '上传成功' : '上传失败'
                                })
                            })
                        }
                    }
                })
            },

            /**
             * 预览大图
             * @param {Object} current 点击的图片索引
             * @param {Object} urls 图片数组
             */
            handlePrevImage(current, urls) {
                urls = urls.map(el => el.picUrl)
                uni.previewImage({
                    urls,
                    current
                })
            },

            /**
             * 上传图片
             * @param {Object} filePath 图片临时路径
             */
            uploadPromise(filePath) {
                return new Promise(async (resolve, reject) => {
                    // 调用自定义压缩方式,上传前压缩图片至200KB以内
                    const compressImagePath = await this.compressImage(filePath, 200)
                    //上传到服务器
                    uni.uploadFile({
                        url: indexConfig.baseUrl + '/base-admin/oss/v2/uploadFile',
                        name: 'file',
                        filePath: compressImagePath,//传入压缩有的图片路径
                        header: {
                            'Authorization': 'Bearer ' + this.$store.state.userInfo.access_token //令牌秘钥(自行获取,这个难度不大)
                        },
                        success: (res) => {
                            //将结果返回
                            resolve(res)
                        },
                        fail: (err) => {
                            reject(err)
                        }
                    })
                })
            },

            /**
             * 压缩图片到指定大小(200KB)
             * @param {string} filePath - 原始图片路径
             * @param {string} maxSize - 最大图片尺寸
             * @returns {Promise<string>} - 压缩后图片路径
             */
            compressImage(filePath, maxSize = 200) {
                return new Promise((resolve, reject) => {
                    // 获取图片信息
                    uni.getFileInfo({
                        filePath,
                        success: (info) => {
                            //计算图片大小,单位KB
                            const originalSize = info.size / 1024; // KB

                            // 如果图片已经小于200KB,直接返回
                            if (originalSize <= maxSize) {
                                resolve(filePath);
                                return;
                            }

                            // 计算压缩比例,渐进式压缩
                            let quality = 10; // 初始质量
                            const maxAttempts = 10; // 最大尝试次数
                            //压缩方法
                            const compress = (currentQuality, attempt, path) => {
                                uni.compressImage({
                                    src: path,
                                    quality: currentQuality,//压缩质量(仅对jpg格式有效)
                                    success: (res) => {
                                        uni.getFileInfo({
                                            filePath: res.tempFilePath,
                                            success: (newInfo) => {
                                                const newSize = newInfo.size / 1024;

                                                // 如果小于200KB或达到最大尝试次数,返回结果
                                                if (newSize <= 200 || attempt >= maxAttempts) {
                                                    // 此处一定要传入压缩之后的路径而非filePath,坑点之一,切记
                                                    resolve(res .tempFilePath);
                                                } else {
                                                    // 否则继续压缩,逐步降低质量
                                                    const nextQuality = Math.max(1, currentQuality -15);
                                                    compress(nextQuality, attempt + 1, res.tempFilePath);
                                                }
                                            },
                                            fail: reject
                                        });
                                    },
                                    fail: reject
                                });
                            };
                            // 开始第一次压缩
                            compress(quality, 1, filePath);
                        },
                        fail: reject
                    });
                });
            },

            /**
             * 删除图片
             * @param {Object} index 图片索引
             */
            async toDelete(row, index) {
                if (row.id) {
                    const result = await API.deleteByIdAPI(row.id)
                    if (result) {
                        this.updateImage(index)
                    }
                } else {
                    this.updateImage(index)
                }

            },

            /**
             * 更新图片列表
             * @param {Object} index
             */
            updateImage(index) {
                const temp = this.value
                const lists = temp.filter((el, key) => key !== index)
                this.fileList = lists
                this.$emit('input', lists)
            },
        }
    }
</script>
3.style部分
<style lang="scss" scoped>
    .upload-container {
        display: flex;
        flex-wrap: wrap;

        .upload-btn {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            width: 148rpx;
            height: 148rpx;
            margin: 0 10rpx 10rpx 0;
            border-radius: 10rpx;
            border: 1rpx solid #ccc;
            font-weight: 400 !important;

            .label {
                font-size: 18rpx;
                color: #ccc;
            }
        }

        .item-image {
            position: relative;
            margin: 0 10rpx 10rpx 0;

            image {
                width: 148rpx;
                height: 148rpx;
                border-radius: 10rpx;
            }

            .toolbars {
                position: absolute;
                top: 0;
                right: 0;
                z-index: 3;
                display: flex;
                align-items: center;
                justify-content: center;
                width: 60rpx;
                height: 60rpx;
                font-weight: 400;
                border-top-right-radius: 30rpx;
                border-bottom-left-radius: 200rpx;
                background-color: rgba(0, 0, 0, .35);

                .icon {
                    position: absolute;
                    top: 8rpx;
                    right: 6rpx;
                }
            }
        }
    }
</style>

以上就是本次分享,如有异议,以你为准!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容