一、文件上传
在app端上传文件时,一般情况下我们会使用uniapp内置组件:uni-file-picker,但是这个组件App端只支持上传图片和视频,而官方文档提供的uni.chooseFile(),也不支持App端,那该怎么办呢?我这里暂时有两种解决方案:
1、使用H5+App提供的Api配合uniapp提供的uni.uploadFile进行选择文件上传
// 选择文件方法(plus.io.chooseFile)
const choose = () => {
const MediaStore = plus.android.importClass('android.provider.MediaStore');
const main = plus.android.runtimeMainActivity();
const Uri = plus.android.importClass('android.net.Uri');
plus.io.chooseFile({
title: '选择文件',
filetypes: ['xlsx'], // 允许的文件类型
multiple: false, // 是否允许多选
},(e) => {
const tempFilePaths = e.files
// 获取文件的虚拟路径
const contentUri = decodeURIComponent(e.files[0])
console.log(contentUri)
const uri = MediaStore.Files.getContentUri("external");
// 给系统导入 contentResolver
plus.android.importClass(main.getContentResolver());
console.log('uri', uri)
console.log('selection', "_id=?")
const arr = contentUri.split(':')
console.log('selectionArgs', [arr[arr.length-1]].toString())
// 通过查询的方式用虚拟路径的id1获取到文件的真实路径
let cursor = main.getContentResolver().query(uri, ['_data'], "_id=?", [arr[arr.length-1]], null);
plus.android.importClass(cursor);
console.log(cursor)
console.log(cursor.moveToFirst())
let result
if (cursor != null && cursor.moveToFirst()) {
let column_index = cursor.getColumnIndexOrThrow('_data');
console.log(column_index)
result = cursor.getString(column_index) // result即文件的真实路径
console.log('result', result)
cursor.close();
}
result = 'file://' + result
this.filePath = result // 此路径为文件的本地真实路径,可使用该路径进行上传文件
}
// 上传
const upload = () => {
uni.showLoading({
mask: true
})
const formData = {
shangpinbianma: this.goodsDetail.bianma
}
uni.uploadFile({
url: this.$URL + '/App/selection/xp/uploadExcel', // 上传地址 需换为你的服务器地址
methods: "POST",
name: 'file',
filePath: this.filePath, // 本地路径
formData, // 额外参数
header: {
token: uni.getStorageSync('token'),
'content-type': 'application/x-www-form-urlencoded'
},
success: res => {
// console.log("res--->",res)
uni.hideLoading()
if(res.data.code=='-110' || res.data.code=='-120' || res.data.code=='-130' || res.data.code=='-150'){
console.log(":登录已失效")
uni.removeStorageSync('token');
uni.removeStorageSync('userInfo');
if(res.data.code != '-120'){
uni.showToast({
title: '登录已失效',
icon: 'none'
})
}
uni.reLaunch({
url: '/pages/login/index'
})
}
let result = JSON.parse(res.data) // 上传成功后返回数据
console.log('上传结果', res)
console.log('upload_result', result)
// 在此做后续操作
},
fail: res => {
uni.hideLoading()
uni.showToast({
title: "文件上传失败"
})
},
complete: res => {}
})
}
上面方法我们使用H5+的plus.io.chooseFile()方法调起选择文件,Android原生调起选择文件方法如下:
const choose = () => { //使用plus选择文件
let that = this;
let filePath = ''
let main = plus.android.runtimeMainActivity();
let Intent = plus.android.importClass('android.content.Intent');
let Activity = plus.android.importClass('android.app.Activity');
let intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType('*/*');
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); //关键!多选参数
intent.addCategory(Intent.CATEGORY_OPENABLE);
main.startActivityForResult(intent, 200);
// 获取回调
main.onActivityResult = (requestCode, resultCode, data) => {
let Activity = plus.android.importClass('android.app.Activity');
let ContentUris = plus.android.importClass('android.content.ContentUris');
let Cursor = plus.android.importClass('android.database.Cursor');
let Uri = plus.android.importClass('android.net.Uri');
let Build = plus.android.importClass('android.os.Build');
let Environment = plus.android.importClass('android.os.Environment');
let DocumentsContract = plus.android.importClass('android.provider.DocumentsContract');
var MediaStore = plus.android.importClass('android.provider.MediaStore');
// 给系统导入 contentResolver
let contentResolver = main.getContentResolver();
plus.android.importClass(contentResolver);
if (resultCode == Activity.RESULT_OK) {
console.log('data', data)
// 解析路径
if (data.getData() != null) {
let uri = data.getData()
console.log('uri', uri)
let path = uri.getPath() // 获取到选择文件的虚拟路径
console.log('path', path)
this.filePath = path
let docId = DocumentsContract.getDocumentId(uri);
let split = docId.split(":");
let type = split[0];
let selection = "_id=?";
let selectionArgs = new Array();
selectionArgs[0] = split[1];
uri = MediaStore.Files.getContentUri("external");
plus.android.importClass(main.getContentResolver());
// 通过查询的方式用虚拟路径的id1获取到文件的真实路径
console.log('uri', uri)
console.log('docId', docId)
console.log('selectionArgs', selectionArgs.toString())
let cursor = main.getContentResolver().query(uri, ['_data'], selection, selectionArgs,
null);
plus.android.importClass(cursor);
console.log('cursor', cursor)
console.log('cursor.moveToFirst()', cursor.moveToFirst())
let result
if (cursor != null) {
let column_index = cursor.getColumnIndexOrThrow('_data');
console.log('column_index', column_index)
result = cursor.getString(column_index) // result即文件的真实路径
console.log('resulttt', result)
cursor.close();
}
result = 'file://' + result
this.filePath = result // 此路径为文件的本地真实路径,可使用该路径进行上传文件
}
}
}
在Android10+版本,推出了分区存储机制,何为分区存储:
- 每个应用程序都有自己的存储空间。
- 应用程序不能翻过自己的目录,去访问公共目录。
- 应用程序请求的数据都要通过权限检测,不符合要求不会被放行
分区存储是一种安全机制,用于防止应用读取其他应用的数据。
Dcloud在分区存储的环境下分出两个可操文件数据目录系统公共目录和应用沙盒目录。
系统公共目录下的的文件只支持读取媒体文件,不支持读取其他文件,所以我们平常通过URL参数,获取目录对象或文件对象而使用的 plus.io.resolveLocalFileSystemURL() Api,会无法获取正确的文件对象,会返回code=15的错误。
其他受限制的H5+API,以及分区存储细节,请参考官方文档(https://ask.dcloud.net.cn/article/id-36199__page-6#reply)
2. 使用插件上传(推荐)
插件的话,我使用的是lsj-upload,用法:
template
<lsj-upload
ref="lsjUpload"
childId="upload1"
width="150rpx"
height="100%"
:option="option"
:size="10"
:debug="true"
:multiple="false"
formats="xlsx"
:count="1"
:instantly="false"
@change="uploadChange"
@uploadEnd="onuploadEnd"
>
<button
type="primary"
size="mini"
style="padding: 0rpx 20rpx;margin-top: 20rpx;font-size:25rpx;background-color: #E62F3D;"
>选择文件</button>
</lsj-upload>
<button
type="primary"
@click="handleUploadSubmit"
style="padding: 0rpx 0;margin-top: 20rpx;font-size:25rpx;background-color: #E62F3D;"
>确认上传</button>
js
export default {
data() {
return {
// 上传组件接口参数
option: {
// 上传服务器地址,需要替换为你的接口地址
url: this.$URL + '/App/selection/xp/uploadExcel',
// 上传附件的key
name: 'file',
// 根据你接口需求自定义请求头,默认不要写content-type,让浏览器自适配
header: {
token: uni.getStorageSync('token'),
},
// 根据你接口需求自定义body参数
formData: {
// 'orderId': 1000
}
},
chooseFile: {},
upload1: 1, // 控件的id
}
},
methods: {
// 上传组件 change
uploadChange(files) {
this.showUploadResult = false
console.log('当前选择的文件列表:', files);
// file 为 Map对象
// 更新选择的文件
// this.files = files;
for (let value of files.values()) {
// 我这里使用的是 单选上传
this.chooseFile = value
console.log(value);
}
},
// 点击上传 按钮
handleUploadSubmit () {
uni.showLoading({mask: true})
if(!this.chooseFile.path) {
uni.showToast({
title: '请选择上传文件',
icon: 'none'
})
return
}
console.log('uploadPath', this.chooseFile)
// 上传组件上传
this.$refs['lsjUpload'].upload();
// 上传组件 获取的文件路径path是一个blobURL,如果不想用组件本身的上传方法,需自行处理
},
// 文件上传完毕 事件
onuploadEnd(item) {
uni.hideLoading()
if(item.type !== 'success') {
uni.showToast({
title: '上传文件失败',
icon: 'none'
})
}
console.log(`${item.name}已上传结束,上传状态=${item.type},`, item);
const result = JSON.parse(item.responseText)
// 这里可以做后续操作
},
}
}
上面代码是,单个文件手动上传的简单示例。更多插件属性及用法可参考文档(https://ext.dcloud.net.cn/plugin?id=5459#detail)
二、文件下载并保存本地
1. uni.downloadFile() 与 uni.saveFile() 下载
使用 uniapp 内置API,uni.saveFIle() 下载文件:
handleDownload() {
// 此处 换为 真实的服务器文件地址
const url = downloadUrl + '/download/xxx.xlsx'
uni.showLoading({mask: true})
uni.downloadFile({
url: url, //下载地址
success: async (data) => {
if (data.statusCode === 200) {
console.log('下载成功', data)
const fileName = data.tempFilePath.split('/').pop()
console.log('文件名', fileName)
// 文件保存到本地
uni.saveFile({
tempFilePath: data.tempFilePath, // 文件临时路径
success: (res) => {
console.log(res)
uni.hideLoading()
// that.url = res.savedFilePath;
// uni.setStorageSync('url', res.savedFilePath);
uni.showToast({
title: '文件已保存至' + res.savedFilePath,
icon: 'none'
})
},
fail: (err) => {
console.log('文件保存失败', err)
uni.hideLoading()
if(err.message) {
uni.showToast({
title: err.message,
icon: 'none'
})
} else {
uni.showToast({
title: '文件保存失败',
icon: 'none'
})
}
}
});
} else {
uni.showToast({
icon: 'none',
mask: true,
title: '下载失败',
});
console.log('下载失败', data)
}
},
fail: (err) => {
uni.hideLoading()
uni.showToast({
icon: 'none',
mask: true,
title: '失败请重新下载',
});
},
})
}
上面代码,使用uniapp内置接口 uni.downloadFile() 下载文件,拿到文件临时路径,然后通过 uni.saveFile() 将文件保存至本地。
这里有一个问题,就是 uni.saveFile() 方法,会将文件保存在
内部存储\Android\data\io.dcloud.HBuilder\apps\HBuilder\doc\uniapp_save 目录下,用户很难找到这个文件,不方便使用。
此时我们可以使用H5+APP的 plus.downloader.createDownload() 来下载:
2. plus.downloader.createDownload()
// 第一个参数为下载地址
// 第二个是一个 对象,filename 表示下载文件保存的路径
// 第三个是 回调函数
// plus.io.PUBLIC_DOWNLOADS 应用公共下载目录常量
let dtask = plus.downloader.createDownload(url, {
filename: plus.io.PUBLIC_DOWNLOADS + "/你的应用名/" //利用保存路径,实现下载文件的重命名
},(d, status)=> {
console.log('ddddd', status, d)
//d为下载的文件对象
// status == 200 表示下载成功
if (status == 200) {
uni.hideLoading();
uni.showToast({
icon: 'none',
mask: true,
title: plus.io.PUBLIC_DOWNLOADS + '/你的应用名/' + d.filename, //保存路径
duration: 2000,
});
//下载成功,d.filename是文件在保存在本地的相对路径,使用下面的API可转为平台绝对路径
// let fileSaveUrl = plus.io.convertLocalFileSystemURL(d.filename);
// setTimeout(()=>{
// plus.runtime.openFile(d.filename); //选择软件打开文件
// },1500)
} else {
//下载失败
uni.hideLoading();
plus.downloader.clear(); //清除下载任务
uni.showToast({
icon:'none',
mask:true,
title: '下载失败,请稍后重试',
});
}
})
// 开始下载任务
dtask.start()
更多用法可参考H5+APP文档