316 lines
9.2 KiB
TypeScript
316 lines
9.2 KiB
TypeScript
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) // 通知上传失败
|
||
}
|
||
}
|
||
}
|
||
})
|