【后续 断点续传】前端大文件分片下载解决方案,没用你来砍我

前言

之前已经出过 大文件分片下载 的教程,期间也收到很多小伙伴的疑问说是功能上有点问题,也抽时间将一些大的问题修改了,验证了很多次,应该不会有什么问题了;在下载方案中涉及到断点续传部分的没有细讲,因为当时时间有限,所以只是稍微带过了,最近突然又闲下来了, 所以还是抽点时间将之前的方案细节更新完整

直接开始整

这里只涉及到续传功能的修改,要了解分片下载 请移步 前端大文件分片下载解决方案,没用你来砍我

修改工具类download.js

// 添加获取下载列表的方法

// 定义文件存储数据库名的前缀
const progress_file_prefix = "progress_file_"
/**
 * @description 获取下载列表
 * @param {*} page 页面
 * @param {*} user 用户
 * @param {*} callback 返回
 */
export async function getDownloadList(page, user, callback) {
    // 取得基础数据库
    const baseDataBase = createInstance(baseDataBaseName)
    await baseDataBase.keys().then(async function (keys) {
        // 包含所有key 名的数据
        let fileList = []
        for (let i = 0; i < keys.length; i++) {
            if (keys[i].indexOf(progress_file_prefix) > -1) {
                // 获取数据
                await getData(keys[i], baseDataBase, async (res) => {
                    // 存储文件名和对应的数据库实例名
                    fileList.push(
                        { fileName: res, dataInstance: keys[i] }
                    )
                })
            }
        }
        // 获取下载进度
        for (let i = 0; i < fileList.length; i++) {
            // 获取数据库实例
            const dataBase = createInstance(fileList[i].dataInstance)
            // 获取数据
            await getData(progressKey, dataBase, async (progress) => {
                if (progress) {
                    // 赋值进度及状态
                    fileList[i].fileSize = progress.fileSize
                    fileList[i].progress = progress.progress ? progress.progress : 0
                    fileList[i].status = progress.status ? progress.status : "stopped"
                    fileList[i].url = progress.url
                }
            })
        }
        callback(fileList)
    }).catch(function (err) {
        callback(err)
    })
}

添加 store/index.js

store/inde.js

import Vue from 'vue'
import Vuex from 'vuex'
import { getDownloadList } from "@/utils/download.js"

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        // 下载进度列表
        progressList: []
    },
    mutations: {
        setProgress: (state, progressObj) => {
            // 如果进度表有数据
            if (state.progressList.length) {
                const obj = state.progressList.find(item => item.dataInstance == progressObj.dataInstance)
                if (obj) {
                    if (progressObj.progress) {
                        // 改变当前进度对象的进度
                        obj.progress = progressObj.progress
                    }
                    if (progressObj.status) {
                        // 改变当前进度对象的状态
                        obj.status = progressObj.status
                    }
                } else {
                    // 添加新的进度
                    state.progressList.push(progressObj)
                }
            } else {
                // 添加新的进度
                state.progressList.push(progressObj)
            }
        },
        delProgress: (state, props) => {
            // 删除进度对象
            state.progressList.splice(state.progressList.findIndex(item => item.dataInstance == props), 1)
        }
    },
    actions: {
        // 从数据库中加载进度数据
        loadProgressList({ commit, state }) {
            return new Promise(resolve => {
                getDownloadList("", state.username, function (res) {
                    state.progressList = res
                    resolve()
                })
            })
        }
    }
})

在main.js里调用 store里的方法加载下载数据

main.js

import store from './store'
// 加载下载进度
store.dispatch("loadProgressList")

创建组件DownloadProgress.vue

<template>
    <div class="download-container">
        <div class="download">
            <div @click="btnDownload">
                <i class="el-icon-download"></i>
                <div class="text">下载</div>
            </div>
        </div>
        <el-dialog class="dialog-form" title="下载列表" :visible.sync="downloadDialog" :close-on-click-modal="false"
            :show-close="false" append-to-body width="60%">
            <div style="padding:16px">
                <el-table ref="table" :data="$store.state.progressList" :header-cell-style="{ backgroundColor: '#f8f8f8' }">
                    <el-table-column prop="fileName" label="文件名"></el-table-column>
                    <el-table-column prop="status" label="下载状态">
                        <template #default="scope">
                            <el-tag v-if="scope.row.status == 'downloading'">Downloading</el-tag>
                            <el-tag v-if="scope.row.status == 'success'" type="success">Success</el-tag>
                            <el-tag v-if="scope.row.status == 'error'" type="danger">Download Failed</el-tag>
                            <el-tag v-if="scope.row.status == 'stopped'">Stopped</el-tag>
                        </template>
                    </el-table-column>
                    <el-table-column prop="progress" label="下载进度">
                        <template #default="scope">
                            <el-progress :stroke-width="12" :percentage="scope.row.progress"></el-progress>
                        </template>
                    </el-table-column>
                    <el-table-column label="操作">
                        <template #default="scope">
                            <template v-if="scope.row.status == 'stopped'">
                                <el-button title="开始" circle icon="el-icon-video-play"
                                    @click="start(scope.row)"></el-button>
                                <el-button title="结束" circle icon="el-icon-delete" @click="del(scope.row)"></el-button>
                            </template>
                            <template v-else-if="scope.row.status == 'downloading'">
                                <el-button title="暂停" circle icon="el-icon-video-pause"
                                    @click="stop(scope.row)"></el-button>
                                <el-button title="结束" circle icon="el-icon-delete" @click="del(scope.row)"></el-button>
                            </template>
                            <template v-else-if="scope.row.status == 'error'">
                                <el-button title="重试" circle icon="el-icon-refresh" @click="retry(scope.row)"></el-button>
                                <el-button title="结束" circle icon="el-icon-delete" @click="del(scope.row)"></el-button>
                            </template>
                        </template>
                    </el-table-column>
                </el-table>
            </div>
            <div class="dialog-operate-box">
                <el-button size="mini" @click="downloadDialog = false">取消</el-button>
            </div>
        </el-dialog>
    </div>
</template>
<script>
import { downloadByBlock } from '@/utils/download.js'
export default {
    data() {
        return {
            downloadDialog: false,
            // 记录每个下载文件
            fileStatus: {}
        }
    },
    watch: {
        // 监听下载列表的变化
        "$store.state.progressList": function () {
            this.$nextTick(() => {
                const progressList = this.$store.state.progressList
                progressList.forEach(item => {
                    // 获取之前下载状态 还原操作
                    const status = sessionStorage.getItem(item.dataInstance)
                    if (status == 'downloading' && item.status != status) {
                        // 如果是下载中的  就继续,这里是防止手动刷新页面后把正在下载中的任务暂停了
                        this.start(item)
                    }
                })
            })
        }
    },
    methods: {
        /**
         * 重试
         * @param {*} row 
         */
        retry(row) {
            this.start(row)
        },
        /**
         * 开始下载
         * @param {*} row 
         */
        start(row) {
            // 记录文件的下载状态 方便后续的操作
            this.fileStatus[row.dataInstance] = {
                type: 'continue',
                progress: row.progress
            }
            downloadByBlock(row.fileName, row.url, row.dataInstance, this.fileStatus[row.dataInstance])
        },
        /**
         * 暂停下载
         * @param {*} row 
         */
        stop(row) {
            this.$set(this.fileStatus[row.dataInstance], "type", "stop")
        },
        /**
         * 删除下载
         * @param {*} row 
         */
        del(row) {
            if (this.fileStatus[row.dataInstance] && row.status != "stopped") {
                this.$set(this.fileStatus[row.dataInstance], "type", "cancel")
            } else {
                this.fileStatus[row.dataInstance] = { type: "cancel" }
                downloadByBlock(row.fileName, row.url, row.dataInstance, this.fileStatus[row.dataInstance])
            }
        },
        /**
         * 打开下载列表
         */
        btnDownload() {
            this.downloadDialog = true
        },
        /**
         * 下载文件
         * @param {*} fileName 
         * @param {*} url 
         * @param {*} dataBaseName 
         */
        downloadFile(fileName, url, dataBaseName) {
            this.fileStatus[dataBaseName] = { type: null }
            downloadByBlock(fileName, url, dataBaseName, this.fileStatus[dataBaseName])
            this.btnDownload()
        }
    }
}
</script>
<style scoped>
.download-container {
    position: fixed;
    right: 0px;
    bottom: 60px;
    z-index: 2041;
}

.download i {
    font-size: 18px
}

.download>div {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    font-size: 14px;
    padding: 12px;
    border-radius: 4px;
    color: #fff;
    margin: 16px 2px 16px 0;
    cursor: pointer;
}

.download text {
    padding-top: 10px;
    word-break: break-all;
}

.download {
    background: #9a4b9b;
}
</style>

App.vue 调用

<template>
  <div id="app">
    <el-button @click="download">下载文件</el-button>
    <!--之所以放到APP里,是可以做成全局通用的,在任何一个页面都可以打开下载列表-->
    <DownloadProgress ref="downloadProgress"></DownloadProgress>
  </div>
</template>

<script>
// 引入之前创建的组件
import DownloadProgress from "@/components/DownloadProgress"
export default {
  name: 'App',
  components: {
    DownloadProgress
  },
  methods: {
    download() {
      // 这里的 dataBaseName 需要以 progress_file_ 开头(也可自定义,要与download.js 里的progress_file_prefix 值一致)
      const dataBaseName = "progress_file_1"
      const parent = this.getAppVue(this)
      parent.$refs.downloadProgress.downloadFile("Subnautica.v63112.part01.rar", "/api/downloadByBlock", dataBaseName)
    },
    /**
     * 递归寻找App里的 DownloadProgress组件
     * @param {*} vue 
     */
    getAppVue(vue) {
      if (vue.$refs.downloadProgress) {
        return vue
      } else {
        this.getAppVue(vue.$parent)
      }
    }
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

效果展示

  • 新增下载
  • 暂停下载
  • 继续下载
  • 删除下载
  • 页面刷新不影响下载

在这里插入图片描述

最后

直接拿去整吧

  • 25
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值