前言
之前已经出过 大文件分片下载 的教程,期间也收到很多小伙伴的疑问说是功能上有点问题,也抽时间将一些大的问题修改了,验证了很多次,应该不会有什么问题了;在下载方案中涉及到断点续传部分的没有细讲,因为当时时间有限,所以只是稍微带过了,最近突然又闲下来了, 所以还是抽点时间将之前的方案细节更新完整
直接开始整
这里只涉及到续传功能的修改,要了解分片下载 请移步 前端大文件分片下载解决方案,没用你来砍我
修改工具类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>
效果展示
- 新增下载
- 暂停下载
- 继续下载
- 删除下载
- 页面刷新不影响下载
最后
直接拿去整吧