当前位置: 首页 > news >正文

vue3:vue3 + elementplus + pinia实现js的XMLHttpRequest 下载功能。

问:

使用js的XMLHttpRequest 方法,在vue3+ts+elementplus+pinia中实现一个记录下载功能:包括下载进度、下载速度、下载剩余时间、下载进度条、取消按钮取消下载、下载文件名、下载状态(下载中、已取消、下载失败、已完成),实现一个公共组件使用el-table来展示下载的文件可以有多个同时下载,最好用pinia进行多个下载文件的数据存储。

回答:

下面是一个基于 Vue 3 + TypeScript + Element Plus + Pinia 的完整示例,使用 XMLHttpRequest 实现 多文件下载管理功能,包含:

  • 下载进度、速度、剩余时间
  • 下载进度条
  • 可取消下载
  • 文件名、状态(下载中、已取消、下载失败、已完成)
  • 使用 el-table 展示多个下载任务
  • 使用 Pinia 管理下载状态
  • 公共可复用的组件

✅ 一、项目结构

src/
├── stores/
│   └── downloadStore.ts
├── components/
│   └── DownloadManager.vue
├── utils/
│   └── downloadHelper.ts

✅ 二、Pinia Store(src/stores/downloadStore.ts

// src/stores/downloadStore.ts
import { defineStore } from 'pinia'export interface DownloadTask {id: stringurl: stringfilename: stringstatus: 'pending' | 'downloading' | 'completed' | 'canceled' | 'failed'progress: number // 0-100speed: string // e.g., "1.2 MB/s"remainingTime: string // e.g., "30s"cancelToken: (() => void) | nullxhr: XMLHttpRequest | null
}export const useDownloadStore = defineStore('download', {state: () => ({tasks: [] as DownloadTask[],}),actions: {addTask(url: string, filename?: string) {const id = Date.now().toString() + Math.random().toString(36).substr(2, 9)const name = filename || url.split('/').pop() || 'unknown-file'const task: DownloadTask = {id,url,filename: name,status: 'pending',progress: 0,speed: '0 KB/s',remainingTime: '--',cancelToken: null,xhr: null,}this.tasks.push(task)return id},updateTask(id: string, data: Partial<DownloadTask>) {const task = this.tasks.find(t => t.id === id)if (task) Object.assign(task, data)},removeTask(id: string) {this.tasks = this.tasks.filter(t => t.id !== id)},cancelTask(id: string) {const task = this.tasks.find(t => t.id === id)if (task && task.cancelToken) {task.cancelToken()task.status = 'canceled'}},clearCompleted() {this.tasks = this.tasks.filter(t => !['completed', 'canceled', 'failed'].includes(t.status))},},
})

✅ 三、下载工具类(src/utils/downloadHelper.ts

// src/utils/downloadHelper.ts
export interface ProgressData {progress: numberspeed: number // bytes per secondremainingTime: number // seconds
}export type OnProgress = (data: ProgressData) => void
export type OnComplete = () => void
export type OnError = (error: Error) => voidexport function startDownload(url: string,filename: string,onProgress: OnProgress,onComplete: OnComplete,onError: OnError
): { xhr: XMLHttpRequest; cancel: () => void } {const xhr = new XMLHttpRequest()let startTime = Date.now()let loadedBytes = 0let totalBytes = 0xhr.open('GET', url, true)xhr.responseType = 'blob'// 监听下载进度xhr.onprogress = (e) => {if (e.lengthComputable) {loadedBytes = e.loadedtotalBytes = e.totalconst now = Date.now()const timeElapsed = (now - startTime) / 1000 // secondsconst speed = timeElapsed > 0 ? loadedBytes / timeElapsed : 0const progress = (loadedBytes / totalBytes) * 100let remainingTime = 0if (speed > 0) {remainingTime = (totalBytes - loadedBytes) / speed}onProgress({progress,speed,remainingTime,})}}xhr.onload = () => {if (xhr.status === 200) {const blob = xhr.responseconst link = document.createElement('a')link.href = window.URL.createObjectURL(blob)link.download = filenamelink.click()window.URL.revokeObjectURL(link.href)onComplete()} else {onError(new Error(`HTTP ${xhr.status}`))}}xhr.onerror = () => {onError(new Error('Network error'))}xhr.onabort = () => {onError(new Error('Download canceled'))}xhr.send()return {xhr,cancel: () => xhr.abort(),}
}

✅ 四、公共组件:DownloadManager.vue

<!-- src/components/DownloadManager.vue -->
<template><div class="download-manager"><h3>下载管理器</h3><el-table :data="tasks" style="width: 100%" max-height="400"><el-table-column prop="filename" label="文件名" width="200" /><el-table-column label="进度" width="200"><template #default="{ row }"><el-progress :percentage="row.progress" :stroke-width="16" /></template></el-table-column><el-table-column prop="speed" label="速度" width="100" /><el-table-column prop="remainingTime" label="剩余时间" width="100" /><el-table-column prop="status" label="状态" width="100"><template #default="{ row }"><el-tag size="small" :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag></template></el-table-column><el-table-column label="操作" width="120"><template #default="{ row }"><el-buttonv-if="row.status === 'downloading'"size="small"@click="cancelDownload(row.id)"type="danger"plain>取消</el-button><el-buttonv-else-if="row.status === 'completed'"size="small"@click="removeTask(row.id)"type="success"plain>移除</el-button><el-buttonv-elsesize="small"@click="removeTask(row.id)"type="info"plain>移除</el-button></template></el-table-column></el-table><div style="margin-top: 10px;"><el-button size="small" @click="clearCompleted" :disabled="tasks.length === 0">清除完成项</el-button></div></div>
</template><script setup lang="ts">
import { computed, onMounted, onUnmounted } from 'vue'
import { useDownloadStore } from '@/stores/downloadStore'
import { startDownload } from '@/utils/downloadHelper'const store = useDownloadStore()// 格式化字节
const formatBytes = (bytes: number): string => {if (bytes === 0) return '0 KB'const k = 1024const sizes = ['B', 'KB', 'MB', 'GB']const i = Math.floor(Math.log(bytes) / Math.log(k))return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}// 格式化速度
const formatSpeed = (bytesPerSec: number): string => {if (bytesPerSec < 1) return '0 KB/s'return formatBytes(bytesPerSec) + '/s'
}// 格式化剩余时间
const formatTime = (seconds: number): string => {if (seconds < 0) return '--'const mins = Math.floor(seconds / 60)const secs = Math.floor(seconds % 60)if (mins > 0) return `${mins}m ${secs}s`return `${secs}s`
}// 状态映射
const getStatusText = (status: string): string => {const map: Record<string, string> = {pending: '等待中',downloading: '下载中',completed: '已完成',canceled: '已取消',failed: '下载失败',}return map[status] || status
}const getStatusType = (status: string): string => {const map: Record<string, string> = {pending: 'info',downloading: 'primary',completed: 'success',canceled: 'warning',failed: 'danger',}return map[status] || 'info'
}// 开始下载任务
const startTask = (id: string) => {const task = store.tasks.find(t => t.id === id)if (!task) returnstore.updateTask(id, { status: 'downloading' })const { xhr, cancel } = startDownload(task.url,task.filename,(progressData) => {const speedStr = formatSpeed(progressData.speed)const timeStr = formatTime(progressData.remainingTime)store.updateTask(id, {progress: progressData.progress,speed: speedStr,remainingTime: timeStr,})},() => {store.updateTask(id, { status: 'completed', progress: 100 })},(error) => {console.error(`Download failed: ${error.message}`)store.updateTask(id, { status: 'failed' })})// 保存引用以便取消store.updateTask(id, { xhr, cancelToken: cancel })
}// 取消下载
const cancelDownload = (id: string) => {store.cancelTask(id)
}// 移除任务
const removeTask = (id: string) => {store.removeTask(id)
}// 清除已完成/已取消/失败的任务
const clearCompleted = () => {store.clearCompleted()
}// 自动启动所有 pending 任务(可扩展为队列)
const startPendingTasks = () => {const pendingTasks = store.tasks.filter(t => t.status === 'pending')pendingTasks.forEach(task => startTask(task.id))
}// 挂载时启动所有待下载任务
onMounted(() => {startPendingTasks()
})// 定期清理已完成任务(可选)
// onUnmounted(() => {})// 响应式任务列表
const tasks = computed(() => store.tasks)
</script><style scoped>
.download-manager {padding: 10px;border: 1px solid #ebeef5;border-radius: 4px;
}
</style>

✅ 五、如何使用(在任意页面中)

<!-- ExamplePage.vue -->
<template><div><h2>文件下载示例</h2><el-button @click="addDownload">添加下载任务</el-button><!-- 引入下载管理器 --><DownloadManager /></div>
</template><script setup lang="ts">
import { useDownloadStore } from '@/stores/downloadStore'
import DownloadManager from '@/components/DownloadManager.vue'const store = useDownloadStore()const addDownload = () => {const url = 'https://example.com/large-file.zip' // 替换为真实可下载链接const filename = 'demo-' + Date.now() + '.zip'const id = store.addTask(url, filename)// 任务添加后,DownloadManager 会自动开始下载
}
</script>

✅ 六、功能亮点

功能实现方式
多任务并发Pinia 存储任务列表,每个任务独立 xhr
进度条XMLHttpRequest.onprogress + el-progress
速度计算(已下载字节数) / (耗时秒数)
剩余时间(剩余字节) / (当前速度)
取消下载xhr.abort() + 状态更新
文件名从 URL 或手动指定
状态管理Pinia 统一维护
UI 展示el-table + el-progress + el-tag

✅ 注意事项

  1. CORS 限制XMLHttpRequest 下载受跨域限制,确保后端支持 CORS。
  2. 大文件内存:Blob 下载大文件可能占用较多内存,建议结合流式处理(但浏览器限制)。
  3. 取消后无法恢复abort() 后任务不可继续。
  4. 文件名获取:更优方式是读取 Content-Disposition header,需后端配合。

✅ 可扩展建议

  • 支持暂停/恢复(需后端支持 Range 请求)
  • 限制同时下载数量(任务队列)
  • 持久化任务(localStorage

这个方案是一个生产可用的下载管理器雏形,你可以根据项目需求进一步优化 UI 和逻辑

http://www.dtcms.com/a/506614.html

相关文章:

  • 如何在macOS上免密登录阿里云ECS服务器
  • 把“天猫”装进 JVM:Java 关键词商品爬虫从 0 到 1(含完整可运行代码)
  • tar打包过滤指定目录指南
  • 塘厦镇住房规划建设局网站wordpress主题生成
  • 5-SpringCloud-服务链路追踪 Micrometer Tracing
  • 网站怎样做谷歌推广没有网站怎么做淘宝客
  • 【C/C++基本功】union联合体彻底详解
  • 万字 Apache ShardingSphere 完全指南:从分库分表到分布式数据库生态
  • WebPages PHP:深入理解与高效实践
  • 数据结构-滑动窗口三题
  • 做黑网站吗怎么建设营销型网站
  • 免费企业网站程序关注love石家庄公众号微信
  • 如何进行网站开发网站开发郑州
  • 东营建网站wordpress商城汉化主题
  • 合肥昱天建设有限公司网站2016手机网站制作规范
  • 网站制作 青岛seo工具下载
  • 做初中物理题目的网站photoshop 做网站logo
  • 网站开发技术项目邢台网站建设最新报价
  • 木地板企业网站模版网站空间到期怎么办
  • 佛山著名网站建设公司赣州瑞金网站建设
  • 免费模板下载网站推荐免费asp企业网站源码
  • 华企立方网站深圳广告设计公司深圳画册设计
  • 网站制作全包价格中国商标商标查询网
  • 做学校和企业对接的网站做企业网站接单
  • 开发区网站开发语言网站建设5000费用
  • 自己做网站用什么软件深圳建设交易宝安
  • 建站教程下载网站建设首页模板下载
  • 网站建设中英文版软件科技公司网站模板
  • 内部网站建设青青网站怎么做
  • 做孵化的网站php网站开发书籍