UniApp制作支持多图上传的图片选择器:打造高性能移动端上传体验
在移动应用开发中,图片上传功能是一个非常常见的需求。本文将详细介绍如何在UniApp框架下实现一个功能完善的多图选择上传组件,并重点关注鸿蒙系统的适配与优化。
一、需求分析
在开发多图上传功能时,我们需要考虑以下几个关键点:
- 图片选择与预览
- 图片压缩与质量控制
- 上传进度管理
- 失败重试机制
- 鸿蒙系统特有的媒体能力
二、技术方案设计
2.1 核心技术栈
- UniApp框架
- HMS Image Kit
- Vuex状态管理
- 自定义组件封装
2.2 功能特性
- 支持多选与预览
- 智能压缩算法
- 批量上传队列
- 进度监控
- 失败重试机制
三、具体实现
3.1 图片选择器组件
首先,我们实现一个基础的图片选择器组件:
vue
<template>
<view class="image-uploader">
<!-- 已选图片预览区 -->
<view class="preview-list">
<view
class="preview-item"
v-for="(item, index) in imageList"
:key="index"
>
<image
:src="item.path"
mode="aspectFill"
@tap="previewImage(index)"
/>
<view
class="delete-btn"
@tap.stop="removeImage(index)"
>×</view>
<view
class="upload-progress"
v-if="item.progress !== 100"
>
{{ item.progress }}%
</view>
</view>
</view>
<!-- 选择图片按钮 -->
<view
class="selector"
@tap="chooseImages"
v-if="imageList.length < maxCount"
>
<text class="iconfont icon-add"></text>
<text class="tip">点击选择</text>
</view>
</view>
</template>
<script>
import { compress } from './utils/imageProcessor'
import { uploadToServer } from './utils/uploader'
export default {
name: 'ImageUploader',
props: {
maxCount: {
type: Number,
default: 9
},
maxSize: {
type: Number,
default: 5 * 1024 * 1024 // 5MB
}
},
data() {
return {
imageList: [],
uploadQueue: []
}
},
methods: {
async chooseImages() {
try {
// #ifdef HARMONY-OS
const result = await this.chooseImagesHMS()
// #endif
// #ifndef HARMONY-OS
const result = await uni.chooseImage({
count: this.maxCount - this.imageList.length,
sizeType: ['compressed'],
sourceType: ['album', 'camera']
})
// #endif
this.handleChooseSuccess(result)
} catch (error) {
uni.showToast({
title: '选择图片失败',
icon: 'none'
})
}
},
// 鸿蒙系统图片选择实现
async chooseImagesHMS() {
return new Promise((resolve, reject) => {
// HMS Image Kit实现
const imageKit = this.getHMSImageKit()
imageKit.pickImages({
maxSelectCount: this.maxCount - this.imageList.length,
compressFormat: 'JPEG',
compressQuality: 80
})
.then(resolve)
.catch(reject)
})
},
async handleChooseSuccess(result) {
const images = result.tempFiles || result.tempFilePaths.map(path => ({ path }))
// 处理每张图片
for (const image of images) {
// 压缩图片
const compressed = await compress(image.path, {
quality: 80,
maxWidth: 1920,
maxHeight: 1920
})
// 添加到列表
this.imageList.push({
path: compressed.path,
size: compressed.size,
progress: 0,
status: 'waiting'
})
}
// 开始上传队列
this.startUpload()
},
async startUpload() {
const waiting = this.imageList.filter(img => img.status === 'waiting')
for (const image of waiting) {
try {
image.status = 'uploading'
await uploadToServer(image.path, {
onProgress: (progress) => {
image.progress = progress
}
})
image.status = 'success'
image.progress = 100
this.$emit('upload-success', image)
} catch (error) {
image.status = 'failed'
this.$emit('upload-error', { image, error })
}
}
},
removeImage(index) {
this.imageList.splice(index, 1)
this.$emit('remove', index)
},
previewImage(index) {
const urls = this.imageList.map(img => img.path)
uni.previewImage({
urls,
current: index
})
}
}
}
</script>
<style lang="scss">
.image-uploader {
display: flex;
flex-wrap: wrap;
padding: 20rpx;
.preview-list {
display: flex;
flex-wrap: wrap;
}
.preview-item {
position: relative;
width: 200rpx;
height: 200rpx;
margin: 10rpx;
image {
width: 100%;
height: 100%;
border-radius: 8rpx;
}
.delete-btn {
position: absolute;
top: -20rpx;
right: -20rpx;
width: 40rpx;
height: 40rpx;
line-height: 40rpx;
text-align: center;
background: rgba(0, 0, 0, 0.5);
color: #fff;
border-radius: 50%;
}
.upload-progress {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 40rpx;
line-height: 40rpx;
text-align: center;
background: rgba(0, 0, 0, 0.5);
color: #fff;
font-size: 24rpx;
}
}
.selector {
width: 200rpx;
height: 200rpx;
margin: 10rpx;
border: 2rpx dashed #ddd;
border-radius: 8rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.icon-add {
font-size: 60rpx;
color: #999;
}
.tip {
margin-top: 10rpx;
font-size: 24rpx;
color: #999;
}
}
}
</style>
3.2 图片压缩处理
为了优化上传性能,我们实现了智能压缩功能:
javascript
// utils/imageProcessor.js
export const compress = async (path, options = {}) => {
const {
quality = 80,
maxWidth = 1920,
maxHeight = 1920
} = options
// 获取图片信息
const imageInfo = await uni.getImageInfo({
src: path
})
// 计算压缩后的尺寸
let { width, height } = imageInfo
const ratio = Math.min(
maxWidth / width,
maxHeight / height,
1
)
width = Math.floor(width * ratio)
height = Math.floor(height * ratio)
// 压缩图片
return new Promise((resolve, reject) => {
// #ifdef HARMONY-OS
// 使用HMS Image Kit压缩
const imageKit = getHMSImageKit()
imageKit.compressImage({
uri: path,
format: 'JPEG',
quality,
targetWidth: width,
targetHeight: height
})
.then(resolve)
.catch(reject)
// #endif
// #ifndef HARMONY-OS
uni.compressImage({
src: path,
quality,
width,
height,
success: resolve,
fail: reject
})
// #endif
})
}
3.3 上传队列管理
实现一个可靠的上传队列管理器:
javascript
// utils/uploader.js
class UploadQueue {
constructor(options = {}) {
this.queue = []
this.concurrent = options.concurrent || 3
this.running = 0
this.retryTimes = options.retryTimes || 3
this.retryDelay = options.retryDelay || 1000
}
add(task) {
this.queue.push({
...task,
retryCount: 0
})
this.check()
}
async check() {
if (this.running >= this.concurrent) return
const task = this.queue.find(t => !t.isRunning)
if (!task) return
this.running++
task.isRunning = true
try {
await this.runTask(task)
} catch (error) {
if (task.retryCount < this.retryTimes) {
task.retryCount++
task.isRunning = false
await this.delay(this.retryDelay)
this.check()
} else {
task.onError && task.onError(error)
}
}
this.running--
this.check()
}
async runTask(task) {
const formData = new FormData()
formData.append('file', task.file)
const response = await fetch(task.url, {
method: 'POST',
body: formData,
headers: task.headers
})
if (!response.ok) {
throw new Error(`Upload failed: ${response.statusText}`)
}
const result = await response.json()
task.onSuccess && task.onSuccess(result)
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
}
export const uploadToServer = (filePath, options = {}) => {
return new Promise((resolve, reject) => {
const uploader = new UploadQueue()
uploader.add({
file: filePath,
url: options.url || '/api/upload',
headers: options.headers,
onProgress: options.onProgress,
onSuccess: resolve,
onError: reject
})
})
}
四、鸿蒙系统适配
4.1 HMS Image Kit集成
在鸿蒙系统上,我们利用HMS Image Kit提供的能力:
javascript
export class HMSImageManager {
constructor() {
this.imageKit = null
}
async init() {
try {
// 初始化HMS Image Kit
const hms = await this.getHMSCore()
this.imageKit = hms.imageKit({
compressFormat: 'JPEG',
compressQuality: 80
})
return true
} catch (error) {
console.error('HMS Image Kit初始化失败:', error)
return false
}
}
async pickImages(options) {
if (!this.imageKit) await this.init()
return this.imageKit.pickImages({
...options,
mediaType: ['image/jpeg', 'image/png'],
selectionMode: 'multiple'
})
}
async compressImage(options) {
if (!this.imageKit) await this.init()
return this.imageKit.compressImage(options)
}
}
4.2 性能优化
针对鸿蒙系统的特点,我们进行了一些性能优化:
javascript
const optimizeForHMS = {
// 图片缓存策略
cacheStrategy: {
mode: 'memory_first',
maxMemorySize: 20 * 1024 * 1024, // 20MB
maxDiskSize: 100 * 1024 * 1024 // 100MB
},
// 预加载配置
preloadConfig: {
preloadCount: 3,
preloadDistance: 2
},
// 内存管理
memoryManagement: {
clearThreshold: 0.8,
autoRelease: true
}
}
五、实践总结
通过以上实现,我们的图片上传组件具备以下特点:
- 支持多图选择与预览
- 智能压缩,优化传输性能
- 可靠的上传队列管理
- 完善的失败重试机制
- 鸿蒙系统深度适配
在实际应用中,我们还可以根据具体需求进行以下优化:
- 自定义图片裁剪功能
- 添加水印处理
- 优化大图片加载
- 实现拖拽排序
六、未来展望
随着鸿蒙生态的发展,我们可以期待:
- 更强大的HMS Image Kit功能
- 更优秀的图像处理能力
- 更完善的媒体管理接口
- 更多创新的交互方式
结语
图片上传是一个看似简单但实际涉及诸多技术点的功能,需要我们在性能、体验和可靠性之间找到平衡。通过本文的实践,相信大家已经掌握了实现一个专业图片上传组件的核心要点。在实际开发中,还需要根据具体场景进行调整和优化。
希望本文对大家有所帮助,也欢迎在评论区分享你的开发经验!