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) // 通知上传失败 } } } })