fix(chat): loadInitial/loadTalkerName 加请求序号防会话切换竞态(ChatMain+ChatTimeline同修)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,12 @@ const total = ref(0)
|
||||
/** 首次加载是否完成(msgid 定位只在已加载数据上尝试,避免误报「不在当前页」)。 */
|
||||
const initialLoaded = ref(false)
|
||||
|
||||
// 请求序号防竞态:快速切换会话时,旧会话的请求仍在途、响应后到,若不拦截会把
|
||||
// 新会话已渲染的 messages/total/currentPage/标题 覆盖回旧会话数据(自愈但错乱)。
|
||||
// 每次 loadInitial/loadTalkerName 自增取号,响应回来时号不对即整体丢弃。
|
||||
let loadSeq = 0
|
||||
let nameSeq = 0
|
||||
|
||||
const isGroup = computed(() => props.kind === 'group')
|
||||
/** 是否已选定具体会话(决定是否拉消息 / 显示空态)。账号级入口(talkerId='')→ 空态。 */
|
||||
const hasActive = computed(() => !!props.talkerId)
|
||||
@@ -62,12 +68,14 @@ const visibleMessages = computed(() => {
|
||||
const talkerName = ref<string | null>(null)
|
||||
|
||||
async function loadTalkerName(): Promise<void> {
|
||||
const seq = ++nameSeq
|
||||
talkerName.value = null
|
||||
if (!hasActive.value) return
|
||||
try {
|
||||
if (isGroup.value && props.talkerId) {
|
||||
// 群名(room_id 精确单行直查,不再拉整页群列表)
|
||||
const res = await getGroups({ channel: CHANNEL, room_id: props.talkerId, page: 1, page_size: 1 })
|
||||
if (seq !== nameSeq) return // 竞态:已有更新的请求,丢弃旧会话响应
|
||||
talkerName.value = res.items[0]?.room_name || null
|
||||
} else if (props.talkerId) {
|
||||
// 客户昵称
|
||||
@@ -78,6 +86,7 @@ async function loadTalkerName(): Promise<void> {
|
||||
page: 1,
|
||||
page_size: 1,
|
||||
})
|
||||
if (seq !== nameSeq) return // 竞态:已有更新的请求,丢弃旧会话响应
|
||||
const c = res.items[0]
|
||||
talkerName.value = c?.friend_nickname || c?.friend_remark || null
|
||||
}
|
||||
@@ -184,6 +193,7 @@ function queryFor(pageNum: number) {
|
||||
* returns asc, so the last page holds the newest slice.
|
||||
*/
|
||||
async function loadInitial(): Promise<void> {
|
||||
const seq = ++loadSeq
|
||||
highlightId.value = null
|
||||
// 账号级入口(无具体会话):不拉消息,清空状态,留空态给右列。
|
||||
if (!hasActive.value) {
|
||||
@@ -193,12 +203,15 @@ async function loadInitial(): Promise<void> {
|
||||
currentPage.value = 1
|
||||
pendingMsgid.value = null
|
||||
initialLoaded.value = true
|
||||
// 本分支已取最新序号,在途旧请求的 finally 会被拦下,须自行复位加载态
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
errorMsg.value = null
|
||||
try {
|
||||
const first = await getMessages(queryFor(1))
|
||||
if (seq !== loadSeq) return // 竞态:已有更新的加载,丢弃旧会话响应
|
||||
total.value = first.total
|
||||
totalPages.value = Math.max(1, Math.ceil(first.total / PAGE_SIZE))
|
||||
|
||||
@@ -206,16 +219,21 @@ async function loadInitial(): Promise<void> {
|
||||
messages.value = first.items
|
||||
} else {
|
||||
const last = await getMessages(queryFor(totalPages.value))
|
||||
if (seq !== loadSeq) return // 竞态:已有更新的加载,丢弃旧会话响应
|
||||
messages.value = last.items
|
||||
}
|
||||
currentPage.value = totalPages.value
|
||||
scrollToBottom()
|
||||
} catch (e) {
|
||||
if (seq !== loadSeq) return // 竞态:旧请求的报错不展示(会话已切换)
|
||||
errorMsg.value = (e as Error).message
|
||||
messages.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
initialLoaded.value = true
|
||||
// 只有最新请求才能复位加载态:被取代的旧请求不得清掉新请求的 loading
|
||||
if (seq === loadSeq) {
|
||||
loading.value = false
|
||||
initialLoaded.value = true
|
||||
}
|
||||
}
|
||||
// DOM 渲染完再尝试 msgid 定位(覆盖 scrollToBottom 的置底)。
|
||||
nextTick(() => tryLocate())
|
||||
|
||||
@@ -42,6 +42,12 @@ const currentPage = ref(1)
|
||||
const totalPages = ref(1)
|
||||
const total = ref(0)
|
||||
|
||||
// 请求序号防竞态:快速切换会话时,旧会话的请求仍在途、响应后到,若不拦截会把
|
||||
// 新会话已渲染的 messages/total/currentPage/标题 覆盖回旧会话数据(自愈但错乱)。
|
||||
// 每次 loadInitial/loadTalkerName 自增取号,响应回来时号不对即整体丢弃。
|
||||
let loadSeq = 0
|
||||
let nameSeq = 0
|
||||
|
||||
const channelTyped = computed(() => props.channel as Channel)
|
||||
|
||||
/** True when this timeline is a group chat (drives the query + title). */
|
||||
@@ -72,12 +78,14 @@ const visibleMessages = computed(() => {
|
||||
const talkerName = ref<string | null>(null)
|
||||
|
||||
async function loadTalkerName(): Promise<void> {
|
||||
const seq = ++nameSeq
|
||||
talkerName.value = null
|
||||
if (!hasActive.value) return
|
||||
try {
|
||||
if (isGroup.value && props.roomId) {
|
||||
// 群名(room_id 精确单行直查,不再拉整页群列表)
|
||||
const res = await getGroups({ channel: channelTyped.value, room_id: props.roomId, page: 1, page_size: 1 })
|
||||
if (seq !== nameSeq) return // 竞态:已有更新的请求,丢弃旧会话响应
|
||||
talkerName.value = res.items[0]?.room_name || null
|
||||
} else if (props.talkerId) {
|
||||
// 客户昵称
|
||||
@@ -88,6 +96,7 @@ async function loadTalkerName(): Promise<void> {
|
||||
page: 1,
|
||||
page_size: 1,
|
||||
})
|
||||
if (seq !== nameSeq) return // 竞态:已有更新的请求,丢弃旧会话响应
|
||||
const c = res.items[0]
|
||||
talkerName.value = c?.friend_nickname || c?.friend_remark || null
|
||||
}
|
||||
@@ -188,18 +197,22 @@ function queryFor(pageNum: number) {
|
||||
* returns asc, so the last page holds the newest slice.
|
||||
*/
|
||||
async function loadInitial(): Promise<void> {
|
||||
const seq = ++loadSeq
|
||||
// 账号级入口(无具体会话):不拉消息,清空状态,留空态给右侧主区。
|
||||
if (!hasActive.value) {
|
||||
messages.value = []
|
||||
total.value = 0
|
||||
totalPages.value = 1
|
||||
currentPage.value = 1
|
||||
// 本分支已取最新序号,在途旧请求的 finally 会被拦下,须自行复位加载态
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
errorMsg.value = null
|
||||
try {
|
||||
const first = await getMessages(queryFor(1))
|
||||
if (seq !== loadSeq) return // 竞态:已有更新的加载,丢弃旧会话响应
|
||||
total.value = first.total
|
||||
totalPages.value = Math.max(1, Math.ceil(first.total / PAGE_SIZE))
|
||||
|
||||
@@ -207,15 +220,18 @@ async function loadInitial(): Promise<void> {
|
||||
messages.value = first.items
|
||||
} else {
|
||||
const last = await getMessages(queryFor(totalPages.value))
|
||||
if (seq !== loadSeq) return // 竞态:已有更新的加载,丢弃旧会话响应
|
||||
messages.value = last.items
|
||||
}
|
||||
currentPage.value = totalPages.value
|
||||
scrollToBottom()
|
||||
} catch (e) {
|
||||
if (seq !== loadSeq) return // 竞态:旧请求的报错不展示(会话已切换)
|
||||
errorMsg.value = (e as Error).message
|
||||
messages.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
// 只有最新请求才能复位加载态:被取代的旧请求不得清掉新请求的 loading
|
||||
if (seq === loadSeq) loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user