Ant Design Pro of Vue2 文件上传组件 Upload 自定义上传实战笔记

  • 原创
  • 作者:程序员三丰
  • 发布时间:2022-10-07 01:16
  • 浏览量:1797
在使用 Ant Design Pro of Vue2 框架进行项目开发过程中,不可避免的遇到文件上传类的功能需求,本文就实战过程中遇到的一些经验和问题做个分享,为自己做个笔记,也为需要帮助的同学做个参考!

最终效果

确认 Ant Design Vue 版本

  • 首先要清楚 Ant Design Vue 和 Ant Design Pro of Vue 的区别:
    可以理解为 Ant Design Vue 是一套 Vue 组件库,而 Pro 是使用了这套组件库的完整前端脚手架。

  • 从 Ant Design Pro of Vue 构建的项目的 package-lock.json 文件中确认
    查看ant-design-vue的版本
    截止本文发布时间(2022 年 9 月 16 日)按照 Pro 官方文档的安装方式默认安装都是 1.7.8 的版本。

  • 明确了框架的版本,那么我们在阅读官方文档使用 upload 或者其他组件时,也一定要确认文档版本,否则会因为版本差异,会影响您的工作。
    切换ant-design-vue版本
    上图中选择 1.x 就是 1.7.8 的版本,选择之后就会显示完整的版本号 1.7.8

【特别说明】为什么要讲上面这些呢,因为我在实际工作中就因为这个问题,官方文档默认打开的版本是 v3 或者 v2,再加上当时项目比较赶,就掉到这坑里了!!!

Upload 组件 API 关键参数和事件介绍

参数

  • action:设置上传地址,可以设置两种类型的值:

    • 字符串:就是一个有效的上传 URL 地址;
    • 回调函数:(file) => Promise这个后续探索后,再给大家分享^_^
  • method:设置上传请求方式,一般为 POST

  • beforeUpload:上传文件之前的钩子,参数为上传的文件

  • customRequest:通过覆盖默认的上传行为,可以自定义自己的上传实现,该参数的值类型为Function

  • data:设置上传所需参数,如:{type: 'article'}

  • fileList:已经上传的文件列表(受控)

  • listType:上传列表的内建样式,支持三种基本样式 text, picturepicture-card

  • multiple:是否支持多选文件,ie10+ 支持。开启后按住 ctrl 可选择多个文件。

  • name:设置发到后台的文件参数名,默认 file

  • remove:点击移除文件时的回调,返回值为 false 时不移除。Function(file): boolean

事件

  • change:上传文件状态改变时的回调,上传中、完成、失败都会调用这个函数。返回三个参数:

    • file 当前操作的文件对象,数据结构为:

      {
          uid: 'uid',      // 文件唯一标识,建议设置为负数,防止和内部产生的 id 冲突
          name: 'xx.png',   // 文件名
          status: 'done', // 状态有:uploading done error removed
          response: '{"status": "success"}', // 服务端响应内容
          linkProps: '{"download": "image"}', // 下载链接额外的 HTML 属性
          xhr: 'XMLHttpRequest{ ... }', // XMLHttpRequest Header
      }
      
    • fileList 当前的文件列表。

    • event 上传中的服务端响应内容,包含了上传进度等信息,高级浏览器支持。
  • preview:点击文件链接或预览图标时的回调,返回参数点击的文件对象。

  • reject:拖拽文件不符合 accept 类型时的回调

Upload 组件代码实战

基础代码构建

<template>
  <div>
    <a-card title="多图上传">
      <!-- 引入 Upload 上传组件 -->
      <a-upload list-type="picture-card">
        <div>
          <a-icon type="plus" />
        </div>
      </a-upload>
    </a-card>
  </div>
</template>

<script>
export default {
  data() {
    return {};
  },
  methods: {},
};
</script>

此时效果图如下,但是还不能成功上传文件,因为我们还没有做服务端上传相关配置。
基础代码构建效果

自定义上传实现(customRequest)

通过给 upload 组件设置 customRequest 参数来实现自定义上传,设置该从参数会覆盖组件的默认上传行为。

此时 upload 组件代码如下:

<a-upload
  list-type="picture-card"
  :custom-request="customUploadHandle"  // 设置 custom-request 参数
></a-upload>

JS 部分增加自定义上传的实现,代码如下:

// 引用定义好的上传API
import { uploadFile } from '@/api/common';

// 在 methods 增加自定义的实现,就是一个函数回调
// 函数有一个参数 data ,就是打开文件选择器选择的文件对象
methods: {
  customUploadHandle(data) {
    // 构建表单上传对象
    const formData = new FormData();
    // 上传的文件,就是我们熟悉的HTML表单的input[type=file]的值
    formData.append("file", data.file);
    // 设置其他(表单)参数,你可以添加多个该行代码表示增加多个参数,本例参数type=covers代表我上传的是封面图片
    formData.append("type", "covers");
    // 请求上传API
    uploadFile(formData)
      .then((res) => {
        this.uploadLoading = false;
        if (res.code * 1 === 0) {
          // 上传成功的回调
          data.onSuccess(res.data);
        } else {
          // 上传失败的回调
          data.onError(res.msg);
          this.$message.error(res.msg);
        }
      })
      .catch((error) => {
        this.uploadLoading = false;
        this.$message.error(error.message);
      });
  }
}

文件验证(类型、大小)

beforeUpload 参数(上传前的钩子)中进行文件类型、大小进行判断,如果不满足条件,返回 false 则停止上传。

<a-upload
  list-type="picture-card"
  :custom-request="customUploadHandle"
  :before-upload="beforeUploadHandle"  // 设置 before-upload 参数
></a-upload>

JS 部分增加 beforeUpload 的实现,代码如下:


// 在 methods 增加 beforeUpload 的实现,就是一个函数回调
// 函数有两个参数:file为当前已选择待上传的文件, fileList为已经选择上传的文件列表
methods: {
  beforeUploadHandle (file, fileList) {
    // 检查文件 file 的大小和类型,
    // result 为验证果 true or false,messages 为验证失败时的错误消息
    const { result, messages } = fileCheckForImage(file)
    if (!result) { // 验证失败的处理
      fileList.pop() // 从已选择的文件列表移除最后一个文件,也就是当前验证的文件,因为默认会显示
      this.$message.error(messages) // 弹出验证失败的提示信息
      return false // 返回 false 阻止上传
    }
  }
}

// 文件大小和类型验证的实现,我把该函数放到了公共函数文件,你可以根据实际情况来处理
export function fileCheckForImage (file) {
  const messages = []

  // 文件类型验证
  const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png'
  if (!isJpgOrPng) {
    messages.push('上传文件格式不正确!只允许上传图片!')
  }

  // 文件大小验证(2M以内)
  const isLt2M = file.size / 1024 / 1024 < 2
  if (!isLt2M) {
    messages.push('上传文件大小超出限制!不能超过2MB!')
  }

  return {
    result: isJpgOrPng && isLt2M,
    messages
  }
}

模拟实现上传进度条效果

由于采用了自定义上传方式,上传进度条显示异常,使用显示为一个白条,正确的应该是白条是进度条的总长度,实时的进度用蓝色来动态显示。所以模拟实现上传进度条效果是为了更好的体验,只是由于目前的我还不能获取的真正的上传进度的数据,实现效果不太理想,勉强可以使用。

代码实现其实很简单,通过 setInterval 来更新组件的上传进度状态,代码如下:

methods: {
  customUploadHandle(data) {
    const formData = new FormData();
    formData.append("file", data.file);
    formData.append("type", "covers");

    // 模拟上传进度条
    const uploadProgress = { percent: 0 }
    const intervalId = setInterval(() => {
      if (uploadProgress.percent < 100) {
        uploadProgress.percent += 0.00001
      }
      data.onProgress(uploadProgress) // 更新组件的上传进度
      if (uploadProgress.percent >= 100) {
        clearInterval(intervalId)
      }
    })

    uploadFile(formData)
      .then((res) => {
        this.uploadLoading = false;
        if (res.code * 1 === 0) {
          uploadProgress.percent = 100 // 上传完成
          data.onSuccess(res.data);
        } else {
          data.onError(res.msg);
          this.$message.error(res.msg);
        }
      })
      .catch((error) => {
        this.uploadLoading = false;
        this.$message.error(error.message);
      });
  }
}

上传图片预览

通过 preview 事件来实现文件预览,preview 事件是点击文件链接或预览图标时的回调。

UI 组件代码变动如下:

<!-- 此时 upload 组件代码如下: -->
<a-upload
  list-type="picture-card"
  :custom-request="customUploadHandle"
  :before-upload="beforeUploadHandle"
  @preview="previewHandle" // 设置 preview 事件
>
  <div>
    <a-icon type='plus' />
  </div>
</a-upload>

<!-- 增加图片预览弹窗 modal 组件 -->
<a-modal
  :visible="previewVisible"
  :footer="null"
  @cancel="closePreviewHandle"
  :bodyStyle="{ padding: '41px 10px 20px' }"
>
  <img alt='' style='width: 100%' :src='previewImage' />
  <div style='padding: 30px 0;text-align: center' v-show='previewLoading'>
    <a-spin :spinning='previewLoading'></a-spin>
  </div>
</a-modal>

JS 部分增加的代码变动如下:

export default {
  data() {
    return {
      previewVisible: false, // 预览弹窗的显示状态
      previewImage: '', // 当前预览的图片(临时动态存储)
      previewLoading: true, // 预览弹窗的加载状态
    };
  },
  methods: {
    /**
     * 图片预览的实现,实质就是打开一个 modal 弹窗显示图片
     */
    previewHandle (file) {
      this.previewVisible = true
      this.previewLoading = true

      // 此处根据自己的数据结构来处理,file.response 的值为自定义上传 API 返回的数据结构
      const previewImage = file.url || file.response.path
      const imageObj = new Image()
      imageObj.src = previewImage
      imageObj.onload = () => {
        this.previewImage = imageObj.src
        this.previewLoading = false
      }
    },
    /**
     * 关闭预览 modal 的回调
     */
    closePreviewHandle () {
      this.previewVisible = false
      setTimeout(() => {
        this.previewImage = ''
        this.previewLoading = true
      }, 300)
    }
  },
};

完全控制上传列表 && 限制上传列表数量

通过参数 fileList 和 事件 change 对列表进行完全控制,以实现某些自定义功能。

UI 组件代码变动如下:

<a-upload
  list-type="picture-card"
  :custom-request="customUploadHandle"
  :before-upload="beforeUploadHandle"
  :file-list="uploadFileList" // 设置 fileList 参数
  @preview="previewHandle"
  @change="uploadChangeHandle" // 设置 change 事件
>
  <div>
    <a-icon type='plus' />
  </div>
</a-upload>

<!-- 点击下面的按钮,控制台会打印 fileList  -->
<a-button type='primary' @click='fileListShowHandle'>控制台打印上传列表</a-button>

JS 部分增加的代码变动如下:

export default {
  data() {
    return {
      previewVisible: false, // 预览弹窗的显示状态
      previewImage: '', // 当前预览的图片(临时动态存储)
      previewLoading: true, // 预览弹窗的加载状态

      uploadFileList: [],
    };
  },
  methods: {
    /**
     * 图片预览的实现,实质就是打开一个 modal 弹窗显示图片
     */
    uploadChangeHandle (info) {
      let fileList = [...info.fileList]

      // TODO:此处可以对 fileList 进行处理,比如下面的代码来限制上传数量
      if (fileList.length >= 3) {
        this.$message.error('已达最多允许上传数量(3个)')
        fileList = fileList.slice(0, 3)
      }

      this.uploadFileList = fileList
    },
    fileListShowHandle () {
      console.log(this.uploadFileList)
    }
  },
};

已上传图片回显

通过初始化参数 fileList 的值,来回显已上传的图片。

JS 部分增加的代码变动如下:

/**
 * 导入获取已上传图片的API,该接口为简单演示接口,数据结构简单如下:
 * {code: 0, message: 'success', data: [{id: 100, path: '/path/xxx1.png'}, {id: 101, path: '/path/xxx2.png'}]}
 */
import { photos } from '@/api/common'
export default {
  data() {
    return {
      // ...其他参数
      uploadFileList: [], // 必须
    };
  },
  created () {
    this.uploadListRender()
  },
  methods: {
    uploadListRender () {
      photos({}).then(res => {
        this.uploadFileList = res.data.map(file => {
          const filename = file.path.split('.').pop()

          // fileList 数据结构如下
          return {
            uid: '-' + String(file.id), // uid为其在上传列表中的文件唯一标识,官方建议设置为负数,防止和内部产生的 id 冲突
            name: filename,
            status: 'done',
            url: file.path
          }
        })
      }).catch(error => {
        this.$message.error(JSON.stringify(error))
      })
    }
  },
};

完整代码

api 文件 common.js

import request from '@/utils/request'

const apiMap = {
  FileUpload: '/upload',
  PhotoList: '/photos'
}

// 文件上传接口
export function uploadFile (data) {
  return request({
    url: apiMap.FileUpload,
    method: 'post',
    data
  })
}

// 已上传文件接口
export function photos (data) {
  return request({
    url: apiMap.PhotoList,
    data
  })
}

公共函数文件 util.js

/**
 * 文件上传验证
 * @param file
 * @returns {{result: boolean, messages: *[]}}
 */
export function fileCheckForImage (file) {
  const messages = []
  const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png'
  if (!isJpgOrPng) {
    messages.push('上传文件格式不正确!只允许上传图片!')
  }

  const isLt2M = file.size / 1024 / 1024 < 2
  if (!isLt2M) {
    messages.push('上传文件大小超出限制!不能超过2MB!')
  }

  return {
    result: isJpgOrPng && isLt2M,
    messages
  }
}

主文件 Upload.vue

<template>
  <div>
    <a-card title='多图上传'>
      <a-upload
        list-type='picture-card'
        :custom-request='customUploadHandle'
        :before-upload='beforeUploadHandle'
        :file-list='uploadFileList'
        @preview='previewHandle'
        @change='uploadChangeHandle'>
        <div>
          <a-icon type='plus' />
        </div>
      </a-upload>
      <a-button type='primary' @click='fileListShowHandle'>控制台打印上传列表</a-button>
      <a-modal
        :visible='previewVisible'
        :footer='null'
        @cancel='closePreviewHandle'
        :bodyStyle="{ padding: '41px 10px 20px' }">
        <img alt='' style='width: 100%' :src='previewImage' />
        <div style='padding: 30px 0;text-align: center' v-show='previewLoading'>
          <a-spin :spinning='previewLoading'></a-spin>
        </div>
      </a-modal>
    </a-card>
  </div>
</template>

<script>
import { fileCheckForImage } from '@/utils/util'
import { uploadFile, photos } from '@/api/common'

export default {
  name: 'Basic',
  data () {
    return {
      previewVisible: false,
      previewImage: '',
      previewLoading: true,
      uploadFileList: [],
      uploadLoading: false
    }
  },
  created () {
    this.uploadListRender()
  },
  methods: {
    uploadListRender () {
      photos({}).then(res => {
        this.uploadFileList = res.data.map(file => {
          const filename = file.path.split('.').pop()
          return {
            uid: '-' + String(file.id),
            name: filename,
            status: 'done',
            url: file.path
          }
        })
      }).catch(error => {
        this.$message.error(JSON.stringify(error))
      })
    },
    customUploadHandle (data) {
      // 构建表单上传对象
      const formData = new FormData()
      // 上传的文件,就是我们熟悉的HTML表单的input[type=file]的值
      formData.append('file', data.file)
      // 设置其他(表单)参数,你可以添加多个该行代码表示增加多个参数,本例参数type=covers代表我上传的是封面图片
      formData.append('type', 'covers')

      // 模拟上传进度条
      const uploadProgress = { percent: 0 }
      const intervalId = setInterval(() => {
        if (uploadProgress.percent < 100) {
          uploadProgress.percent += 0.00001
        }
        data.onProgress(uploadProgress)
        if (uploadProgress.percent >= 100) {
          clearInterval(intervalId)
        }
      })

      // 请求上传API
      uploadFile(formData).then(res => {
        this.uploadLoading = false
        if (res.code * 1 === 0) {
          uploadProgress.percent = 100
          // 上传成功的回调
          setTimeout(() => {
            data.onSuccess(res.data)
          }, 50)
        } else {
          // 上传失败的回调
          data.onError(res.msg)
          this.$message.error(res.msg)
        }
      }).catch(error => {
        this.uploadLoading = false
        this.$message.error(error.message)
      })
    },
    beforeUploadHandle (file, fileList) {
      const { result, messages } = fileCheckForImage(file)
      if (!result) {
        fileList.pop()
        this.$message.error(messages)
        return false
      }
    },
    fileListShowHandle () {
      console.log(this.uploadFileList)
    },
    uploadChangeHandle (info) {
      let fileList = [...info.fileList]
      fileList = fileList.map(file => {
        if (file.response) {
          file.url = file.response.path
        }
        return file
      })

      if (fileList.length > 3) {
        this.$message.error('已达最多允许上传数量(3个)')
        fileList = fileList.slice(0, 3)
      }

      this.uploadFileList = fileList
    },
    previewHandle (file) {
      this.previewVisible = true
      this.previewLoading = true

      const previewImage = file.url || file.response.path
      const imageObj = new Image()
      imageObj.src = previewImage
      imageObj.onload = () => {
        this.previewImage = imageObj.src
        this.previewLoading = false
      }
    },
    closePreviewHandle () {
      this.previewVisible = false
      setTimeout(() => {
        this.previewImage = ''
        this.previewLoading = true
      }, 300)
    }
  }
}
</script>

<style scoped>

</style>

完结。。。。

声明:本文为原创文章,51blog.xyz和作者拥有版权,如需转载,请注明来源于51blog.xyz并保留原文链接:https://www.51blog.xyz/article/40

文章归档

强烈推荐的PHP全栈开发后台管理系统
buildadmin logo
Thinkphp8 Vue3 Element PLus TypeScript Vite Pinia

🔥BuildAdmin是一个永久免费开源,无需授权即可商业使用,且使用了流行技术栈快速创建商业级后台管理系统。

推荐文章

热门标签

PHP ThinkPHP ThinkPHP5.1 Go Mysql Mysql5.7 Redis Linux CentOS7 Git HTML CSS CSS3 Javascript JQuery Vue LayUI VMware Uniapp 微信小程序 docker wiki Confluence7 学习笔记 uView ES6 Ant Design Pro of Vue React ThinkPHP6.0 chrome 扩展 翻译工具 Nuxt SSR 服务端渲染 scrollreveal.js ThinkPHP8.0 Mac webman 跨域CORS vscode GitHub ECharts Canvas vue3 three.js 微信支付 PHP全栈开发 Python AI 人工智能 AI辅助 工作经验 实战笔记