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

316 lines
9.2 KiB
TypeScript
Raw Normal View History

2024-12-24 08:14:21 +00:00
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'
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)
},
// 发送上传消息
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,
clientUploadId: 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: clientUploadId, // 客户端生成的上传ID用于前端标识
2024-12-24 08:14:21 +00:00
uploadIndex: 0,
percentage: 0,
status: 0, // 文件上传状态 0:等待上传 1:上传中 2:上传完成 3:网络异常
files: fileChunks,
is_paused: false,
onProgress: onProgress,
onComplete: onComplete,
2024-12-24 08:14:21 +00:00
})
this.isShow = false // 不显示上传管理抽屉
// 开始上传分片
this.triggerUpload(upload_id, clientUploadId)
2024-12-24 08:14:21 +00:00
} else {
message.error(res.message)
this.handleUploadError(upload_id, clientUploadId)
2024-12-24 08:14:21 +00:00
}
} catch (error) {
console.error("初始化分片上传失败:", error);
message.error("初始化上传失败,请重试")
this.handleUploadError(upload_id, clientUploadId)
}
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
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)
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 || '未知错误'}`);
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
2024-12-24 08:14:21 +00:00
})
// 从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)
2024-12-24 08:14:21 +00:00
},
// 上传失败处理
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) // 通知上传失败
}
}
2024-12-24 08:14:21 +00:00
}
})