2024-12-24 08:14:21 +00:00
|
|
|
|
import { defineStore } from 'pinia'
|
2025-06-27 08:26:02 +00:00
|
|
|
|
// import { message } from 'naive-ui'
|
|
|
|
|
import {
|
|
|
|
|
ServeSendTalkFile
|
|
|
|
|
} from '@/api/chat'
|
|
|
|
|
import {
|
|
|
|
|
uploadImg,
|
|
|
|
|
ServeFindFileSplitInfo,
|
|
|
|
|
ServeFileSubareaUpload
|
|
|
|
|
} from '@/api/upload'
|
2025-05-14 03:50:52 +00:00
|
|
|
|
import {
|
|
|
|
|
useDialogueStore
|
|
|
|
|
} from '@/store'
|
2024-12-24 08:14:21 +00:00
|
|
|
|
// @ts-ignore
|
|
|
|
|
const message = window.$message
|
|
|
|
|
|
2025-05-14 03:50:52 +00:00
|
|
|
|
// 定义上传项接口
|
|
|
|
|
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,
|
2025-05-14 03:50:52 +00:00
|
|
|
|
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
|
|
|
|
|
},
|
2025-05-14 03:50:52 +00:00
|
|
|
|
// 获取分片文件数组索引
|
|
|
|
|
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
|
|
|
|
|
2025-05-15 08:07:56 +00:00
|
|
|
|
// 初始化上传(使用分片上传方式)
|
2025-05-14 03:50:52 +00:00
|
|
|
|
async initUploadFile(
|
|
|
|
|
file: File,
|
|
|
|
|
talkType: number,
|
|
|
|
|
receiverId: number,
|
2025-05-26 08:43:11 +00:00
|
|
|
|
clientUploadId: string,
|
2025-05-14 03:50:52 +00:00
|
|
|
|
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
|
2025-05-14 03:50:52 +00:00
|
|
|
|
|
|
|
|
|
// 使用较小的分片大小,以获得更细粒度的进度控制
|
|
|
|
|
// 将分片大小减半,增加分片数量
|
|
|
|
|
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,
|
2025-05-26 08:43:11 +00:00
|
|
|
|
client_upload_id: clientUploadId, // 客户端生成的上传ID,用于前端标识
|
2024-12-24 08:14:21 +00:00
|
|
|
|
uploadIndex: 0,
|
|
|
|
|
percentage: 0,
|
|
|
|
|
status: 0, // 文件上传状态 0:等待上传 1:上传中 2:上传完成 3:网络异常
|
2025-05-14 03:50:52 +00:00
|
|
|
|
files: fileChunks,
|
|
|
|
|
is_paused: false,
|
|
|
|
|
onProgress: onProgress,
|
|
|
|
|
onComplete: onComplete,
|
2024-12-24 08:14:21 +00:00
|
|
|
|
})
|
2025-05-14 03:50:52 +00:00
|
|
|
|
|
|
|
|
|
this.isShow = false // 不显示上传管理抽屉
|
|
|
|
|
|
|
|
|
|
// 开始上传分片
|
2025-05-26 08:43:11 +00:00
|
|
|
|
this.triggerUpload(upload_id, clientUploadId)
|
2024-12-24 08:14:21 +00:00
|
|
|
|
} else {
|
|
|
|
|
message.error(res.message)
|
2025-06-27 08:26:02 +00:00
|
|
|
|
this.handleUploadError(upload_id, clientUploadId)
|
2024-12-24 08:14:21 +00:00
|
|
|
|
}
|
2025-05-14 03:50:52 +00:00
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("初始化分片上传失败:", error);
|
|
|
|
|
message.error("初始化上传失败,请重试")
|
2025-06-27 08:26:02 +00:00
|
|
|
|
this.handleUploadError(upload_id, clientUploadId)
|
2025-05-14 03:50:52 +00:00
|
|
|
|
}
|
2024-12-24 08:14:21 +00:00
|
|
|
|
},
|
2025-05-14 03:50:52 +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
|
2025-06-11 06:47:13 +00:00
|
|
|
|
const updatedItem:any = this.findItem(uploadId)
|
2025-05-14 03:50:52 +00:00
|
|
|
|
// 上传当前分片
|
|
|
|
|
try {
|
2025-05-26 08:43:11 +00:00
|
|
|
|
|
2025-05-14 03:50:52 +00:00
|
|
|
|
const res = await ServeFileSubareaUpload(form)
|
2025-05-26 08:43:11 +00:00
|
|
|
|
|
2025-05-14 03:50:52 +00:00
|
|
|
|
// 获取最新的项目状态,确保仍然存在且没有被暂停
|
2025-06-11 06:47:13 +00:00
|
|
|
|
|
2025-05-14 03:50:52 +00:00
|
|
|
|
if (res.code == 200) {
|
|
|
|
|
// 当前分片上传成功,增加索引
|
|
|
|
|
updatedItem.uploadIndex++
|
2025-05-26 08:43:11 +00:00
|
|
|
|
|
2025-05-14 03:50:52 +00:00
|
|
|
|
// 计算上传进度
|
|
|
|
|
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 {
|
2025-05-14 03:50:52 +00:00
|
|
|
|
// 继续上传下一个分片
|
|
|
|
|
this.triggerUpload(uploadId, clientUploadId)
|
2024-12-24 08:14:21 +00:00
|
|
|
|
}
|
2025-05-14 03:50:52 +00:00
|
|
|
|
} else {
|
|
|
|
|
// 上传失败处理
|
|
|
|
|
console.error(`分片上传失败,错误码: ${res.code},错误信息: ${res.message || '未知错误'}`);
|
2025-06-27 08:26:02 +00:00
|
|
|
|
this.handleUploadError(uploadId, clientUploadId || '')
|
2025-05-14 03:50:52 +00:00
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("分片上传错误:", error);
|
|
|
|
|
|
|
|
|
|
// 获取最新的项目状态
|
|
|
|
|
if (!updatedItem) return
|
|
|
|
|
|
|
|
|
|
// 如果是暂停导致的错误,不改变状态
|
|
|
|
|
if (updatedItem.is_paused) return
|
|
|
|
|
|
2025-06-27 08:26:02 +00:00
|
|
|
|
this.handleUploadError(uploadId, clientUploadId || '')
|
2025-05-14 03:50:52 +00:00
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2025-05-26 08:43:11 +00:00
|
|
|
|
|
2025-05-14 03:50:52 +00:00
|
|
|
|
|
|
|
|
|
// 完成上传
|
|
|
|
|
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
|
|
|
|
})
|
2025-05-14 03:50:52 +00:00
|
|
|
|
|
2025-06-27 08:26:02 +00:00
|
|
|
|
// 从DialogueStore中移除上传任务
|
|
|
|
|
const dialogueStore = useDialogueStore()
|
|
|
|
|
dialogueStore.removeUploadTask(clientUploadId)
|
|
|
|
|
|
2025-05-14 03:50:52 +00:00
|
|
|
|
if (item.onComplete) {
|
|
|
|
|
item.onComplete(item)
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("发送文件消息失败:", error);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2025-05-15 08:07:56 +00:00
|
|
|
|
|
|
|
|
|
pauseUpload(clientUploadId: string) {
|
2025-05-14 03:50:52 +00:00
|
|
|
|
const item = this.findItemByClientId(clientUploadId)
|
|
|
|
|
if (!item) return
|
|
|
|
|
|
|
|
|
|
item.is_paused = true
|
|
|
|
|
},
|
|
|
|
|
|
2025-05-15 08:07:56 +00:00
|
|
|
|
// 恢复上传
|
|
|
|
|
resumeUpload(clientUploadId: string) {
|
2025-05-14 03:50:52 +00:00
|
|
|
|
const item = this.findItemByClientId(clientUploadId)
|
|
|
|
|
if (!item) return
|
|
|
|
|
|
|
|
|
|
item.is_paused = false
|
|
|
|
|
|
|
|
|
|
// 继续上传
|
|
|
|
|
if (item.upload_id) {
|
|
|
|
|
this.triggerUpload(item.upload_id, clientUploadId)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 重试文件上传
|
2025-05-27 03:20:55 +00:00
|
|
|
|
retryCommonUpload(clientUploadId: string) {
|
|
|
|
|
const item = this.findItemByClientId(clientUploadId)
|
2025-05-14 03:50:52 +00:00
|
|
|
|
if (!item) return
|
|
|
|
|
|
2025-05-27 03:20:55 +00:00
|
|
|
|
// 重新初始化上传,以便重新获取分片信息
|
|
|
|
|
this.initUploadFile(
|
|
|
|
|
item.file,
|
|
|
|
|
item.talk_type,
|
|
|
|
|
item.receiver_id,
|
|
|
|
|
clientUploadId,
|
|
|
|
|
item.onProgress || ((percentage: number) => {}),
|
|
|
|
|
item.onComplete || ((data: any) => {})
|
|
|
|
|
)
|
2025-05-14 03:50:52 +00:00
|
|
|
|
|
2025-05-27 03:20:55 +00:00
|
|
|
|
// 从上传列表中移除旧的上传项
|
|
|
|
|
this.items = this.items.filter(i => i.client_upload_id !== clientUploadId)
|
2024-12-24 08:14:21 +00:00
|
|
|
|
},
|
2025-06-27 08:26:02 +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
|
|
|
|
}
|
|
|
|
|
})
|