这里不做过多赘述,代码都有相应的注释,作为简单的demo,这里直接贴上代码,这里使用的是vue3
<script setup>
import { UploadFilled } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { ref } from 'vue'
import { fileParse } from './assets/utils'
import sparkMD5 from 'spark-md5'
import axios from 'axios'
// 图片路径
const img = ref(null)
// 状态
const btn = ref("暂停")
const btnStatus = ref(false)
// total
const total = ref(0)
// 显示用total
const viewTotal = ref(0)
// 外层使用partList
const outerPartList = ref([])
// 外层的hash
const outerHash = ref([])
// vieo路径
const video = ref("")
// 当前传入的文件大小
const outerSize = ref(0)
const open = (text) => {
ElMessage(text)
}
// const totalText = (total) => {
// return total > 100 ? 100 : total
// }
const changeFile = async (file) => {
if(!file) return
// 解析为BUFFER数据
// 我们会把文件切片处理:把一个文件分割成为好几个部分(固定数量/固定大小)
// 每一个切片有自己的部分数据和自己的名字
// HASH名字
// HASH-1.mp4
// HASH-2.mp4
// ...
let buffer = await fileParse(file, "buffer"),
spark = new sparkMD5.ArrayBuffer(),
hash,
suffix
spark.append(buffer)
hash = spark.end()
suffix = /\.([0-9a-zA-Z]+)$/i.exec(file.raw.name)[1] // 取出扩展名
// 创建切片
let {size} = file.raw
let partList = [],
partSize = 1 * 1024 * 1024,
partTotal = Number.isInteger(size / partSize) ? size / partSize : parseInt(size / partSize) + 1,
cur = 0
// 将当前的值赋给外部
outerSize.value = partTotal
for(let i = 1; i <= partTotal; i++) {
let item = {
chunk: (i != partTotal) ? file.raw.slice(cur, cur + partSize) : file.raw.slice(cur, size),
filename: `${hash}_${i}.${suffix}`
}
cur += partSize
partList.push(item)
}
outerPartList.value = partList
outerHash.value = hash
console.log("输出当前outerPartList", outerPartList.value)
sendRequest()
}
const sendRequest = async () => {
// 根据当前切片数量创造对应数量的请求(集合)
let requestList = []
console.log(outerPartList)
outerPartList.value.forEach((item, index) => {
// 每一个函数的都是发送一个切片的请求
let fn = () => {
let formData = new FormData()
formData.append('chunk', item.chunk)
formData.append('filename', item.filename)
return axios.post(
'/api/single3',
formData,
{headers:
{"Content-Type": "multipart/form-data"}
}
).then(result => {
result = result.data
if(result.code ==0) {
total.value += 1
// 计算当前的百分比
viewTotal.value = parseInt((total.value / outerSize.value) * 100)
// 传完的切片我们把它移除掉
outerPartList.value.splice(0, 1)
}
})
}
requestList.push(fn)
})
// 传递:并发(ajax.abort()强行中断)/串行(基于标识控制不发送)
let i = 0
let complete = async () => {
let result = await axios.get('/api/merge', {
params: {
hash: outerHash.value
}
})
result = result.data
if(result.code == 0) {
video.value = result.path
}
}
let send = async () => {
if(btnStatus.value) {
return
}
if(i >= requestList.length) {
// 都传输完毕
complete()
return
}
await requestList[i]()
i++
send()
}
send();
}
const handleBtn = () => {
if(btn.value == "继续") {
// 断点续传
btn.value = "暂停"
btnStatus.value = false
sendRequest()
return
}
btn.value = "继续"
btnStatus.value = true
// 暂停上传
}
</script>
<template>
<header>
<!--
action: 存放的时文件上传到服务器的接口地址
-->
<el-upload
class="upload-demo"
drag
action
:auto-upload="false"
:show-file-list="false"
:on-change="changeFile"
multiple
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
Drop file here or <em>click to upload</em>
</div>
<template #tip>
<div class="el-upload__tip">
jpg/png files with a size less than 500kb
</div>
</template>
</el-upload>
</header>
<div class="progress">
<span>上传进度:{{ viewTotal }}%</span>
<el-link type="primary" v-if="viewTotal>0 && viewTotal<100" @click="handleBtn">{{ btn }}</el-link>
</div>
<!-- IMG -->
<div class="uploadImg" v-show="img">
<img :src="img" alt="">
</div>
<!-- VIDEO -->
<div class="uploadImg" v-if="video">
<video :src="video" controls></video>
</div>
</template>
<style scoped>
.uploadImg {
width: 100%;
}
.uploadImg video {
width: 100%;
}
</style>