在实际开发中,我们经常遇到需要让用户选择各种类型的文件如PDF文档、Excel表格、Word文档等的需求。然而,由于各平台API的差异性,实现这一功能并非易事。本文将介绍如何在uni-app中实现跨平台选择非媒体文件的功能。
一、面临的问题
各平台文件选择面临以下差异:
App端:需调用原生API(如plus.io.chooseFile)H5端:支持uni.chooseFile,但浏览器兼容性存在限制微信小程序:需使用专用API(wx.chooseMessageFile)其他小程序:各平台API支持程度不一
二、解决方案概述
uni-app官方提供了组件,但在实际项目中,对于特定格式文件的选择可能需要定制化处理。我们可以结合多种API来实现完整的跨平台方案。
uni-file-picker
三、具体实现
1. App端采用相应的文件选择方法:
① Android 文件选择解决方案:
首先获取存储读取权限,防止获取的文件路径没有访问权限
// 判断是否是Android端 (plus.os.name == 'Android' 可以通过html5+ API获取)
function equestAndroidPermission() {
if (uni.getSystemInfoSync().platform == 'android') {
return new Promise((resolve, reject) => {
plus.android.requestPermissions(['android.permission.READ_EXTERNAL_STORAGE'], (e) => {
if (e.granted.includes('android.permission.READ_EXTERNAL_STORAGE')) {
resolve();
} else {
if (e.deniedAlways.length > 0) { //权限被永久拒绝
uni.showModal({
title: '提示',
content: '文件读取权限被拒绝,是否前往开启权限',
success: (res) => {
if (res.confirm) {
// 弹出提示框解释为何需要读写手机储存权限,引导用户打开设置页面开启
var main = plus.android.runtimeMainActivity();
var Intent = plus.android.importClass(
"android.content.Intent");
//直接进入应用列表的权限设置
var mIntent = new Intent(
'android.settings.APPLICATION_SETTINGS'
);
main.startActivity(mIntent);
}
}
});
reject("权限被拒绝了");
}
// 权限被临时拒绝
if (e.deniedPresent.length > 0) {
// 弹出提示框解释为何需要读写手机储存权限,可再次调用plus.android.requestPermissions申请权限
plus.android.requestPermissions([
'android.permission.READ_EXTERNAL_STORAGE'
]);
reject("权限被拒绝了");
}
}
},(error) => {
reject(error.message);
});
});
}
},
安卓系统通过Intent机制调用系统文件管理器(一定要调用equestAndroidPermission这个方法先获取读取文件权限)
// 调用原生Intent类调取文件系统管理器并选取文件获取文件地址
function PickFile(callback, errcallback, mimeTypes='*/*') {
equestAndroidPermission().then(() => {
// mimeTypes为你要查的文件类型"image/*","audio/*","video/*;image/*"
// 选择图片 intent.setType("image/*");
// 选择音频 intent.setType("audio/*");
//选择图片和视频 intent.setType("video/*;image/*");
//选择视频 (mp4 3gp 是android支持的视频格式)
const CODE_REQUEST = 1000;
const main = plus.android.runtimeMainActivity();
var Intent = plus.android.importClass('android.content.Intent');
var intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
if (mimeTypes) {
intent.setType("*/*");
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
} else {
intent.setType("*/*");
}
let _this = pickFile;
main.onActivityResult = function(requestCode, resultCode, data) {
if (requestCode == CODE_REQUEST) {
var uri = data.getData();
plus.android.importClass(uri); ①
var Build = plus.android.importClass('android.os.Build');
var isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
var DocumentsContract = plus.android.importClass('android.provider.DocumentsContract');
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(main, uri)) {
console.log("版本大于 4.4 ");
// ExternalStorageProvider
if ("com.android.externalstorage.documents" == uri.getAuthority()) {
var docId = DocumentsContract.getDocumentId(uri);
var split = docId.split(":");
var type = split[0];
if ("primary" == type) {
var Environment = plus.android.importClass('android.os.Environment');
callback(Environment.getExternalStorageDirectory() + "/" + split[1]);
} else {
var System = plus.android.importClass('java.lang.System');
var sdPath = System.getenv("SECONDARY_STORAGE");
if (sdPath) {
callback(sdPath + "/" + split[1]);
}
}
}
// DownloadsProvider
else if ("com.android.providers.downloads.documents" == uri.getAuthority()) {
var id = DocumentsContract.getDocumentId(uri);
var ContentUris = plus.android.importClass('android.content.ContentUris');
var contentUri = ContentUris.withAppendedId(
// Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
Uri.parse("content://downloads/public_downloads"), id);
callback(_this.getDataColumn(main, contentUri, null, null));
}
// MediaProvider
else if ("com.android.providers.media.documents" == uri.getAuthority()) {
var docId = DocumentsContract.getDocumentId(uri);
var split = docId.split(":");
var type = split[0];
var MediaStore = plus.android.importClass('android.provider.MediaStore');
if ("image" == type) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video" == type) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio" == type) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
} else {
contentUri = MediaStore.Files.getContentUri("external");
}
console.log("版本大于 4.4 ", type);
var selection = "_id=?";
var selectionArgs = new Array();
selectionArgs[0] = split[1];
callback(_this.getDataColumn(main, contentUri, selection, selectionArgs));
}
}
// MediaStore (and general)
else if ("content" == uri.getScheme()) {
callback(_this.getDataColumn(main, uri, null, null));
}
// File
else if ("file" == uri.getScheme()) {
callback(uri.getPath());
}
}
}
main.startActivityForResult(intent, CODE_REQUEST);
}).catch((err) => {
errcallback(err);
})
}
② ios 文件选择解决方案:
借助 5+App 的
plus.io.chooseFile 实现:
plus.io.chooseFile
// 仅 App 端有效,需判断平台后使用
if (uni.getSystemInfoSync().platform === 'ios') {
plus.io.chooseFile(
{
title: '选择文件', // 选择文件的标题提示
filter: '*', // 筛选文件类型,* 表示所有,也可设 '.pdf,.docx' 等
multiple: false // 是否多选
},
(res) => {
const localPath = res.files[0]; // 获取选中的文件地址
// 执行上传等操作
....
},
(err) => {
console.error('选择文件失败:', err);
}
);
}
2. H5端采用相应的文件选择方法:
H5端可以使用uni-app 提供的 uni.chooseFile(OBJECT) 方法选择非媒体文件,此API只支持H5。



Tips:
如果type属性和extension同时存在,例如 ,则会选择
{type:'image',extension:['.png','.jpg']}文件如果只配置extension属性,例如
image/png,image/jpg ,则会选择
{extension:['.doc','.xlsx','.docx']} 文件,详情见accept属性在微信环境中,如果
.doc,.xlsx,.docx,则
type="all" 属性失效
extension
// 选择图片文件
uni.chooseFile({
count: 10,
type: 'image',
success (res) {
// tempFilePath可以作为img标签的src属性显示图片
const tempFilePaths = res.tempFiles
}
})
3. 微信小程序端采用相应的文件选择方法:
从微信聊天会话中选择文件,详见
// 微信小程序使用 wx.chooseMessageFile
let chooseFile = uni.chooseFile;
if (typeof wx !== 'undefined' && typeof wx.chooseMessageFile === 'function') {
chooseFile = wx.chooseMessageFile;
}
chooseFile({
type: 'all',
count: 1,
success(res) {
// 处理选择的文件
},
fail(err) {
console.error('选择文件失败', err);
}
});
四、常见一些处理文件工具方法
// 文件类型检查
function isFileType(filename) {
const fileExtensions = ['.pdf', '.doc', '.docx', '.xlsx', '.xls'];
const extension = filename.slice(filename.lastIndexOf('.')).toLowerCase();
return fileExtensions.includes(extension);
}
// 检查文件扩展名
function checkFileExt(path) {
const reg = /.+./;
let fileExt = path.replace(reg, "").toLowerCase();
const allowedExts = ['pdf', 'doc', 'docx', 'xlsx', 'xls'];
const isValid = allowedExts.some(ext => ext === fileExt);
if (!isValid) {
uni.showToast({
title: `不允许选择${fileExt}格式的文件`,
icon: 'none'
});
}
return isValid;
}
// 获取文件扩展名(小写)
function getFileExtension(filePath) {
const ext = filePath.split('.').pop().toLowerCase();
return ext;
}
// 判断文件后缀是否允许
funbction checkFileExt(path) {
const limitType = ['png', 'jpg', 'jpeg', 'mp4', 'pdf', 'doc', 'docx', 'xlsx', 'xls'];
// 检查是否在允许的后缀中
let noArrowExt = false;
// 获取后缀名
const reg = /.+./;
let fileExt = path.replace(reg, "").toLowerCase();
// 使用数组的some方法,只要符合limitType中的一个,就返回true
noArrowExt = limitType .some(ext => {
// 转为小写
return ext.toLowerCase() === fileExt;
})
if (!noArrowExt) this.showToast(`不允许选择${fileExt}格式的文件`);
return noArrowExt;
}
五、注意事项
权限处理:Android平台需申请外部存储读取权限文件大小限制:应设置合理的文件大小限制,避免内存溢出文件预览:支持通过uni.openDocument接口预览选定文件错误处理:全面覆盖各类异常场景,优化用户使用体验
通过以上方案,我们可以在uni-app中实现跨平台的非媒体文件选择功能。关键在于根据不同平台采用合适的API,并做好文件类型验证和错误处理。这样既能满足业务需求,又能保证良好的用户体验。
在实际项目中,还需要根据具体需求进行调整和完善,比如增加文件上传进度显示、批量文件选择等功能。