chat-pc/src/store/modules/uploads.ts
2025-07-03 15:52:49 +08:00

316 lines
9.2 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 { message } from 'naive-ui'
import {
ServeSendTalkFile
} from '@/api/chat'
import {
uploadImg,
ServeFindFileSplitInfo,
ServeFileSubareaUpload
} 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)
},
// 发送上传消息
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,
clientUploadId: 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: clientUploadId, // 客户端生成的上传ID用于前端标识
uploadIndex: 0,
percentage: 0,
status: 0, // 文件上传状态 0:等待上传 1:上传中 2:上传完成 3:网络异常
files: fileChunks,
is_paused: false,
onProgress: onProgress,
onComplete: onComplete,
})
this.isShow = false // 不显示上传管理抽屉
// 开始上传分片
this.triggerUpload(upload_id, clientUploadId)
} else {
message.error(res.message)
this.handleUploadError(upload_id, clientUploadId)
}
} catch (error) {
console.error("初始化分片上传失败:", error);
message.error("上传失败,请重试")
this.handleUploadError(upload_id, clientUploadId)
}
},
// 触发分片上传
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
const updatedItem:any = this.findItem(uploadId)
// 上传当前分片
try {
const res = await ServeFileSubareaUpload(form)
// 获取最新的项目状态,确保仍然存在且没有被暂停
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 (updatedItem.uploadIndex === updatedItem.files.length) {
// 所有分片上传完成
if (clientUploadId) {
this.completeUpload(updatedItem, clientUploadId)
}
} else {
// 继续上传下一个分片
this.triggerUpload(uploadId, clientUploadId)
}
} else {
// 上传失败处理
console.error(`分片上传失败,错误码: ${res.code},错误信息: ${res.message || '未知错误'}`);
this.handleUploadError(uploadId, clientUploadId || '')
}
} catch (error) {
console.error("分片上传错误:", error);
// 获取最新的项目状态
if (!updatedItem) return
// 如果是暂停导致的错误,不改变状态
if (updatedItem.is_paused) return
this.handleUploadError(uploadId, clientUploadId || '')
}
},
// 完成上传
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
})
// 从DialogueStore中移除上传任务
const dialogueStore = useDialogueStore()
dialogueStore.removeUploadTask(clientUploadId)
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(clientUploadId: string) {
const item = this.findItemByClientId(clientUploadId)
if (!item) return
// 重新初始化上传,以便重新获取分片信息
this.initUploadFile(
item.file,
item.talk_type,
item.receiver_id,
clientUploadId,
item.onProgress || ((percentage: number) => {}),
item.onComplete || ((data: any) => {})
)
// 从上传列表中移除旧的上传项
this.items = this.items.filter(i => i.client_upload_id !== clientUploadId)
},
// 上传失败处理
async handleUploadError(uploadId: string, clientUploadId: string) {
const item = this.findItem(uploadId)
if (!item) return
item.status = 3 // 设置为上传失败状态
// 从DialogueStore中移除上传任务
const dialogueStore = useDialogueStore()
dialogueStore.removeUploadTask(clientUploadId)
if (item.onProgress) {
item.onProgress(-1) // 通知上传失败
}
}
}
})