Vue简易上传组件

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wcghhk/article/details/86132801

一直找不到合适的上传插件,要么和其他插件有冲突,要么文件类型不支持......。干脆自己写一个。

先上代码

<template>
  <div class="upload">
    <section class="list">
        <transition-group tag="ul" name="slide">
          <li v-for="(item, index) of fileList" :key="item.id">
            <div class="left">
              <img :src="item.preview" alt="">
            </div>
            <div class="center">
              <span>{{item.name}}</span>
              <el-progress :percentage="item.progress"
                v-show="item.progress !== undefined && item.progress !== 100"></el-progress>
            </div>
            <div class="right" @click="deleteMe(item, index)">
              <i class="iconfont icon-cancel"></i>
            </div>
          </li>
        </transition-group>
    </section>
    <footer class="add">
      <div class="layer">
        <i class="iconfont icon-Plus"></i>
        添加附件
      </div>
      <input type="file" ref="file" name="" id=""
        @change="selectFile" :accept="fileType">
    </footer>
    <div class="tip">
      支持以下类型文件:
      <p>{{fileType.join(', ')}}</p>
    </div>
  </div>
</template>

<script>
import axios from 'axios'
export default {
  name: 'upload',
  props: {
    file: String,
    name: String
  },
  data () {
    return {
      fileType: ['.png', '.jpg', '.jpeg', '.gif', '.mp4', '.xls', '.xlsx', '.doc', '.docx', '.ppt', '.pptx', '.pdf'],
      fileList: [],
      others: { // 其他办公文件预览图
        doc: require('@/assets/imgs/word.png'),
        docx: require('@/assets/imgs/word.png'),
        ppt: require('@/assets/imgs/ppt.png'),
        pptx: require('@/assets/imgs/ppt.png'),
        xls: require('@/assets/imgs/excel.png'),
        xlsx: require('@/assets/imgs/excel.png'),
        pdf: require('@/assets/imgs/pdf.png')
      },
      /**
      * 用于生成列表唯一的ID,由于transition-group不支持index作为key(不唯一)
      */
      count: 0,
      maxSize: 100 * 1000 * 1000
    }
  },
  methods: {
    selectFile () {
      let file = this.$refs.file.files[0]
      if (!file) return
      if (file.size > this.maxSize) {
        alert('文件太大')
        return
      }
      let arr = file.name.split('.')
      let type = arr[arr.length - 1]
      let item = {
        id: this.count++,
        // 生成预览图
        preview: this.others[type] || URL.createObjectURL(file),
        name: file.name,
        progress: 0,
        cancel: undefined
      }
      this.fileList.push(item)
      this.upload(file, item)
    },
    upload (file, item) {
      let data = new FormData()
      data.append('file', file)
      let config = {
        onUploadProgress: e => {
          item.progress = (100 * e.loaded / e.total) | 0
        },
        // 取消上传
        cancelToken: new axios.CancelToken(function executot (c) {
          item.cancel = c
        })
      }
      axios.post(this.uploadURL, data, config).then(res => {
        if (res.data.data) {
          item.src = res.data.data.site_url
          // 每次上传成功后向父组件更新一次list
          this.$emit('uploadSuccess', this.fileList)
        } else {
          alert('不支持此类文件上传')
        }
      }).catch(err => {
        console.log(err)
      })
    },
    deleteMe (item, index) { // 删除或取消上传
      this.fileList.splice(index, 1)
      if (item.progress !== 100 && item.progress !== undefined) {
        item.cancel()
      }
      this.$emit('uploadSuccess', this.fileList)
    }
  },
  mounted () {
    // 处理默认存在的文件列表
    if (this.file) {
      let fileArr = this.file.split('|')
      let nameArr = this.name ? this.name.split('|') : []
      this.fileList = []
      fileArr.forEach((e, i) => {
        let arr = e.split('.')
        let type = arr[arr.length - 1]
        this.fileList.push({
          id: i,
          src: e,
          preview: this.others[type] || e,
          name: nameArr[i] || e
        })
      })
      this.count = this.fileList.length
    }
  },
  destroyed () {
    // 组件上传未完成但被销毁,比如直接提交,要把上传请求取消
    this.fileList.forEach(e => {
      if (e.progress !== undefined && e.progress !== 100) {
        e.cancel && e.cancel()
      }
    })
  },
  computed: {
    uploadURL () {
      return this.$store.state.origin + '/duty/bill_data/upload'
    }
  }
}
</script>

<style lang="stylus" scoped>
@import '~styles/varibles.styl'
.slide-enter
  transform translateY(-100%)
  opacity 0
.slide-leave-active
  transform translateX(100%)
  opacity 0
.upload
  padding .2rem .4rem
  .list
    ul
      li
        display flex
        border 1px solid $baseColor
        background #ffffff
        padding .2rem
        border-radius .1rem
        margin-top .2rem
        transition .4s
        .left
          width 1rem
          height 1rem
          margin-right .3rem
          img
            width 100%
            height 100%
        .center
          flex 1
          overflow hidden
          display flex
          flex-direction column
          justify-content center
          // align-items center
          line-height 1.2
        &:first-child
          margin-top 0
  .add
    position relative
    height .8rem
    line-height .8rem
    margin-top .2rem
    input
      width 100%
      height 100%
    .layer
      pointer-events none
      position absolute
      background #ffffff
      top 0
      left 0
      width 100%
      height 100%
      text-align center
      border 1px dashed #999
      // color $baseColor
      font-size .32rem
      .iconfont
        font-size .4rem
        vertical-align middle
        font-weight bold
  .tip
    margin-top .2rem
</style>

需要注意两点:

一是vue的transition-group需要有特定的key才能正常工作,否则执行动画的总是最后一个。

二是在弱网络环境下或者上传文件过大,要根据用户操作适当取消请求,以免占用带宽。

组件基于axios,css预处理器用的stylus

猜你喜欢

转载自blog.csdn.net/wcghhk/article/details/86132801
今日推荐