Ant Design Pro 2项目upload视频上传弹窗预览

基础环境

我在前面的文章中写明了当前具体环境版本,可以参考
Ant Design Pro 2(动态路由和菜单)

upload这个官方组件存在的问题

  • 第一次看这个组件觉得效果很不错,结果一用才发现,开发体验极差,官方文档也是举例试,完全需要开发人员自己拓展,说好的组件化呢,还需要代工的组件化我还要你干嘛,对比下就项目用到的组件bootstrap fileinput,心累,因为框架迭代就是要有人去肝....

  • 算了,不吐槽,处理现有的问题:
    官方提供的案例中有图片弹窗预览效果,但是上传视频等非图片文件后,无法点击预览按钮,事件也是无法触发,这,我编辑查看详情页面时要怎么展示??

修改思路

  • 细心观察的前端兄弟通过审查元素可以看到预览按钮灰色实际上是可以通过样式修改,达到可以跟图片一样预览效果,并且可以触发点击事件preview的,我们要做的就是在change选择事件中延迟下悄悄把样式改了,同时modal中判断如果是视频类型就展示video标签,然后弹窗关闭事件中判断下把视频关闭就可以了

组件页面代码

  • src/components/UploadFile/AntdUploadImg.vue
  • 这个组件是结合了其他网友分享的文档进行改造而成,暂时满足了我们当前项目的业务需求,可以根据实际情况进行拓展
<template>
  <!-- 主视图 -->
  <div class="upload-view">
    <!-- 上传组件 -->
    <a-upload
      list-type="picture-card"
      :accept="accept"
      :multiple="multiple"
      :disabled="disabled"
      :showUploadList="showUploadList"
      :fileList="fileList"
      :beforeUpload="beforeUpload"
      :customRequest="customRequest"
      :remove="remove"
      @preview="handlePreview"
      @change="handleChange"
    >
      <div v-if="fileList.length < fileNumber">
        <a-icon type="plus" />
        <div class="ant-upload-text">
          上传文件
        </div>
      </div>
    </a-upload>
    <a-modal :visible="previewVisible" :footer="null" @cancel="handleCancel">
      <img v-if="fileType === 'img'" alt="example" style="width: 100%" :src="previewImage" />
      <video v-if="fileType === 'video'" width="100%" controls type="video" id="video">
        <source :src="videoSrc" type="video/mp4" />
        <object :data="videoSrc" width="100%">
          <embed :src="videoSrc" width="100%" />
        </object>
        您的浏览器不支持video标签哦,请联系管理员
      </video>
    </a-modal>
  </div>
</template>

<script>
import { uploadCreate, uploadDelete } from '@/api/upload'

function getBase64 (file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader()
    reader.readAsDataURL(file)
    reader.onload = () => resolve(reader.result)
    reader.onerror = error => reject(error)
  })
}

export default {
  name: 'AntdUploadImg',
  data () {
    return {
      previewVisible: false,
      previewImage: '',
      fileType: 'img',
      videoSrc: '',
      showUploadList: {
        showRemoveIcon: true,
        showPreviewIcon: true
      }
    }
  },
  props: {
    // =============================== 原生属性 - a-upload 自带属性扩展 ========
    // 接受上传的文件类型
    // 参考地址:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers
    // 音频:'audio/*'
    // 视频:'video/*'
    // 图片:'image/*'
    // 特殊匹配自行传入正则
    accept: {
      type: String,
      default: () => undefined
    },
    // 已经上传的文件列表(受控)
    // 文件案例:
    // [{
    //   uid: '必带,文件唯一标识',
    //   name: '必带,文件名',
    //   // uploading(上传中)、done(上传成功)、error(上传失败)、removed(移除,点击组件自带的删除按钮会被设置为移除状态,通常只需要前三种状态)
    //   status: '必带,上传状态',
    //   dupid: '可选,防止重复文件标识(file.lastModified)',
    //   upid: '可选,本轮上传唯一标识,提交服务器时可剔除',
    //   如果需要什么其他字段或辅助字段,可以自行添加,或者通过拦截 beforeUploadPro 拿到 fileJson 自行附带
    // }]
    fileList: {
      type: Array,
      default: () => []
    },
    // 上传时携带的参数
    // {
    //   product_id: 0,//主键id
    //   file_type_id: 60,//类型
    //   tables: 6,//表标识
    //   table_name: 'bar_id'//上传存储文件夹
    // }
    uploadParameter: {
      type: Array,
      default: () => []
    },
    // 是否支持多选文件,ie10+ 支持。开启后按住 ctrl 可选择多个文件。
    multiple: {
      type: Boolean,
      default: () => true
    },
    // 是否禁用
    disabled: {
      type: Boolean,
      default: () => false
    },
    // 点击移除文件时的回调,返回值为 false 时不移除
    // 类型:(file) => boolean || Promise
    removePro: {
      type: Function,
      default: undefined
    },
    // =============================== 公用检测 - 文件数量限制 ========
    // 上传文件数量限制:0 -> 不限制,随便传
    fileNumber: {
      type: Number,
      default: () => 0
    },
    // =============================== 公用检测 - 文件大小 ========

    // 文件大小检测模式(单位 kb):
    // 0 -> 关闭
    // 1 -> 小于
    // 2 -> 大于
    // 3 -> 等于
    // 11 -> 小于或等于
    // 22 -> 大于或等于
    kbCompareMode: {
      type: Number,
      default: () => 0
    },
    // 文件大小(单位 kb)
    kbCompareSize: {
      type: Number,
      default: () => 0
    }
  },
  methods: {
    // 暴露方法获取filelist
    getNewFileLIst () {
      return this.fileList
    },
    // 暴露方法初初始化文件列表
    customSetFileList (record) {
      // 清空,在延迟赋值
      this.fileList = []
      setTimeout(() => {
        this.fileList = record
      }, 20)
    },
    // 自定义上传
    customRequest (record) {
      this.uploadParameter = record
      // 找到对应的上传文件对象
      const { uploadParameter } = this
      const { fileList } = this
      return new Promise((resolve, reject) => {
        if (fileList.length > 0) {
          const formData = new FormData()
          // eslint-disable-next-line camelcase
          let is_file = 0
          fileList.forEach(file => {
            if (file.originFileObj) {
              formData.append('FileModel[file]', file.originFileObj)
              // eslint-disable-next-line camelcase
              is_file = 1
            }
          })
          // eslint-disable-next-line camelcase
          if (is_file) {
            formData.append('product_id', uploadParameter.product_id)
            formData.append('file_type_id', uploadParameter.file_type_id)
            formData.append('tables', uploadParameter.tables)
            formData.append('table_name', uploadParameter.table_name)
            return uploadCreate(formData).then(res => {
              resolve()
            }).catch(() => {
              // 不允许上传
              // eslint-disable-next-line prefer-promise-reject-errors
              reject('上传失败了')
            })
          } else {
            resolve()
          }
        } else {
          resolve()
        }
      })
    },
    // 准备上传
    beforeUpload (file, fileList) {
      // console.log('file', file)
      // 文件基本信息获取
      if (this.isImage(file.name)) {
        this.fileType = 'img'
      } else if (this.isVideo(file.name)) {
        this.fileType = 'video'
      }
      // 获取文件大小(单位:kb)
      const fileSize = file.size / 1024 / 1024
      // eslint-disable-next-line camelcase
      let is_error = 0
      if (fileSize > this.kbCompareSize) {
        this.$message.error('文件必须小于或者等于 ' + this.kbCompareSize + 'MB!')
        // eslint-disable-next-line camelcase
        is_error = 1
      } else if (this.accept !== undefined) {
        // 还是要拦截判断下文件后缀,选择的时候用户可能恶意选择全部类型
        if (file.type) {
          const types = this.accept.split(',')
          if (types.indexOf(file.type) === -1) {
            this.$message.error('文件类型不合法')
          }
        } else {
          const suffix = this.fileExtension(file.name)
          // 当前实验出excel是识别不出来的,所以这里应该要单独验证了
          if (suffix === 'xlsx' && this.accept !== 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {
            this.$message.error('文件类型不合法')
          }
        }
        // eslint-disable-next-line camelcase
        if (is_error) {
          setTimeout(() => {
            const index = this.fileList.indexOf(file)
            const newFileList = this.fileList.slice()
            newFileList.splice(index, 1)
            this.fileList = newFileList
          })
        }
      } else {
        this.fileList = [...this.fileList, file]
      }
      return false
    },
    // 点击移除文件时的回调
    remove (file) {
      // console.log(file)
      // 删除文件
      const index = this.fileList.indexOf(file)
      this.fileList.splice(index, 1)
      if (file.url) {
        return uploadDelete(file.uid)
          .then(res => {
            this.$message.success('操作成功')
          })
      }
    },
    // 关闭预览弹窗
    handleCancel () {
      this.previewVisible = false
    },
    // 点击预览图标时
    async handlePreview (file) {
      // console.log(file)
      if (file.url) {
        // 文件基本信息获取
        if (this.isImage(file.name)) {
          this.fileType = 'img'
        } else if (this.isVideo(file.name)) {
          this.fileType = 'video'
        }
      }
      if (!file.url && !file.preview && this.fileType === 'img') {
        file.preview = await getBase64(file.originFileObj)
      }
      if (this.fileType === 'img') {
        this.previewImage = file.url || file.preview
        this.previewVisible = true
      } else if (this.fileType === 'video') {
        if (file.url) {
          this.videoSrc = file.url
          this.previewVisible = true
        } else {
          const current = file.originFileObj
          const fileReader = new FileReader()
          fileReader.readAsDataURL(current)
          const that = this
          fileReader.onload = function (e) {
            that.videoSrc = e.currentTarget.result
            that.previewVisible = true
          }
        }
      }
    },
    // 选择文件后事件
    handleChange ({ fileList }) {
      this.fileList = fileList
      // 预览图片特殊处理,上传非图片文件时,修改样式
      setTimeout(() => {
      const classActions = document.getElementsByClassName('ant-upload-list-item-actions')
        classActions.forEach(ca => {
          ca.children[0].style.opacity = '1'
          ca.children[0].style.pointerEvents = 'initial'
        })
        // console.log(classActions)
      }, 30)
    },
    // 是否为图片
    isImage (filePath) {
      // 图片后缀
      const types = ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp', 'psd', 'svg', 'tiff']
      // 文件后缀
      const type = this.fileExtension(filePath)
      // 是否包含
      return types.indexOf(type) !== -1
    },
    // 是否为视频
    isVideo (filePath) {
      // 图片后缀
      const types = ['avi', 'wmv', 'mpg', 'mpeg', 'mov', 'rm', 'ram', 'swf', 'flv', 'mp4', 'mp3', 'wma', 'avi', 'rm', 'rmvb', 'flv', 'mpg', 'mkv']
      // 文件后缀
      const type = this.fileExtension(filePath)
      // 是否包含
      return types.indexOf(type) !== -1
    },
    // 获取文件后缀类型
    fileExtension (filePath) {
      // 获取最后一个.的位置
      const index = filePath.lastIndexOf('.')
      // 获取后缀
      const type = filePath.substr(index + 1)
      // 返回类型
      return type.toLowerCase()
    }
  },
  watch: {
    // 预览弹窗关闭,监听下,如果是视频,要把视频停止
    previewVisible: function (val) {
        if (this.previewVisible === false && this.fileType === 'video') {
          const thisVideo = document.getElementById('video')
          thisVideo.pause()
        }
    }
  }
}
</script>

<style scoped>
/* you can make up upload button and sample style by using stylesheets */
.ant-upload-select-picture-card i {
  font-size: 32px;
  color: #999;
}

.ant-upload-select-picture-card .ant-upload-text {
  margin-top: 8px;
  color: #666;
}
.upload-view .ant-upload-list-item-list-type-picture-card .ant-upload-list-item-actions a {
  pointer-events: initial;
  opacity: 1;
}
</style>

新增页面调用

  • 标签
                  <antd-upload-img
                    ref="videoUploadRef"
                    :accept="'video/mp4'"
                    :multiple="false"
                    :fileNumber="1"
                    :uploadParameter="videoParameter"
                    :kbCompareSize="100"
                  ></antd-upload-img>
  • 注入声明,初始化变量
  • videoParameter变量是我放上传时的一些额外参数,我这边上传走的是独立的接口,所以要先走业务表新增,返回标识,才会继续走上传接口
import AntdUploadImg from '@/components/UploadFile/AntdUploadImg'
components: {
    AntdUploadImg
  },
  data () {
    return {
    videoParameter: {
        product_id: this.bar_id,
        file_type_id: 64,
        tables: 6,
        table_name: 'b_bar'
      }
    }
}
  • 调用上传接口,customRequest是组件暴露出来触发上传的方法
this.$refs.videoUploadRef.customRequest(this.videoParameter)

编辑页面调用

  • 编辑页面跟新增页面差不多,因为走的是独立上传接口,只要根据暴露的组件方法去初始化fileList就可以了
this.videoFileList.push({
                uid: item.files_middle_id,
                name: item.photos.name,
                status: 'done',
                url: item.photos.url
              })
this.$refs.videoUploadRef.customSetFileList(this.videoFileList)
  • 至于文件删除的话,因为我们走的都是独立附件处理接口,所以就直接放在组件中了,如果需要触发删除事件,可以自己写$emit
remove (file) {
      // console.log(file)
      // 删除文件
      const index = this.fileList.indexOf(file)
      this.fileList.splice(index, 1)
      if (file.url) {
        return uploadDelete(file.uid)
          .then(res => {
            this.$message.success('操作成功')
          })
      }
    }

视频图片相关效果图

初始化页面

选择视频后缩略图

鼠标移动过去后
点击预览效果

最后总结

作为一个伪全栈搬砖人员,最讨厌的就改造了(因为啥都只懂一点......),之前的框架上传用的组件是bootstrap-fileinput,用习惯了,而a-upload提供出来的方法和案例又少,改起来真的很烦,a-upload给我的感觉就是太过轻量级了,用轮子来说就是样子货,下个项目必须要去整理下vue+bootstrap-fileinput,如果大家有已经成熟的antdv+bootstrap-fileinput的案例也可以分享给我,开开心心开发敲代码他不香么。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容