您好,登錄后才能下訂單哦!
這篇文章主要講解了“如何使用Vue3和ElementPlus前端實(shí)現(xiàn)分片上傳”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“如何使用Vue3和ElementPlus前端實(shí)現(xiàn)分片上傳”吧!
將 一個(gè)文件 切割為 一系列特定大小的 數(shù)據(jù)片段,將這些 數(shù)據(jù)片段 分別上傳到服務(wù)端;
全部上傳完成后,再由服務(wù)端將這些 數(shù)據(jù)片段 合并成為一個(gè)完整的資源;
上傳過(guò)程中,由于外部因素(比如網(wǎng)絡(luò)波動(dòng))導(dǎo)致上傳中斷,下次上傳時(shí)會(huì)保留該文件的上傳進(jìn)度(斷點(diǎn)續(xù)傳);
包含三部分:
上傳組件,使用 el-upload
進(jìn)度條組件,使用 el-progress
上傳完成狀態(tài)組件,使用 el-input 自定義
<el-form-item label="上傳附件" prop="uploadFile"> <el-upload v-if="!editForm.inlineAppVersionModel.fileName" class="upload-demo" drag :show-file-list="false" :action="APP_MANAGEMENT.uploadFile" // 根據(jù)項(xiàng)目的接口傳遞參數(shù) :data="{ applicationId: applicationId, applicationVersion: applicationVersion, bucketName: 'app' }" // 覆蓋默認(rèn)的http請(qǐng)求 :http-request="handleFileUpload" > <el-icon class="el-icon--upload"> <upload-filled /> </el-icon> <div v-if="!progress" class="el-upload__text"> Drop file here or <em>click to upload</em> </div> // 進(jìn)度條 <el-progress v-else :text-inside="true" :stroke-width="24" :percentage="progress" status="success" /> </el-upload> // 上傳成功之后隱藏上傳文件組件 <div v-else > <el-input v-model="editForm.inlineAppVersionModel.fileName" readonly> </el-input> <div > <el-button type="primary" :icon="Download" size="small" @click="handleFileDownload" /> <el-button type="primary" :icon="Delete" size="small" @click="handleFileDel" /> </div> </div> </el-form-item>
使用 el-upload 選擇文件
選擇成功的 回調(diào)函數(shù) 可以讀取文件信息,用于前端校驗(yàn)文件的合法性
前端校驗(yàn)文件合法后,將文件進(jìn)行切片
通過(guò) 請(qǐng)求輪詢(xún) 把切片傳遞給后端
在這一步,可以獲得文件信息
根據(jù)文件信息,對(duì)文件進(jìn)行合法性校驗(yàn)
校驗(yàn)成功后,調(diào)用文件切片方法
/** * @description: 選擇上傳文件 * @param file el-upload 返回的參數(shù) */ const handleFileUpload = async (file: any) => { console.log('el-upload 返回的參數(shù) === ', file.file); // 如果文件合法,則進(jìn)行分片上傳 if (await checkMirrorFile(file)) { // 文件信息 const files = file.file; // 從 0 開(kāi)始的切片 const shardIndex = 0; // 調(diào)用 文件切片 方法 uploadFileSilce(files, shardIndex); // 文件非法,則進(jìn)行提示 } else { ElMessage.error('請(qǐng)檢查文件是否合法!'); } };
校驗(yàn)文件格式
校驗(yàn)文件大小
調(diào)用接口,校驗(yàn)磁盤(pán)剩余空間大小
/** * @description: 校驗(yàn)文件合法性 */ const checkMirrorFile = async (file) => { // 校驗(yàn)文件格式,支持.zip/.tar const fileType = file.file.name.split('.') if (fileType[fileType.length - 1] !== 'zip' && fileType[fileType.length - 1] !== 'tar') { ElMessage.warning('文件格式錯(cuò)誤,僅支持 .zip/.tar') return false } // 校驗(yàn)文件大小 const fileSize = file.file.size; // 文件大小是否超出 2G if (fileSize > 2 * 1024 * 1024 * 1024) { ElMessage.warning('上傳文件大小不能超過(guò) 2G') return false } // 調(diào)用接口校驗(yàn)文件合法性,比如判斷磁盤(pán)空間大小是否足夠 const res = await checkMirrorFileApi() if (res.code !== 200) { ElMessage.warning('暫時(shí)無(wú)法查看磁盤(pán)可用空間,請(qǐng)重試') return false } // 查看磁盤(pán)容量大小 if (res.data.diskDevInfos && res.data.diskDevInfos.length > 0) { let saveSize = 0 res.data.diskDevInfos.forEach(i => { // 磁盤(pán)空間賦值 if (i.devName === '/dev/mapper/centos-root') { // 返回值為GB,轉(zhuǎn)為字節(jié)B saveSize = i.free * 1024 * 1024 * 1024 } }) // 上傳的文件大小沒(méi)有超出磁盤(pán)可用空間 if (fileSize < saveSize) { return true } else { ElMessage.warning('文件大小超出磁盤(pán)可用空間容量') return false } } else { ElMessage.warning('文件大小超出磁盤(pán)可用空間容量') return false } }
此處文件上傳用 MD5 進(jìn)行加密,需要安裝依賴(lài) spark-md5
npm i spark-md5
/** * @description: 文件加密處理 */ const getMD5 = (file: any): Promise<string> => new Promise((resolve, reject) => { const spark = new SparkMD5.ArrayBuffer(); // 獲取文件二進(jìn)制數(shù)據(jù) const fileReader = new FileReader(); fileReader.readAsArrayBuffer(file); // file 就是獲取到的文件 // 異步執(zhí)行函數(shù) fileReader.addEventListener('load', (e: any) => { spark.append(e.target.result); const md5: string = spark.end(); resolve(md5); }); fileReader.addEventListener('error', (e) => { reject(e); }); });
通過(guò)接口合并上傳文件,接口需要的參數(shù):
文件名
文件唯一 hash 值
接口合并完成后,前端展示已上傳的文件名稱(chēng)
/** * @description: 合并文件 * @param name 文件名 * @param hash 文件唯一 hash 值 * @return 命名名稱(chēng) */ const composeFile = async (name: string, hash: string) => { console.log('開(kāi)始文件合并'); const res = await uploadFileMerge({ applicationId: props.applicationId, applicationVersion: props.applicationVersion, bucketName: 'app', fileName: name, hash, }); console.log('后端接口合并文件 ===', res); if (res.status === 200 && res.data.code) { // 合并成功后,調(diào)整已上傳的文件名稱(chēng) state.editForm.inlineAppVersionModel.fileName = name; } };
接口輪詢(xún) —— 每次攜帶一個(gè)文件切片給后端;后端接受到切片 并 返回成功狀態(tài)碼后,再進(jìn)行下一次切片上傳
/** * @description: 分片函數(shù) * @param file 文件 * @param shardIndex 分片數(shù)量 */ const uploadFileSilce = async (file: File, shardIndex: number) => { // 文件名 const { name } = file; // 文件大小 const { size } = file; // 分片大小 const shardSize = 1024 * 1024 * 5; // 文件加密 const hash: string = await getMD5(file); // 分片總數(shù) const shardTotal = Math.ceil(size / shardSize); // 如果 當(dāng)前分片索引 大于 總分片數(shù) if (shardIndex >= shardTotal) { isAlive.value = false; progress.value = 100; // 合并文件 composeFile(name, hash); return; } // 文件開(kāi)始結(jié)束的位置 const start = shardIndex * shardSize; const end = Math.min(start + shardSize, size); // 開(kāi)始切割 const packet = file.slice(start, end); // 拼接請(qǐng)求參數(shù) const formData = new FormData(); formData.append('file', packet); formData.append('applicationId', props.applicationId); formData.append('applicationVersion', props.applicationVersion); formData.append('bucketName', 'app'); formData.append('hash', hash); formData.append('shardSize', shardSize as unknown as string); formData.append('seq', shardIndex as unknown as string); // 如果 當(dāng)前分片索引 小于 總分片數(shù) if (shardIndex < shardTotal) { // 進(jìn)度條保留兩位小數(shù)展示 progress.value = Number(((shardIndex / shardTotal) * 100).toFixed(2)) * 1; // 調(diào)用文件上傳接口 const res = await uploadFile(formData); if (res.status !== 200) { ElMessage.error('上傳失敗'); progress.value = 0; return; } if (res.status === 200 && res.data.code === 200) { // 這里為所有切片上傳成功后進(jìn)行的操作 console.log('上傳成功'); } // eslint-disable-next-line no-param-reassign shardIndex++; // 遞歸調(diào)用 分片函數(shù) uploadFileSilce(file, shardIndex); } };
nginx 默認(rèn)上傳大小為 1MB,若超過(guò) 1MB,則需要修改 nginx 配置 解除上傳限制
/** * @description: 動(dòng)態(tài)創(chuàng)建 a 標(biāo)簽,實(shí)現(xiàn)大文件下載 */ const downloadMirror = async (item) => { let t = { id: item.id, } const res = await downloadMirrorApi(t) if (res.headers["content-disposition"]) { let temp = res.headers["content-disposition"].split(";")[1].split("filename=")[1] let fileName = decodeURIComponent(temp) // 通過(guò)創(chuàng)建a標(biāo)簽實(shí)現(xiàn)文件下載 let link = document.createElement('a') link.download = fileName link.style.display = 'none' link.href = res.data.msg document.body.appendChild(link) link.click() document.body.removeChild(link) } else { ElMessage({ message: '該文件不存在', type: 'warning', }) } }
感謝各位的閱讀,以上就是“如何使用Vue3和ElementPlus前端實(shí)現(xiàn)分片上傳”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)如何使用Vue3和ElementPlus前端實(shí)現(xiàn)分片上傳這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。