👨💻 作者简介:程序员半夏 , 一名全栈程序员,擅长使用各种编程语言和框架,如JavaScript、React、Node.js、Java、Python、Django、MySQL等.专注于大前端与后端的硬核干货分享,同时是一个随缘更新的UP主. 你可以在各个平台找到我!
🏆 本文收录于专栏: uniapp踩坑指南
🔥 专栏介绍: 本专栏提供了uni-app开发过程中必不可少的组件和解决方案。本书详细介绍了各种常用组件的使用方法和技巧,以及如何应对uniapp开发中遇到的各种问题。
前言
本文主要针对移动端(iOS和Android)。uni-app官方提供了uni.chooseFile(OBJECT)
接口从本地选择选择非媒体文件。如果是图片视频的话,可以选择uni.chooseMedia(OBJECT)
、uni.chooseVideo(OBJECT)
、uni.chooseImage(OBJECT)
等接口。
但是 uni.chooseFile(OBJECT)
仅仅支持H5。
App | H5 | 微信小程序 | 支付宝小程序 | 百度小程序 | 抖音小程序、飞书小程序 | QQ小程序 | 快手小程序 | 京东小程序 |
---|---|---|---|---|---|---|---|---|
x | √(HBuilder X2.9.9+)
|
x(可使用wx.chooseMessageFile)
|
x | x | x | x | x | x |
除了uni.chooseFile(OBJECT)
官方还提供了uni-file-picker
来选择任意类型的文件上传。但是请注意该组件在移动端仍然仅支持选择图片和视频。
file-mediatype | String | image | image/video/all | 选择文件类型,all 只支持 H5 和微信小程序平台 |
---|
那么移动端,想要上传文件,我们该如何操作,按照官方的说法:App端如需选择非媒体文件,可在插件市场搜索文件选择,其中Android端可以使用Native.js,无需原生插件,而iOS端需要原生插件。。
这里我们选择使用插件:全文件上传选择非原生2.0版
组件原理
在生命周期mounted
调用下面的代码,根据不同的平台去渲染不同的上传组件。
这段代码首先检查this.dom
是否已经存在。如果不存在,根据运行环境(H5或APP-PLUS)创建一个新的input
元素或webview
。
在H5环境下,代码直接创建一个input
元素并设置其样式、属性和事件监听器。
在APP-PLUS(移动端)环境下,使用plus.webview.create()
创建一个webview
并设置其样式、额外属性和数据,然后再加载了一个<input :multiple="multiple" @change="onChange" :accept="accept" ref="file" class="file" type="file" />
。最后,返回创建的DOM元素。
plus.webview.create()
是一个用于创建Webview窗口的方法,它在uni-app的APP-PLUS环境下可用。这个方法接收四个参数:
path
:要加载的HTML文件的路径,通常是一个相对路径。this.id
:Webview窗口的标识,用于在后续操作中引用该窗口。styles
:Webview窗口的样式设置,包括位置、尺寸、背景等属性。extras
:Webview窗口的额外属性,可以在窗口创建后通过plus.webview.getWebviewById()
方法获取。在这段代码中,
plus.webview.create()
方法用于创建一个Webview窗口,用于处理文件选择和上传。path
参数指定了Webview窗口要加载的HTML文件,this.id
参数为Webview窗口分配了一个唯一标识。styles
参数定义了Webview窗口的样式,例如位置、尺寸和背景颜色。extras
参数包含了一些额外的属性,例如debug
、instantly
和prohibited
,这些属性可以在Webview窗口创建后通过plus.webview.getWebviewById()
方法获取。
/**
* 创建File节点
* @param {string} path webview地址
*/
create(path) {
if (!this.dom) {
// #ifdef H5
// 创建一个input元素
let dom = document.createElement('input');
// 设置input类型为file
dom.type = 'file';
// 初始化input的值为空
dom.value = '';
// 设置input的样式
dom.style.height = this.height;
dom.style.width = this.width;
dom.style.position = 'absolute';
dom.style.top = 0;
dom.style.left = 0;
dom.style.right = 0;
dom.style.bottom = 0;
dom.style.opacity = 0;
dom.style.zIndex = 999;
// 设置文件类型限制
dom.accept = this.prohibited.accept;
// 设置是否允许多选
if (this.prohibited.multiple) {
dom.multiple = 'multiple';
}
// 监听input的change事件
dom.onchange = event => {
// 遍历选中的文件
for (let file of event.target.files) {
// 如果已选文件数量超过限制,显示提示并清空input值
if (this.files.size >= this.prohibited.count) {
this.toast(`只允许上传${this.prohibited.count}个文件`);
this.dom.value = '';
break;
}
// 添加文件
this.addFile(file);
}
// 上传文件后的操作
this._uploadAfter();
// 清空input值,以便下次选择
this.dom.value = '';
};
// 将创建的input元素赋值给this.dom
this.dom = dom;
// #endif
// #ifdef APP-PLUS
// 设置webview的样式
let styles = {
top: '-200px',
left: 0,
width: '1px',
height: '200px',
background: 'transparent',
};
// 设置webview的额外属性
let extras = {
debug: this.debug,
instantly: this.instantly,
prohibited: this.prohibited,
};
// 创建webview并赋值给this.dom
this.dom = plus.webview.create(path, this.id, styles, extras);
// 设置webview的数据
this.setData(this.option);
// 重写webview的URL加载行为
this._overrideUrlLoading();
// #endif
// 返回创建的DOM元素
return this.dom;
}
}
页面
<template>
<view>
<view class="uni-uploader">
<view class="uni-uploader-head">
<view class="uni-uploader-info">{{ files.size }}/5</view>
</view>
<view class="uni-uploader-body">
<view v-for="(item,index) in files.values()" :key="index" style="margin:5px">
<text>{{item.name.split('.')[0]}}</text>
<text style="margin-left: 10rpx;">上传进度:{{item.progress}}%</text>
<text @click="resetUpload(item.name)" v-if="item.type=='fail'"
style="margin-left: 10rpx;padding: 0 10rpx;color:#fff;background-color: #007aff;">重新上传</text>
<text @click="clear(item.name)"
style="margin-left: 10rpx;padding: 0 10rpx;color:#fff;background-color: #e64340;">删除</text>
</view>
<view class="uni-uploader__input-box" v-show="files.size < 5">
<view class="">
<lsj-upload ref="lsjUpload" childId="upload1" width="210rpx" height="210rpx" :option="option"
:size="size" :count="count" :debug="false" :instantly="true" @change="onChange"
@progress="onprogress" @uploadEnd="onuploadEnd">
<view class="uni-uploader__input">选择附件</view>
</lsj-upload>
</view>
</view>
</view>
</view>
</view>
</template>
设置参数
更详细的配置请参考官方文档:https://ext.dcloud.net.cn/plugin?id=5459
上传接口的参数
参数 | 是否必填 | 说明 |
---|---|---|
url | 是 | 上传接口地址 |
name | 否 | 上传接口文件key,默认为file |
header | 否 | 上传接口请求头,header一般用来携带token |
formData | 否 | 上传接口额外参数 |
当后端接口如下面:需要接受额外的参数,此时就需要使用formData来传递需要的参数。
// 上传单个文件
@PostMapping(value = "/UpFiles")
public Result UpFiles(@RequestParam("id") String id ,@RequestParam("file") MultipartFile file) {
return Result ;
}
data() {
return { // 上传接口参数
option: {
// 上传服务器地址,需要替换为你的接口地址
url: 'https://example/api/UpFiles',
// 上传接口文件key,默认为file
name: 'file',
// 上传接口额外参数
formData: {
'id': id,
},
header:{
"token": uni.getStorageSync("ACCESS_TOKEN")
}
},
};
},
设置上传设定(文件大小,数量,多选)
data() {
return {
// 文件上传大小限制
size: 10,
// 文件数量限制
count: 5,
// 是否多选
multiple: true,
// 文件回显列表
files: new Map(),
};
},
文件选择
回调的参数,是当前所有的文件,包括之前选中的文件。我们赋值给this.files
,然后强制更新视图
// 文件选择回调
onChange(files) {
this.files = files;
// 强制更新视图
this.$forceUpdate();
},
上传状态
在页面中,我们演示了进度的监控,这个进度可以在onprogress回调中监控。
// 上传进度回调
onprogress(item) {
// 更新当前状态变化的文件
this.files.set(item.name, item);
// 强制更新视图
this.$forceUpdate();
},
上传成功/失败
上传结束回调,参数是后端接口返回的结果,此时我们可以通过['responseText']
获取结果,然后加入到当前的文件中。文章来源:https://www.toymoban.com/news/detail-771105.html
例如我这里将返回的结果添加到文件属性中,把文件上传到服务器后的存放地址添加到文件中。把文件存在数据库的id添加到文件中,方便后面可以删除服务器的文件。文章来源地址https://www.toymoban.com/news/detail-771105.html
// 某文件上传结束回调(成功失败都回调)
onuploadEnd(item) {
// 更新当前窗口状态变化的文件
this.files.set(item.name, item);
// 上传完成后取服务端数据
if (item['responseText']) {
console.log('演示服务器返回的字符串JSON转Object对象');
this.files.get(item.name).responseText = JSON.parse(item.responseText);
this.files.get(item.name).romoteUrl = decodeURIComponent(item.responseText.data.url);
this.files.get(item.name).id = decodeURIComponent(item.responseText.data.id);
}
// 强制更新视图
this.$forceUpdate();
},
重传
// 指定上传某个文件
resetUpload(name) {
this.$refs['lsjUpload'].upload(name);
},
移除文件
// 移除某个文件
clear(name) {
console.log(name)
// name=指定文件名,不传name默认移除所有文件
this.$refs['lsjUpload'].clear(name);
// 调用后端接口删除文件
······
},
完整代码
<template>
<view>
<view class="uni-uploader">
<view class="uni-uploader-head">
<view class="uni-uploader-info">{{ files.size }}/5</view>
</view>
<view class="uni-uploader-body">
<view v-for="(item,index) in files.values()" :key="index" style="margin:5px">
<text>{{item.name.split('.')[0]}}</text>
<text style="margin-left: 10rpx;">上传进度:{{item.progress}}%</text>
<text @click="resetUpload(item.name)" v-if="item.type=='fail'"
style="margin-left: 10rpx;padding: 0 10rpx;color:#fff;background-color: #007aff;">重新上传</text>
<text @click="clear(item.name)"
style="margin-left: 10rpx;padding: 0 10rpx;color:#fff;background-color: #e64340;">删除</text>
</view>
<view class="uni-uploader__input-box" v-show="files.size < 5">
<view class="">
<lsj-upload ref="lsjUpload" childId="upload1" width="210rpx" height="210rpx" :option="option"
:size="size" :count="count" :debug="false" :instantly="true" @change="onChange"
@progress="onprogress" @uploadEnd="onuploadEnd">
<view class="uni-uploader__input">选择附件</view>
</lsj-upload>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
baseImageUrl: getApp().globalData.config.baseImageUrl,
baseUrl: getApp().globalData.config.baseUrl,
// 上传接口参数
option: {
url: 'https://example/api/UpFiles',
// 上传接口文件key,默认为file
name: 'file',
// 上传接口额外参数
formData: {
'id': id,
},
header:{
"token": uni.getStorageSync("ACCESS_TOKEN")
}
},
// 文件上传大小限制,单位是M
size: 10,
// 文件数量限制
count: 5,
// 是否多选
multiple: true,
// 文件回显列表
files: new Map(),
};
},
methods: {
// 某文件上传结束回调(成功失败都回调)
onuploadEnd(item) {
this.files.set(item.name, item);
if (item['responseText']) {
console.log('演示服务器返回的字符串JSON转Object对象');
this.files.get(item.name).responseText = JSON.parse(item.responseText);
this.files.get(item.name).romoteUrl = decodeURIComponent(item.responseText.data.url);
this.files.get(item.name).id = decodeURIComponent(item.responseText.data.id);
}
// 强制更新视图
this.$forceUpdate();
},
// 上传进度回调
onprogress(item) {
// 更新当前状态变化的文件
this.files.set(item.name, item);
// 强制更新视图
this.$forceUpdate();
},
// 文件选择回调
onChange(files) {
this.files = files;
// 强制更新视图
this.$forceUpdate();
},
// 指定上传某个文件
resetUpload(name) {
this.$refs['lsjUpload'].upload(name);
},
// 移除某个文件
clear(name) {
console.log(name)
// name=指定文件名,不传name默认移除所有文件
this.$refs['lsjUpload'].clear(name);
console.log(this.files)
},
}
}
</script>
<style lang="scss">
.feedback-body {
background: #fff;
}
.uni-uploader {
flex: 1;
flex-direction: column;
background-color: #fff;
}
.uni-uploader-head {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.uni-uploader-info {
color: #B2B2B2;
}
.uni-uploader-body {
margin-top: 16rpx;
}
.uni-uploader__files {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.uni-uploader__file {
margin: 10rpx;
width: 210rpx;
height: 210rpx;
}
.uni-uploader__img {
display: block;
width: 210rpx;
height: 210rpx;
}
.uni-uploader__input-box {
position: relative;
margin: 10rpx;
width: 208rpx;
height: 208rpx;
border: 2rpx solid #D9D9D9;
}
.uni-uploader__input {
position: absolute;
z-index: 1;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
}
</style>
到了这里,关于uni-app - 移动端(iOS&Android)批量上传文件,支持重传、删除、多选,携带参数,进度监控的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!