feat(upload): 重构文件上传逻辑并添加全局上传任务管理

- 在dialogue store中添加globalUploadList和uploadTaskMap管理上传任务
- 修改PanelFooter.vue使用addUploadTask替代直接添加记录
- 在uploads.ts中完善上传失败处理和任务清理逻辑
- 在useTalkRecord.ts中加载完成后恢复上传任务
- 移除调试用的console.log语句
This commit is contained in:
Phoenix 2025-06-27 16:26:02 +08:00
parent efd61b30f4
commit 871e33990a
11 changed files with 7808 additions and 332 deletions

7350
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -227,14 +227,13 @@ class Talk extends Base {
}) })
}, 1000) }, 1000)
} }
console.log('输出加载1')
// 获取聊天面板元素节点 // 获取聊天面板元素节点
const el = document.getElementById('imChatPanel') const el = document.getElementById('imChatPanel')
if (!el) return if (!el) return
// 判断的滚动条是否在底部 // 判断的滚动条是否在底部
const isBottom = isScrollAtBottom(el) const isBottom = isScrollAtBottom(el)
if (isBottom || record.user_id == this.getAccountId()) { if (isBottom || record.user_id == this.getAccountId()) {
scrollToBottom() scrollToBottom()
} else { } else {

View File

@ -167,7 +167,7 @@ export const useTalkRecord = (uid: number) => {
nextTick(() => { nextTick(() => {
const el = document.getElementById('imChatPanel') const el = document.getElementById('imChatPanel')
console.log('request',request)
if (el) { if (el) {
if (request.cursor == 0) { if (request.cursor == 0) {
// el.scrollTop = el.scrollHeight // el.scrollTop = el.scrollHeight
@ -175,6 +175,12 @@ export const useTalkRecord = (uid: number) => {
// setTimeout(() => { // setTimeout(() => {
// el.scrollTop = el.scrollHeight + 1000 // el.scrollTop = el.scrollHeight + 1000
// }, 500) // }, 500)
console.log('滚动到底部')
// 在初次加载完成后恢复上传任务
// 确保在所有聊天记录加载完成后再恢复上传任务
dialogueStore.restoreUploadTasks()
scrollToBottom() scrollToBottom()
} else { } else {
el.scrollTop = el.scrollHeight - scrollHeight el.scrollTop = el.scrollHeight - scrollHeight
@ -322,6 +328,8 @@ export const useTalkRecord = (uid: number) => {
}) })
} else { } else {
// 其他情况滚动到底部 // 其他情况滚动到底部
// 在特殊参数模式下也需要恢复上传任务
dialogueStore.restoreUploadTasks()
scrollToBottom() scrollToBottom()
} }
} }
@ -332,6 +340,7 @@ export const useTalkRecord = (uid: number) => {
loadConfig.specialParams = undefined // 普通模式清空 loadConfig.specialParams = undefined // 普通模式清空
// 原有逻辑 // 原有逻辑
console.log('onLoad()执行load')
load(params) load(params)
} }
@ -369,6 +378,7 @@ export const useTalkRecord = (uid: number) => {
} else { } else {
// 如果不匹配,重置为普通模式 // 如果不匹配,重置为普通模式
resetLoadConfig() resetLoadConfig()
console.log('load执行2')
load({ load({
receiver_id: loadConfig.receiver_id, receiver_id: loadConfig.receiver_id,
talk_type: loadConfig.talk_type, talk_type: loadConfig.talk_type,
@ -377,6 +387,7 @@ export const useTalkRecord = (uid: number) => {
} }
} else { } else {
// 原有逻辑 // 原有逻辑
console.log('load执行3')
load({ load({
receiver_id: loadConfig.receiver_id, receiver_id: loadConfig.receiver_id,
talk_type: loadConfig.talk_type, talk_type: loadConfig.talk_type,

View File

@ -15,7 +15,9 @@ export const useDialogueStore = defineStore('dialogue', {
return { return {
// 对话索引(聊天对话的唯一索引) // 对话索引(聊天对话的唯一索引)
index_name: '', index_name: '',
globalUploadList:[],
// 添加一个映射,用于快速查找每个会话的上传任务
uploadTaskMap: {}, // 格式: { "talk_type_receiver_id": [task1, task2, ...] }
// 对话节点 // 对话节点
talk: { talk: {
avatar:'', avatar:'',
@ -129,8 +131,10 @@ export const useDialogueStore = defineStore('dialogue', {
if (data.talk_type == 2) { if (data.talk_type == 2) {
this.updateGroupMembers() this.updateGroupMembers()
this.getGroupInfo() this.getGroupInfo()
} }
// 注意:上传任务的恢复将在聊天记录加载完成后进行
// 在useTalkRecord.ts的onLoad方法中会在加载完聊天记录后调用restoreUploadTasks方法
}, },
// 更新提及列表 // 更新提及列表
@ -292,6 +296,16 @@ export const useDialogueStore = defineStore('dialogue', {
// 更新视频上传进度 // 更新视频上传进度
updateUploadProgress(uploadId, percentage) { updateUploadProgress(uploadId, percentage) {
// 更新全局列表中的进度
const globalTask = this.globalUploadList.find(item =>
item.extra && item.extra.is_uploading && item.extra.upload_id === uploadId
)
if (globalTask) {
globalTask.extra.percentage = percentage
}
// 更新当前会话记录中的进度
const record = this.records.find(item => const record = this.records.find(item =>
item.extra && item.extra.is_uploading && item.extra.upload_id === uploadId item.extra && item.extra.is_uploading && item.extra.upload_id === uploadId
) )
@ -301,6 +315,44 @@ export const useDialogueStore = defineStore('dialogue', {
} }
}, },
// 添加上传任务
addUploadTask(task) {
// 添加到全局列表
this.globalUploadList.push(task)
// 添加到会话映射
const sessionKey = `${task.talk_type}_${task.receiver_id}`
if (!this.uploadTaskMap[sessionKey]) {
this.uploadTaskMap[sessionKey] = []
}
this.uploadTaskMap[sessionKey].push(task)
// 同时添加到当前会话记录
this.addDialogueRecord(task)
},
// 上传完成后移除任务
removeUploadTask(uploadId) {
// 从全局列表中找到任务
const taskIndex = this.globalUploadList.findIndex(item => item.msg_id === uploadId)
if (taskIndex >= 0) {
const task = this.globalUploadList[taskIndex]
const sessionKey = `${task.talk_type}_${task.receiver_id}`
// 从会话映射中移除
if (this.uploadTaskMap[sessionKey]) {
const mapIndex = this.uploadTaskMap[sessionKey].findIndex(item => item.msg_id === uploadId)
if (mapIndex >= 0) {
this.uploadTaskMap[sessionKey].splice(mapIndex, 1)
}
}
// 从全局列表中移除
this.globalUploadList.splice(taskIndex, 1)
}
},
// 视频上传完成后更新消息 // 视频上传完成后更新消息
completeUpload(uploadId, videoInfo) { completeUpload(uploadId, videoInfo) {
const record = this.records.find(item => const record = this.records.find(item =>
@ -317,6 +369,23 @@ export const useDialogueStore = defineStore('dialogue', {
// 更新会话信息 // 更新会话信息
updateDialogueTalk(params){ updateDialogueTalk(params){
Object.assign(this.talk, params) Object.assign(this.talk, params)
},
// 恢复当前会话的上传任务
restoreUploadTasks() {
// 获取当前会话的sessionKey
const sessionKey = `${this.talk.talk_type}_${this.talk.receiver_id}`
// 检查是否有需要恢复的上传任务
if (this.uploadTaskMap[sessionKey] && this.uploadTaskMap[sessionKey].length > 0) {
// 按照插入顺序排序上传任务
const tasks = [...this.uploadTaskMap[sessionKey]].sort((a, b) => a.insert_sequence - b.insert_sequence)
// 将上传任务添加到当前会话记录中
tasks.forEach(task => {
this.addDialogueRecord(task)
})
}
} }
} }
}) })

View File

@ -1,7 +1,13 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ServeFindFileSplitInfo, ServeFileSubareaUpload } from '@/api/upload' // import { message } from 'naive-ui'
import { ServeSendTalkFile } from '@/api/chat' import {
import { uploadImg } from '@/api/upload' ServeSendTalkFile
} from '@/api/chat'
import {
uploadImg,
ServeFindFileSplitInfo,
ServeFileSubareaUpload
} from '@/api/upload'
import { import {
useDialogueStore useDialogueStore
} from '@/store' } from '@/store'
@ -140,12 +146,12 @@ export const useUploadsStore = defineStore('uploads', {
this.triggerUpload(upload_id, clientUploadId) this.triggerUpload(upload_id, clientUploadId)
} else { } else {
message.error(res.message) message.error(res.message)
onProgress(-1) // 通知上传失败 this.handleUploadError(upload_id, clientUploadId)
} }
} catch (error) { } catch (error) {
console.error("初始化分片上传失败:", error); console.error("初始化分片上传失败:", error);
message.error("初始化上传失败,请重试") message.error("初始化上传失败,请重试")
onProgress(-1) this.handleUploadError(upload_id, clientUploadId)
} }
}, },
@ -201,26 +207,20 @@ export const useUploadsStore = defineStore('uploads', {
this.triggerUpload(uploadId, clientUploadId) this.triggerUpload(uploadId, clientUploadId)
} }
} else { } else {
updatedItem.onProgress(-1)
// 上传失败处理 // 上传失败处理
console.error(`分片上传失败,错误码: ${res.code},错误信息: ${res.message || '未知错误'}`); console.error(`分片上传失败,错误码: ${res.code},错误信息: ${res.message || '未知错误'}`);
updatedItem.status = 3 this.handleUploadError(uploadId, clientUploadId || '')
} }
} catch (error) { } catch (error) {
updatedItem.onProgress(-1)
console.error("分片上传错误:", error); console.error("分片上传错误:", error);
// 获取最新的项目状态 // 获取最新的项目状态
// 这里不应该重新定义变量而是使用已有的updatedItem
// const updatedItem = this.findItem(uploadId)
if (!updatedItem) return if (!updatedItem) return
// 如果是暂停导致的错误,不改变状态 // 如果是暂停导致的错误,不改变状态
if (updatedItem.is_paused) return if (updatedItem.is_paused) return
updatedItem.status = 3 this.handleUploadError(uploadId, clientUploadId || '')
} }
}, },
@ -244,6 +244,10 @@ export const useUploadsStore = defineStore('uploads', {
talk_type: item.talk_type talk_type: item.talk_type
}) })
// 从DialogueStore中移除上传任务
const dialogueStore = useDialogueStore()
dialogueStore.removeUploadTask(clientUploadId)
if (item.onComplete) { if (item.onComplete) {
item.onComplete(item) item.onComplete(item)
} }
@ -291,5 +295,21 @@ export const useUploadsStore = defineStore('uploads', {
// 从上传列表中移除旧的上传项 // 从上传列表中移除旧的上传项
this.items = this.items.filter(i => i.client_upload_id !== clientUploadId) 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) // 通知上传失败
}
}
} }
}) })

View File

@ -54,7 +54,6 @@ request.interceptors.request.use((config) => {
// 响应拦截器 // 响应拦截器
request.interceptors.response.use((response) => { request.interceptors.response.use((response) => {
console.log('response.data.status',response.data.status)
if(response.data.code !==200&&response.data.status!==0){ if(response.data.code !==200&&response.data.status!==0){
window['$message'].warning(response.data.msg) window['$message'].warning(response.data.msg)
} }

View File

@ -592,8 +592,6 @@ const indexName = computed(() => dialogueStore.index_name)
// //
const onTabTalk = (item: ISession, follow = false) => { const onTabTalk = (item: ISession, follow = false) => {
console.log('onTabTalk')
if (item.index_name === indexName.value) return if (item.index_name === indexName.value) return
searchKeyword.value = '' searchKeyword.value = ''
@ -638,7 +636,7 @@ const onReload = () => {
// //
const onInitialize = () => { const onInitialize = () => {
let index_name = getCacheIndexName() let index_name = getCacheIndexName()
console.log('index_name',index_name)
index_name && onTabTalk(talkStore.findItem(index_name), true) index_name && onTabTalk(talkStore.findItem(index_name), true)
} }

View File

@ -393,7 +393,7 @@ watch(
}, 3000) }, 3000)
return return
} }
console.log('执行逻辑')
onLoad( onLoad(
{ {
receiver_id: newProps.receiver_id, receiver_id: newProps.receiver_id,
@ -403,7 +403,7 @@ watch(
specialParams ? { specifiedMsg: specialParams } : undefined specialParams ? { specifiedMsg: specialParams } : undefined
) )
}, },
{ immediate: true, deep: true } { deep: true }
) )
// onMounted(() => { // onMounted(() => {
@ -589,12 +589,25 @@ const handleIntersection = (entries) => {
watch( watch(
() => records.value, () => records.value,
() => { () => {
console.log()
nextTick(() => { nextTick(() => {
// //
if (observer) { if (observer) {
observer.disconnect() observer.disconnect()
} }
//
// const recordsCopy = [...dialogueStore.records];
// for (const [y, iy] of dialogueStore.globalUploadList.entries()) {
// console.log('y',y)
// console.log('iy',iy)
// for (const [x, ix] of recordsCopy.entries()) {
// if(x.msg_id === y.pre_msg){
// // ix
// dialogueStore.records.splice(ix + 1 + (dialogueStore.records.length - recordsCopy.length), 0, y);
// }
// }
// }
// console.log('dialogueStore.records',dialogueStore.records)
// //
const options = { const options = {
root: null, root: null,

View File

@ -111,10 +111,13 @@ const onSendVideoEvent = async ({ data }) => {
// ID // ID
const uploadId = `video-${Date.now()}-${Math.floor(Math.random() * 1000)}` const uploadId = `video-${Date.now()}-${Math.floor(Math.random() * 1000)}`
// //
const tempMessage = { const tempMessage = {
msg_id: uploadId, msg_id: uploadId,
insert_sequence: dialogueStore.records.length > 0
? dialogueStore.records[dialogueStore.records.length-1].sequence
: 0,
sequence: Date.now(), sequence: Date.now(),
talk_type: props.talk_type, talk_type: props.talk_type,
msg_type: 5, // msg_type: 5, //
@ -137,8 +140,8 @@ const onSendVideoEvent = async ({ data }) => {
float: 'right' // float: 'right' //
} }
// // 使
dialogueStore.addDialogueRecord(tempMessage) dialogueStore.addUploadTask(tempMessage)
nextTick(()=>{ nextTick(()=>{
scrollToBottom() scrollToBottom()
}) })
@ -151,8 +154,8 @@ const onSendVideoEvent = async ({ data }) => {
dialogueStore.updateUploadProgress(uploadId, percentage) dialogueStore.updateUploadProgress(uploadId, percentage)
}, },
async () => { async () => {
dialogueStore.batchDelDialogueRecord([uploadId]) // removeUploadTask
// globalUploadList
} }
) )
} }
@ -171,6 +174,9 @@ const onSendFileEvent = ({ data }) => {
const clientUploadId = `file-${Date.now()}-${Math.floor(Math.random() * 1000)}` const clientUploadId = `file-${Date.now()}-${Math.floor(Math.random() * 1000)}`
const tempMessage = { const tempMessage = {
msg_id: clientUploadId, msg_id: clientUploadId,
insert_sequence: dialogueStore.records.length > 0
? dialogueStore.records[dialogueStore.records.length-1].sequence
: 0,
sequence: Date.now(), sequence: Date.now(),
talk_type: props.talk_type, talk_type: props.talk_type,
msg_type: 6, msg_type: 6,
@ -192,7 +198,7 @@ const onSendFileEvent = ({ data }) => {
}, },
float: 'right' float: 'right'
} }
dialogueStore.addDialogueRecord(tempMessage) dialogueStore.addUploadTask(tempMessage)
nextTick(()=>{ nextTick(()=>{
scrollToBottom() scrollToBottom()
}) })
@ -201,8 +207,8 @@ const onSendFileEvent = ({ data }) => {
dialogueStore.updateUploadProgress(clientUploadId, percentage) dialogueStore.updateUploadProgress(clientUploadId, percentage)
}, },
async () => { async () => {
dialogueStore.batchDelDialogueRecord([clientUploadId]) // removeUploadTask
// records
} }
) )
} }

View File

@ -46,9 +46,9 @@ export default defineConfig(({ mode }) => {
vueJsx({}), vueJsx({}),
compressPlugin(), compressPlugin(),
UnoCSS(), UnoCSS(),
// vueDevTools({ vueDevTools({
// launchEditor: 'trae', launchEditor: 'trae',
// }) })
], ],
define: { define: {
__APP_ENV__: env.APP_ENV __APP_ENV__: env.APP_ENV