uniapp中APP上传文件

发布于:2025-03-25 ⋅ 阅读:(46) ⋅ 点赞:(0)

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>

改组件可以直接调用,希望可以帮助到大家。


网站公告

今日签到

点亮在社区的每一天
去签到