Implementing OSS Multipart Upload with Cancellation in Vue2 + Element-UI

The project requires uploading videos larger than 500MB. Initially, I used the el-upload component calling a backend API, but encountered an issue: the onprogress event showed 100% completion, yet the API request remained in pending state for a long time. For large files, the API request would timeout.

Solution

Implement Aliyun OSS multipart upload. When calling OSS, you may encounter a No 'Access-Control-Allow-Origin' error.

Important: You must configure CORS rules in the OSS console! Otherwise, this error will occur.

If security settings prevent the above configuration, add your project's production and testing anvironments to the whitelist instead.

Implementation Code

First, install the package: npm install --save ali-oss

1. Create oss.js configuration file

Note: While accessKeyId and accessKeySecret are directly written in the JS file here for demonstration, there are security risks. In production, retrieve these credentials through a secure backend API.

let OSS = require('ali-oss');

let client = new OSS({
  region: '', // Specify the Bucket region. For East China 1 (Hangzhou), use oss-cn-hangzhou.
  accessKeyId: '',
  accessKeySecret: '',
  bucket: '', // Specify the Bucket name
});

let cdnUrl = '' // Callback URL after file upload

export { client, cdnUrl };

2. Create MediaUpload.vue component

<template>
  <div class="media-upload-wrapper">
    <el-upload 
      action="" 
      :http-request="initiateUpload" 
      class="media-uploader" 
      :limit="maxFiles"
      :on-error="handleUploadError" 
      :on-exceed="handleExceed" 
      name="file" 
      :show-file-list="false" 
      :file-list="fileList"
      ref="uploadRef">
      <video v-if="mediaForm.displayUrl && !isUploading" :src="mediaForm.displayUrl" class="media-preview video-preview" controls="controls">
        Your browser does not support video playback
      </video>
      <i v-else-if="!mediaForm.displayUrl && !isUploading" class="el-icon-plus upload-icon"></i>
      <el-progress v-if="isUploading" type="circle" :percentage="uploadProgress" style="margin-top: 7px"></el-progress>
    </el-upload>
    <el-button v-if="showResetBtn && mediaForm.displayUrl" class="mt-20" plain round @click="resetUpload" size="small" type="primary">Re-upload<i class="el-icon-upload el-icon--right"></i></el-button>
  </div>
</template>

<script>
import { client, cdnUrl } from "./oss";

export default {
  props: {
    value: [String, Object, Array],
    maxFiles: {
      type: Number,
      default: 1,
    },
    fileSizeLimit: {
      type: Number,
      default: 5120,
    },
    allowedTypes: {
      type: Array,
      default: () => ["video/*"],
    },
    showHint: {
      type: Boolean,
      default: true,
    },
    enableProgress: {
      type: Boolean,
      default: false,
    },
    showResetBtn: {
      type: Boolean,
      default: true,
    },
  },
  data() {
    return {
      dialogImageUrl: "",
      dialogVisible: false,
      fileList: [],
      mediaForm: {
        displayUrl: "",
      },
      isUploading: false,
      uploadProgress: 0,
      isCancelled: false
    };
  },
  watch: {
    value: {
      handler(val) {
        if (val) {
          this.mediaForm.displayUrl = val;
          const list = Array.isArray(val) ? val : this.value.split(",");
          this.fileList = list.map((item) => {
            if (typeof item === "string") {
              item = { name: item, url: item };
            }
            return item;
          });
        } else {
          this.fileList = [];
          return [];
        }
      },
      deep: true,
      immediate: true,
    },
  },
  computed: {
    showHintText() {
      return this.showHint && (this.allowedTypes || this.fileSizeLimit);
    },
  },
  methods: {
    performUpload(file) {
      this.isCancelled = false;
      const extensionIndex = file.file.name.lastIndexOf(".");
      const extension = file.file.name.substring(extensionIndex + 1);
      const storagePath = "media/" + file.file.uid + "." + extension;
      
      const trackProgress = (percentage, checkpoint) => {
        this.isUploading = true;
        this.uploadProgress = Number((Number(percentage) * 100).toFixed(1));
        console.log(this.isCancelled);
        if (this.isCancelled) {
          console.log("Upload cancelled");
          client.cancel();
        }
      };
      
      client.multipartUpload(storagePath, file.file, {
        progress: trackProgress,
        partSize: 5 * 1024 * 1024,
      }).then((response) => {
        this.isUploading = false;
        if (response.name) {
          this.mediaForm.displayUrl = cdnUrl + response.name;
          this.$emit("input", this.mediaForm.displayUrl, this.duration);
        } else {
          this.$modal.msgError("Upload failed, please retry");
          this.resetUpload();
        }
      })
        .catch((error) => {
          console.log(error);
          if (error.name === 'cancel') {
            this.$message('Upload cancelled');
          } else {
            this.$modal.msgError(error);
          }
          this.resetUpload();
        });
    },
    resetUpload() {
      this.isCancelled = true;
      this.isUploading = false;
      this.$refs.uploadRef.clearFiles();
      this.duration = 0;
      this.mediaForm.displayUrl = "";
      this.$emit("input", this.mediaForm.displayUrl, this.duration);
    },
    initiateUpload(file) {
      const fileSizeValid = file.file.size / 1024 / 1024 < this.fileSizeLimit;
      console.log(file.file.type);
      if (this.allowedTypes.indexOf(file.file.type) == -1) {
        this.$modal.msgError(
          `Invalid file format. Please upload ${this.allowedTypes.join("/")} video format!`
        );
        return false;
      }
      if (!fileSizeValid) {
        this.$modal.msgError(`Upload size cannot exceed ${this.fileSizeLimit} MB!`);
        return false;
      }
      const objectUrl = URL.createObjectURL(file.file);
      const audioElement = new Audio(objectUrl);
      let time;
      const that = this;
      audioElement.addEventListener("loadedmetadata", function () {
        time = audioElement.duration;
        that.duration = time;
      });
      this.performUpload(file);
    },
    handleExceed() {
      this.$message.error(`Upload count cannot exceed ${this.maxFiles} file(s)!`);
    },
    handleUploadError() {
      this.$modal.msgError("Upload failed, please retry");
    },
  },
};
</script>
<style scoped lang="scss">
::v-deep .el-upload--picture-card {
  width: 104px;
  height: 104px;
  line-height: 104px;
}

::v-deep .el-upload-list--picture-card .el-upload-list__item {
  width: 104px;
  height: 104px;
}

.upload-icon {
  border: 1px dashed #d9d9d9 !important;
}

.media-uploader .el-upload {
  border: 1px dashed #d9d9d9 !important;
  border-radius: 6px !important;
  position: relative !important;
  overflow: hidden !important;
}

.media-uploader .el-upload:hover {
  border: 1px dashed #d9d9d9 !important;
  border-color: #409eff;
}

.upload-icon {
  font-size: 28px;
  color: #8c939d;
  width: 300px;
  height: 178px;
  line-height: 178px;
  text-align: center;
}

.media-preview {
  width: 300px;
  height: 178px;
  display: block;
}
</style>

3. Usage in parent component

<template>
  <div class="app-container">
    <el-dialog title="" :visible.sync="isDialogOpen" append-to-body @close="closeDialog">
      <MediaUpload 
        v-model="formData.ossUrl" 
        :maxFiles="1" 
        :allowedTypes="fileType" 
        :fileSizeLimit="5120" 
        :enableProgress="true" 
        @input="handleVideoUpload" 
        ref="mediaUploadRef" 
        :showResetBtn="true">
      </MediaUpload>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="confirmSubmit">Confirm</el-button>
        <el-button @click="closeDialog">Cancel</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isDialogOpen: false,
      formData: {
        ossUrl: "",
      },
      fileType: ["video/mp4"],
    };
  },
  methods: {
    confirmSubmit() {},
    closeDialog() {
      if (this.$refs.mediaUploadRef) {
        this.$refs.mediaUploadRef.resetUpload();
      }
      this.isDialogOpen = false;
    },
  },
};
</script>

Posted on Tue, 12 May 2026 22:56:29 +0000 by phpdeveloper82