欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue大文件分片上傳組件實(shí)現(xiàn)解析及關(guān)鍵代碼

 更新時(shí)間:2025年09月08日 09:17:11   作者:嘴巴嘟嘟  
在開發(fā)中,如果上傳的文件過(guò)大,可以考慮分片上傳,分片就是說(shuō)將文件拆分來(lái)進(jìn)行上傳,將各個(gè)文件的切片傳遞給后臺(tái),然后后臺(tái)再進(jìn)行合并,這篇文章主要介紹了Vue大文件分片上傳組件實(shí)現(xiàn)解析及關(guān)鍵代碼的相關(guān)資料,需要的朋友可以參考下

一、功能概述

1.1本組件基于 Vue + Element UI 實(shí)現(xiàn),主要功能特點(diǎn):

  1. 大文件分片上傳:支持 2MB 分片切割上傳
  2. 實(shí)時(shí)進(jìn)度顯示:可視化展示每個(gè)文件上傳進(jìn)度
  3. 智能格式校驗(yàn):支持文件類型、大小、特殊字符校驗(yàn)
  4. 文件預(yù)覽刪除:已上傳文件可預(yù)覽和刪除
  5. 斷點(diǎn)續(xù)傳能力:網(wǎng)絡(luò)中斷后可恢復(fù)上傳
  6. 失敗自動(dòng)重試:分片級(jí)失敗重試機(jī)制(最大3次)

用戶選擇文件 → 前端校驗(yàn) → 分片切割 → 并行上傳 → 合并確認(rèn) → 完成上傳

二、核心實(shí)現(xiàn)解析

2.1 分片上傳機(jī)制

// 分片切割邏輯
const chunkSize = 2 * 1024 * 1024 // 2MB分片
const totalChunks = Math.ceil(file.size / chunkSize)

for (let chunkNumber = 1; chunkNumber <= totalChunks; chunkNumber++) {
  const start = (chunkNumber - 1) * chunkSize
  const end = Math.min(start + chunkSize, file.size)
  const chunk = file.slice(start, end)
  
  // 構(gòu)造分片數(shù)據(jù)包
  const formData = new FormData()
  formData.append('file', chunk)
  formData.append('chunkNumber', chunkNumber)
  formData.append('totalChunks', totalChunks)
}

2.2 斷點(diǎn)續(xù)傳實(shí)現(xiàn)

// 使用Map存儲(chǔ)上傳記錄
uploadedChunksMap = new Map() 

// 上傳前檢查已傳分片
if (!uploadedChunks.has(chunkNumber)) {
  // 執(zhí)行上傳
}

// 上傳成功記錄分片
uploadedChunks.add(chunkNumber)

2.3 智能重試機(jī)制

const maxRetries = 3 // 最大重試次數(shù)
const baseDelay = 1000 // 基礎(chǔ)延遲

// 指數(shù)退避算法
const delay = Math.min(
  baseDelay * Math.pow(2, retries - 1) + Math.random() * 1000, 
  10000
)

三、關(guān)鍵代碼詳解

3.1 文件標(biāo)識(shí)生成

createFileIdentifier(file) {
  // 文件名 + 大小 + 時(shí)間戳 生成唯一ID
  return `${file.name}-${file.size}-${new Date().getTime()}`
}

3.2 進(jìn)度計(jì)算原理

// 實(shí)時(shí)更新進(jìn)度
this.$set(this.uploadProgress, file.name, 
  Math.floor((uploadedChunks.size / totalChunks) * 100))

3.3 文件校驗(yàn)體系

handleBeforeUpload(file) {
  // 類型校驗(yàn)
  const fileExt = file.name.split('.').pop()
  if (!this.fileType.includes(fileExt)) return false
  
  // 特殊字符校驗(yàn)
  if (file.name.includes(',')) return false
  
  // 大小校驗(yàn)(MB轉(zhuǎn)換)
  return file.size / 1024 / 1024 < this.fileSize
}

四、服務(wù)端對(duì)接指南

4.1 必要接口清單

五、性能優(yōu)化建議

5.1 并發(fā)上傳控制

// 設(shè)置并行上傳數(shù)
const parallelUploads = 3 
const uploadQueue = []

for (let i=0; i<parallelUploads; i++) {
  uploadQueue.push(uploadNextChunk())
}

await Promise.all(uploadQueue)

5.2 內(nèi)存優(yōu)化策略

// 分片上傳后立即釋放內(nèi)存
chunk = null
formData = null

5.3 秒傳功能實(shí)現(xiàn)

// 計(jì)算文件哈希值
const fileHash = await calculateMD5(file)


// 查詢服務(wù)器是否存在相同文件
const res = await checkFileExist(fileHash)
if (res.exist) {
  this.handleUploadSuccess(res)
  return
}

六、錯(cuò)誤處理機(jī)制

6.1 常見錯(cuò)誤類型

七、完整版代碼

7.1 代碼

<template>
  <div class="upload-file">
    <el-upload
      multiple
      :action="'#'"
      :http-request="customUpload"
      :before-upload="handleBeforeUpload"
      :file-list="fileList"
      :limit="limit"
      :on-error="handleUploadError"
      :on-exceed="handleExceed"
      :on-success="handleUploadSuccess"
      :show-file-list="false"
      :headers="headers"
      class="upload-file-uploader"
      ref="fileUpload"
      v-if="!disabled"
    >
      <!-- 上傳按鈕 -->
      <el-button size="mini" type="primary">選取文件</el-button>
      <!-- 上傳提示 -->
      <div class="el-upload__tip" slot="tip" v-if="showTip">
        請(qǐng)上傳
        <template v-if="fileSize">
          大小不超過(guò) <b style="color: #f56c6c">{{ fileSize }}MB</b>
        </template>
        <template v-if="fileType.length > 0">
          格式為 <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
        </template>
        的文件
      </div>
    </el-upload>

    <!-- 文件列表 -->
    <transition-group
      class="upload-file-list el-upload-list el-upload-list--text"
      name="el-fade-in-linear"
      tag="ul"
    >
      <li
        :key="file.url"
        class="el-upload-list__item ele-upload-list__item-content"
        v-for="(file, index) in fileList"
      >
        <el-link
          :href="`${baseUrl}${file.url}`" rel="external nofollow" 
          :underline="false"
          target="_blank"
        >
          <span class="el-icon-document"> {{ getFileName(file.name) }} </span>
        </el-link>
        <div class="ele-upload-list__item-content-action">
          <el-link
            :underline="false"
            @click="handleDelete(index)"
            type="danger"
            v-if="!disabled"
            >刪除</el-link
          >
        </div>
      </li>
    </transition-group>
    <!-- 上傳進(jìn)度展示 -->
    <div
      v-for="(progress, fileName) in uploadProgress"
      :key="fileName"
      class="upload-progress"
    >
      <div class="progress-info">
        <span class="file-name">{{ fileName }}</span>
        <span class="percentage">{{ progress }}%</span>
      </div>
      <el-progress :percentage="progress" :show-text="false"></el-progress>
    </div>
  </div>
</template>

<script>
import { getToken } from "@/utils/auth";
import { uploadFileProgress } from "@/api/resource";
export default {
  name: "FileUpload",
  props: {
    // 值
    value: [String, Object, Array],
    // 數(shù)量限制
    limit: {
      type: Number,
      default: 5,
    },
    // 大小限制(MB)
    fileSize: {
      type: Number,
      default: 5,
    },
    // 文件類型, 例如['png', 'jpg', 'jpeg']
    fileType: {
      type: Array,
      default: () => [
        "doc",
        "docx",
        "xls",
        "xlsx",
        "ppt",
        "pptx",
        "txt",
        "pdf",
      ],
    },
    // 是否顯示提示
    isShowTip: {
      type: Boolean,
      default: true,
    },
    // 禁用組件(僅查看文件)
    disabled: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      number: 0,
      uploadList: [],
      baseUrl: process.env.VUE_APP_BASE_API,
      uploadFileUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上傳文件服務(wù)器地址
      headers: {
        Authorization: "Bearer " + getToken(),
      },
      fileList: [],
      uploadProgress: {}, // 存儲(chǔ)文件上傳進(jìn)度
      uploadedChunksMap: new Map(), // 新增:存儲(chǔ)每個(gè)文件的已上傳分片記錄
    };
  },
  watch: {
    value: {
      handler(val) {
        if (val) {
          let temp = 1;
          // 首先將值轉(zhuǎn)為數(shù)組
          const list = Array.isArray(val) ? val : this.value.split(",");
          // 然后將數(shù)組轉(zhuǎn)為對(duì)象數(shù)組
          this.fileList = list.map((item) => {
            if (typeof item === "string") {
              item = { name: item, url: item };
            }
            item.uid = item.uid || new Date().getTime() + temp++;
            return item;
          });
        } else {
          this.fileList = [];
          return [];
        }
      },
      deep: true,
      immediate: true,
    },
  },
  computed: {
    // 是否顯示提示
    showTip() {
      return this.isShowTip && (this.fileType || this.fileSize);
    },
  },
  methods: {
    // 上傳前校檢格式和大小
    handleBeforeUpload(file) {
      // 校檢文件類型
      if (this.fileType && this.fileType.length > 0) {
        const fileName = file.name.split(".");
        const fileExt = fileName[fileName.length - 1];
        const isTypeOk = this.fileType.indexOf(fileExt) >= 0;
        if (!isTypeOk) {
          this.$modal.msgError(
            `文件格式不正確,請(qǐng)上傳${this.fileType.join("/")}格式文件!`
          );
          return false;
        }
      }
      // 校檢文件名是否包含特殊字符
      if (file.name.includes(",")) {
        this.$modal.msgError("文件名不正確,不能包含英文逗號(hào)!");
        return false;
      }
      // 校檢文件大小
      if (this.fileSize) {
        const isLt = file.size / 1024 / 1024 < this.fileSize;
        if (!isLt) {
          this.$modal.msgError(`上傳文件大小不能超過(guò) ${this.fileSize} MB!`);
          return false;
        }
      }
      // this.$modal.loading("正在上傳文件,請(qǐng)稍候...");
      this.number++;
      return true;
    },
    // 文件個(gè)數(shù)超出
    handleExceed() {
      this.$modal.msgError(`上傳文件數(shù)量不能超過(guò) ${this.limit} 個(gè)!`);
    },
    // 上傳失敗
    handleUploadError(err) {
      // 確保在上傳錯(cuò)誤時(shí)移除進(jìn)度條
      if (err.file && err.file.name) {
        this.$delete(this.uploadProgress, err.file.name);
      }
      this.$modal.msgError("上傳文件失敗,請(qǐng)重試");
      this.$modal.closeLoading();
    },
    // 上傳成功回調(diào)
    handleUploadSuccess(res, file) {
      if (res.code === 200) {
        this.uploadList.push({ name: res.fileName, url: res.fileName });
        this.uploadedSuccessfully();
      } else {
        this.number--;
        this.$modal.closeLoading();
        this.$modal.msgError(res.msg);
        this.$refs.fileUpload.handleRemove(file);
        this.uploadedSuccessfully();
      }
    },
    // 刪除文件
    handleDelete(index) {
      this.fileList.splice(index, 1);
      this.$emit("input", this.listToString(this.fileList));
    },
    // 上傳結(jié)束處理
    uploadedSuccessfully() {
      if (this.number > 0 && this.uploadList.length === this.number) {
        this.fileList = this.fileList.concat(this.uploadList);
        this.uploadList = [];
        this.number = 0;
        this.$emit("input", this.listToString(this.fileList));
        this.$modal.closeLoading();
      }
    },
    // 獲取文件名稱
    getFileName(name) {
      // 如果是url那么取最后的名字 如果不是直接返回
      if (name.lastIndexOf("/") > -1) {
        return name.slice(name.lastIndexOf("/") + 1);
      } else {
        return name;
      }
    },
    // 對(duì)象轉(zhuǎn)成指定字符串分隔
    listToString(list, separator) {
      let strs = "";
      separator = separator || ",";
      for (let i in list) {
        strs += list[i].url + separator;
      }
      return strs != "" ? strs.substr(0, strs.length - 1) : "";
    },
    // Create unique identifier for file
    createFileIdentifier(file) {
      return `${file.name}-${file.size}-${new Date().getTime()}`;
    },
    async customUpload({ file }) {
      try {
        const chunkSize = 2 * 1024 * 1024;
        const totalChunks = Math.ceil(file.size / chunkSize);
        const identifier = this.createFileIdentifier(file);
        const maxRetries = 3;
        const baseDelay = 1000;

        // 獲取或創(chuàng)建該文件的已上傳分片記錄
        if (!this.uploadedChunksMap.has(identifier)) {
          this.uploadedChunksMap.set(identifier, new Set());
        }
        const uploadedChunks = this.uploadedChunksMap.get(identifier);

        this.$set(this.uploadProgress, file.name,
          Math.floor((uploadedChunks.size / totalChunks) * 100));

        for (let chunkNumber = 1; chunkNumber <= totalChunks; chunkNumber++) {
          // 如果分片已上傳成功,跳過(guò)
          if (uploadedChunks.has(chunkNumber)) {
            continue;
          }

          let currentChunkSuccess = false;
          let retries = 0;

          while (!currentChunkSuccess && retries < maxRetries) {
            try {
              const start = (chunkNumber - 1) * chunkSize;
              const end = Math.min(start + chunkSize, file.size);
              const chunk = file.slice(start, end);

              const formData = new FormData();
              formData.append('file', chunk);
              formData.append('identifier', identifier);
              formData.append('totalChunks', totalChunks);
              formData.append('chunkNumber', chunkNumber);
              formData.append('fileName', file.name);

              const res = await uploadFileProgress(formData);

              if (res.code !== 200) {
                throw new Error(res.msg || '上傳失敗');
              }

              uploadedChunks.add(chunkNumber);
              this.$set(this.uploadProgress, file.name,
                Math.floor((uploadedChunks.size / totalChunks) * 100));

              currentChunkSuccess = true;

              // 所有分片上傳完成
              if (uploadedChunks.size === totalChunks) {
                const successRes = {
                  code: 200,
                  fileName: res.fileName,
                  url: res.url,
                };

                // 清理該文件的上傳記錄
                this.uploadedChunksMap.delete(identifier);
                // 立即移除進(jìn)度條
                this.$delete(this.uploadProgress, file.name);

                this.handleUploadSuccess(successRes, file);
                return;
              }
            } catch (error) {
              retries++;
              // if (retries === maxRetries) {
              //   throw new Error(`分片 ${chunkNumber} 上傳失敗,已重試 ${maxRetries} 次`);
              // }
              const delay = Math.min(baseDelay * Math.pow(2, retries - 1) + Math.random() * 1000, 10000);
              // this.$message.warning(`分片 ${chunkNumber} 上傳失敗,${retries}秒后重試...`);
              await new Promise(resolve => setTimeout(resolve, delay));
            }
          }

          if (!currentChunkSuccess) {
            throw new Error(`分片 ${chunkNumber} 上傳失敗`);
          }
        }
      } catch (error) {
        // 確保在錯(cuò)誤時(shí)也移除進(jìn)度條
        this.$delete(this.uploadProgress, file.name);
        this.uploadedChunksMap.delete(identifier); // 清理上傳記錄
        this.$modal.closeLoading();
        this.$modal.msgError(error.message || '上傳文件失敗,請(qǐng)重試');
      }
    },
  },
};
</script>

<style scoped lang="scss">
.upload-file-uploader {
  margin-bottom: 5px;
}
.upload-file-list .el-upload-list__item {
  border: 1px solid #e4e7ed;
  line-height: 2;
  margin-bottom: 10px;
  position: relative;
}
.upload-file-list .ele-upload-list__item-content {
  display: flex;
  justify-content: space-between;
  align-items: center;
  color: inherit;
}
.ele-upload-list__item-content-action .el-link {
  margin-right: 10px;
}

.upload-progress {
  margin: 10px 0;
  padding: 8px 12px;
  background-color: #f5f7fa;
  border-radius: 4px;

  .progress-info {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 8px;

    .file-name {
      color: #606266;
      font-size: 14px;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      max-width: 80%;
    }

    .percentage {
      color: #409eff;
      font-size: 13px;
      font-weight: 500;
    }
  }

  .el-progress {
    margin-bottom: 4px;
  }
}
</style>

7.2使用說(shuō)明

<FileUpload 
  v-model="fileUrls"
  :limit="3"
  :fileSize="10"
  :fileType="['pdf','docx']"
/>

該組件為Vue應(yīng)用提供了一個(gè)可靠的大文件上傳解決方案,結(jié)合分塊、斷點(diǎn)續(xù)傳和進(jìn)度顯示,顯著提升了用戶體驗(yàn)和上傳成功率。適合集成到需要處理大文件或弱網(wǎng)環(huán)境的系統(tǒng)中

總結(jié)

到此這篇關(guān)于Vue大文件分片上傳組件實(shí)現(xiàn)解析及關(guān)鍵代碼的文章就介紹到這了,更多相關(guān)Vue大文件分片上傳組件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Element UI 自定義正則表達(dá)式驗(yàn)證方法

    Element UI 自定義正則表達(dá)式驗(yàn)證方法

    今天小編就為大家分享一篇Element UI 自定義正則表達(dá)式驗(yàn)證方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-09-09
  • 詳解vue更改頭像功能實(shí)現(xiàn)

    詳解vue更改頭像功能實(shí)現(xiàn)

    這篇文章主要介紹了vue更改頭像功能實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • vue2基本響應(yīng)式實(shí)現(xiàn)方式之讓數(shù)組也變成響應(yīng)式

    vue2基本響應(yīng)式實(shí)現(xiàn)方式之讓數(shù)組也變成響應(yīng)式

    這篇文章主要介紹了vue2基本響應(yīng)式實(shí)現(xiàn)方式之讓數(shù)組也變成響應(yīng)式問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • vue Element-ui input 遠(yuǎn)程搜索與修改建議顯示模版的示例代碼

    vue Element-ui input 遠(yuǎn)程搜索與修改建議顯示模版的示例代碼

    本文分為html,js和css代碼給大家詳細(xì)介紹了vue Element-ui input 遠(yuǎn)程搜索與修改建議顯示模版功能,感興趣的朋友一起看看吧
    2017-10-10
  • vue3提示用戶版本更新方式

    vue3提示用戶版本更新方式

    本文介紹了如何在項(xiàng)目中創(chuàng)建和使用自定義插件,以在構(gòu)建過(guò)程中檢查版本號(hào),具體步驟包括在項(xiàng)目根目錄下創(chuàng)建buildLifeHook.ts文件,并在public目錄下創(chuàng)建version文件夾,然后在vite.config.ts中引用該插件,并在src/utils目錄下創(chuàng)建XxzUtils.ts文件
    2024-12-12
  • vue-video-player 斷點(diǎn)續(xù)播的實(shí)現(xiàn)

    vue-video-player 斷點(diǎn)續(xù)播的實(shí)現(xiàn)

    這篇文章主要介紹了vue-video-player 斷點(diǎn)續(xù)播的實(shí)現(xiàn),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-02-02
  • 在vue3項(xiàng)目中給頁(yè)面添加水印的實(shí)現(xiàn)方法

    在vue3項(xiàng)目中給頁(yè)面添加水印的實(shí)現(xiàn)方法

    這篇文章主要給大家介紹一下在vue3項(xiàng)目中添加水印的實(shí)現(xiàn)方法,文中有詳細(xì)的代碼示例供大家參考,具有一定的參考價(jià)值,感興趣的小伙伴跟著小編一起來(lái)看看吧
    2023-08-08
  • vue3前端獲取文件的絕對(duì)路徑問(wèn)題解決

    vue3前端獲取文件的絕對(duì)路徑問(wèn)題解決

    這篇文章主要給大家介紹了關(guān)于vue3前端獲取文件的絕對(duì)路徑問(wèn)題解決的相關(guān)資料,文中通過(guò)代碼示例介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-09-09
  • 在Vue開發(fā)過(guò)程中解決和預(yù)防內(nèi)存泄漏問(wèn)題的方法詳解

    在Vue開發(fā)過(guò)程中解決和預(yù)防內(nèi)存泄漏問(wèn)題的方法詳解

    Vue作為一款流行的前端框架,已經(jīng)在許多項(xiàng)目中得到廣泛應(yīng)用,然而,隨著我們?cè)赩ue中構(gòu)建更大規(guī)模的應(yīng)用程序,我們可能會(huì)遇到一個(gè)嚴(yán)重的問(wèn)題,那就是內(nèi)存泄漏,因此,我們需要認(rèn)識(shí)到在Vue開發(fā)過(guò)程中,內(nèi)存泄漏問(wèn)題的重要性,本文將給大家介紹如何解決和預(yù)防內(nèi)存泄漏問(wèn)題
    2023-10-10
  • vue3之Suspense加載異步數(shù)據(jù)的使用

    vue3之Suspense加載異步數(shù)據(jù)的使用

    本文主要介紹了vue3之Suspense加載異步數(shù)據(jù)的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-02-02

最新評(píng)論