vue大檔案斷點續傳
阿新 • • 發佈:2021-12-15
<template> <div id="app"> <!-- 上傳元件 --> <el-upload action drag :auto-upload="false" :show-file-list="false" :on-change="handleChange"> <i class="el-icon-upload"></i> <div class="el-upload__text">將檔案拖到此處,或<em>點選上傳</em></div> <div class="el-upload__tip" slot="tip">大小不超過 200M 的視訊</div> </el-upload> <!-- 進度顯示 --> <div class="progress-box"> <span>上傳進度:{{ percent.toFixed() }}%</span> <el-button type="primary" size="mini" @click="handleClickBtn">{{ upload | btnTextFilter}}</el-button> </div> <!-- 展示上傳成功的視訊 --> <div v-if="videoUrl"> <video :src="videoUrl" controls /> </div> </div> </template> <script> import SparkMD5 from "spark-md5" import axios from "axios" export default { name: 'App3', filters: { btnTextFilter(val) { return val ? '暫停' : '繼續' } }, data() { return { percent: 0, videoUrl: '', upload: true, percentCount: 0 } }, methods: { async handleChange(file) { if (!file) return this.percent = 0 this.videoUrl = '' // 獲取檔案並轉成 ArrayBuffer 物件 const fileObj = file.raw let buffer try { buffer = await this.fileToBuffer(fileObj) } catch (e) { console.log(e) } // 將檔案按固定大小(2M)進行切片,注意此處同時聲明瞭多個常量 const chunkSize = 2097152, chunkList = [], // 儲存所有切片的陣列 chunkListLength = Math.ceil(fileObj.size / chunkSize), // 計算總共多個切片 suffix = /\.([0-9A-z]+)$/.exec(fileObj.name)[1] // 檔案字尾名 // 根據檔案內容生成 hash 值 const spark = new SparkMD5.ArrayBuffer() spark.append(buffer) const hash = spark.end() // 生成切片,這裡後端要求傳遞的引數為位元組資料塊(chunk)和每個資料塊的檔名(fileName) let curChunk = 0 // 切片時的初始位置 for (let i = 0; i < chunkListLength; i++) { const item = { chunk: fileObj.slice(curChunk, curChunk + chunkSize), fileName: `${hash}_${i}.${suffix}` // 檔名規則按照 hash_1.jpg 命名 } curChunk += chunkSize chunkList.push(item) } this.chunkList = chunkList // sendRequest 要用到 this.hash = hash // sendRequest 要用到 this.sendRequest() }, // 傳送請求 sendRequest() { const requestList = [] // 請求集合 this.chunkList.forEach((item, index) => { const fn = () => { const formData = new FormData() formData.append('chunk', item.chunk) formData.append('filename', item.fileName) return axios({ url: '/single3', method: 'post', headers: { 'Content-Type': 'multipart/form-data' }, data: formData }).then(res => { if (res.data.code === 0) { // 成功 if (this.percentCount === 0) { // 避免上傳成功後會刪除切片改變 chunkList 的長度影響到 percentCount 的值 this.percentCount = 100 / this.chunkList.length } this.percent += this.percentCount // 改變進度 this.chunkList.splice(index, 1) // 一旦上傳成功就刪除這一個 chunk,方便斷點續傳 } }) } requestList.push(fn) }) let i = 0 // 記錄傳送的請求個數 // 檔案切片全部發送完畢後,需要請求 '/merge' 介面,把檔案的 hash 傳遞給伺服器 const complete = () => { axios({ url: '/merge', method: 'get', params: { hash: this.hash } }).then(res => { if (res.data.code === 0) { // 請求傳送成功 this.videoUrl = res.data.path } }) } const send = async () => { if (!this.upload) return if (i >= requestList.length) { // 傳送完畢 complete() return } await requestList[i]() i++ send() } send() // 傳送請求 }, // 按下暫停按鈕 handleClickBtn() { this.upload = !this.upload // 如果不暫停則繼續上傳 if (this.upload) this.sendRequest() }, // 將 File 物件轉為 ArrayBuffer fileToBuffer(file) { return new Promise((resolve, reject) => { const fr = new FileReader() fr.onload = e => { resolve(e.target.result) } fr.readAsArrayBuffer(file) fr.onerror = () => { reject(new Error('轉換檔案格式發生錯誤')) } }) } } } </script> <style scoped> .progress-box { box-sizing: border-box; width: 360px; display: flex; justify-content: space-between; align-items: center; margin-top: 10px; padding: 8px 10px; background-color: #ecf5ff; font-size: 14px; border-radius: 4px; } </style>
連結:https://juejin.cn/post/6977555547570569223
來源:稀土掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。