chat-pc/src/store/modules/uploads.ts

352 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { defineStore } from 'pinia'
import { ServeFindFileSplitInfo, ServeFileSubareaUpload } from '@/api/upload'
import { ServeSendTalkFile } from '@/api/chat'
import { uploadImg } from '@/api/upload'
import {
useDialogueStore
} from '@/store'
// @ts-ignore
const message = window.$message
// 定义上传项接口
interface UploadItem {
file: File;
talk_type: number;
receiver_id: number;
upload_id: string;
client_upload_id?: string; // 上传时的客户端ID
uploadIndex: number;
percentage: number;
status: number; // 文件上传状态 0:等待上传 1:上传中 2:上传完成 3:网络异常
files: FormData[];
avatar: string;
username: string;
is_paused?: boolean; // 是否暂停上传
form?: FormData;
progress_interval?: any;
upload_controller?: AbortController;
onProgress?: (percentage: number) => void;
onComplete?: (data: any) => void;
}
// 处理拆分上传文件
function fileSlice(file: File, uploadId: string, eachSize: number) {
const splitNum = Math.ceil(file.size / eachSize) // 分片总数
const items: FormData[] = []
// 处理每个分片的上传操作
for (let i = 0; i < splitNum; i++) {
const start = i * eachSize
const end = Math.min(file.size, start + eachSize)
const form = new FormData()
form.append('file', file.slice(start, end))
form.append('upload_id', uploadId)
form.append('split_index', `${i + 1}`)
form.append('split_num', `${splitNum}`)
items.push(form)
}
return items
}
export const useUploadsStore = defineStore('uploads', {
state: () => {
return {
isShow: false,
items: [] as UploadItem[],
dialogueStore: useDialogueStore()
}
},
getters: {
successCount: (state) => {
return state.items.filter((item: any) => {
return item.status === 2
}).length
}
},
actions: {
close() {
this.isShow = false
},
// 获取分片文件数组索引
findItem(uploadId: string): UploadItem | undefined {
return this.items.find((item) => item.upload_id === uploadId)
},
// 通过客户端ID查找上传项
findItemByClientId(clientUploadId: string): UploadItem | undefined {
return this.items.find((item) => item.client_upload_id === clientUploadId)
},
// // 暂停文件上传
// pauseUpload(uploadId: string) {
// const item = this.findItem(uploadId)
// if (!item) return
// item.is_paused = true
// console.log(`暂停上传: ${uploadId}`)
// },
// 恢复文件上传
// resumeUpload(uploadId: string) {
// const item = this.findItem(uploadId)
// if (!item) return
// item.is_paused = false
// console.log(`恢复上传: ${uploadId}`)
// // 继续上传
// this.triggerUpload(uploadId)
// },
// 发送上传消息
async sendUploadMessage(item: any) {
try {
await ServeSendTalkFile({
upload_id: item.upload_id,
receiver_id: item.receiver_id,
talk_type: item.talk_type
})
} catch (error) {
console.error("发送上传消息失败:", error)
}
},
// 初始化上传(使用分片上传方式)
async initUploadFile(
file: File,
talkType: number,
receiverId: number,
username: string,
uploadId: string,
onProgress: (percentage: number) => void,
onComplete: (data: any) => void
) {
// 使用分片上传机制,先获取分片信息
try {
const res = await ServeFindFileSplitInfo({
file_name: file.name,
file_size: file.size
})
if (res.code == 200) {
const { upload_id, split_size } = res.data
// 使用较小的分片大小,以获得更细粒度的进度控制
// 将分片大小减半,增加分片数量
const actualSplitSize = Math.min(split_size, 512 * 1024); // 使用更小的分片如512KB
// 创建分片数组
const fileChunks = fileSlice(file, upload_id, actualSplitSize)
// @ts-ignore
this.items.unshift({
file: file,
talk_type: talkType,
receiver_id: receiverId,
upload_id: upload_id,
client_upload_id: uploadId, // 客户端生成的上传ID用于前端标识
uploadIndex: 0,
percentage: 0,
status: 0, // 文件上传状态 0:等待上传 1:上传中 2:上传完成 3:网络异常
files: fileChunks,
avatar: '',
username: username,
is_paused: false,
onProgress: onProgress,
onComplete: onComplete,
})
this.isShow = false // 不显示上传管理抽屉
// 开始上传分片
this.triggerUpload(upload_id, uploadId)
} else {
message.error(res.message)
onProgress(-1) // 通知上传失败
}
} catch (error) {
console.error("初始化分片上传失败:", error);
message.error("初始化上传失败,请重试")
onProgress(-1)
}
},
// 触发分片上传
async triggerUpload(uploadId: string, clientUploadId?: string) {
const currentItem = this.findItem(uploadId)
if (!currentItem) return
// 如果已暂停,不继续上传
if (currentItem.is_paused) return
// 如果已上传完成,不继续上传
if (currentItem.uploadIndex >= currentItem.files.length) {
if (clientUploadId) {
this.completeUpload(currentItem, clientUploadId)
}
return
}
// 获取当前要上传的分片
const form = currentItem.files[currentItem.uploadIndex]
// 更新状态为上传中
currentItem.status = 1
// 上传当前分片
try {
const res = await ServeFileSubareaUpload(form)
// 获取最新的项目状态,确保仍然存在且没有被暂停
const updatedItem = this.findItem(uploadId)
if (!updatedItem || updatedItem.is_paused) return
if (res.code == 200) {
// 当前分片上传成功,增加索引
updatedItem.uploadIndex++
// 计算上传进度
const percentage = (updatedItem.uploadIndex / updatedItem.files.length) * 100
updatedItem.percentage = parseFloat(percentage.toFixed(1))
// 回调进度
if (updatedItem.onProgress) {
updatedItem.onProgress(updatedItem.percentage)
}
// if (clientUploadId) {
// this.dialogueStore.updateUploadProgress(clientUploadId, percentage)
// }
// 检查是否全部上传完成
if (updatedItem.uploadIndex === updatedItem.files.length) {
// 所有分片上传完成
if (clientUploadId) {
this.completeUpload(updatedItem, clientUploadId)
}
} else {
// 继续上传下一个分片
this.triggerUpload(uploadId, clientUploadId)
}
} else {
// 上传失败处理
console.error(`分片上传失败,错误码: ${res.code},错误信息: ${res.message || '未知错误'}`);
updatedItem.status = 3
// 尝试重试当前分片
this.retryUpload(uploadId, clientUploadId, res.message || '上传失败,请重试')
}
} catch (error) {
console.error("分片上传错误:", error);
// 获取最新的项目状态
const updatedItem = this.findItem(uploadId)
if (!updatedItem) return
// 如果是暂停导致的错误,不改变状态
if (updatedItem.is_paused) return
updatedItem.status = 3
// 尝试重试当前分片
this.retryUpload(uploadId, clientUploadId, '网络错误,正在重试')
}
},
// 重试上传
retryUpload(uploadId: string, clientUploadId?: string, errorMessage?: string) {
const item = this.findItem(uploadId)
if (!item) return
// 如果有暂停/恢复按钮,先告知用户上传出错
if (item.onProgress) {
item.onProgress(-1)
}
// 显示错误提示
message.warning(errorMessage)
// 创建一个5秒后自动重试的机制
setTimeout(() => {
const currentItem = this.findItem(uploadId)
if (!currentItem) return
// 如果用户没有手动暂停,则自动重试
if (!currentItem.is_paused) {
console.log('正在重试上传分片...');
this.triggerUpload(uploadId, clientUploadId)
}
}, 5000)
},
// 完成上传
async completeUpload(item: UploadItem, clientUploadId: string) {
if (!item) return;
item.status = 2
item.percentage = 100
if (item.onProgress) {
item.onProgress(100)
}
// 获取最终URL并回调
try {
await ServeSendTalkFile({
upload_id: item.upload_id,
receiver_id: item.receiver_id,
talk_type: item.talk_type
})
if (item.onComplete) {
item.onComplete(item)
}
} catch (error) {
console.error("发送文件消息失败:", error);
}
},
pauseUpload(clientUploadId: string) {
const item = this.findItemByClientId(clientUploadId)
if (!item) return
item.is_paused = true
},
// 恢复上传
resumeUpload(clientUploadId: string) {
const item = this.findItemByClientId(clientUploadId)
if (!item) return
item.is_paused = false
// 继续上传
if (item.upload_id) {
this.triggerUpload(item.upload_id, clientUploadId)
}
},
// 重试文件上传
retryCommonUpload(uploadId: string, errorMessage: string) {
const item = this.findItem(uploadId)
if (!item) return
// 显示错误提示
message.warning(errorMessage)
// 创建一个5秒后自动重试的机制
setTimeout(() => {
const currentItem = this.findItem(uploadId)
if (!currentItem) return
// 如果用户没有手动暂停,则自动重试
if (!currentItem.is_paused) {
console.log('正在重试上传分片...');
this.triggerUpload(uploadId)
}
}, 5000)
},
}
})