uniapp提供了uni.chooseImage(选择图片), uni.chooseVideo(选择视频)这两个api,但是对于打包成APP的话就没有上传文件的api了。因此我采用了plus.android中的方式来打开手机的文件管理从而上传文件。
下面是我封装的APP端选择上传图片,视频,文件的一个上传组件。
<template>
<view class="upload-container">
<u-icon name="plus" @click="showActionSheet" size="23"></u-icon>
<!-- <view class="upload-list">
<view
class="upload-item"
v-for="(item, index) in fileList"
:key="index"
>
<image
v-if="item.type === 'image'"
class="preview-image"
:src="item.url"
mode="aspectFill"
@click="previewFile(item)"
></image>
<view v-else-if="item.type === 'video'" class="preview-video" @click="previewFile(item)">
<image class="video-cover" :src="item.cover || item.url" mode="aspectFill"></image>
<view class="video-icon">
<text class="iconfont icon-play"></text>
</view>
</view>
<view v-else class="preview-file" @click="previewFile(item)">
<text class="iconfont icon-file"></text>
<text class="file-name">{{ item.name }}</text>
</view>
<text
class="delete-icon"
@click.stop="deleteFile(index)"
>×</text>
</view>
<view
class="upload-button"
v-if="fileList.length < maxCount"
@click="showActionSheet"
>
<text class="iconfont icon-add"></text>
<text class="upload-text">上传{{ getUploadTypeText() }}</text>
</view>
</view> -->
<!-- <view class="upload-tips" v-if="tips">{{ tips }}</view> -->
</view>
</template>
<script>
export default {
name: 'FileUploader',
props: {
// 上传文件类型:all-所有类型, image-图片, video-视频, file-文件
uploadType: {
type: String,
default: 'all'
},
// 标题
title: {
type: String,
default: '文件上传'
},
// 提示文字
tips: {
type: String,
default: '支持jpg、png、mp4、doc、pdf等格式'
},
// 最大上传数量
maxCount: {
type: Number,
default: 9
},
// 初始文件列表
value: {
type: Array,
default: () => []
},
// 图片类型限制
imageType: {
type: Array,
default: () => ['jpg', 'jpeg', 'png', 'gif']
},
// 视频类型限制
videoType: {
type: Array,
default: () => ['mp4', 'mov', 'avi']
},
// 文件类型限制
fileType: {
type: Array,
default: () => ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'pdf', 'txt']
},
// 是否上传到服务器
uploadToServer: {
type: Boolean,
default: true
},
// 上传接口地址
uploadUrl: {
type: String,
default: ''
}
},
data() {
return {
fileList: [],
arrFile:[]
}
},
created() {
// 初始化文件列表
if (this.value && this.value.length) {
this.fileList = JSON.parse(JSON.stringify(this.value))
}
},
watch: {
value: {
handler(newVal) {
if (newVal && newVal.length) {
this.fileList = JSON.parse(JSON.stringify(newVal))
}
},
deep: true
}
},
methods: {
// 获取上传类型文本
getUploadTypeText() {
switch (this.uploadType) {
case 'image':
return '图片'
case 'video':
return '视频'
case 'file':
return '文件'
default:
return '文件'
}
},
// 显示操作菜单
showActionSheet() {
let itemList = []
// if (this.uploadType === 'all' || this.uploadType === 'image') {
// itemList.push('上传图片')
// }
// if (this.uploadType === 'all' || this.uploadType === 'video') {
// itemList.push('上传视频')
// }
if (this.uploadType === 'all' || this.uploadType === 'file') {
// 检查平台支持
// #ifdef APP-PLUS
const appPlus = plus.os.name.toLowerCase();
if (appPlus === 'android' || appPlus === 'ios') {
itemList.push('上传文件')
}
// #endif
// #ifdef H5
itemList.push('上传文件')
// #endif
// #ifdef MP-WEIXIN
itemList.push('上传文件')
// #endif
}
uni.showActionSheet({
itemList,
success: res => {
const index = res.tapIndex
if (itemList[index] === '上传图片') {
this.chooseImage()
} else if (itemList[index] === '上传视频') {
this.chooseVideo()
} else if (itemList[index] === '上传文件') {
this.selectAndUploadFile()
}
}
})
},
// 选择图片
chooseImage() {
uni.chooseImage({
count: this.maxCount - this.fileList.length,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: res => {
const tempFiles = res.tempFiles
// 检查文件类型
for (let i = 0; i < tempFiles.length; i++) {
const file = tempFiles[i]
const extension = this.getFileExtension(file.path)
if (!this.imageType.includes(extension.toLowerCase())) {
uni.showToast({
title: `不支持${extension}格式的图片`,
icon: 'none'
})
continue
}
// 添加到文件列表
const fileItem = {
name: this.getFileName(file.path),
url: file.path,
size: file.size,
type: 'image',
extension: extension,
status: 'ready' // ready, uploading, success, fail
}
this.fileList.push(fileItem)
// 上传到服务器
if (this.uploadToServer) {
this.uploadFileToServer(fileItem, this.fileList.length - 1)
}
}
this.emitChange()
}
})
},
// 选择视频
chooseVideo() {
uni.chooseVideo({
count: 1,
sourceType: ['album', 'camera'],
success: res => {
const extension = this.getFileExtension(res.tempFilePath)
if (!this.videoType.includes(extension.toLowerCase())) {
uni.showToast({
title: `不支持${extension}格式的视频`,
icon: 'none'
})
return
}
// 添加到文件列表
const fileItem = {
name: this.getFileName(res.tempFilePath),
url: res.tempFilePath,
cover: '', // 视频封面,可以通过后端生成
size: res.size,
duration: res.duration,
type: 'video',
extension: extension,
status: 'ready'
}
this.fileList.push(fileItem)
// 上传到服务器
if (this.uploadToServer) {
this.uploadFileToServer(fileItem, this.fileList.length - 1)
}
this.emitChange()
}
})
},
// 选择并上传文件函数
selectAndUploadFile(){
// 显示加载提示
uni.showLoading({
title: '准备选择文件',
});
// 选择文件
this.chooseFile()
.then(filePath => {
// 选择文件成功后上传
return this.uploadFile(filePath);
})
.then(result => {
// 上传成功的处理
uni.hideLoading();
uni.showToast({
title: '上传成功',
icon: 'success'
});
console.log('上传结果:', result);
// 在此处理上传成功后的业务逻辑
})
.catch(error => {
// 错误处理
uni.hideLoading();
uni.showToast({
title: error.message || '操作失败',
icon: 'none'
});
console.error('文件操作错误:', error);
});
},
// 选择文件方法
chooseFile(){
return new Promise((resolve, reject) => {
try {
// #ifdef APP-PLUS
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', 'xls', 'pdf', 'doc', 'docx'], // 允许的文件类型
multiple: false, // 是否允许多选
}, (event) => {
if (event.files && event.files.length > 0) {
const tempFilePath = decodeURIComponent(event.files[0]);
console.log('选择的虚拟路径:', tempFilePath);
// 解析文件ID
const uri = MediaStore.Files.getContentUri("external");
// 导入contentResolver
plus.android.importClass(main.getContentResolver());
// 从虚拟路径中提取ID
const parts = tempFilePath.split(':');
const fileId = parts[parts.length - 1];
console.log('文件ID:', fileId);
// 查询真实路径
let cursor = main.getContentResolver().query(
uri,
['_data'],
"_id=?",
[fileId],
null
);
plus.android.importClass(cursor);
let realPath = null;
if (cursor != null && cursor.moveToFirst()) {
const columnIndex = cursor.getColumnIndexOrThrow('_data');
realPath = cursor.getString(columnIndex);
cursor.close();
}
if (realPath) {
// 转换为file://格式
const filePath = 'file://' + realPath;
console.log('文件真实路径:', filePath);
resolve(filePath);
} else {
reject(new Error('无法获取文件路径'));
}
} else {
reject(new Error('未选择文件'));
}
}, (error) => {
reject(new Error('选择文件失败: ' + error.message));
});
// #endif
// #ifdef H5
// H5环境下的文件选择
const input = document.createElement('input');
input.type = 'file';
input.accept = '.xlsx,.xls,.pdf,.doc,.docx';
input.onchange = (e) => {
const file = e.target.files[0];
if (file) {
resolve(file);
} else {
reject(new Error('未选择文件'));
}
};
input.click();
// #endif
// #ifdef MP-WEIXIN
// 微信小程序环境
wx.chooseMessageFile({
count: 1,
type: 'file',
extension: ['xlsx', 'xls', 'pdf', 'doc', 'docx'],
success: (res) => {
if (res.tempFiles && res.tempFiles.length > 0) {
resolve(res.tempFiles[0].path);
} else {
reject(new Error('未选择文件'));
}
},
fail: (err) => {
reject(new Error('选择文件失败: ' + err.errMsg));
}
});
// #endif
// 其他平台
// #ifdef MP-ALIPAY || MP-BAIDU || MP-TOUTIAO || MP-QQ
reject(new Error('当前平台不支持文件选择'));
// #endif
} catch (e) {
reject(new Error('选择文件出错: ' + e.message));
}
});
},
// 上传文件方法
uploadFile (filePath){
return new Promise((resolve, reject) => {
uni.showLoading({
title: '上传中...',
mask: true
});
const token = uni.getStorageSync('token');
if (!token) {
reject(new Error('登录状态已失效,请重新登录'));
return;
}
// 准备表单数据
const formData = {
// 这里可以添加业务所需参数
shangpinbianma: this.goodsDetail?.bianma || '',
uploadTime: new Date().getTime()
};
console.log(filePath,'filepath')
uni.uploadFile({
url: '...', // 替换为您的服务器地址
method: 'POST',
name: 'file',
filePath: filePath,
formData: formData,
header: {
accept: "application/json",
Authorization:"",
},
success: (res) => {
console.log('上传响应:', res);
// 检查登录状态
if (res.statusCode === 200) {
let result;
try {
// 解析返回结果
if (typeof res.data === 'string') {
result = JSON.parse(res.data);
} else {
result = res.data;
}
// 检查登录状态
if (result.code === '-110' || result.code === '-120' ||
result.code === '-130' || result.code === '-150') {
console.log('登录已失效');
if (result.code !== '-120') {
uni.showToast({
title: '登录已失效',
icon: 'none'
});
}
// 跳转到登录页
uni.reLaunch({
url: '/pages/login/index'
});
reject(new Error('登录已失效'));
} else if (result.success) {//此处记得修改成为你的响应判断
// 上传成功
resolve(result);
} else {
// 其他业务错误
reject(new Error(result.message || '上传失败'));
}
} catch (e) {
reject(new Error('解析上传结果失败: ' + e.message));
}
} else {
reject(new Error('上传请求失败,状态码: ' + res.statusCode));
}
},
fail: (err) => {
reject(new Error('上传失败: ' + (err.errMsg || JSON.stringify(err))));
},
complete: () => {
uni.hideLoading();
}
});
});
},
// 获取文件扩展名
getFileExtension(path) {
if (!path) return ''
return path.substring(path.lastIndexOf('.') + 1) || ''
},
// 获取文件名
getFileName(path) {
if (!path) return ''
return path.substring(path.lastIndexOf('/') + 1) || '未命名文件'
},
// 触发变更事件
emitChange() {
this.$emit('input', this.fileList)
this.$emit('change', this.fileList)
}
}
}
</script>
<style lang="scss">
.upload-container {
width: 100%;
padding: 20rpx;
box-sizing: border-box;
.upload-title {
font-size: 30rpx;
font-weight: bold;
margin-bottom: 20rpx;
}
.upload-list {
display: flex;
flex-wrap: wrap;
}
.upload-item, .upload-button {
position: relative;
width: 200rpx;
height: 200rpx;
margin: 0 20rpx 20rpx 0;
border-radius: 8rpx;
overflow: hidden;
box-sizing: border-box;
}
.upload-item {
border: 1rpx solid #eee;
.preview-image {
width: 100%;
height: 100%;
}
.preview-video {
width: 100%;
height: 100%;
position: relative;
.video-cover {
width: 100%;
height: 100%;
}
.video-icon {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 60rpx;
height: 60rpx;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
.icon-play {
color: #fff;
font-size: 30rpx;
}
}
}
.preview-file {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #f7f7f7;
.icon-file {
font-size: 60rpx;
color: #999;
margin-bottom: 10rpx;
}
.file-name {
font-size: 24rpx;
color: #666;
width: 90%;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.delete-icon {
position: absolute;
right: 0;
top: 0;
width: 40rpx;
height: 40rpx;
background-color: rgba(0, 0, 0, 0.5);
color: #fff;
font-size: 30rpx;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
}
.upload-button {
border: 1rpx dashed #ddd;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #f7f7f7;
.icon-add {
font-size: 60rpx;
color: #999;
margin-bottom: 10rpx;
}
.upload-text {
font-size: 24rpx;
color: #999;
}
}
.upload-tips {
font-size: 24rpx;
color: #999;
margin-top: 10rpx;
}
}
</style>
改组件可以直接调用,希望可以帮助到大家。