Appearance
文件上传(网速、时间、进度)
在一些特殊的情况下,上传文件时,需要实时显示上传进度、上传速度、上传时间等信息,这个不算简单的,上传进度不难,有自带的监听,但是上传速度和上传时间就有点麻烦了,需要自己计算。
计算方法
计算当前上传速度 (已上传字节数 / 已用时间)
剩余时间 (剩余字节数(文件大小的总数 - 已上传字节数) / 当前速度)
同时可能需要做一些优化,比如>60秒显示分,<60秒显示秒,还可能需要显示网速。其实还有其他的突然中断、网速的剧烈变化都需要结合实际情况而定。
前端代码
vue
<template>
<div>
<input type="file" @change="handleFileChange" />
<div v-if="uploading" class="status">
已上传 {{ progress }}%,剩余时间:{{ formattedRemainingTime }},速度:{{ uploadSpeed }}
</div>
<div v-else class="status">等待上传...</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import axios from 'axios'
// 响应式数据
const file = ref(null)
const uploading = ref(false)
const loaded = ref(0)
const total = ref(0)
const progress = ref(0)
const remainingTime = ref(0) // 秒
const speed = ref(0) // 字节/秒
// 格式化显示
const formattedRemainingTime = ref('0秒')
const uploadSpeed = ref('0 KB/s')
// 文件选择事件
function handleFileChange(event) {
const selectedFile = event.target.files[0]
if (!selectedFile) return
file.value = selectedFile
startUpload()
}
// 启动上传
function startUpload() {
uploading.value = true
loaded.value = 0
total.value = file.value.size
const startTime = Date.now()
const formData = new FormData()
formData.append('file', file.value)
// 使用 axios 发起上传请求
axios
.post('http://localhost:3000/api/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
onUploadProgress: (progressEvent) => {
if (progressEvent.lengthComputable) {
loaded.value = progressEvent.loaded
total.value = progressEvent.total
progress.value = Math.round((loaded.value / total.value) * 100)
const elapsedTime = (Date.now() - startTime) / 1000 // 秒
speed.value = loaded.value / elapsedTime || 0
// 计算剩余时间(秒)
const remaining = (total.value - loaded.value) / speed.value || 0
remainingTime.value = Math.max(0, Math.floor(remaining))
// 格式化剩余时间
const minutes = Math.floor(remainingTime.value / 60)
const seconds = remainingTime.value % 60
if (minutes > 0) {
formattedRemainingTime.value = `${minutes}分钟${seconds}秒`
} else {
formattedRemainingTime.value = `${seconds}秒`
}
// 格式化上传速度(KB/s 或 MB/s)
if (speed.value < 1024) {
uploadSpeed.value = `${speed.value.toFixed(2)} B/s`
} else if (speed.value < 1024 * 1024) {
uploadSpeed.value = `${(speed.value / 1024).toFixed(2)} KB/s`
} else {
uploadSpeed.value = `${(speed.value / (1024 * 1024)).toFixed(2)} MB/s`
}
}
},
})
.then(() => {
uploading.value = false
formattedRemainingTime.value = '0秒'
uploadSpeed.value = '0 KB/s'
alert('上传完成!')
})
.catch((error) => {
uploading.value = false
formattedRemainingTime.value = '上传失败'
uploadSpeed.value = 'N/A'
alert('上传失败,请检查网络')
console.error('Upload error:', error)
})
}
</script>
<style scoped>
.status {
margin-top: 10px;
font-size: 14px;
color: #333;
}
</style>
<template>
<div>
<input type="file" @change="handleFileChange" />
<div v-if="uploading" class="status">
已上传 {{ progress }}%,剩余时间:{{ formattedRemainingTime }},速度:{{ uploadSpeed }}
</div>
<div v-else class="status">等待上传...</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import axios from 'axios'
// 响应式数据
const file = ref(null)
const uploading = ref(false)
const loaded = ref(0)
const total = ref(0)
const progress = ref(0)
const remainingTime = ref(0) // 秒
const speed = ref(0) // 字节/秒
// 格式化显示
const formattedRemainingTime = ref('0秒')
const uploadSpeed = ref('0 KB/s')
// 文件选择事件
function handleFileChange(event) {
const selectedFile = event.target.files[0]
if (!selectedFile) return
file.value = selectedFile
startUpload()
}
// 启动上传
function startUpload() {
uploading.value = true
loaded.value = 0
total.value = file.value.size
const startTime = Date.now()
const formData = new FormData()
formData.append('file', file.value)
// 使用 axios 发起上传请求
axios
.post('http://localhost:3000/api/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
onUploadProgress: (progressEvent) => {
if (progressEvent.lengthComputable) {
loaded.value = progressEvent.loaded
total.value = progressEvent.total
progress.value = Math.round((loaded.value / total.value) * 100)
const elapsedTime = (Date.now() - startTime) / 1000 // 秒
speed.value = loaded.value / elapsedTime || 0
// 计算剩余时间(秒)
const remaining = (total.value - loaded.value) / speed.value || 0
remainingTime.value = Math.max(0, Math.floor(remaining))
// 格式化剩余时间
const minutes = Math.floor(remainingTime.value / 60)
const seconds = remainingTime.value % 60
if (minutes > 0) {
formattedRemainingTime.value = `${minutes}分钟${seconds}秒`
} else {
formattedRemainingTime.value = `${seconds}秒`
}
// 格式化上传速度(KB/s 或 MB/s)
if (speed.value < 1024) {
uploadSpeed.value = `${speed.value.toFixed(2)} B/s`
} else if (speed.value < 1024 * 1024) {
uploadSpeed.value = `${(speed.value / 1024).toFixed(2)} KB/s`
} else {
uploadSpeed.value = `${(speed.value / (1024 * 1024)).toFixed(2)} MB/s`
}
}
},
})
.then(() => {
uploading.value = false
formattedRemainingTime.value = '0秒'
uploadSpeed.value = '0 KB/s'
alert('上传完成!')
})
.catch((error) => {
uploading.value = false
formattedRemainingTime.value = '上传失败'
uploadSpeed.value = 'N/A'
alert('上传失败,请检查网络')
console.error('Upload error:', error)
})
}
</script>
<style scoped>
.status {
margin-top: 10px;
font-size: 14px;
color: #333;
}
</style>
后端代码
后端就用nodejs,简单、方便、快捷
js
const express = require('express')
const multer = require('multer')
const cors = require('cors')
const path = require('path')
const fs = require('fs')
// 创建 Express 应用
const app = express()
// 配置 CORS 支持跨域请求
app.use(cors())
// 确保 uploads 目录存在
const uploadDir = path.join(__dirname, 'uploads')
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir)
}
// 配置 multer 存储
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, uploadDir) // 文件存储路径
},
filename: function (req, file, cb) {
// 保留原始文件名(或自定义)
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9)
const ext = path.extname(file.originalname)
const filename = file.fieldname + '-' + uniqueSuffix + ext;
req.uploadingFileName = filename; // 关键:存储文件名到请求对象
cb(null, file.fieldname + '-' + uniqueSuffix + ext);
}
})
// 创建 multer 实例
const upload = multer({
storage
})
// 处理上传接口
app.post('/api/upload', upload.single('file'), (req, res) => {
try {
// req.file 是上传的文件信息
console.log('文件已上传:', req.file)
// 返回成功响应
res.status(200).json({
message: '上传成功',
file: {
filename: req.file.filename,
path: req.file.path,
size: req.file.size
}
})
} catch (error) {
console.error('上传失败:', error)
res.status(500).json({ message: '上传失败', error: error.message })
}
})
// 启动服务器
const PORT = 3000
app.listen(PORT, () => {
console.log(`Server is running at http://localhost:${PORT}`)
})
const express = require('express')
const multer = require('multer')
const cors = require('cors')
const path = require('path')
const fs = require('fs')
// 创建 Express 应用
const app = express()
// 配置 CORS 支持跨域请求
app.use(cors())
// 确保 uploads 目录存在
const uploadDir = path.join(__dirname, 'uploads')
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir)
}
// 配置 multer 存储
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, uploadDir) // 文件存储路径
},
filename: function (req, file, cb) {
// 保留原始文件名(或自定义)
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9)
const ext = path.extname(file.originalname)
const filename = file.fieldname + '-' + uniqueSuffix + ext;
req.uploadingFileName = filename; // 关键:存储文件名到请求对象
cb(null, file.fieldname + '-' + uniqueSuffix + ext);
}
})
// 创建 multer 实例
const upload = multer({
storage
})
// 处理上传接口
app.post('/api/upload', upload.single('file'), (req, res) => {
try {
// req.file 是上传的文件信息
console.log('文件已上传:', req.file)
// 返回成功响应
res.status(200).json({
message: '上传成功',
file: {
filename: req.file.filename,
path: req.file.path,
size: req.file.size
}
})
} catch (error) {
console.error('上传失败:', error)
res.status(500).json({ message: '上传失败', error: error.message })
}
})
// 启动服务器
const PORT = 3000
app.listen(PORT, () => {
console.log(`Server is running at http://localhost:${PORT}`)
})