uni-app——uni-app小程序附件上传的文件类型限制问题
uni-app——uni-app小程序附件上传的文件类型限制问题
·
小程序附件上传的文件类型限制问题
问题背景
在小程序开发中,"附件上传"是常见功能。但很多开发者在实现时会遇到一个问题:用户只能选择图片,无法选择PDF、Word等其他类型的文件。
最近在开发审批功能时就遇到了这个问题:审批申请需要上传附件(如合同、发票等),但用户反馈只能选择图片,无法选择其他文件。
问题现象
期望效果:
┌─────────────────────────────────────┐
│ 添加附件 │
│ [图片] [PDF] [Word] [Excel] ... │
└─────────────────────────────────────┘
实际效果:
┌─────────────────────────────────────┐
│ 添加附件 │
│ [图片] [图片] [图片] (只能选图片) │
└─────────────────────────────────────┘
问题根因
小程序文件选择 API 对比
小程序提供了多个文件选择 API,各有不同的用途和限制:
| API | 用途 | 支持的文件类型 | 平台支持 |
|---|---|---|---|
uni.chooseImage() |
选择图片 | 仅图片(jpg/png/gif等) | 全平台 |
uni.chooseVideo() |
选择视频 | 仅视频(mp4等) | 全平台 |
uni.chooseFile() |
选择文件 | 多种文件类型 | H5/App(小程序不支持) |
uni.chooseMessageFile() |
从聊天记录选择 | 多种文件类型 | 仅微信小程序 |
问题代码
// 错误写法:只能选择图片
const chooseFile = () => {
uni.chooseImage({
count: 9,
success: (res) => {
// 只能获取到图片
uploadFiles(res.tempFilePaths)
}
})
}
问题在于使用了 chooseImage,这个 API 顾名思义只能选择图片。
解决方案
方案一:使用 chooseMessageFile(微信小程序推荐)
微信小程序专属 API,允许用户从微信聊天记录中选择文件:
const chooseFile = () => {
uni.chooseMessageFile({
count: 9,
type: 'file', // 'all' | 'image' | 'video' | 'file'
success: (res) => {
// res.tempFiles 包含文件信息
// [{ path, size, name, type }]
const files = res.tempFiles.map(file => ({
path: file.path,
name: file.name,
size: file.size,
type: file.type
}))
uploadFiles(files)
}
})
}
优点:
- 支持选择各种文件类型(PDF、Word、Excel等)
- 用户可以从聊天记录快速选择已有文件
- 体验流畅,符合微信用户习惯
缺点:
- 仅微信小程序支持
- 文件必须存在于聊天记录中
方案二:组合多个 API
提供多种选择入口,满足不同需求:
<template>
<view class="upload-section">
<view class="upload-title">添加附件</view>
<view class="upload-buttons">
<button @click="chooseImage">选择图片</button>
<button @click="chooseFromChat">从聊天记录选择</button>
</view>
<!-- 已选文件列表 -->
<view class="file-list">
<view
v-for="(file, index) in fileList"
:key="index"
class="file-item"
>
<image
v-if="isImage(file)"
:src="file.path"
class="file-thumb"
/>
<view v-else class="file-icon">
{{ getFileIcon(file.name) }}
</view>
<text class="file-name">{{ file.name }}</text>
<text class="file-delete" @click="removeFile(index)">删除</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
const fileList = ref([])
// 选择图片
const chooseImage = () => {
uni.chooseImage({
count: 9 - fileList.value.length,
success: (res) => {
const newFiles = res.tempFilePaths.map((path, index) => ({
path,
name: `图片${fileList.value.length + index + 1}.jpg`,
type: 'image'
}))
fileList.value.push(...newFiles)
}
})
}
// 从聊天记录选择(微信小程序)
const chooseFromChat = () => {
// #ifdef MP-WEIXIN
uni.chooseMessageFile({
count: 9 - fileList.value.length,
type: 'file',
success: (res) => {
const newFiles = res.tempFiles.map(file => ({
path: file.path,
name: file.name,
size: file.size,
type: getFileType(file.name)
}))
fileList.value.push(...newFiles)
}
})
// #endif
// #ifndef MP-WEIXIN
uni.showToast({
title: '当前平台不支持',
icon: 'none'
})
// #endif
}
// 判断是否为图片
const isImage = (file) => {
return file.type === 'image' || /\.(jpg|jpeg|png|gif|webp)$/i.test(file.name)
}
// 获取文件图标
const getFileIcon = (fileName) => {
const ext = fileName.split('.').pop().toLowerCase()
const iconMap = {
pdf: '📄',
doc: '📝',
docx: '📝',
xls: '📊',
xlsx: '📊',
ppt: '📽️',
pptx: '📽️',
zip: '📦',
rar: '📦'
}
return iconMap[ext] || '📎'
}
// 获取文件类型
const getFileType = (fileName) => {
if (/\.(jpg|jpeg|png|gif|webp)$/i.test(fileName)) return 'image'
if (/\.(mp4|mov|avi)$/i.test(fileName)) return 'video'
return 'file'
}
// 删除文件
const removeFile = (index) => {
fileList.value.splice(index, 1)
}
</script>
方案三:跨平台兼容封装
封装一个通用的文件选择函数,自动适配不同平台:
// utils/chooseFile.js
/**
* 跨平台文件选择
* @param {Object} options
* @param {number} options.count - 最大选择数量
* @param {string} options.type - 文件类型:'all' | 'image' | 'video' | 'file'
* @returns {Promise<Array>} 文件列表
*/
export const chooseFile = (options = {}) => {
const { count = 9, type = 'all' } = options
return new Promise((resolve, reject) => {
// 微信小程序:使用 chooseMessageFile
// #ifdef MP-WEIXIN
if (type === 'image') {
uni.chooseImage({
count,
success: (res) => {
resolve(res.tempFilePaths.map((path, i) => ({
path,
name: `image_${Date.now()}_${i}.jpg`,
type: 'image'
})))
},
fail: reject
})
} else {
uni.chooseMessageFile({
count,
type: type === 'all' ? 'all' : type,
success: (res) => {
resolve(res.tempFiles.map(file => ({
path: file.path,
name: file.name,
size: file.size,
type: file.type
})))
},
fail: reject
})
}
// #endif
// H5/App:使用 chooseFile
// #ifdef H5 || APP-PLUS
uni.chooseFile({
count,
type: type === 'all' ? 'all' : type,
success: (res) => {
resolve(res.tempFiles.map(file => ({
path: file.path,
name: file.name,
size: file.size,
type: file.type
})))
},
fail: reject
})
// #endif
})
}
使用方式:
import { chooseFile } from '@/utils/chooseFile'
// 选择任意文件
const files = await chooseFile({ count: 5, type: 'all' })
// 只选择图片
const images = await chooseFile({ count: 9, type: 'image' })
文件上传的完整流程
选择文件后,还需要上传到服务器:
/**
* 上传文件到服务器
* @param {Array} files - 文件列表
* @returns {Promise<Array>} 上传结果
*/
export const uploadFiles = async (files) => {
const uploadTasks = files.map(file => {
return new Promise((resolve, reject) => {
uni.uploadFile({
url: 'https://api.example.com/upload',
filePath: file.path,
name: 'file',
formData: {
fileName: file.name,
fileType: file.type
},
success: (res) => {
const data = JSON.parse(res.data)
resolve({
...file,
url: data.url, // 服务器返回的文件URL
id: data.id
})
},
fail: reject
})
})
})
return Promise.all(uploadTasks)
}
注意事项
1. 文件大小限制
小程序对上传文件大小有限制,建议在选择后进行校验:
const MAX_FILE_SIZE = 10 * 1024 * 1024 // 10MB
const validateFileSize = (files) => {
const oversizedFiles = files.filter(f => f.size > MAX_FILE_SIZE)
if (oversizedFiles.length > 0) {
uni.showToast({
title: `文件大小不能超过10MB`,
icon: 'none'
})
return false
}
return true
}
2. 文件类型校验
防止用户上传不支持的文件类型:
const ALLOWED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx', 'xls', 'xlsx']
const validateFileType = (files) => {
const invalidFiles = files.filter(f => {
const ext = f.name.split('.').pop().toLowerCase()
return !ALLOWED_EXTENSIONS.includes(ext)
})
if (invalidFiles.length > 0) {
uni.showToast({
title: `不支持的文件类型`,
icon: 'none'
})
return false
}
return true
}
3. 用户体验优化
- 显示上传进度
- 支持取消上传
- 上传失败自动重试
const uploadWithProgress = (file, onProgress) => {
return new Promise((resolve, reject) => {
const task = uni.uploadFile({
url: 'https://api.example.com/upload',
filePath: file.path,
name: 'file',
success: (res) => resolve(JSON.parse(res.data)),
fail: reject
})
// 监听上传进度
task.onProgressUpdate((res) => {
onProgress && onProgress(res.progress)
})
})
}
总结
-
API 选择很重要:
chooseImage只能选图片,需要选择其他文件类型时应使用chooseMessageFile(微信)或chooseFile(H5/App) -
平台差异需处理:不同平台支持的 API 不同,建议封装统一的文件选择函数
-
完善的校验机制:文件大小、文件类型都需要校验,避免上传失败或服务器压力
-
良好的用户体验:提供多种选择方式、显示上传进度、处理异常情况
本文源于实际项目中的问题修复经验,希望对遇到类似问题的开发者有所帮助。
更多推荐
所有评论(0)