全面重构的 uni-app 多平台上传组件,支持图片、视频、文档全类型上传!

15 阅读12分钟

一、前言

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

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

二、组件核心优势

1. 多文件类型支持

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

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

0.png

2. 双模式展示

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

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

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

1.png

列表模式 - 适合文件展示

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

2.png

3. v-model 双向绑定

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

<u-upload :action="action" v-model="fileList"></u-upload>

4. 全平台兼容

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

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

三、快速上手

1. 基础用法

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

<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 参数一键切换文件类型:

<!-- 上传图片(默认) -->
<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. 展示模式切换

<!-- 网格模式 - 适合图片 -->
<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>

4.png

四、进阶功能

1. 手动上传控制

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

<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>

3.gif

2. 上传前处理

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

<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. 文件限制

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

<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 属性开启自定义选择:

<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. 自定义上传按钮

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

5.png

<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 插槽完全自定义文件列表的展示方式,实现更灵活的文件管理界面:

6.png

<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 组件显示上传进度

五、实际应用场景

场景一:用户头像上传

<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>

7.png

场景二:资料文档上传

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

场景三:视频作品发布

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

六、平台适配说明

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

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

最佳实践建议:

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

七、总结

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

核心亮点:

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

附录:API 完整参考

Props 参数

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

说明:

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

文件列表对象结构

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

{
  // 基础信息
  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/

开源地址: