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

352 lines
10 KiB
TypeScript
Raw Normal View History

2024-12-24 08:14:21 +00:00
import { defineStore } from 'pinia'
import { ServeFindFileSplitInfo, ServeFileSubareaUpload } from '@/api/upload'
import { ServeSendTalkFile } from '@/api/chat'
import { uploadImg } from '@/api/upload'
import {
useDialogueStore
} from '@/store'
2024-12-24 08:14:21 +00:00
// @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;
}
2024-12-24 08:14:21 +00:00
// 处理拆分上传文件
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()
2024-12-24 08:14:21 +00:00
}
},
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)
}
},
2024-12-24 08:14:21 +00:00
// 初始化上传(使用分片上传方式)
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
})
2024-12-24 08:14:21 +00:00
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)
2024-12-24 08:14:21 +00:00
// @ts-ignore
this.items.unshift({
file: file,
talk_type: talkType,
receiver_id: receiverId,
upload_id: upload_id,
client_upload_id: uploadId, // 客户端生成的上传ID用于前端标识
2024-12-24 08:14:21 +00:00
uploadIndex: 0,
percentage: 0,
status: 0, // 文件上传状态 0:等待上传 1:上传中 2:上传完成 3:网络异常
files: fileChunks,
2024-12-24 08:14:21 +00:00
avatar: '',
username: username,
is_paused: false,
onProgress: onProgress,
onComplete: onComplete,
2024-12-24 08:14:21 +00:00
})
this.isShow = false // 不显示上传管理抽屉
// 开始上传分片
this.triggerUpload(upload_id, uploadId)
2024-12-24 08:14:21 +00:00
} else {
message.error(res.message)
onProgress(-1) // 通知上传失败
2024-12-24 08:14:21 +00:00
}
} catch (error) {
console.error("初始化分片上传失败:", error);
message.error("初始化上传失败,请重试")
onProgress(-1)
}
2024-12-24 08:14:21 +00:00
},
// 触发分片上传
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)
2024-12-24 08:14:21 +00:00
}
} else {
// 继续上传下一个分片
this.triggerUpload(uploadId, clientUploadId)
2024-12-24 08:14:21 +00:00
}
} 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
2024-12-24 08:14:21 +00:00
})
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)
2024-12-24 08:14:21 +00:00
},
}
})