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">
        </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>
    </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('操作成功')
    

    视频图片相关效果图