前言

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

直接开始整

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

修改工具类download.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 添加获取下载列表的方法

// 定义文件存储数据库名的前缀
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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里的方法加载下载数据

1
2
3
4
5
main.js

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

创建组件DownloadProgress.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
<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 调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<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>

效果展示

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

在这里插入图片描述

最后

直接拿去整吧