diff --git a/yunkes/src/components/staffchat/SessionPane.vue b/yunkes/src/components/staffchat/SessionPane.vue index 4f4091a..0fb5303 100644 --- a/yunkes/src/components/staffchat/SessionPane.vue +++ b/yunkes/src/components/staffchat/SessionPane.vue @@ -29,11 +29,21 @@ const errorMsg = ref(null) const pinnedOpen = ref(true) /** search 模式聚合结果(q 命中的消息按会话去重,取首条作为该会话快照)。 */ const searchResults = ref([]) +/** 搜索命中总数与截断标记(后端单页上限 100,超出时只聚合已取回条内的会话)。 */ +const searchTotal = ref(0) +const searchTruncated = ref(false) + +/** 清搜索态: 结果与截断提示一起清,不残留上次的统计。 */ +function clearSearch() { + searchResults.value = [] + searchTotal.value = 0 + searchTruncated.value = false +} async function load() { loading.value = true errorMsg.value = null - searchResults.value = [] + clearSearch() try { const res = await getSessions({ owner_account_id: props.ownerId, @@ -48,16 +58,21 @@ async function load() { loading.value = false } } -watch(() => [props.ownerId, props.kind, friendTab.value] as const, load, { immediate: true }) +watch(() => [props.ownerId, props.kind] as const, load, { immediate: true }) +/** 好友/已删 tab 只过滤 list 会话列表(is_del);search 模式的消息搜索与 is_del + 无关——切 tab 不重载、不清关键词/结果(搜索跨 tab 保持有效)。 */ +watch(friendTab, () => { + if (mode.value === 'list') load() +}) /** 切模式清关键词与旧结果,避免 list 过滤词被误当搜索词。 */ watch(mode, () => { keyword.value = '' - searchResults.value = [] + clearSearch() }) /** search 模式清空关键词 → 回到未搜索态(不残留上次聚合结果)。 */ watch(keyword, (k) => { - if (mode.value === 'search' && !k.trim()) searchResults.value = [] + if (mode.value === 'search' && !k.trim()) clearSearch() }) /** 置顶区: 仅 单聊+未删好友+list 模式(后端 pinned 目前只有 filehelper)。 */ @@ -106,7 +121,7 @@ function sessionKey(s: ChatSession): string { async function runSearch() { const k = keyword.value.trim() if (mode.value !== 'search' || !k) { - searchResults.value = [] + clearSearch() return } loading.value = true @@ -119,10 +134,14 @@ async function runSearch() { page: 1, page_size: 100, }) + searchTotal.value = res.total + searchTruncated.value = res.total > res.items.length // 已加载的会话列表提供昵称/头像;未命中(search 只回消息)再以 sender_name/键兜底 const known = new Map(sessions.value.map(s => [sessionKey(s), s])) const seen = new Map() - for (const m of res.items) { + // 陷阱: 后端 /messages 按 timestamp ASC 排序,page 1 取回的是最旧的 100 条命中。 + // 倒序遍历(新→旧)+ 首见即取,保证每个会话的快照时间 = 已取回命中里最新的那条。 + for (const m of [...res.items].reverse()) { const key = m.room_id ?? m.talker_id ?? '' if (!key || seen.has(key)) continue const hit = known.get(key) @@ -131,7 +150,8 @@ async function runSearch() { type: m.room_id ? 'group' : 'friend', talker_id: m.room_id ? undefined : m.talker_id ?? undefined, room_id: m.room_id ?? undefined, - name: hit?.name ?? m.sender_name ?? key, + // is_send=true 时 sender_name 是员工自己,不能当卡片标题 + name: hit?.name ?? (!m.is_send ? m.sender_name : key), avatar: hit?.avatar ?? undefined, today_count: 0, last_chat_time: m.timestamp ?? null, @@ -142,7 +162,7 @@ async function runSearch() { ) } catch (e) { errorMsg.value = (e as Error).message - searchResults.value = [] + clearSearch() } finally { loading.value = false } @@ -212,6 +232,11 @@ async function runSearch() { + +
+ 共 {{ searchTotal }} 条命中,仅聚合最旧 100 条内的会话 +
+
{{ emptyText }}
n.key)