最近公司的项目有个上传图片且要图片压缩到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>
以上就是本次分享,如有异议,以你为准!