全面重构的 uni-app 多平台上传组件,功能强到离谱!

一、前言

在移动应用开发中,文件上传是一个高频且复杂的需求场景,无论是用户头像上传、图片分享,还是文档提交、视频发布,都离不开一个稳定、易用的上传组件。

uView Pro 的 u-upload 组件经过几次迭代、重构,现已支持图片、视频、文档等多种文件类型 ,提供网格(grid)和列表(list)两种展示模式,完全向后兼容的同时带来了更强大的功能和更优雅的使用体验。

二、组件核心优势

1. 多文件类型支持

不再局限于图片上传,u-upload 现已支持:

  • 图片 - 支持预览、压缩、多选
  • 视频 - 支持时长限制、摄像头方向设置
  • 文件 - PDF、Word、Excel 等文档类型(H5/微信小程序)
  • 媒体文件 - 图片+视频混合选择
  • 所有类型 - 一键开启全类型支持

2. 双模式展示

根据文件类型自动适配最佳展示方式:

网格模式(默认) - 适合图片展示

  • 宫格布局,视觉整齐
  • 支持图片预览、删除
  • 适合头像、相册等场景

列表模式 - 适合文件展示

  • 显示文件名、文件大小
  • 进度条直观展示上传状态
  • 适合文档、资料上传场景

3. v-model 双向绑定

最新版本告别繁琐的事件监听,支持双向绑定,一行代码实现数据同步:

html 复制代码
<u-upload :action="action" v-model="fileList"></u-upload>

4. 全平台兼容

完美适配 uni-app 所有平台:

  • App(Android/iOS/鸿蒙)
  • H5
  • 微信小程序、支付宝小程序、百度小程序、头条小程序、QQ小程序

三、快速上手

1. 基础用法

最简单的上传配置,只需设置服务器地址:

html 复制代码
<template>
    <u-upload :action="action" v-model="fileList"></u-upload>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const action = ref('https://your-server.com/upload')
const fileList = ref([
    {
        url: 'https://example.com/avatar.jpg',
        name: 'avatar.jpg',
        size: 1024 * 50,
        progress: 100,
        error: false
    }
])
</script>

2. 上传不同文件类型

通过 accept 参数一键切换文件类型:

html 复制代码
<!-- 上传图片(默认) -->
<u-upload :action="action" accept="image"></u-upload>

<!-- 上传视频 -->
<u-upload :action="action" accept="video" :max-duration="120"></u-upload>

<!-- 上传文件(H5/微信小程序) -->
<u-upload :action="action" accept="file" :extension="['.pdf', '.docx']"></u-upload>

<!-- 上传所有类型 -->
<u-upload :action="action" accept="all"></u-upload>

3. 展示模式切换

html 复制代码
<!-- 网格模式 - 适合图片 -->
<u-upload :action="action" accept="image" mode="grid"></u-upload>

<!-- 列表模式 - 适合文件 -->
<u-upload :action="action" accept="file" mode="list" :show-file-name="true" :show-file-size="true"></u-upload>

四、进阶功能

1. 手动上传控制

默认自动上传,也可改为手动控制:

html 复制代码
<template>
    <view>
        <u-upload ref="uUploadRef" :action="action" :auto-upload="false"></u-upload>
        <u-button @click="submit" type="primary">提交上传</u-button>
    </view>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const action = ref('https://your-server.com/upload')
const uUploadRef = ref()

function submit() {
    // 手动触发上传
    uUploadRef.value?.upload()
}
</script>

2. 上传前处理

通过 before-upload 钩子实现自定义逻辑:

html 复制代码
<template>
    <u-upload :before-upload="beforeUpload" :action="action"></u-upload>
</template>

<script setup lang="ts">
async function beforeUpload(index: number, list: any[]) {
    // 示例:上传前获取签名
    const sign = await getUploadSign()
    
    // 返回 true 继续上传,false 跳过当前文件
    return !!sign
}

async function getUploadSign() {
    // 模拟获取上传签名
    return 'upload-sign-xxx'
}
</script>

3. 文件限制

灵活控制上传文件的数量、大小和类型:

html 复制代码
<u-upload 
    :action="action"
    :max-count="6"                    <!-- 最多选择6个文件 -->
    :max-size="5 * 1024 * 1024"       <!-- 单个文件最大5MB -->
    accept="image"
    :limit-type="['png', 'jpg', 'jpeg']"  <!-- 限制图片格式 -->
></u-upload>

4. 自定义文件选择

对于不支持文件选择的平台(如 App),可以通过 custom-choose 属性开启自定义选择:

html 复制代码
<template>
    <u-upload 
        ref="uploadRef"
        accept="file"
        :custom-choose="true"
        :action="action"
        @on-choose="handleCustomChoose"
    ></u-upload>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const action = ref('https://your-server.com/upload')
const uploadRef = ref()

// 自定义文件选择
function handleCustomChoose({ accept, maxCount, fileList, index }: any) {
    // App 端使用原生文件选择
    // #ifdef APP-PLUS
    plus.runtime.chooseFile({
        success: (res: any) => {
            const files = res.files.map((file: any) => ({
                path: file.path,
                name: file.name,
                size: file.size,
                fileType: 'file'
            }))
            // 将文件添加到组件
            uploadRef.value?.addFiles(files)
        }
    })
    // #endif
}
</script>

核心要点:

  1. 设置 :custom-choose="true" 开启自定义选择模式
  2. 监听 @on-choose 事件,自行处理文件选择逻辑
  3. 选择完成后调用 uploadRef.value?.addFiles(files) 将文件添加到组件

5. 自定义上传按钮

通过插槽打造个性化上传入口:

html 复制代码
<u-upload :custom-btn="true">
    <template #addBtn>
        <view class="custom-upload-btn">
            <u-icon name="plus" size="40" color="#2979ff"></u-icon>
            <text class="upload-text">点击上传</text>
        </view>
    </template>
</u-upload>

<style>
.custom-upload-btn {
    width: 200rpx;
    height: 200rpx;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: #f5f5f5;
    border-radius: 10rpx;
    border: 2rpx dashed #ddd;
}
.upload-text {
    margin-top: 10rpx;
    font-size: 24rpx;
    color: #666;
}
</style>

6. 完全自定义文件列表展示

通过 file 插槽完全自定义文件列表的展示方式,实现更灵活的文件管理界面:

html 复制代码
<template>
  <u-upload
    ref="customFileListRef"
    v-model="customFileList"
    accept="file"
    mode="list"
    :action="action"
    :show-upload-list="false"
    :custom-btn="true"
    :max-count="5"
  >
    <!-- 自定义文件列表 -->
    <template #file="{ file }">
      <view class="custom-file-list">
        <view 
          v-for="(item, index) in file" 
          :key="index" 
          class="custom-file-item"
        >
          <!-- 文件类型图标 -->
          <u-icon
            :name="isImageFile(item) ? 'photo' : 'file-text'"
            size="40"
            color="var(--u-type-primary)"
          />
          
          <!-- 文件信息 -->
          <view class="custom-file-info">
            <text class="custom-file-name">{{ item.name || '未命名文件' }}</text>
            <text v-if="item.size" class="custom-file-size">
              {{ formatSize(item.size) }}
            </text>
          </view>
          
          <!-- 上传进度条 -->
          <view
            v-if="item.progress < 100 && item.progress > 0"
            class="custom-file-progress"
          >
            <u-line-progress :percent="item.progress" height="8" />
          </view>
          
          <!-- 上传状态 -->
          <view class="custom-file-status">
            <u-icon
              v-if="item.progress === 100"
              :name="item.error ? 'close-circle' : 'checkmark-circle'"
              size="34"
              :color="item.error ? 'var(--u-type-error)' : 'var(--u-type-success)'"
            />
            <text v-else class="custom-file-progress-text">
              {{ Math.floor(item.progress || 0) }}%
            </text>
          </view>
          
          <!-- 删除按钮 -->
          <view class="custom-file-delete" @click="removeCustomFile(index)">
            <u-icon name="close" size="24" color="var(--u-tips-color)" />
          </view>
        </view>
      </view>
    </template>
    
    <!-- 自定义添加按钮 -->
    <template #addBtn>
      <view class="custom-file-add-btn">
        <u-icon name="plus" size="32" color="var(--u-type-primary)" />
        <text class="custom-file-add-text">添加文件</text>
      </view>
    </template>
  </u-upload>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import type { UploadFileItem } from '@/uni_modules/uview-pro/types/global'

const action = ref('https://your-server.com/upload')
const customFileList = ref<UploadFileItem[]>([])
const customFileListRef = ref()

// 判断是否为图片文件
function isImageFile(item: UploadFileItem): boolean {
  const ext = item.name?.split('.').pop()?.toLowerCase() || ''
  return ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'svg'].includes(ext)
}

// 格式化文件大小
function formatSize(bytes: number): string {
  if (bytes === 0) return '0 B'
  const k = 1024
  const sizes = ['B', 'KB', 'MB', 'GB']
  const i = Math.floor(Math.log(bytes) / Math.log(k))
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}

// 删除文件
function removeCustomFile(index: number) {
  customFileListRef.value?.remove(index)
}
</script>

<style scoped>
.custom-file-list {
  width: 100%;
  margin-bottom: 20rpx;
}

.custom-file-item {
  display: flex;
  align-items: center;
  padding: 24rpx;
  background: var(--u-bg-white);
  border-radius: 12rpx;
  margin-bottom: 16rpx;
  border: 1rpx solid var(--u-border-color);
}

.custom-file-item:last-child {
  margin-bottom: 0;
}

.custom-file-info {
  flex: 1;
  margin-left: 20rpx;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.custom-file-name {
  font-size: 28rpx;
  color: var(--u-main-color);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.custom-file-size {
  font-size: 24rpx;
  color: var(--u-tips-color);
  margin-top: 8rpx;
}

.custom-file-progress {
  width: 120rpx;
  margin-left: 20rpx;
}

.custom-file-progress-text {
  font-size: 24rpx;
  color: var(--u-primary-color);
}

.custom-file-status {
  margin-left: 20rpx;
  min-width: 48rpx;
  display: flex;
  justify-content: center;
  align-items: center;
}

.custom-file-delete {
  display: flex;
  align-items: center;
  margin-left: 20rpx;
  padding: 8rpx;
}

.custom-file-add-btn {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  background: var(--u-bg-white);
}

.custom-file-add-text {
  margin-left: 16rpx;
  font-size: 28rpx;
  color: var(--u-tips-color);
}
</style>

核心要点:

  1. 隐藏默认列表 :设置 :show-upload-list="false"
  2. file 插槽 :接收 { file } 参数,file 即当前文件列表
  3. 文件属性
    • item.name - 文件名
    • item.size - 文件大小(字节)
    • item.progress - 上传进度 0-100
    • item.error - 上传失败标记
  4. 操作文件 :通过 ref 调用 remove(index) 删除文件
  5. 进度展示 :使用 u-line-progress 组件显示上传进度

五、实际应用场景

场景一:用户头像上传

html 复制代码
<u-upload 
    accept="image"
    image-shape="circle"
    :action="action" 
    :max-count="1"
    :max-size="2 * 1024 * 1024"
    :limit-type="['jpg', 'png']"
    @on-success="onAvatarSuccess"
></u-upload>

场景二:资料文档上传

html 复制代码
<u-upload 
    accept="file"
    mode="list"
    :action="action"
    :show-file-name="true"
    :show-file-size="true"
    :extension="['.pdf', '.doc', '.docx']"
></u-upload>

场景三:视频作品发布

html 复制代码
<u-upload 
    accept="video"
    camera="back"
    :action="action" 
    :max-count="1"
    :max-size="50 * 1024 * 1024"
    :max-duration="300"
></u-upload>

六、平台适配说明

虽然 u-upload 已实现全平台支持,但部分功能在不同平台存在差异:

功能 App H5 微信小程序 支付宝小程序
图片上传
视频上传
文件上传
文件预览
压缩选项

最佳实践建议:

  • 文件上传功能在 H5 和微信小程序体验最佳
  • 如需在 App 中使用文件上传,建议使用原生能力或第三方 SDK
  • 生产环境务必做好各平台的真机测试

七、总结

uView Pro 的 u-upload 组件经历了从单一图片上传到全能文件管理。无论是简单的头像上传,还是复杂的资料提交,还支持高度自定义,无论如何都能找到最适合的配置方案。

核心亮点:

  • 多类型支持 - 图片、视频、文档全覆盖
  • 双模式展示 - 网格/列表随心切换
  • 高度自定义 - 插槽机制、自定义满足个性需求
  • 全平台适配 - 一套代码多端运行

附录:API 完整参考

Props 参数

参数 说明 类型 默认值 可选值
action 服务器上传地址 String '' -
accept 接受的文件类型 String image image / video / file / media / all
image-shape 图片/图标展示形状 String square circle / square
modelValue 文件列表(推荐,v-model 双向绑定) Array [] -
file-list 默认显示的文件列表(旧版,建议使用 v-model) Array [] -
custom-choose 是否使用自定义文件选择 Boolean false true / false
mode 展示模式 String grid grid / list
max-count 最大选择文件的数量 String/Number 52 -
max-size 选择单个文件的最大大小,单位字节 String/Number Number.MAX_VALUE -
width 预览区域和添加按钮的宽度,单位rpx String/Number 200 -
height 预览区域和添加按钮的高度,单位rpx String/Number 200 -
multiple 是否开启文件多选 Boolean true true / false
disabled 是否禁用组件 Boolean false true / false
auto-upload 选择完文件是否自动上传 Boolean true true / false
deletable 是否显示删除文件的按钮 Boolean true true / false
show-confirm 删除文件前是否显示确认弹窗 Boolean true true / false
show-tips 特殊情况下是否自动提示toast Boolean true true / false
show-progress 是否显示上传进度条 Boolean true true / false
show-upload-list 是否显示组件内部的文件预览列表 Boolean true true / false
show-file-name 是否显示文件名 Boolean true true / false
show-file-size 是否显示文件大小 Boolean false true / false
preview-full-image 是否可以通过 uni.previewImage 预览已选择的图片 Boolean true true / false
preview-file 是否可预览文件(非图片类型) Boolean true true / false
custom-btn 是否自定义选择文件的按钮 Boolean false true / false
upload-text 选择文件按钮的提示文字 String 根据accept自动显示 -
image-mode 预览图片的显示模式 String aspectFill -
del-icon 右上角删除图标名称 String close -
del-bg-color 右上角删除按钮的背景颜色 String var(--u-type-error) -
del-color 右上角删除按钮图标的颜色 String var(--u-white-color) -
header 上传携带的请求头信息 Object {} -
form-data 上传额外携带的参数 Object {} -
name 上传文件的字段名 String file -
size-type original 原图,compressed 压缩图 Array ['original', 'compressed'] -
source-type 选择文件的来源,album-相册,camera-相机 Array ['album', 'camera'] -
limit-type 限制允许上传的文件后缀,优先级高于accept Array [] -
extension 选择文件时的扩展名过滤,仅H5和微信小程序有效 Array [] -
file-icon-map 文件类型图标映射配置 Object {} -
compressed 选择视频时是否压缩 Boolean true true / false
max-duration 选择视频时拍摄最长时长,单位秒 Number 60 -
camera 选择视频时摄像头方向 String back front / back
before-upload 上传前钩子,返回 true/false/Promise Function null -
before-remove 删除前钩子,返回 true/false/Promise Function null -
to-json 如果上传后返回值为json字符串,是否自动转为json Boolean true true / false
index 在各个回调事件中的最后一个参数返回,用于区别是哪一个组件的事件 String/Number '' -
custom-style 自定义根节点样式 String/Object {} -
custom-class 自定义根节点样式类 String '' -

Methods 方法

通过 ref 手动调用组件方法:

名称 说明 参数
upload 手动触发上传文件 -
clear 清空内部文件列表 -
reUpload 重新上传所有失败/未上传的文件 -
retry(index) 重新上传指定索引的文件 index: 文件索引
remove(index) 手动移除指定索引的文件 index: 文件索引
selectFile 手动触发文件选择 -
doPreviewImage(url, index) 预览图片 url: 图片地址, index: 索引
doPreviewFile(item, index) 预览/打开文件 item: 文件对象, index: 索引
addFiles(files) 添加文件到列表(配合 custom-choose 使用) files: 文件数组

Slots 插槽

名称 说明
addBtn 自定义选择文件按钮
file 自定义文件列表插槽

Events 事件

事件名 说明 回调参数
on-oversize 文件大小超出 max-size 限制时触发 (file, lists, name)
on-exceed 文件数量超出 max-count 限制时触发 (file, lists, name)
on-choose-complete 每次选择文件后触发 (lists, name)
on-choose-fail 文件选择失败时触发 (error)
on-uploaded 所有文件上传完毕触发 (lists, name)
on-success 单个文件上传成功时触发 (data, index, lists, name)
on-error 单个文件上传失败时触发 (res, index, lists, name)
on-change 单个文件上传状态改变时触发(无论成功或失败) (res, index, lists, name)
on-progress 文件上传过程中的进度变化时触发 (res, index, lists, name)
on-remove 移除文件时触发 (index, lists, name)
on-preview 预览文件时触发 (url, lists, name)
on-list-change 文件列表发生变化时触发 (lists, name)
on-choose 启用 custom-choose 时触发,用户可自定义文件选择逻辑 ({ accept, maxCount, currentFiles, index })
update:modelValue v-model 双向绑定事件,文件列表变化时触发 (lists)

说明:

  • lists - 当前组件内的所有文件数组
  • index - 当前操作的文件索引
  • name - 通过 props 传递的 index 参数,用于区分多个组件实例

文件列表对象结构

lists 数组中每个元素(UploadFileItem)的结构:

typescript 复制代码
{
  // 基础信息
  url: string,           // 文件地址(上传成功后返回)
  path: string,          // 文件本地路径
  name: string,          // 文件名
  size: number,          // 文件大小(字节)
  fileType: 'image' | 'video' | 'file',  // 文件类型
  
  // 上传状态
  progress: number,      // 上传进度 0-100,100表示上传成功
  error: boolean,        // 上传失败标记
  response?: any,        // 服务器返回的数据
  
  // 媒体文件特有
  thumb?: string,        // 视频缩略图(仅视频)
  width?: number,        // 图片/视频宽度
  height?: number,       // 图片/视频高度
  duration?: number,     // 视频时长(秒)
  
  // 原始文件对象
  file?: any,            // 原始文件对象
  uploadTask?: UniApp.UploadTask  // 上传任务对象(用于取消上传)
}

文件类型说明

根据 accept 参数,支持以下文件类型:

accept 值 说明 自动检测的文件后缀
image 图片 png, jpg, jpeg, gif, webp, bmp, svg
video 视频 mp4, avi, mov, wmv, flv, mkv, rmvb, 3gp, m3u8
file 文件 根据 extension 参数或允许所有
media 媒体(图片+视频) 图片和视频后缀合集
all 所有文件 允许所有文件类型

注意:

  • 文件上传(accept=file)仅在 H5 和微信小程序支持
  • 媒体选择(accept=media)仅在微信小程序、App、头条小程序支持
  • 文件预览功能在 H5 体验最佳,其他平台可能受限
  • 通过自定义,你也可以实现不支持的平台特性功能

现在就开始使用 u-upload,让文件上传功能开发变得更加方便!更多内容请参考官方文档。

文档地址: uviewpro.cn/

开源地址:

相关推荐
小赵同学WoW1 小时前
作用域链与闭包
前端
暗不需求1 小时前
告别“class 命名地狱”:从面向对象 CSS 到原子 CSS(Tailwind) 的思维跃迁
前端·css·react.js
幼儿园技术家1 小时前
前端如何做监控体系(埋点 → 上报 → 分析 → 数据分流)
前端·js or ts
盖丽男2 小时前
彻底搞懂:前端MVVM、后端MVC、DDD极致面向对象的区别与落地真相
前端·mvc
澄江静如练_2 小时前
vue2中使用provide和inject出现的问题
前端·vue
WebGirl3 小时前
【Vue3】withDefaults和defineProps设置默认值
vue.js
星辰徐哥3 小时前
表单优化:AI驱动HTML5表单的智能验证与提示功能
前端·人工智能·html5
普通网友3 小时前
HTML5新增了哪些重要标签?多多学习也是成长的一部分
前端·学习·html5
2501_906467633 小时前
html5网页中如何实现内网大文件的加密下载?
前端·html·html5·vue上传解决方案·vue断点续传·vue分片上传下载·vue分块上传下载