Wot Design Uni 文件上传组件:如何实现异步上传的强大功能
Wot Design Uni 文件上传组件:如何实现异步上传的强大功能
【免费下载链接】wot-design-uni一个基于Vue3+TS开发的uni-app组件库,提供70+高质量组件,支持暗黑模式、国际化和自定义主题。项目地址: https://gitcode.com/gh_mirrors/wo/wot-design-uni
在现代移动应用开发中,文件上传是一个复杂但至关重要的功能。Wot Design Uni 作为一款基于 Vue3+TS 开发的 uni-app 组件库,其 wd-upload 组件提供了强大且灵活的异步上传解决方案。本文将深入探讨如何利用 wd-upload 的异步上传功能,实现复杂的业务需求。
为什么需要异步上传功能?
在真实业务场景中,文件上传往往不只是简单的文件传输。开发者通常需要在文件上传前进行一系列预处理操作:
- 文件格式转换- 如 HEIC 转 JPG、视频转码等
- 文件大小校验- 检查文件是否超过限制
- 内容安全检查- 扫描文件内容是否安全
- 后端接口验证- 验证用户权限或配额
- 分片上传准备- 大文件分片上传的前置处理
Wot Design Uni 的 wd-upload 组件通过支持 Promise 类型的upload-method属性,让这些异步处理变得简单而优雅。
异步上传的核心实现原理
让我们深入源码,看看 wd-upload 如何实现异步上传支持:
// 在 useUpload.ts 中的关键实现 const startUpload = (file: UploadFileItem, options: UseUploadOptions) => { const { uploadMethod } = options const uploadOptions = { action, header, name, fileName: name, fileType, statusCode, abortPrevious, onSuccess: (res, file, formData) => { file[statusKey] = UPLOAD_STATUS.SUCCESS currentTask = null options.onSuccess?.(res, file, formData) }, onError: (error, file, formData) => { file[statusKey] = UPLOAD_STATUS.FAIL file.error = error.errMsg currentTask = null options.onError?.(error, file, formData) }, onProgress: (res, file) => { file.percent = res.progress options.onProgress?.(res, file) } } // 关键代码:支持自定义上传方法 if (isFunction(uploadMethod)) { return uploadMethod(file, formData, uploadOptions) } else { return defaultUpload(file, formData, uploadOptions) } }组件内部通过isFunction(uploadMethod)判断是否传入了自定义上传函数,如果传入了,则调用该函数并传递完整的配置选项。这种设计让开发者可以完全控制上传流程。
实战:构建异步上传处理链
下面是一个完整的异步上传示例,展示了如何处理复杂的业务逻辑:
<template> <wd-upload v-model:file-list="fileList" :upload-method="customUpload" :limit="5" :max-size="10 * 1024 * 1024" // 10MB @change="handleChange" @success="handleSuccess" @fail="handleError" > <wd-button type="primary" custom-class="upload-btn"> 上传文件 </wd-button> </wd-upload> </template> <script setup lang="ts"> import { ref } from 'vue' import type { UploadMethod, UploadFileItem } from '@/uni_modules/wot-design-uni' const fileList = ref<UploadFileItem[]>([]) // 核心的异步上传函数 const customUpload: UploadMethod = async (file, formData, options) => { try { // 1. 文件类型校验 if (!isValidFileType(file)) { throw new Error('不支持的文件类型') } // 2. 文件大小校验(虽然组件已有 max-size,但这里可以做更复杂的校验) if (file.size > 10 * 1024 * 1024) { throw new Error('文件大小超过10MB限制') } // 3. 异步压缩处理(如果是图片) if (isImageFile(file)) { const compressedFile = await compressImage(file) file = compressedFile } // 4. 获取上传凭证(异步接口调用) const uploadToken = await getUploadToken() // 5. 调用后端上传接口 const result = await uploadToServer(file, uploadToken, { onProgress: (progress) => { // 手动触发进度更新 options.onProgress?.({ progress }, file) } }) // 6. 上传成功回调 options.onSuccess?.(result, file, formData) // 返回上传任务实例(如果需要取消上传) return { abort: () => { console.log('取消上传') } } } catch (error) { // 错误处理 options.onError?.({ errMsg: error.message || '上传失败' }, file, formData) throw error } } // 辅助函数 const isValidFileType = (file: UploadFileItem): boolean => { const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'] return allowedTypes.includes(file.type || '') } const isImageFile = (file: UploadFileItem): boolean => { return file.type?.startsWith('image/') || false } const compressImage = async (file: UploadFileItem): Promise<UploadFileItem> => { // 使用uni.compressImage进行图片压缩 return new Promise((resolve, reject) => { uni.compressImage({ src: file.url, quality: 80, success: (res) => { resolve({ ...file, url: res.tempFilePath, size: res.tempFileSize }) }, fail: reject }) }) } const getUploadToken = async (): Promise<string> => { // 调用后端接口获取上传凭证 const response = await uni.request({ url: '/api/upload/token', method: 'GET' }) return response.data.token } const uploadToServer = async ( file: UploadFileItem, token: string, options: { onProgress?: (progress: number) => void } ): Promise<any> => { return new Promise((resolve, reject) => { const uploadTask = uni.uploadFile({ url: 'https://api.example.com/upload', filePath: file.url, name: 'file', header: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'multipart/form-data' }, formData: { token, timestamp: Date.now() }, success: (res) => { if (res.statusCode === 200) { resolve(JSON.parse(res.data)) } else { reject(new Error('上传失败')) } }, fail: reject }) // 监听上传进度 uploadTask.onProgressUpdate((res) => { options.onProgress?.(res.progress) }) }) } const handleChange = ({ fileList }) => { console.log('文件列表变化:', fileList) } const handleSuccess = ({ file, fileList }) => { console.log('上传成功:', file) } const handleError = ({ error, file }) => { console.error('上传失败:', error) uni.showToast({ title: `上传失败: ${error.errMsg}`, icon: 'none' }) } </script> <style scoped> .upload-btn { width: 200rpx; height: 80rpx; } </style>异步上传的进阶技巧
1. 分片上传大文件
对于大文件上传,我们可以实现分片上传功能:
const chunkedUpload: UploadMethod = async (file, formData, options) => { const CHUNK_SIZE = 1 * 1024 * 1024 // 1MB const totalChunks = Math.ceil(file.size / CHUNK_SIZE) // 1. 获取上传ID const uploadId = await getUploadId(file.name, file.size) // 2. 分片上传 for (let i = 0; i < totalChunks; i++) { const start = i * CHUNK_SIZE const end = Math.min(start + CHUNK_SIZE, file.size) const chunk = await readFileChunk(file.url, start, end) await uploadChunk(uploadId, i, chunk, { onProgress: (progress) => { const totalProgress = ((i + 1) / totalChunks) * 100 options.onProgress?.({ progress: totalProgress }, file) } }) } // 3. 合并分片 const result = await mergeChunks(uploadId) options.onSuccess?.(result, file, formData) }2. 并发上传优化
const concurrentUpload: UploadMethod = async (file, formData, options) => { const MAX_CONCURRENT = 3 const uploadPromises = [] // 创建多个上传任务 for (let i = 0; i < MAX_CONCURRENT; i++) { uploadPromises.push(uploadFilePart(file, i)) } // 并发执行 const results = await Promise.allSettled(uploadPromises) // 处理结果 const successfulUploads = results.filter(r => r.status === 'fulfilled') if (successfulUploads.length > 0) { options.onSuccess?.(successfulUploads[0].value, file, formData) } else { options.onError?.({ errMsg: '所有上传都失败' }, file, formData) } }3. 断点续传实现
const resumeUpload: UploadMethod = async (file, formData, options) => { // 1. 检查是否有未完成的上传 const uploadRecord = await checkUploadRecord(file) if (uploadRecord && uploadRecord.chunks) { // 2. 继续上传未完成的块 const remainingChunks = getRemainingChunks(uploadRecord.chunks) for (const chunk of remainingChunks) { try { await uploadChunkWithRetry(chunk, { maxRetries: 3, onProgress: (progress) => { const totalProgress = calculateTotalProgress(uploadRecord, progress) options.onProgress?.({ progress: totalProgress }, file) } }) // 3. 更新上传记录 await updateUploadRecord(file, chunk) } catch (error) { console.error('分片上传失败:', error) throw error } } } else { // 4. 开始新的上传 return startNewUpload(file, formData, options) } }错误处理与调试技巧
1. 完善的错误处理
const robustUpload: UploadMethod = async (file, formData, options) => { try { // 1. 网络状态检查 if (!navigator.onLine) { throw new Error('网络连接不可用') } // 2. 重试机制 let retries = 3 while (retries > 0) { try { return await attemptUpload(file, formData, options) } catch (error) { retries-- if (retries === 0) throw error await sleep(1000 * (4 - retries)) // 指数退避 } } } catch (error) { // 3. 错误分类处理 if (error.message.includes('network')) { options.onError?.({ errMsg: '网络错误,请检查连接' }, file, formData) } else if (error.message.includes('timeout')) { options.onError?.({ errMsg: '上传超时,请重试' }, file, formData) } else { options.onError?.({ errMsg: error.message }, file, formData) } } }2. 调试日志
const debugUpload: UploadMethod = async (file, formData, options) => { console.group('📤 文件上传调试信息') console.log('文件信息:', { name: file.name, size: formatFileSize(file.size), type: file.type, url: file.url }) console.log('上传配置:', { action: options.action, headers: options.header, formData }) try { const startTime = Date.now() const result = await uploadFile(file, formData, options) const endTime = Date.now() console.log('上传结果:', result) console.log('上传耗时:', `${endTime - startTime}ms`) console.groupEnd() return result } catch (error) { console.error('上传错误:', error) console.groupEnd() throw error } }性能优化建议
1. 图片压缩优化
const optimizeImageUpload = async (file: UploadFileItem): Promise<UploadFileItem> => { // 根据设备像素比和网络状况动态调整压缩质量 const devicePixelRatio = uni.getSystemInfoSync().pixelRatio const networkType = uni.getNetworkTypeSync().networkType let quality = 80 // 默认质量 if (networkType === 'wifi') { quality = 90 // WiFi环境下使用高质量 } else if (networkType === '4g') { quality = 75 // 4G网络使用中等质量 } else { quality = 60 // 其他网络使用低质量 } // 高分辨率设备适当降低质量 if (devicePixelRatio > 2) { quality = Math.max(60, quality - 10) } return compressImageWithQuality(file, quality) }2. 上传队列管理
class UploadQueue { private queue: Array<() => Promise<void>> = [] private concurrent = 0 private maxConcurrent = 3 async add(uploadTask: () => Promise<void>) { this.queue.push(uploadTask) await this.processQueue() } private async processQueue() { while (this.concurrent < this.maxConcurrent && this.queue.length > 0) { this.concurrent++ const task = this.queue.shift()! task() .catch(error => { console.error('上传任务失败:', error) }) .finally(() => { this.concurrent-- this.processQueue() }) } } } // 使用队列管理上传 const uploadQueue = new UploadQueue() const queuedUpload: UploadMethod = async (file, formData, options) => { return new Promise((resolve, reject) => { uploadQueue.add(async () => { try { const result = await defaultUpload(file, formData, options) resolve(result) } catch (error) { reject(error) } }) }) }最佳实践总结
- 合理使用异步钩子:充分利用
before-upload、before-choose等钩子函数进行预处理 - 实现完善的错误处理:包括网络错误、文件错误、服务器错误等
- 添加进度反馈:通过
onProgress回调提供实时上传进度 - 支持取消操作:实现上传任务的取消功能
- 添加重试机制:网络不稳定时的自动重试
- 优化用户体验:根据网络状况调整上传策略
结语
Wot Design Uni 的 wd-upload 组件通过支持 Promise 类型的异步上传函数,为开发者提供了极大的灵活性。无论是简单的文件上传,还是复杂的业务场景,都可以通过自定义的upload-method来实现。
通过本文的深入分析,相信你已经掌握了如何充分利用 wd-upload 的异步上传功能。在实际开发中,结合具体业务需求,你可以构建出更加强大、稳定的文件上传解决方案。
记住,好的上传体验不仅仅是技术实现,更是对用户需求的深度理解。Wot Design Uni 为你提供了强大的工具,剩下的就是发挥你的创造力,构建出优秀的用户体验。
【免费下载链接】wot-design-uni一个基于Vue3+TS开发的uni-app组件库,提供70+高质量组件,支持暗黑模式、国际化和自定义主题。项目地址: https://gitcode.com/gh_mirrors/wo/wot-design-uni
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
