element-ui上传列表自定义实现,上传错误重传等公共组件

1,240 阅读1分钟

为什么需要自定义实现

当然是原生的element-ui不满足

  • 展示的列表,elementui里面的请求错误的,不会展示在列表里面
  • element判断上传成功和失败是通过最外层的服务端返回的状态,而对于我们公司的后端,不论是成功还是失败都是返回的200,在返回值里面在封装了一层code来反应真正的成功或者失败,200表示成功,-1表示失败

image.png image.png

实现的功能

  • 会标明哪些上传成功和哪些是上传失败的
  • 上传失败的可以重新上传
  • 上传失败有错误提示,相同的错误提示只会提示一次
  • 可以调节一排显示的个数

由于时间比较紧张,代码可能有一点乱,后期有时间在优化 其中用到的前端后端代码都有 1.gif

后端代码

这里你可以随意返回,我是根据公司这边需求来做的 启动服务

node ./app.js 
// app.js
const express = require('express')
const app = express()
const path = require('path')

//引入multer
const multer = require('multer')

//注册一个对象,dest里放的是上传的文件存储的位置,可以在当前目录下,建立一个static目录,上传的文件都放在这里
const upload = multer({dest: './static/'})

//使用中间件,没有挂载路径,应用的每个请求都会执行该中间件。any表示接受一切,具体参考文档。
app.use(upload.any())
let errorNum = 0
//在req.files中获取文件数据
app.post('/api/upload',function(req, res){
  const { originalname } = req.files[0]
  console.log(originalname, req.files)
  if (path.extname(originalname) === '.pdf') {
    res.send({
      code: '-1',
      message: '上傳格式不能是txt和pdf'
    })
  }
  else if(path.extname(originalname) === '.txt' && errorNum < 5) {
    errorNum++
    res.status(500).send({
      code: '500',
      message: '上传失败'
    })
  } else {
    res.send({
      code: '200',
      message: '成功'
    })

  }
})


app.listen(3000)

前端组件的封装

// CommonUpload/index.vue
<template>
  <div>
    <el-upload
      :show-file-list="false"
      :on-success="handleSuccess"
      v-bind="$attrs"
      v-on="$listeners"
      :on-progress="handleProgress"
      :on-error="handleError"
      ref="upload-outer"
    >
      <slot>
        <el-button size="small" type="primary">点击上传</el-button>
      </slot>
    </el-upload>
    <UploadList
      :file-list="fileList"
      @refreshUpload="refreshUpload"
      @handleDelete="handleDelete"
    ></UploadList>
  </div>
</template>

<script>
import UploadList from '@/components/CommonUpload/UploadList';
export default {
  name: 'CommonUpload',
  components: {
    UploadList
  },
  props: {
    attachList: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
      fileList: [],
      errTipsMap: {},
      refreshUploadFileInfo: { // 主要是为了上传失败,上传失败过后位置会发生变化
        file: '',
        index: 0,
        progressing: false // 是否在重新上传
      }
    }
  },
  watch: {
    attachList: {
      handler(newVal) {
        this.fileList = newVal
      },
      immediate: true,
      deep: true
    },
    fileList: {
      handler(newVal) {
        this.$emit('update:attachList', newVal)
      },
      immediate: true,
      deep: true
    }
  },
  methods: {
    changeResponseStatus(fileList) {
      fileList.forEach(item => {
        if ((item.status !== 'success' || item.status !== 'fail') && item.response && item.response.code !== '200') {
          item.status = 'fail'
        }
      })
      return fileList
    },
    // 成功回调函数
    handleSuccess(response, file, fileList) {
      this.fileList = this.changeResponseStatus(fileList)
      // 这里给出错误提示
      const statusList = this.fileList.filter(item => ['success', 'fail'].includes(item.status) && item.response)
      const isAllEnd = statusList.length === fileList.length
      // 错误提示等全部上传完了,在给出提示,并且去除重复的提示
      if (isAllEnd) {
        const errFileList = this.fileList.filter(item => item.status === 'fail')
        const errMessageList = []
        errFileList.forEach(item => {
          if (!this.errTipsMap[item.uid]) {
            errMessageList.push(item.response.message)
            this.errTipsMap[item.uid] = true
          }
        })
        // 错误信息去重
        const noRepeatMsgList = [...new Set(errMessageList)]
        if (noRepeatMsgList.length) {
          this.$message.error(noRepeatMsgList.join(','))
        }
      }
    },
    isRefreshUpload() {
      if (this.refreshUploadFileInfo.progressing) {
        return true
      }
    },
    handleError(err, file, fileList) {
      if (file.status !== 'fail') {
        return
      }
      if (this.isRefreshUpload()) {
        this.refreshUploadFileInfo.progressing = false
        this.fileList.splice(this.refreshUploadFileInfo.index, 0, file)
      } else {
        this.fileList.push(file)
      }
      this.errTipsMap[file.uid] = true
      this.$message.error(file.response?.message || '上传失败啦~~')
    },
    handleProgress(event, file, fileList) {
      this.fileList = this.changeResponseStatus(fileList)
    },
    refreshUpload(file) {
      this.errTipsMap[file.uid] = false
      this.refreshUploadFileInfo = {
        file: file,
        index: this.fileList.findIndex(item => item.uid === file.uid),
        progressing: true
      }
      this.$refs['upload-outer'].$refs['upload-inner'].upload(file.raw)
    },
    handleDelete(file) {
      const delInd = this.fileList.findIndex(item => item.uid === file.uid)
      this.fileList.splice(delInd, 1)
    }
  }
}
</script>

<style scoped>

</style>
//	CommonUpload/UploadList
<template>
  <div class="upload-list__container">
    <div
      v-for="file in fileList"
      :key="file.uid"
      class="upload-list__item"
    >
      <div class="upload-list__item-name">
        <i class="el-icon-tickets file-prev-icon"></i>
        <div class="file-name" :title="file.name" >
          <span>{{ file.name }}</span>
        </div>
        <div class="after-icon-box">
          <div class="file-after-icon">
            <div v-if="file.status === 'uploading'">{{ parsePercentage(file.percentage) + '%' }}</div>
            <div v-else-if="file.status === 'success'">
              <i class="el-icon-success"></i>
            </div>
            <div v-else-if="file.status === 'fail'">
              <i class="el-icon-error"></i>
            </div>
          </div>
          <div class="hover-delete">
            <i class="el-icon-refresh-right" v-if="file.status === 'fail'" @click="refreshUpload(file)"></i>
            <i class="el-icon-close" @click="handleDelete(file)"></i>
          </div>
        </div>

      </div>
      <el-progress
        class="progress-box"
        v-if="file.status === 'uploading'"
        :type="'line'"
        :stroke-width="2"
        :percentage="parsePercentage(file.percentage)"
        :show-text="false"
      >
      </el-progress>
    </div>
  </div>
</template>

<script>
export default {
  name: 'UploadList',
  props: {
    fileList: {
      type: Array,
      default: () => []
    }
  },
  methods: {
    parsePercentage(val) {
      return parseInt(val, 10);
    },
    refreshUpload(file) {
      this.$emit('refreshUpload', file)
    },
    handleDelete(file) {
      this.$emit('handleDelete', file)
    }
  }
}
</script>

<style scoped lang="scss">
  .upload-list__item {
    height: 32px;
    display: flex;
    align-items: center;
    position: relative;
    font-size: 12px;
    color: #2f2725;
    overflow: hidden;
  }
  .upload-list__item-name {
    display: flex;
    align-items: center;
    padding: 0 12px;
    height: 100%;
    width: 100%;
  }
  .file-after-icon, file-prev-icon, hover-delete {
    flex-shrink: 0;
  }
  .after-icon-box {
    width: 40px;
    text-align: end;
  }
  .file-name {
    flex: 1;
    margin-right: 10px;
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow: hidden;
  }
  .file-prev-icon {
    margin-right: 10px;
  }

  .progress-box {
    position: absolute;
    left: 0;
    bottom: 0;
    width: 100%;
  }
  .hover-delete {
    display: none;
    .el-icon-close {
      color: #c1cfe0;
      cursor: pointer;
    }
    .el-icon-refresh-right {
      margin-right: 5px;
      cursor: pointer;
    }
  }
  .upload-list__item-name:hover {
    background: #eff7ff;
      .file-after-icon {
        display: none;
      }
      .hover-delete {
        display: block;
      }
  }
  .file-after-icon {
    font-size: 12px;
    .el-icon-success {
      color: #7cd4ab;
    }
    .el-icon-error {
      color: #f66969;
    }
  }
  .upload-list__container {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-column-gap: 20px;
  }
</style>

测试代码

<template>
  <div>
    <div>jobTest</div>
    <div style="width: 800px;">
      <CommonUpload
        action="/api/upload"
        multiple
        ref="upload-outer"
        :attach-list.sync="attachList"
      >
      </CommonUpload>
    </div>
  </div>
</template>

<script>
import CommonUpload from '@/components/CommonUpload'
export default {
  name: 'JobTest',
  components: { CommonUpload },
  data() {
    return {
      // 附件列表格式
      attachList: [
        // {
        //   uid: 1000,
        //   name: 'vueyuanma',
        //   raw: 'file',
        //   response: null,
        //   status: 'success'
        // },
        // {
        //   uid: 1001,
        //   name: '哈哈',
        //   raw: 'file',
        //   response: null,
        //   status: 'success'
        // }
      ]
    }
  },
  methods: {}
}
</script>

<style scoped>

</style>