A preface.

It is common to upload files on the Web, for example, uploading a single file or uploading a batch. Many resources on the network are for single-file operations. As a result, some front-end users with poor foundation cannot understand multi-file batch operations. Therefore, the complete case record of the actual project is as follows

preview

features

  • Support vue2
  • Based on theelement-uiComponent library el – upload
  • Support the shard
  • Support breakpoint
  • Batch upload

Install dependencies

Github address: ali-oss

npm install ali-oss
# or 
yarn add ali-oss
Copy the code

Create an upload component

Create an upload component in the SRC directory. The complete directory is as follows:

└ ─ ─ compontents ├ ─ ─ the Upload │ └ ─ ─ index. The vue │ └ ─ ─ index, jsCopy the code

3.1 Uploading the Component View

File path: SRC/compontents/Upload/index. The vue

3.1.1 encapsulationel-uploadcomponent

<template>
  <div class="oss-upload">
    <el-upload
      ref="upload"
      action
      :show-file-list="false"
      :multiple="multiple"
      :on-change="handleChange"
      :auto-upload="false"
      :accept="accept"
     >
      <el-button type="primary" icon="el-icon-upload2" round>On the</el-button>
    </el-upload>
 </div>
</template>
Copy the code

The above code requires us to pay attention to whether multiple supports the multi-file attribute. HandleChange () file selection change event,auto-upload attribute, value false disables automatic upload, accept accepts file type parameter attribute, null indicates no restriction

3.1.2 File Upload view

<template>
  <div class="oss-upload">
  <! -- El upload component code in section 3.1.1 -->
     <el-dialog
      :visible.sync="dialogVisible"
      width="650px"
      destroy-on-close
      :close-on-click-modal="false"
      :before-close="handleClose"
     >
      <div slot="title">
        <span>upload</span>
        <span class="num">
          {{ fileList.length - unList.length }}/{{ fileList.length }}
        </span>
      </div>
       <div class="dialog-head">
        <div class="head-btn">
          <el-button
            size="small"
            type="primary"
            :disabled="uploadDisabled"
            icon="el-icon-video-play"
            @click="startUpload"
          >To upload</el-button>
          <el-button
            class="item-btn"
            size="small"
            :disabled="resumeDisabled"
            icon="el-icon-refresh-right"
            type="success"
            @click="resumeUpload"
          >Continue to</el-button>
          <el-button
            class="item-btn"
            size="small"
            icon="el-icon-video-pause"
            type="danger"
            :disabled="pauseDisabled"
            @click="stopUplosd"
          >suspended</el-button>
        </div>
      </div>
      
   </el-dialog>
  </div>
</template>
Copy the code

The above code we need to pay attention to whether dialogVisible shows the upload view; Close-on-click-modal Whether you can click the mask to close the upload view; The event before the handleClose() view closes; FileList is a fileList parameter. It is used to display list data. UnList List of unfinished uploads; UploadDisabled Indicates the status of the upload button. StartUpload () starts uploading events; ResumeDisabled Restores the upload button status. ResumeUpload () continues to upload events; PauseDisabled; StopUplosd () suspend event;

3.1.3 Display view of file list

 <div class="file-list">
      <div class="file-item" v-for="(item, index) in fileList" :key="index">
          <div class="file-name">
            <div class="name">
              <span class="file-name-item">
                {{ index + 1 }}.{{ item.name }}
              </span>
              <span class="speed" v-if="item.isLoading && ! item.isPlay">ready</span>
              <span class="speed" v-if="item.isPlay && item.percentage ! = = 100">
                {{ item.speed }}/s
              </span>
              <span v-if="item.percentage === 100" class="success">complete</span>
              <div class="total">
                {{ filterSize(item.size) }}
              </div>
            </div>
            <span class="name error" v-if="item.errMsg">{{ item.errMsg }}</span>
            <el-progress
              :percentage="item.percentage"
              v-if="item.percentage < 100 && ! item.errMsg"
            ></el-progress>
            <template v-else>
              <el-progress
                :percentage="item.percentage"
                :status="item.errMsg ? 'exception' : 'success'"
              ></el-progress>
            </template>
          </div>
          <div class="tool">
            <span
              v-if=! "" item.percentage || (0 < item.percentage < 100 && ! item.isPlay) "
              class="icon delete"
              @click="handleDeleteChangeFile(index)"
            >
              <i class="el-icon-close"></i>
            </span>
            <span
              v-if="item.percentage && item.percentage ! = = 100"
              class="icon"
              :class="item.isPlay ? 'delete' : 'success'"
              @click="handleChangeFileStatus(index, item)"
            >
              <i
                :class="`el-icon-${ item.isPlay ? 'video-pause' : 'caret-right' }`"
              ></i>
            </span>
          </div>
        </div>
      </div>
Copy the code

HandleChangeFileStatus () changes the file upload event; HandleDeleteChangeFile Deletes a file.

3.1.4 CSS style

<style lang="scss" scoped>
 .file-list {
    max-height: 500px;
    overflow-y: auto;
    overflow-x: hidden;
  }
  .icon-file {
    width: 2.5 em;
    height: 2.5 em;
    vertical-align: -0.15 em;
    fill: currentColor;
    overflow: hidden;
  }
  ::v-deep {
    .el-progress-circle {
      width: 40px ! important;
      height: 40px ! important; }}.file-item {
    display: flex;
    align-content: center;
    .file-name {
      flex: 1;
      .name {
        width: 100%;
        display: flex;
        .total {
          margin-left: 20px;
        }
        // justify-content: space-between;
        .file-name-item {
          font-weight: 500;
          width: 290px;
          overflow: hidden;
          text-overflow: ellipsis;
          white-space: nowrap;
        }
        .speed {
          width: 120px;
          text-align: center;
          font-size: 13px;
          color: $base-color-default;
        }
        .success {
          text-align: center;
          width: 120px;
          color: #91cc75;
        }
        &.error {
          color: #f45;
          font-size: 12px; }}}border-bottom: 1px solid #ddd;
    padding: 15px 0;
    &:last-child {
      border-bottom: 0;
    }
    .tool {
      margin-left: 15px;
      .icon {
        display: inline-flex;
        justify-content: center;
        align-items: center;
        width: 30px;
        height: 30px;
        background-color: #eee;
        border-radius: 5px;
        margin: 0px 4px;
        cursor: pointer;
        font-size: 15px;
        color: rgb(255.68.85);
        font-weight: 600;
        &.success {
          color: #91cc75;
          background-color: #eee; }}}}.dialog-head {
    display: flex;
    justify-content: space-between;
  }
  ::v-deep {
    .el-progress-bar {
      width: 320px ! important; }}.num {
    background: # 515256a8;
    padding: 2px 8px;
    border-radius: 4px;
    margin-left: 5px;
    color: #fff;
  }
</style>
Copy the code

3.2 Component Parameter/event handling

File path: SRC/compontents/Upload/index, js

3.2.1 props/data

export default {
  props: {
    // Accept uploaded file types, default all
    accept: {
      type: String.default: ' ',},// Whether multiple file uploads are supported by default
    multiple: {
      type: Boolean.default: true,},/ / binding values
    value: {
      type: Array.default: () = > {
        return[]}},},data() {
    return {
      unList: [].// The list is not uploaded
      fileList: [].// List of files
      file: null.// File information
      uploadDisabled: true.// Upload button status
      resumeDisabled: true.// Restore the upload button status
      pauseDisabled: true.// Pause the upload button state
      partSize: 1024 * 1024.// Fragment size
      parallel: 4.// Number of concurrent requests
      checkpoints: {}, // Fragment information
      credentials: null.// oss
      fileMap: {},
      map_max_key: 0,}},Copy the code

3.2.2 handleChangeThe event processing

methods: {
     / * * *@description Select file event *@param {*} File File information *@param {*} FileList List of files */
    handleChange(file, fileList) {
       
      fileList.forEach((item) = > {
        item.client = null // Initialize oss to be able to control single files individually
        item.isPlay = false // Whether to start the control on state
        item.isLoading = false // Is in the ready state
        item.abortCheckpoint = false // Whether to fragment
      })
      this.fileList = fileList
      this.file = file.raw
      this.uploadDisabled = false // Upload is enabled by default
      this.pauseDisabled = this.resumeDisabled = true // Close the pause recovery button}},Copy the code

3.2.3 fileListMonitor process

watch: {
 fileList: {
      handler(val) {
        if (val.length) {
          this.dialogVisible = true
          let list = []
          let unList = []
          val.forEach((item) = > {
            // If the upload progress does not meet 100%, the upload will be saved to the unfinished list, otherwise the list will be completed
            if (item.percentage === 100) list.push(item)
            else unList.push(item)
          })
          // Check whether all completed
          if (list.length === val.length) {
            this.pauseDisabled = true
          }
          this.unList = unList
        }
      },
      deep: true,}}Copy the code

3.2.4 handleCloseThe event processing

 handleClose() {
      // Close the event
      this.$emit('on-close')
      // Process the logical code of the file being uploaded, can be developed according to your own business
    },
Copy the code

3.2.5 startUploadStart upload event

import request from '@/utils/request'
let OSS = require('ali-oss')

/ * * *@description Click Upload to server */
startUpload() {
  this.uploadDisabled = true
  this.pauseDisabled = false
  / / upload
  this.multipartUpload()
},
    
/ * * *@description Slice upload */
async multipartUpload() {
  if (!this.file) {
    this.$message.error('Please select file')
    return
  }

  this.fileList.forEach(async (item) => {
    // Set the ready state
    item.isLoading = true
    // Obtain the OSS temporary certificate
    const getOssRes = await this.getOss()
    const { AccessKeyId, AccessKeySecret, SecurityToken } = this.credentials
    // Initialize the file oss
    item.client = new OSS({
      accessKeyId: AccessKeyId,
      accessKeySecret: AccessKeySecret,
      stsToken: SecurityToken,
      bucket: 'buckle-pan'.region: 'oss-cn-hangzhou',})if(! getOssRes.pass) {return this.$message.error('Failed to obtain oss upload certificate')}// 
    await this.ossUpload(item, this.fileList)
  })
},

/ * * *@description Gets the current date *@returns Returns the current date */
getToday() {
  const date = new Date(a)return `${date.getFullYear()}${
    date.getMonth() + 1
  }${date.getDate()}${date.getHours()}${date.getMinutes()}${date.getSeconds()}`
},

Copy the code

3.2.6 Obtaining oss Temporary Credentials

/ * * *@description Get a temporary certificate */
async getOss() {
  let res = await request({
    url: '/StsToken'.// Obtain the OSS temporary credential interface and modify it according to your own configuration
  })
  let isPass = {
    pass: true,}if (res.status === 200) {
    this.credentials = res.data
  } else{ isPass = { ... res,pass: false}}return isPass
},
Copy the code

3.2.7 Uploading data to OSS

/ * * *@description Upload to OSS *@param {*} Item File information *@param {*} fileList
 * @returns* /
async ossUpload(item, fileList) {
  let isPass = {
    pass: true.filePath: ' ',}try {
    const { raw, percentage } = item
    // Initialize the file size
    item.partSize = 0
    // Check whether the upload progress is less than 100
    if (percentage < 100) {
      const file = raw
      const time = this.getToday()
      const path = time + file.name
      await item.client
        .multipartUpload(path, file, {
          parallel: this.parallel,
          partSize: this.partSize,
          progress: async (p, checkpoint, res) => {
            await this.onUploadProgress(item, p, checkpoint, res, path)
          },
        })
        .then(({ res }) = > {
          this.$emit('input'.this.fileList)
          this.resumeDisabled = true
          if (this.unList.length && this.uploadDisabled)
            this.resumeDisabled = false
        })
        .catch(async (err) => {
          await this.resetUpload(err, item)
        })
    }
  } catch (e) {
    // Upload failedisPass = { ... e,pass: false.filePath: ' ',}}// Upload success returns Filepath
  return isPass
},
Copy the code

3.2.8 OSS Upload Progress

/ * * *@description Upload progress */
async onUploadProgress(item, p, checkpoint, res, path) {
  if (checkpoint) {
    this.checkpoints[checkpoint.uploadId] = checkpoint
    item.speed = this.handle_network_speed(res, this.partSize, p)
    item.tempCheckpoint = checkpoint
    item.abortCheckpoint = checkpoint
    item.upload = checkpoint.uploadId
  }
  // Change the upload status
  item.isPlay = true
  // Change the ready state
  if (item.isPlay) item.isLoading = false

  item.uploadName = path
  // Upload progress
  item.percentage = Number((p * 100).toFixed(2))},Copy the code

3.2.9 Obtaining the Upload Speed

async change(i, value) {
  this.fileMap[i] = value
  this.map_max_key = i
},

async handle_network_speed_change(start_time, end_time, network_speed) {
  // If no data is transmitted for more than 10 seconds, clear the map
  if (start_time - this.map_max_key >= 10000) {
    this.fileMap = {}
  }
  for (let i = start_time; i <= end_time; i++) {
    const value = await this.fileMap[i]
    if (value) {
      await change(i, value + network_speed)
    } else {
      await change(i, network_speed)
    }
  }
},

/ * * *@description Gets the network status of the upload *@param {*} Res File information *@param {*} PartSize Fragment size *@param {*} P Upload progress *@returns Network speed network_speed */
handle_network_speed(res, partSize, p) {
  const spend_time = res.rt / 1000 //单位s
  const end_time = new Date(res.headers.date).getTime()
  const start_time = end_time - spend_time
  let network_speed = parseInt(partSize / spend_time) // The number of bytes (b) uploaded per second
  if (p === 0) network_speed = 0
  if (network_speed === 0) {
    // nothing to do
  } else {
    this.handle_network_speed_change(start_time, end_time, network_speed)
  }
  return network_speed ? filterSize(network_speed) : 0
},
Copy the code

3.3.0 File size conversion

export function filterSize(size) {
  if(! size)return The '-'
  if (size < pow1024(1)) return size + ' B'
  if (size < pow1024(2)) return (size / pow1024(1)).toFixed(2) + ' KB'
  if (size < pow1024(3)) return (size / pow1024(2)).toFixed(2) + ' MB'
  if (size < pow1024(4)) return (size / pow1024(3)).toFixed(2) + ' GB'
  return (size / pow1024(4)).toFixed(2) + ' TB'
}
Copy the code

3.3.1 resumeUploadRestore to upload

/ * * *@description Resume upload */
async resumeUpload() {
  this.pauseDisabled = false
  this.uploadDisabled = this.resumeDisabled = true
  await this.resumeMultipartUpload()
},

/ * * *@description Resume upload */
async resumeMultipartUpload(item) {
  // Restore a single file
  if (item) {
    const { tempCheckpoint } = item
    this.resumeUploadFile(item, tempCheckpoint)
  } else {
    / / files
    Object.values(this.checkpoints).forEach((checkpoint) = > {
      const { uploadId } = checkpoint
      const index = this.fileList.findIndex(
        (option) = > option.upload === uploadId
      )
      const item = this.fileList[index]
      this.resumeUploadFile(item, checkpoint)
    })
  }
},

/ * * *@description Resume uploading@param {*} Item File information *@param {*} Checkpoint Fragment information */
async resumeUploadFile(item, checkpoint) {
  const { uploadId, file, name } = checkpoint
  try {
    const { raw, percentage } = item
    item.partSize = 0

    if (percentage < 100 && raw.name.indexOf('. ')! = = -1) {
      item.client
        .multipartUpload(uploadId, file, {
          parallel: this.parallel,
          partSize: this.partSize,
          progress: async (p, checkpoint, res) => {
            await this.onUploadProgress(item, p, checkpoint, res, name)
          },
          checkpoint,
        })
        .then((result) = > {
          delete this.checkpoints[checkpoint.uploadId]
          this.$emit('input'.this.fileList)
          this.resumeDisabled = true
          if (this.unList.length && this.uploadDisabled)
            this.resumeDisabled = false
        })
        .catch(async (err) => {
          await this.resetUpload(err, item)
        })
    }
  } catch {
    console.log('---err---')}},Copy the code

3.3.2 rainfall distribution on 10-12stopUplosdSuspend events

/ * * *@description Suspend sharding upload */
stopUplosd() {
  this.resumeDisabled = false
  this.pauseDisabled = true
  // window.removeEventListener('online', this.resumeUpload)
  // let result = this.client.cancel()
  this.fileList.forEach((item) = > {
    item.client.cancel()
    item.isPlay = false})},Copy the code

3.3.3 handleChangeFileStatusChanged the file upload event

 handleStopChangeFile(index, item){ item.isPlay = ! item.isPlaythis.fileList.splice(index, 1, item)
      if(! item.isPlay) item.client.cancel()else this.resumeMultipartUpload(item)
    },
Copy the code

We dohandleDeleteChangeFileDelete events

 handleDeleteChangeFile(index) {
      this.fileList.splice(index, 1)
      if (!this.fileList.length) this.dialogVisible = false
      // Can be developed by oneself
    },
Copy the code

All the above upload operations have been completed