uniapp实现图片压缩并上传
2026年2月26日 10:51
最近在使用uniapp开发时,有个功能既要支持H5和小程序双平台,又要实现图片自动压缩,还要处理好接口响应的各种异常情况。最终封装了这个 useUploadMethod 自定义上传方法,今天分享给大家。
痛点分析
先看看我们平时会遇到哪些问题:
// 痛点1:图片太大,上传慢
uni.uploadFile({
filePath: 'big-image.jpg' // 5MB的图片直接上传
// 用户等得花儿都谢了
})
// 痛点2:登录态过期
uni.uploadFile({
success: (res) => {
// {"code":405,"msg":"未登录"}
// 啥也没发生,用户继续操作,然后报错
}
})
// 痛点3:H5和小程序API不统一
// H5用 File/Blob
// 小程序用 tempFilePath
// 代码里到处都是 #ifdef
技术方案
1. 整体架构
整个上传方法分为三个核心层:
- 预处理层:图片压缩、参数组装
- 上传层:跨平台上传、进度监听
- 响应层:状态码处理、登录态管理
2. 图片压缩模块
跨平台压缩策略
async function compressImage(file: UploadFileItem, options: any): Promise<File | string> {
// 未启用压缩,直接返回
if (!options?.enabled) return file.url
// H5平台:使用 compressorjs
// #ifdef H5
return compressImageH5(file, options)
// #endif
// 小程序平台:使用 uni.compressImage
// #ifndef H5
return new Promise((resolve) => {
uni.compressImage({
src: file.url,
quality: options.quality || 80,
width: options.maxWidth,
height: options.maxHeight,
success: (res) => resolve(res.tempFilePath),
fail: () => resolve(file.url) // 压缩失败回退原图
})
})
// #endif
}
设计亮点:
- 条件编译处理平台差异
- 压缩失败自动降级使用原图
- 统一返回类型,上层无感知
H5平台深度优化(compressorjs)
async function compressImageH5(file: UploadFileItem, options?: CompressOptions): Promise<File | string> {
let { name: fileName, url: filePath } = file
return new Promise((resolve) => {
// 从blob URL获取文件
fetch(filePath)
.then(res => res.blob())
.then((blob) => {
// compressorjs压缩配置
new Compressor(blob, {
quality: (options?.quality || 80) / 100, // 转换为0-1范围
maxWidth: options?.maxWidth,
maxHeight: options?.maxHeight,
mimeType: blob.type,
success: (compressedBlob) => {
// 生成标准File对象
const fileName = `file-${Date.now()}.${blob.type.split('/')[1]}`
const file = new File([compressedBlob], fileName, { type: blob.type })
resolve(file)
},
error: () => resolve(filePath) // 压缩失败回退
})
})
.catch(() => resolve(filePath))
})
}
关键点:
-
fetch+blob()获取原始文件数据 -
compressorjs提供高质量的图片压缩 - 返回
File对象,H5上传更标准
3. 核心上传方法
export function useUploadMethod(httpOptions: HttpOptions) {
const { url, name, formData: data, header, timeout, onStart, onFinish, onSuccess, compress } = httpOptions
const uploadMethod: UploadMethod = async (file, formData, options) => {
// 1. 上传开始钩子
onStart?.()
// 2. 图片压缩(如果启用)
let filePath = file.url
try {
filePath = await compressImage(file, compress)
} catch {
filePath = file.url // 异常降级
}
// 3. 创建上传任务
const uploadTask = uni.uploadFile({
url: options.action || url,
header: { ...header, ...options.header },
name: options.name || name,
formData: { ...data, ...formData },
timeout: timeout || 60000,
// 4. 跨平台文件参数处理
...(typeof File !== 'undefined' && filePath instanceof File
? { file: filePath } // H5: File对象
: { filePath }), // 小程序: 路径字符串
// 5. 响应处理
success: (res) => handleSuccess(res, file, options),
fail: (err) => handleError(err, file, options)
})
// 6. 进度监听
uploadTask.onProgressUpdate((res) => {
options.onProgress(res, file)
})
}
return { uploadMethod }
}
4. 智能响应处理器
// 上传成功处理
function handleSuccess(res: any, file: UploadFileItem, options: any) {
try {
// 解析响应数据
const resData = JSON.parse(res.data) as ResData<any>
// 状态码检查
if (res.statusCode >= 200 && res.statusCode < 300) {
const { code, msg: errMsg = '上传失败' } = resData
if (+code === 200) {
// 上传成功
options.onSuccess(res, file, resData)
onSuccess?.(res, file, resData)
return
}
// 登录态过期处理
if (+code === 405 || errMsg.includes('未登录')) {
toast.show(errMsg || '登录态失效')
logout()
login() // 自动跳转登录页
return
}
// 其他业务错误
toast.show(errMsg)
options.onError({ ...res, errMsg }, file, resData)
return
}
// HTTP 401处理
if (res.statusCode === 401) {
toast.show('登录态失效')
logout()
login()
return
}
// 其他HTTP错误
toast.show(resData.msg || `服务出错:${res.statusCode}`)
options.onError({ ...res, errMsg: '服务开小差了' }, file)
} finally {
onFinish?.() // 无论成功失败都调用
}
}
// 上传失败处理
function handleError(err: any, file: UploadFileItem, options: any) {
try {
toast.show('网络错误,请稍后再试')
// 设置上传失败
options.onError(err, file, formData)
} finally {
// 文件上传完成时调用
onFinish?.()
}
} as any)
基础用法
<template>
<wd-upload
:upload-method="uploadMethod"
v-model:file-list="fileList"
@change="handleChange"
/>
</template>
<script setup>
import { useUploadMethod } from './upload-method'
// 配置上传方法
const { uploadMethod } = useUploadMethod({
url: '/api/upload',
name: 'file',
header: {
'Authorization': 'Bearer ' + getToken()
},
// 图片压缩配置
compress: {
enabled: true,
quality: 80,
maxWidth: 1920,
maxHeight: 1080
},
// 钩子函数
onStart: () => console.log('开始上传'),
onSuccess: (res, file) => console.log('上传成功', file),
onFinish: () => console.log('上传完成')
})
</script>