行业知识
Uniapp App(Android)端 非媒体文件的上传与下载
一、文件上传
在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");// 给系统导入 contentResolverplus.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 resultif (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://' + resultthis.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');// 给系统导入 contentResolverlet 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 = pathlet 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 resultif (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://' + resultthis.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
选择文件确认上传
js
export default {data() {return { // 上传组件接口参数option: {// 上传服务器地址,需要替换为你的接口地址url: this.$URL + '/App/selection/xp/uploadExcel',// 上传附件的keyname: 'file',// 根据你接口需求自定义请求头,默认不要写content-type,让浏览器自适配header: {token: uni.getStorageSync('token'),},// 根据你接口需求自定义body参数formData: {// 'orderId': 1000}}, chooseFile: {}, upload1: 1, // 控件的id}},methods: {// 上传组件 changeuploadChange(files) {this.showUploadResult = falseconsole.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文档
上一篇:头像剪切上传