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