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

君凉夜 2024-10-03 11:03:00 阅读 74

前言

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

直接开始整

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

修改工具类download.js

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

// 定义文件存储数据库名的前缀

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">code>

<div class="download">code>

<div @click="btnDownload">code>

<i class="el-icon-download"></i>code>

<div class="text">下载</div>code>

</div>

</div>

<el-dialog class="dialog-form" title="下载列表" :visible.sync="downloadDialog" :close-on-click-modal="false"code>

:show-close="false" append-to-body width="60%">code>

<div style="padding:16px">code>

<el-table ref="table" :data="$store.state.progressList" :header-cell-style="{ backgroundColor: '#f8f8f8' }">code>

<el-table-column prop="fileName" label="文件名"></el-table-column>code>

<el-table-column prop="status" label="下载状态">code>

<template #default="scope">code>

<el-tag v-if="scope.row.status == 'downloading'">Downloading</el-tag>code>

<el-tag v-if="scope.row.status == 'success'" type="success">Success</el-tag>code>

<el-tag v-if="scope.row.status == 'error'" type="danger">Download Failed</el-tag>code>

<el-tag v-if="scope.row.status == 'stopped'">Stopped</el-tag>code>

</template>

</el-table-column>

<el-table-column prop="progress" label="下载进度">code>

<template #default="scope">code>

<el-progress :stroke-width="12" :percentage="scope.row.progress"></el-progress>code>

</template>

</el-table-column>

<el-table-column label="操作">code>

<template #default="scope">code>

<template v-if="scope.row.status == 'stopped'">code>

<el-button title="开始" circle icon="el-icon-video-play"code>

@click="start(scope.row)"></el-button>code>

<el-button title="结束" circle icon="el-icon-delete" @click="del(scope.row)"></el-button>code>

</template>

<template v-else-if="scope.row.status == 'downloading'">code>

<el-button title="暂停" circle icon="el-icon-video-pause"code>

@click="stop(scope.row)"></el-button>code>

<el-button title="结束" circle icon="el-icon-delete" @click="del(scope.row)"></el-button>code>

</template>

<template v-else-if="scope.row.status == 'error'">code>

<el-button title="重试" circle icon="el-icon-refresh" @click="retry(scope.row)"></el-button>code>

<el-button title="结束" circle icon="el-icon-delete" @click="del(scope.row)"></el-button>code>

</template>

</template>

</el-table-column>

</el-table>

</div>

<div class="dialog-operate-box">code>

<el-button size="mini" @click="downloadDialog = false">取消</el-button>code>

</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">code>

<el-button @click="download">下载文件</el-button>code>

<!--之所以放到APP里,是可以做成全局通用的,在任何一个页面都可以打开下载列表-->

<DownloadProgress ref="downloadProgress"></DownloadProgress>code>

</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>

效果展示

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

在这里插入图片描述

最后

直接拿去整吧



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。