在移动应用和跨平台开发中,文件上传是一个常见且关键的功能模块。一个设计良好的文件上传组件不仅能提升用户体验,还能减少开发者的重复劳动。今天我将分享一个基于 Uni-app 框架开发的通用文件上传组件,它具备文件选择、验证、上传进度显示、批量操作等完整功能,适用于多种业务场景。
组件设计思路
在开发文件上传组件前,我们需要明确核心需求:
- 支持多种文件类型的选择与过滤
- 提供文件大小限制功能
- 显示上传进度和状态
- 支持单个和批量上传操作
- 允许移除已选文件
- 提供清晰的视觉反馈和错误提示
- 具备良好的可扩展性和复用性
基于这些需求,我设计了一个名为FileUploader
的组件,采用模块化结构,将 UI 展示与业务逻辑分离,同时通过 props 和 events 实现与父组件的灵活交互。
组件完整实现
下面是组件的完整代码实现,我会分模块进行解析:
模板结构(Template)
模板部分主要包含三个核心区域:上传按钮、已选文件列表和批量上传按钮,采用了清晰的层次结构和语义化命名。
<template>
<view class="file-uploader">
<!-- 上传按钮 -->
<view class="upload-btn" @click="chooseFile">
<view class="icon">
<uni-icons type="plus" size="24" color="#666"></uni-icons>
</view>
<text class="text">点击上传文件</text>
<text class="hint">支持 docx、doc、pdf 格式,最大 10MB</text>
</view>
<!-- 已选文件列表 -->
<view class="file-list" v-if="fileList.length > 0">
<view class="file-item" v-for="(file, index) in fileList" :key="index">
<!-- 文件图标 -->
<view class="file-icon">
<uni-icons
:type="getFileIcon(file.name)"
size="28"
color="#409eff"
></uni-icons>
</view>
<!-- 文件信息 -->
<view class="file-info">
<text class="file-name">{{ file.name }}</text>
<text class="file-size">{{ formatFileSize(file.size) }}</text>
<!-- 上传进度 -->
<view class="progress-bar" v-if="file.uploading">
<view
class="progress"
:style="{ width: file.progress + '%' }"
></view>
</view>
<!-- 上传状态 -->
<text class="status" v-if="!file.uploading">
{{ file.success ? '上传成功' : '等待上传' }}
</text>
</view>
<!-- 操作按钮 -->
<view class="file-actions">
<uni-icons
type="trash"
size="20"
color="#f56c6c"
@click.stop="removeFile(index)"
></uni-icons>
<uni-icons
type="cloud-upload"
size="20"
color="#409eff"
@click.stop="uploadSingleFile(index)"
v-if="!file.uploading && !file.success"
></uni-icons>
</view>
</view>
</view>
<!-- 批量上传按钮 -->
<button
class="batch-upload-btn"
type="primary"
@click="uploadAllFiles"
v-if="fileList.length > 0 && hasFilesToUpload"
>
批量上传
</button>
</view>
</template>
js逻辑(JavaScript)
实现了文件选择、验证、上传、进度监听等核心功能,通过清晰的函数划分提高了代码的可读性和可维护性。
<script>
export default {
name: 'FileUploader',
props: {
// 上传接口地址
uploadUrl: {
type: String,
required: true
},
// 额外的上传参数
uploadParams: {
type: Object,
default: () => ({})
},
// 最大文件大小(MB)
maxSize: {
type: Number,
default: 10
},
// 支持的文件类型
fileTypes: {
type: Array,
default: () => ['docx', 'doc', 'pdf']
}
},
data() {
return {
fileList: [] // 文件列表
}
},
computed: {
// 是否有等待上传的文件
hasFilesToUpload() {
return this.fileList.some(file => !file.uploading && !file.success)
}
},
methods: {
// 选择文件
chooseFile() {
const that = this
// 转换支持的文件类型为微信小程序所需的格式
const acceptTypes = this.fileTypes.map(type => `.${type}`)
uni.chooseMessageFile({
count: 5, // 最多选择5个文件
type: 'file',
extension: acceptTypes,
success(res) {
// 处理选择的文件
res.tempFiles.forEach(file => {
that.validateAndAddFile(file)
})
}
})
},
// 验证文件并添加到列表
validateAndAddFile(file) {
// 获取文件扩展名
const ext = file.name.split('.').pop().toLowerCase()
// 验证文件类型
if (!this.fileTypes.includes(ext)) {
uni.showToast({
title: `不支持${ext}格式,请上传${this.fileTypes.join('、')}文件`,
icon: 'none',
duration: 3000
})
return false
}
// 验证文件大小 (转换为MB)
const fileSizeMB = file.size / 1024 / 1024
if (fileSizeMB > this.maxSize) {
uni.showToast({
title: `文件大小不能超过${this.maxSize}MB`,
icon: 'none',
duration: 3000
})
return false
}
// 添加到文件列表
this.fileList.push({
name: file.name,
path: file.path,
size: file.size,
progress: 0,
uploading: false,
success: false,
result: null
})
return true
},
// 移除文件
removeFile(index) {
this.fileList.splice(index, 1)
},
// 上传单个文件
uploadSingleFile(index) {
const file = this.fileList[index]
if (!file || file.uploading || file.success) return
// 设置上传状态
this.$set(this.fileList[index], 'uploading', true)
this.$set(this.fileList[index], 'progress', 0)
const that = this
const uploadTask = uni.uploadFile({
url: this.uploadUrl,
filePath: file.path,
name: 'file', // 后端接收文件的参数名
formData: this.uploadParams,
success(res) {
// 处理上传成功
that.$set(that.fileList[index], 'uploading', false)
that.$set(that.fileList[index], 'success', true)
that.$set(that.fileList[index], 'result', JSON.parse(res.data))
// 通知父组件
that.$emit('uploadSuccess', {
index,
file: that.fileList[index],
response: JSON.parse(res.data)
})
},
fail(err) {
// 处理上传失败
that.$set(that.fileList[index], 'uploading', false)
that.$set(that.fileList[index], 'success', false)
uni.showToast({
title: '上传失败,请重试',
icon: 'none',
duration: 2000
})
// 通知父组件
that.$emit('uploadFail', {
index,
file: that.fileList[index],
error: err
})
}
})
// 监听上传进度
uploadTask.onProgressUpdate(progress => {
this.$set(this.fileList[index], 'progress', progress.progress)
this.$emit('uploadProgress', {
index,
file: this.fileList[index],
progress: progress.progress
})
})
},
// 批量上传所有未上传的文件
uploadAllFiles() {
this.fileList.forEach((file, index) => {
if (!file.uploading && !file.success) {
this.uploadSingleFile(index)
}
})
},
// 根据文件名获取对应的图标
getFileIcon(fileName) {
const ext = fileName.split('.').pop().toLowerCase()
switch (ext) {
case 'doc':
case 'docx':
return 'file-text'
case 'pdf':
return 'document'
default:
return 'file'
}
},
// 格式化文件大小显示
formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' B'
else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'
else return (bytes / 1048576).toFixed(2) + ' MB'
}
}
}
</script>
样式设计(Style)
样式部分采用了清晰的层次结构和响应式设计,确保在不同设备上都能提供良好的视觉体验。
<style scoped>
.file-uploader {
width: 100%;
padding: 16rpx;
box-sizing: border-box;
}
/* 上传按钮样式 */
.upload-btn {
width: 100%;
height: 200rpx;
border: 2rpx dashed #ccc;
border-radius: 12rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #f9f9f9;
margin-bottom: 30rpx;
}
.upload-btn .icon {
margin-bottom: 16rpx;
}
.upload-btn .text {
font-size: 30rpx;
color: #666;
margin-bottom: 8rpx;
}
.upload-btn .hint {
font-size: 24rpx;
color: #999;
}
/* 文件列表样式 */
.file-list {
margin-bottom: 30rpx;
}
.file-item {
display: flex;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #eee;
}
.file-icon {
margin-right: 20rpx;
width: 60rpx;
text-align: center;
}
.file-info {
flex: 1;
min-width: 0;
}
.file-name {
font-size: 28rpx;
color: #333;
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 8rpx;
}
.file-size {
font-size: 22rpx;
color: #999;
display: block;
margin-bottom: 8rpx;
}
/* 进度条样式 */
.progress-bar {
width: 100%;
height: 8rpx;
background-color: #eee;
border-radius: 4rpx;
overflow: hidden;
}
.progress {
height: 100%;
background-color: #409eff;
transition: width 0.3s ease;
}
/* 状态文本 */
.status {
font-size: 24rpx;
margin-top: 8rpx;
display: inline-block;
}
.status.success {
color: #67c23a;
}
/* 文件操作按钮 */
.file-actions {
display: flex;
align-items: center;
gap: 20rpx;
margin-left: 20rpx;
}
/* 批量上传按钮 */
.batch-upload-btn {
width: 100%;
height: 80rpx;
line-height: 80rpx;
font-size: 30rpx;
border-radius: 12rpx;
}
</style>
核心功能解析
这个文件上传组件包含多个核心功能模块,每个模块都经过精心设计以确保用户体验和功能完整性:
1. 文件选择与验证
- 文件选择:使用
uni.chooseMessageFile
API 实现跨平台的文件选择功能,支持一次选择多个文件 - 类型验证:通过解析文件扩展名,验证是否为允许的文件类型,并提供清晰的错误提示
- 大小验证:将文件大小转换为 MB 单位,与允许的最大尺寸比较,防止超大文件上传
2. 文件列表管理
- 采用数组存储已选文件的信息,包括名称、路径、大小、上传状态等
- 实现了文件移除功能,允许用户随时删除不需要的文件
- 通过计算属性
hasFilesToUpload
智能判断是否显示批量上传按钮
3. 上传功能实现
- 单个上传:通过
uni.uploadFile
API 实现文件上传,支持自定义上传地址和额外参数 - 批量上传:遍历文件列表,对未上传的文件依次执行上传操作
- 进度监听:利用
uploadTask.onProgressUpdate
实时获取上传进度,并更新 UI 展示
4. 状态管理与反馈
- 为每个文件维护完整的状态信息:上传中、上传成功、等待上传
- 通过进度条直观展示上传进度
- 使用 Toast 提示上传结果和错误信息
- 根据文件类型显示不同的图标,增强视觉识别性
5. 事件交互
组件通过以下事件与父组件进行交互,提供足够的灵活性:
uploadSuccess
:文件上传成功时触发uploadFail
:文件上传失败时触发uploadProgress
:上传进度更新时触发
使用示例
使用这个组件非常简单,只需在父组件中引入并配置必要的参数:
<template>
<view>
<FileUploader
uploadUrl="https://your-api.com/upload"
:uploadParams="{ category: 'document' }"
:maxSize="20"
:fileTypes="['docx', 'doc', 'pdf', 'xlsx']"
@uploadSuccess="handleUploadSuccess"
@uploadFail="handleUploadFail"
@uploadProgress="handleUploadProgress"
/>
</view>
</template>
<script>
import FileUploader from '@/components/FileUploader.vue'
export default {
components: {
FileUploader
},
methods: {
handleUploadSuccess(data) {
console.log('上传成功', data)
// 处理上传成功后的逻辑,如保存文件ID等
},
handleUploadFail(data) {
console.error('上传失败', data)
// 处理上传失败后的逻辑
},
handleUploadProgress(data) {
console.log('上传进度', data)
// 可用于全局进度展示
}
}
}
</script>
组件亮点
这个文件上传组件具有以下亮点:
- 跨平台兼容:基于 Uni-app 开发,可同时运行在微信小程序、H5、App 等多个平台
- 高度可配置:通过 props 灵活配置上传地址、文件类型、大小限制等参数
- 用户体验佳:提供清晰的视觉反馈和操作指引,减少用户认知成本
- 代码规范:结构清晰,命名规范,注释完善,易于维护和扩展
- 功能完整:涵盖了文件上传所需的全部核心功能,开箱即用
总结
本文介绍的FileUploader
组件是一个功能完整、设计精良的文件上传解决方案。它不仅实现了文件选择、验证、上传、进度展示等核心功能,还通过精心的 UI 设计和交互细节提升了用户体验。
组件的主要优势在于:
- 完整性:一站式解决文件上传的所有需求,无需额外开发
- 灵活性:通过参数配置和事件交互,可适应不同的业务场景
- 易用性:简单的引入方式和清晰的 API 设计,降低使用门槛
- 可扩展性:模块化的代码结构使得功能扩展和定制变得容易
在实际项目中,你可以根据具体需求进一步扩展这个组件,例如添加文件预览功能、支持断点续传、增加拖拽上传等特性。希望这个组件能帮助你快速实现高质量的文件上传功能,提升开发效率。