From 8f166a9c1886621b037d29ee663ab0e922aa2ed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Sat, 8 Aug 2026 15:22:20 +0800 Subject: [PATCH] =?UTF-8?q?feat(chat):=20ChatTimeline=20=E5=8F=8C=E6=A0=8F?= =?UTF-8?q?=E5=8C=96(=E5=B7=A6=E4=BC=9A=E8=AF=9D=E5=88=97=E8=A1=A8)+=20?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E5=8F=AF=E9=80=89=E5=8F=82=20+=20badge=20?= =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E7=BA=A7=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude --- yunkes/src/router/index.ts | 8 +- yunkes/src/views/ChatTimeline.vue | 169 +++++++++++++++++++++--------- yunkes/src/views/StaffList.vue | 26 +---- 3 files changed, 128 insertions(+), 75 deletions(-) diff --git a/yunkes/src/router/index.ts b/yunkes/src/router/index.ts index e80ba15..7e7f917 100644 --- a/yunkes/src/router/index.ts +++ b/yunkes/src/router/index.ts @@ -2,8 +2,8 @@ import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router' // `/` redirects to `/customers`. Chat timeline params are path segments so the // URL is shareable + survives refresh: -// /chat/:channel/:ownerId/:talkerId (1:1 DM) -// /group/:channel/:ownerId/:roomId (group chat — same component, room_id) +// /chat/:channel/:ownerId/:talkerId? (1:1 DM; talkerId optional → account-level) +// /group/:channel/:ownerId/:roomId? (group chat — same component, room_id) const routes: RouteRecordRaw[] = [ { path: '/', @@ -58,7 +58,7 @@ const routes: RouteRecordRaw[] = [ meta: { title: '搜索' }, }, { - path: '/chat/:channel/:ownerId/:talkerId', + path: '/chat/:channel/:ownerId/:talkerId?', name: 'chat', component: () => import('@/views/ChatTimeline.vue'), props: true, @@ -67,7 +67,7 @@ const routes: RouteRecordRaw[] = [ { // Group chat timeline — same component, but with a `roomId` prop so it // issues a room_id query instead of a talker_id query. - path: '/group/:channel/:ownerId/:roomId', + path: '/group/:channel/:ownerId/:roomId?', name: 'group-chat', component: () => import('@/views/ChatTimeline.vue'), props: true, diff --git a/yunkes/src/views/ChatTimeline.vue b/yunkes/src/views/ChatTimeline.vue index 33ee895..3e160ee 100644 --- a/yunkes/src/views/ChatTimeline.vue +++ b/yunkes/src/views/ChatTimeline.vue @@ -6,6 +6,7 @@ import { ArrowLeft } from '@element-plus/icons-vue' import { getMessages } from '@/api' import type { Channel, Message } from '@/api/types' import MessageBubble from '@/components/MessageBubble.vue' +import ChatSidebar from '@/components/ChatSidebar.vue' import { channelLabel, formatTime, wechatTypeLabel } from '@/utils/format' import { buildMessageSections } from '@/utils/messageFields' @@ -45,13 +46,26 @@ const channelTyped = computed(() => props.channel as Channel) /** True when this timeline is a group chat (drives the query + title). */ const isGroup = computed(() => !!props.roomId) +/** 当前会话类型(高亮 sidebar + 驱动空态)。账号级入口(无 talkerId/roomId)→ null。 */ +const activeType = computed<'friend' | 'group' | null>(() => + isGroup.value ? 'group' : props.talkerId ? 'friend' : null, +) +/** 当前会话 id(friend→talker_id, group→room_id)。账号级入口→ null。 */ +const activeId = computed(() => props.talkerId ?? props.roomId ?? null) +/** 是否已选定具体会话(决定是否拉消息 / 显示空态)。 */ +const hasActive = computed(() => !!activeId.value) + const hasMoreOlder = computed(() => currentPage.value > 1) const conversationTitle = computed(() => { if (isGroup.value) { return `群聊记录 · ${props.roomId}` } - return `与 ${props.talkerId} 的聊天记录` + if (props.talkerId) { + return `与 ${props.talkerId} 的聊天记录` + } + // 账号级入口(无具体会话):仅展示渠道级标题,避免出现 "与 undefined 的聊天记录"。 + return '聊天记录' }) /** 从路由 query 读日期窗(从 StaffList stat modal 带窗跳来时有值);缺失=全量(向后兼容)。 */ @@ -105,6 +119,14 @@ function queryFor(pageNum: number) { * returns asc, so the last page holds the newest slice. */ async function loadInitial(): Promise { + // 账号级入口(无具体会话):不拉消息,清空状态,留空态给右侧主区。 + if (!hasActive.value) { + messages.value = [] + total.value = 0 + totalPages.value = 1 + currentPage.value = 1 + return + } loading.value = true errorMsg.value = null try { @@ -161,6 +183,25 @@ function goBack(): void { } } +/** 点击左侧会话 → 推到对应 DM/群路由(带具体会话 id)。 */ +function onSelectSession(s: { + type: 'friend' | 'group' + talker_id?: string | null + room_id?: string | null +}): void { + if (s.type === 'group') { + router.push({ + name: 'group-chat', + params: { channel: props.channel, ownerId: props.ownerId, roomId: s.room_id! }, + }) + } else { + router.push({ + name: 'chat', + params: { channel: props.channel, ownerId: props.ownerId, talkerId: s.talker_id! }, + }) + } +} + // All-fields drawer state for the message detail view. const drawerOpen = ref(false) const drawerMessage = ref(null) @@ -195,54 +236,67 @@ onMounted(loadInitial)