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");
		// 给系统导入 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+版本,推出了分区存储机制,何为分区存储:

  1. 每个应用程序都有自己的存储空间。
  2. 应用程序不能翻过自己的目录,去访问公共目录。
  3. 应用程序请求的数据都要通过权限检测,不符合要求不会被放行

分区存储是一种安全机制,用于防止应用读取其他应用的数据。
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文档

  • 16
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值