diff --git a/yunkes/src/components/staffchat/SessionPane.vue b/yunkes/src/components/staffchat/SessionPane.vue index 513bf97..f73a9ac 100644 --- a/yunkes/src/components/staffchat/SessionPane.vue +++ b/yunkes/src/components/staffchat/SessionPane.vue @@ -40,7 +40,13 @@ function clearSearch() { searchTruncated.value = false } +/** 请求序号: ownerId/kind/tab/模式切换后在途旧响应整体丢弃(防竞态覆盖新员工/新 tab 的数据)。 + load 与 runSearch 共用同一序号——两者共享 loading/错误态,且 ownerId 切换只重发 load, + 共号才能把旧 owner 在途的搜索响应一并作废(对齐全 pane 惯例: MomentsPane 单 seq)。 */ +let seq = 0 + async function load() { + const mySeq = ++seq loading.value = true errorMsg.value = null clearSearch() @@ -50,12 +56,14 @@ async function load() { channel: props.channel, ...(props.kind === 'single' ? { is_del: friendTab.value === 'deleted' ? 1 : 0 } : {}), }) + if (mySeq !== seq) return sessions.value = res.sessions } catch (e) { + if (mySeq !== seq) return errorMsg.value = (e as Error).message sessions.value = [] } finally { - loading.value = false + if (mySeq === seq) loading.value = false } } watch(() => [props.ownerId, props.kind] as const, load, { immediate: true }) @@ -128,6 +136,7 @@ async function runSearch() { clearSearch() return } + const mySeq = ++seq loading.value = true errorMsg.value = null try { @@ -138,6 +147,7 @@ async function runSearch() { page: 1, page_size: 100, }) + if (mySeq !== seq) return searchTotal.value = res.total searchTruncated.value = res.total > res.items.length // 已加载的会话列表提供昵称/头像;未命中(search 只回消息)再以 sender_name/键兜底 @@ -165,10 +175,11 @@ async function runSearch() { (a, b) => (b.last_chat_time ?? 0) - (a.last_chat_time ?? 0), ) } catch (e) { + if (mySeq !== seq) return errorMsg.value = (e as Error).message clearSearch() } finally { - loading.value = false + if (mySeq === seq) loading.value = false } } @@ -228,7 +239,8 @@ async function runSearch() {
{{ displayName(s) }} - 今日{{ s.today_count }} + + 今日{{ s.today_count }}
{{ relativeChatTime(s.last_chat_time) }}