亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

前端如何寫一個大文件上傳組件詳細步驟

 更新時間:2025年04月09日 10:16:05   作者:大嘴史努比  
在現(xiàn)代Web應(yīng)用中,大文件上傳是一個常見的需求,尤其是在云存儲、社交媒體和協(xié)作平臺等領(lǐng)域,這篇文章主要介紹了前端如何寫一個大文件上傳組件詳細步驟,需要的朋友可以參考下

1. 核心思路

  • 分片上傳:將大文件分割成多個小文件(chunk),逐個上傳。

  • 記錄上傳進度:通過本地存儲(如 localStorage)或服務(wù)端記錄已上傳的分片。

  • 斷點續(xù)傳:上傳中斷后,重新上傳時只上傳未完成的分片。

  • 合并文件:所有分片上傳完成后,通知服務(wù)端合并文件。

2. 實現(xiàn)步驟

1) 前端分片上傳

  • 使用 File 對象的 slice 方法將文件分割成多個分片。

  • 通過 FormData 將分片上傳到服務(wù)端。

2) 記錄上傳進度

  • 每個分片上傳成功后,記錄已上傳的分片信息(如分片索引、文件唯一標識等)。

  • 可以使用 localStorage 或服務(wù)端存儲記錄上傳進度。

3) 斷點續(xù)傳

  • 重新上傳時,先檢查已上傳的分片,跳過已上傳的部分。

  • 只上傳未完成的分片。

4) 合并文件

  • 所有分片上傳完成后,通知服務(wù)端合并文件。

3. 代碼實現(xiàn)

前端代碼(原生JS)

class FileUploader {
  constructor(file, chunkSize = 5 * 1024 * 1024) { // 默認分片大小為5MB
    this.file = file;
    this.chunkSize = chunkSize;
    this.totalChunks = Math.ceil(file.size / chunkSize); // 總分片數(shù)
    this.chunkIndex = 0; // 當(dāng)前分片索引
    this.fileId = null; // 文件唯一標識
    this.uploadedChunks = new Set(); // 已上傳的分片索引
  }

  // 生成文件唯一標識(可以用文件名+文件大小+最后修改時間)
  generateFileId() {
    return `${this.file.name}-${this.file.size}-${this.file.lastModified}`;
  }

  // 獲取未上傳的分片
  getUnuploadedChunks() {
    const unuploadedChunks = [];
    for (let i = 0; i < this.totalChunks; i++) {
      if (!this.uploadedChunks.has(i)) {
        unuploadedChunks.push(i);
      }
    }
    return unuploadedChunks;
  }

  // 上傳分片
  async uploadChunk(chunkIndex) {
    const start = chunkIndex * this.chunkSize;
    const end = Math.min(start + this.chunkSize, this.file.size);
    const chunk = this.file.slice(start, end);

    const formData = new FormData();
    formData.append("file", chunk);
    formData.append("chunkIndex", chunkIndex);
    formData.append("totalChunks", this.totalChunks);
    formData.append("fileId", this.fileId);

    try {
      await fetch("/upload", {
        method: "POST",
        body: formData,
      });
      this.uploadedChunks.add(chunkIndex); // 記錄已上傳的分片
      this.saveProgress(); // 保存上傳進度
    } catch (error) {
      console.error("上傳失敗:", error);
      throw error;
    }
  }

  // 保存上傳進度到 localStorage
  saveProgress() {
    const progress = {
      fileId: this.fileId,
      uploadedChunks: Array.from(this.uploadedChunks),
    };
    localStorage.setItem(this.fileId, JSON.stringify(progress));
  }

  // 從 localStorage 加載上傳進度
  loadProgress() {
    const progress = JSON.parse(localStorage.getItem(this.fileId));
    if (progress) {
      this.uploadedChunks = new Set(progress.uploadedChunks);
    }
  }

  // 開始上傳
  async startUpload() {
    this.fileId = this.generateFileId();
    this.loadProgress(); // 加載上傳進度

    const unuploadedChunks = this.getUnuploadedChunks();
    for (const chunkIndex of unuploadedChunks) {
      await this.uploadChunk(chunkIndex);
      console.log(`分片 ${chunkIndex} 上傳完成`);
    }

    console.log("所有分片上傳完成,通知服務(wù)端合并文件");
    await this.mergeFile();
  }

  // 通知服務(wù)端合并文件
  async mergeFile() {
    await fetch("/merge", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        fileId: this.fileId,
        fileName: this.file.name,
        totalChunks: this.totalChunks,
      }),
    });
    console.log("文件合并完成");
    localStorage.removeItem(this.fileId); // 清除上傳進度
  }
}

// 使用示例
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener("change", async (e) => {
  const file = e.target.files[0];
  if (file) {
    const uploader = new FileUploader(file);
    await uploader.startUpload();
  }
});

前端代碼(VUE)

<template>
  <div>
    <input type="file" @change="handleFileChange" />
    <button @click="startUpload" :disabled="!file || isUploading">
      {{ isUploading ? '上傳中...' : '開始上傳' }}
    </button>
    <div v-if="progress > 0">
      上傳進度: {{ progress }}%
    </div>
  </div>
</template>

<script>
import { ref } from 'vue';

export default {
  name: 'FileUploader',
  setup() {
    const file = ref(null); // 選擇的文件
    const isUploading = ref(false); // 是否正在上傳
    const progress = ref(0); // 上傳進度
    const chunkSize = 5 * 1024 * 1024; // 分片大?。?MB)
    const fileId = ref(''); // 文件唯一標識
    const uploadedChunks = ref(new Set()); // 已上傳的分片索引

    // 生成文件唯一標識
    const generateFileId = (file) => {
      return `${file.name}-${file.size}-${file.lastModified}`;
    };

    // 獲取未上傳的分片
    const getUnuploadedChunks = (totalChunks) => {
      const unuploadedChunks = [];
      for (let i = 0; i < totalChunks; i++) {
        if (!uploadedChunks.value.has(i)) {
          unuploadedChunks.push(i);
        }
      }
      return unuploadedChunks;
    };

    // 上傳分片
    const uploadChunk = async (chunkIndex, totalChunks) => {
      const start = chunkIndex * chunkSize;
      const end = Math.min(start + chunkSize, file.value.size);
      const chunk = file.value.slice(start, end);

      const formData = new FormData();
      formData.append('file', chunk);
      formData.append('chunkIndex', chunkIndex);
      formData.append('totalChunks', totalChunks);
      formData.append('fileId', fileId.value);

      try {
        await fetch('/upload', {
          method: 'POST',
          body: formData,
        });
        uploadedChunks.value.add(chunkIndex); // 記錄已上傳的分片
        saveProgress(); // 保存上傳進度
      } catch (error) {
        console.error('上傳失敗:', error);
        throw error;
      }
    };

    // 保存上傳進度到 localStorage
    const saveProgress = () => {
      const progressData = {
        fileId: fileId.value,
        uploadedChunks: Array.from(uploadedChunks.value),
      };
      localStorage.setItem(fileId.value, JSON.stringify(progressData));
    };

    // 從 localStorage 加載上傳進度
    const loadProgress = () => {
      const progressData = JSON.parse(localStorage.getItem(fileId.value));
      if (progressData) {
        uploadedChunks.value = new Set(progressData.uploadedChunks);
      }
    };

    // 通知服務(wù)端合并文件
    const mergeFile = async () => {
      await fetch('/merge', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          fileId: fileId.value,
          fileName: file.value.name,
          totalChunks: Math.ceil(file.value.size / chunkSize),
        }),
      });
      console.log('文件合并完成');
      localStorage.removeItem(fileId.value); // 清除上傳進度
    };

    // 開始上傳
    const startUpload = async () => {
      if (!file.value) return;

      isUploading.value = true;
      fileId.value = generateFileId(file.value);
      loadProgress(); // 加載上傳進度

      const totalChunks = Math.ceil(file.value.size / chunkSize);
      const unuploadedChunks = getUnuploadedChunks(totalChunks);

      for (const chunkIndex of unuploadedChunks) {
        await uploadChunk(chunkIndex, totalChunks);
        progress.value = Math.round((uploadedChunks.value.size / totalChunks) * 100);
        console.log(`分片 ${chunkIndex} 上傳完成`);
      }

      console.log('所有分片上傳完成,通知服務(wù)端合并文件');
      await mergeFile();
      isUploading.value = false;
      progress.value = 100;
    };

    // 選擇文件
    const handleFileChange = (event) => {
      const selectedFile = event.target.files[0];
      if (selectedFile) {
        file.value = selectedFile;
        progress.value = 0;
        uploadedChunks.value = new Set();
      }
    };

    return {
      file,
      isUploading,
      progress,
      startUpload,
      handleFileChange,
    };
  },
};
</script>

<style scoped>
/* 樣式可以根據(jù)需要自定義 */
div {
  margin: 20px;
}
button {
  margin-top: 10px;
}
</style>

服務(wù)器端代碼

const express = require('express');
const fs = require('fs');
const path = require('path');
const multer = require('multer');

const app = express();
const upload = multer({ dest: 'uploads/' }); // 分片存儲目錄

// 上傳分片
app.post('/upload', upload.single('file'), (req, res) => {
  const { chunkIndex, fileId } = req.body;
  const chunkPath = path.join('uploads', `${fileId}-${chunkIndex}`);

  // 將分片移動到指定位置
  fs.renameSync(req.file.path, chunkPath);
  res.send('分片上傳成功');
});

// 合并文件
app.post('/merge', express.json(), (req, res) => {
  const { fileId, fileName, totalChunks } = req.body;
  const filePath = path.join('uploads', fileName);

  // 創(chuàng)建可寫流
  const writeStream = fs.createWriteStream(filePath);

  // 依次讀取分片并寫入文件
  for (let i = 0; i < totalChunks; i++) {
    const chunkPath = path.join('uploads', `${fileId}-${i}`);
    const chunk = fs.readFileSync(chunkPath);
    writeStream.write(chunk);
    fs.unlinkSync(chunkPath); // 刪除分片
  }

  writeStream.end();
  res.send('文件合并完成');
});

app.listen(3000, () => {
  console.log('服務(wù)端運行在 http://localhost:3000');
});

總結(jié) 

到此這篇關(guān)于前端如何寫一個大文件上傳組件的文章就介紹到這了,更多相關(guān)前端大文件上傳組件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • JS返回頁面時自動回滾到歷史瀏覽位置

    JS返回頁面時自動回滾到歷史瀏覽位置

    這篇文章主要介紹了JS返回頁面時自動回滾到歷史瀏覽位置的相關(guān)知識,本文給大家使用的是SessionStorage來存儲頁面內(nèi)容,在返回頁面時重新加載,具體實現(xiàn)代碼大家跟隨小編一起看看吧
    2018-09-09
  • Javascript中字符串相關(guān)常用的使用方法總結(jié)

    Javascript中字符串相關(guān)常用的使用方法總結(jié)

    本篇文章主要介紹了Javascript中字符串相關(guān)常用的使用方法。具有很好的參考價值。下面跟著小編一起來看下吧
    2017-03-03
  • 通過JavaScript腳本復(fù)制網(wǎng)頁上的一個表格

    通過JavaScript腳本復(fù)制網(wǎng)頁上的一個表格

    通過JavaScript腳本復(fù)制網(wǎng)頁上的一個表格...
    2006-07-07
  • 微信小程序視圖template模板引用的實例詳解

    微信小程序視圖template模板引用的實例詳解

    這篇文章主要介紹了微信小程序視圖template模板引用的實例詳解的相關(guān)資料,希望通過本文能幫助到大家,需要的朋友可以參考下
    2017-09-09
  • JavaScript中二分查找的例題詳解

    JavaScript中二分查找的例題詳解

    二分查找在我們學(xué)習(xí)算法中是很重要的一部分,而且面試的時候會經(jīng)常的讓我們手寫一些算法。所以這篇文章將通過三個場景帶大家深入了解二分查找算法
    2023-03-03
  • js實現(xiàn)數(shù)組的扁平化

    js實現(xiàn)數(shù)組的扁平化

    這篇文章主要為大家介紹了js實現(xiàn)數(shù)組扁平化,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2021-12-12
  • JS利用?clip-path?實現(xiàn)動態(tài)區(qū)域裁剪功能

    JS利用?clip-path?實現(xiàn)動態(tài)區(qū)域裁剪功能

    這篇文章主要介紹了JS利用?clip-path?實現(xiàn)動態(tài)區(qū)域裁剪功能,文中主要通過使用 box-shadow 實現(xiàn),代碼簡單易懂,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-12-12
  • JS中如何將base64轉(zhuǎn)換成file

    JS中如何將base64轉(zhuǎn)換成file

    這篇文章主要介紹了JS中如何將base64轉(zhuǎn)換成file的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • JS常用字符串方法(推薦)

    JS常用字符串方法(推薦)

    下面小編就為大家?guī)硪黄狫S常用字符串方法(推薦)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-06-06
  • js防刷新的倒計時代碼 js倒計時代碼

    js防刷新的倒計時代碼 js倒計時代碼

    這篇文章主要為大家詳細介紹了js防刷新的倒計時代碼,js倒計時的實現(xiàn)代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-09-09

最新評論