NutUI上传组件:Uploader的文件选择与进度展示实现

NutUI上传组件:Uploader的文件选择与进度展示实现

【免费下载链接】nutui 京东风格的移动端 Vue2、Vue3 组件库 、支持多端小程序(A Vue.js UI Toolkit for Mobile Web) 【免费下载链接】nutui 项目地址: https://gitcode.com/gh_mirrors/nu/nutui

引言:移动端文件上传的痛点与解决方案

在移动应用开发中,文件上传功能是电商、社交、内容管理类应用的核心需求之一。开发者常常面临三大痛点:文件选择交互不友好上传进度反馈缺失多端适配复杂。NutUI的Uploader组件通过精心设计的架构,提供了一套完整的解决方案,支持图片/文件预览、上传状态管理、进度可视化等核心能力。本文将从实现原理到实战应用,深度解析Uploader组件的设计思路与技术细节。

组件架构:分层设计与核心模块

Uploader组件采用三层架构设计,通过职责分离实现高内聚低耦合:

mermaid

核心文件结构

src/packages/__VUE/uploader/
├── index.vue          # 主组件实现
├── index.taro.vue     # Taro小程序适配
├── demo.vue           # 示例代码
├── uploader.ts        # 上传核心逻辑
└── type.ts            # 类型定义

实现细节:从文件选择到进度展示

1. 文件选择机制

Uploader通过动态创建<input type="file">元素实现文件选择,核心代码如下:

const renderInput = () => {
  let params: any = {
    class: `nut-uploader__input`,
    type: 'file',
    accept: props.accept,
    multiple: props.multiple,
    name: props.name,
    disabled: disabled.value
  }

  if (props.capture) {
    params.capture = 'camera'  // 直接调起相机
    if (!params.accept) {
      params.accept = 'image/*'  // 默认图片类型
    }
  }

  return h('input', params)
}

关键特性

  • accept属性控制文件类型过滤(如image/*video/*
  • multiple支持多文件选择
  • capture属性直接调用设备相机
  • 动态禁用状态与表单联动

2. 文件过滤与预处理

选择文件后,通过三级过滤机制确保上传合法性:

const filterFiles = (files: File[]) => {
  // 1. 文件大小过滤
  const oversizes = files.filter(file => file.size > props.maximize)
  if (oversizes.length) emit('oversize', oversizes)
  
  // 2. 文件数量限制
  const maximum = Number(props.maximum)
  let currentFileLength = files.length + fileList.value.length
  if (currentFileLength > maximum) {
    files.splice(files.length - (currentFileLength - maximum))
  }
  
  return files
}

预处理流程mermaid

3. 预览生成与展示

对图片文件自动生成预览缩略图:

if (props.isPreview && file.type.includes('image')) {
  const reader = new FileReader()
  reader.onload = (event: ProgressEvent<FileReader>) => {
    fileItem.url = (event.target as FileReader).result as string
    fileList.value.push(fileItem)  // 更新预览列表
  }
  reader.readAsDataURL(file)  // 转换为base64
} else {
  fileList.value.push(fileItem)  // 非图片直接添加
}

预览区域模板

<view v-for="(item, index) in fileList" :key="item.uid" class="nut-uploader__preview">
  <!-- 图片预览 -->
  <img 
    v-if="item?.type?.includes('image') && item.url"
    class="nut-uploader__preview-img__c"
    :src="item.url"
    @click="fileItemClick(item)"
  />
  
  <!-- 文件图标 -->
  <view v-else class="nut-uploader__preview-img__file">
    <view class="nut-uploader__preview-img__file__name">
      <view class="file__name_tips">{{ item.name }}</view>
    </view>
  </view>
</view>

4. 上传队列与进度管理

采用Promise队列实现上传任务管理:

const uploadQueue = ref<Promise<Uploader>[]>([])

const executeUpload = (fileItem: FileItem, index: number) => {
  const uploadOption = new UploadOptions()
  // ... 配置上传参数
  
  uploadOption.onProgress = (event, option) => {
    fileItem.status = 'uploading'
    fileItem.percentage = ((event.loaded / event.total) * 100).toFixed(0)
    emit('progress', { event, option, percentage: fileItem.percentage })
  }
  
  let task = new Uploader(uploadOption)
  if (props.autoUpload) {
    task.upload()  // 自动上传
  } else {
    uploadQueue.value.push(Promise.resolve(task))  // 加入队列等待手动触发
  }
}

// 手动触发上传
const submit = () => {
  Promise.all(uploadQueue.value).then(tasks => tasks.forEach(task => task.upload()))
}

进度展示实现

<view v-if="item.status == 'uploading'" class="nut-uploader__preview__progress">
  <Loading v-else name="loading" color="#fff" />
  <view class="nut-uploader__preview__progress__msg">
    {{ item.percentage }}%
  </view>
</view>

<!-- 进度条组件 -->
<nut-progress
  v-if="item.status == 'uploading'"
  size="small"
  :percentage="item.percentage"
  stroke-color="linear-gradient(270deg, rgba(18,126,255,1) 0%,rgba(32,147,255,1) 32.815625%,rgba(13,242,204,1) 100%)"
  :show-text="false"
></nut-progress>

5. 状态管理与错误处理

文件上传状态流转逻辑:

mermaid

状态展示模板

<template v-if="item.status != 'ready'">
  <Failure v-if="item.status == 'error'" color="#fff" />
  <Loading v-else name="loading" color="#fff" />
</template>
<view class="nut-uploader__preview__progress__msg">
  {{ item.message }}  <!-- 状态文本提示 -->
</view>

多端适配:H5与小程序的差异处理

H5与小程序实现对比

特性H5实现Taro小程序实现
文件选择<input type="file">Taro.chooseImage API
上传方式XMLHttpRequestTaro.uploadFile
进度监听onprogress事件内置进度回调
预览功能URL.createObjectURLTaro.previewImage

小程序适配关键代码

// Taro版本上传实现
const chooseImage = async () => {
  const res = await Taro.chooseImage({
    count: props.multiple ? 9 : 1,
    sizeType: ['original', 'compressed'],
    sourceType: props.capture ? ['camera'] : ['album', 'camera']
  })
  
  res.tempFiles.forEach(file => {
    const fileItem = new FileItem()
    fileItem.name = file.name
    fileItem.url = file.path
    fileItem.size = file.size
    fileList.value.push(fileItem)
    executeUpload(fileItem)
  })
}

实战应用:配置参数与事件处理

基础用法示例

<nut-uploader
  url="https://api.example.com/upload"
  :max-count="3"
  @success="onUploadSuccess"
  @failure="onUploadFailure"
>
  <nut-button type="primary">选择文件</nut-button>
</nut-uploader>

核心配置参数

参数类型默认值说明
urlString''上传接口地址
methodString'post'请求方法
fileListArray[]已上传文件列表
maxCountNumber1最大上传数量
maxSizeNumberNumber.MAX_VALUE单文件大小限制(字节)
acceptString'*'接受的文件类型
autoUploadBooleantrue是否自动上传
listTypeString'picture'展示类型(picture/list)
disabledBooleanfalse是否禁用

事件回调

// 上传成功处理
const onUploadSuccess = ({ responseText, fileItem }) => {
  const res = JSON.parse(responseText)
  if (res.code === 200) {
    // 处理成功逻辑,如保存服务器返回的文件ID
    saveFileId(res.data.fileId)
  }
}

// 上传失败处理
const onUploadFailure = ({ responseText, fileItem }) => {
  // 错误处理,如显示错误提示
  showToast('上传失败,请重试')
}

性能优化:提升上传体验的关键策略

1. 分块上传

对于大文件,可通过beforeUpload拦截器实现分块上传:

const beforeUpload = async (files) => {
  const chunkedFiles = await Promise.all(
    Array.from(files).map(file => chunkFile(file, 2 * 1024 * 1024))  // 2MB分块
  )
  return chunkedFiles.flat()
}

2. 上传队列控制

限制并发上传数量,避免网络拥塞:

// 控制最大并发数为3
const uploadWithLimit = async (tasks, limit = 3) => {
  const results = []
  const executing = []
  
  for (const task of tasks) {
    const p = Promise.resolve().then(() => task())
    results.push(p)
    
    if (limit <= tasks.length) {
      const e = p.then(() => executing.splice(executing.indexOf(e), 1))
      executing.push(e)
      if (executing.length >= limit) {
        await Promise.race(executing)
      }
    }
  }
  
  return Promise.all(results)
}

3. 图片压缩

在上传前对图片进行压缩处理:

// 使用canvas压缩图片
const compressImage = (file, quality = 0.8) => {
  return new Promise((resolve) => {
    const img = new Image()
    img.src = URL.createObjectURL(file)
    img.onload = () => {
      const canvas = document.createElement('canvas')
      const ctx = canvas.getContext('2d')
      
      // 计算压缩后尺寸
      let width = img.width
      let height = img.height
      if (width > 1024) {
        height = height * (1024 / width)
        width = 1024
      }
      
      canvas.width = width
      canvas.height = height
      ctx.drawImage(img, 0, 0, width, height)
      
      canvas.toBlob(
        (blob) => resolve(new File([blob], file.name, { type: file.type })),
        file.type,
        quality
      )
    }
  })
}

常见问题与解决方案

Q1: 如何实现上传前预览?

A: 通过FileReader API将文件转换为DataURL:

const reader = new FileReader()
reader.onload = (e) => {
  previewImage.value = e.target.result as string
}
reader.readAsDataURL(file)

Q2: 如何自定义上传参数?

A: 使用data属性传递额外参数:

<nut-uploader
  :data="{ token: 'xxx', category: 'avatar' }"
></nut-uploader>

Q3: 如何处理跨域问题?

A: 配置withCredentials属性:

<nut-uploader
  :with-credentials="true"
></nut-uploader>

同时服务端需要设置相应的CORS头:

Access-Control-Allow-Origin: https://your-domain.com
Access-Control-Allow-Credentials: true

总结与扩展

NutUI的Uploader组件通过组件化设计状态驱动多端适配三大特性,为移动端文件上传提供了完整解决方案。核心优势包括:

  1. 灵活的配置项:支持文件类型、大小、数量等多维度控制
  2. 完善的状态反馈:从选择到完成的全流程状态提示
  3. 优雅的视觉设计:符合京东风格的UI设计,支持主题定制
  4. 强大的扩展能力:通过拦截器和事件系统支持复杂业务场景

扩展方向

  • 支持断点续传
  • 实现云存储直传
  • 增加文件拖拽上传
  • 集成图片裁剪功能

掌握Uploader组件的实现原理,不仅能更好地应用于实际项目,更能深入理解前端组件化开发的精髓。希望本文能为你的移动端开发之路提供有价值的参考。

参考资料

【免费下载链接】nutui 京东风格的移动端 Vue2、Vue3 组件库 、支持多端小程序(A Vue.js UI Toolkit for Mobile Web) 【免费下载链接】nutui 项目地址: https://gitcode.com/gh_mirrors/nu/nutui

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值