Vue+elelemtUI實現斷點續傳(前端)


一、在config->index.js中設置 proxy

1 devServer: {
2         proxy: {
3             '/': {
4                 target: 'http://127.0.0.1:8888',
5                 changeOrigin: true
6             }
7         }
8     }

 

二、下載並引用相應依賴 (main.js)

  本次demo是利用 Element-ui 配合開發。

  所需依賴(我的版本):

    "element-ui": "^2.13.2",

    "axios": "^0.19.2",

    "spark-md5": "^3.0.1",

    "qs": "^6.9.4",

   如果嫌一個一個下載麻煩,可以把上面內容復制到 package.json 中 “dependencies” ,執行  "npm install" 即可。

  main.js引入所需包:

1 import ElementUI from 'element-ui';
2 import 'element-ui/lib/theme-chalk/index.css';
3 Vue.use(ElementUI);

  App.vue中引入所需包:

  

1 import { fileParse } from "./assets/utils";
2 import axios from "axios";
3 import SparkMD5 from "spark-md5";

 

三、HTML部分

  使用Element-UI實現前端樣式展示

 1 <template>
 2   <div id="app">
 3     <el-upload drag action :auto-upload="false" :show-file-list="false" :on-change="changeFile">
 4       <i class="el-icon-upload"></i>
 5       <div class="el-upload__text">
 6         將文件拖到此處,或
 7         <em>點擊上傳</em>
 8       </div>
 9     </el-upload>
10 
11     <!-- PROGRESS -->
12     <div class="progress">
13       <span>上傳進度:{{total|totalText}}%</span>
14       <el-link type="primary" v-if="total>0 && total<100" @click="handleBtn">{{btn|btnText}}</el-link>
15     </div>
16 
17     <!-- VIDEO -->
18     <div class="uploadImg" v-if="video">
19       <video :src="video" controls />
20     </div>
21   </div>
22 </template>

 

四、使用 promise 封裝 fileParse方法

    promise:promise構造函數是同步執行的,並且是立即執行的函數,promise.then中的函數是異步的。並且promise狀態改變后將不會再更改。

    Promise有三個狀態:pending(等待)、fulfilled(實現)、rejected(拒絕),其中resolvereject只有第一次執行有效。

  utils.js(對文件轉義形式進行封裝,根據所傳入參數確定封裝格式【base64、buffer】)

 1 export function fileParse(file, type = "base64") {
 2     return new Promise(resolve => {
 3         let fileRead = new FileReader();
 4         if (type === "base64") {
 5             fileRead.readAsDataURL(file);
 6         } else if (type === "buffer") {
 7             fileRead.readAsArrayBuffer(file);
 8         }
 9         fileRead.onload = (ev) => {
10             resolve(ev.target.result);
11         };
12     });
13 };

 

 

 

 

五、邏輯流程(前端)

   

  1、利用結合promise封裝filepase方法,解析文件為buffer數據

  2、使用sparkMD5生成文件的哈希值,並獲取后綴;使用spark.append(buffer)生成哈希值

  3、通過正則獲取文件后綴

  4、創建切面默認參數,包括:切片個數、索引值和結束值

  5、通過flie.slice()進行切片

  6、進行遍歷上傳切片,判斷當前索引  >=  切片數組后停止,調用合並文件接口告知服務器進行合並

  

六、項目整體代碼

  1 <script>
  2 import { fileParse } from "./assets/utils";
  3 import axios from "axios";
  4 import SparkMD5 from "spark-md5";
  5 
  6 export default {
  7   name: "App",
  8   data() {
  9     return {
 10       total: 0,
 11       video: null,
 12       btn: false,
 13     };
 14   },
 15   filters: {
 16     btnText(btn) {
 17       return btn ? "繼續" : "暫停";
 18     },
 19     totalText(total) {
 20       return total > 100 ? 100 : total;
 21     },
 22   },
 23   methods: {
 24     async changeFile(file) {
 25       if (!file) return;
 26       file = file.raw;
 27 
 28       // 解析為BUFFER數據
 29       // 我們會把文件切片處理:把一個文件分割成為好幾個部分(固定數量/固定大小)
 30       // 每一個切片有自己的部分數據和自己的名字
 31       // HASH_1.mp4
 32       // HASH_2.mp4
 33       // ...
 34       let buffer = await fileParse(file, "buffer"),
 35         spark = new SparkMD5.ArrayBuffer(),
 36         hash,
 37         suffix;
 38       spark.append(buffer);
 39       hash = spark.end();
 40       suffix = /\.([0-9a-zA-Z]+)$/i.exec(file.name)[1];
 41 
 42       // 創建100個切片
 43       let partList = [],
 44         partsize = file.size / 100,
 45         cur = 0;
 46       for (let i = 0; i < 100; i++) {
 47         let item = {
 48           chunk: file.slice(cur, cur + partsize),
 49           filename: `${hash}_${i}.${suffix}`,
 50         };
 51         cur += partsize;
 52         partList.push(item);
 53       }
 54 
 55       this.partList = partList;
 56       this.hash = hash;
 57       this.sendRequest();
 58     },
 59     async sendRequest() {
 60       // 根據100個切片創造100個請求(集合)
 61       let requestList = [];
 62       this.partList.forEach((item, index) => {
 63         // 每一個函數都是發送一個切片的請求
 64         let fn = () => {
 65           let formData = new FormData();
 66           formData.append("chunk", item.chunk);
 67           formData.append("filename", item.filename);
 68           return axios
 69             .post("/single3", formData, {
 70               headers: { "Content-Type": "multipart/form-data" },
 71             })
 72             .then((result) => {
 73               result = result.data;
 74               if (result.code == 0) {
 75                 this.total += 1;
 76                 // 傳完的切片我們把它移除掉
 77                 this.partList.splice(index, 1);
 78               }
 79             });
 80         };
 81         requestList.push(fn);
 82       });
 83 
 84       // 傳遞:並行(ajax.abort())/串行(基於標志控制不發送)
 85       let i = 0;
 86       let complete = async () => {
 87         let result = await axios.get("/merge", {
 88           params: {
 89             hash: this.hash,
 90           },
 91         });
 92         result = result.data;
 93         if (result.code == 0) {
 94           this.video = result.path;
 95         }
 96       };
 97       let send = async () => {
 98         // 已經中斷則不再上傳
 99         if (this.abort) return;
100         if (i >= requestList.length) {
101           // 都傳完了
102           complete();
103           return;
104         }
105         await requestList[i]();
106         i++;
107         send();
108       };
109       send();
110     },
111     handleBtn() {
112       if (this.btn) {
113         //斷點續傳
114         this.abort = false;
115         this.btn = false;
116         this.sendRequest();
117         return;
118       }
119       //暫停上傳
120       this.btn = true;
121       this.abort = true;
122     },
123   },
124 };
125 </script>

 

  參考資料:https://www.bilibili.com/video/BV18v411v7Xu?t=346


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM