diff --git a/frontend/src/features/admin/views/AdminChatsView.vue b/frontend/src/features/admin/views/AdminChatsView.vue index ed42410..c068adb 100644 --- a/frontend/src/features/admin/views/AdminChatsView.vue +++ b/frontend/src/features/admin/views/AdminChatsView.vue @@ -18,6 +18,7 @@ import { uploadAdminFile } from '@/shared/api/files' import ChatAttachmentImage from '@/components/ChatAttachmentImage.vue' import { useChatSSE, type ChatEvent } from '@/features/chats/composables/useChatSSE' import { useDesktopNotification } from '@/features/chats/composables/useDesktopNotification' +import NotificationSettings from '@/features/chats/components/NotificationSettings.vue' import { formatDateMinute } from '@/utils/time' import TransferDialog from '../components/TransferDialog.vue' import QuickReplyDialog from '../components/QuickReplyDialog.vue' @@ -44,11 +45,13 @@ const remarkValue = ref('') const activeMembers = computed(() => { const participants = active.value?.participants || [] - return participants.map(item => { - const remark = getParticipantRemark(item) - const name = remark ? `${remark}(${item.display_name})` : item.display_name - return `${roleLabel(item.role)}:${name}` - }).join(' / ') + return participants + .map(item => { + const remark = getParticipantRemark(item) + const name = remark ? `${remark}(${item.display_name})` : item.display_name + return `${roleLabel(item.role)}:${name}` + }) + .join(' / ') }) const canSend = computed(() => content.value.trim() !== '' || attachments.value.length > 0) @@ -67,31 +70,40 @@ function handleSSEEvent(event: ChatEvent) { if (event.type === 'conversation_updated') { loadConversations(false) } - if (event.type === 'new_message' && active.value && event.conversation_id === active.value.id) { + if (event.type === 'new_message') { const msg = event.message - if (msg && !messages.value.some(m => m.id === msg.id)) { - const isSelf = msg.sender_type === 'admin' && msg.sender_id === currentAdminId - - messages.value = [...messages.value, { - id: msg.id, - conversation_id: msg.conversation_id, - sender_type: msg.sender_type as ChatMessage['sender_type'], - sender_id: msg.sender_id, - sender_role: msg.sender_role as ChatMessage['sender_role'], - sender_name: msg.sender_name, - sender_avatar: '', - is_self: isSelf, - content_type: msg.content_type as ChatMessage['content_type'], - content: msg.content, - attachment_urls: msg.attachment_urls || [], - created_at: msg.created_at, - }] + if (!msg) return + const isSelf = msg.sender_type === 'admin' && msg.sender_id === currentAdminId + if ( + active.value && + event.conversation_id === active.value.id && + !messages.value.some(m => m.id === msg.id) + ) { + messages.value = [ + ...messages.value, + { + id: msg.id, + conversation_id: msg.conversation_id, + sender_type: msg.sender_type as ChatMessage['sender_type'], + sender_id: msg.sender_id, + sender_role: msg.sender_role as ChatMessage['sender_role'], + sender_name: msg.sender_name, + sender_avatar: '', + is_self: isSelf, + content_type: msg.content_type as ChatMessage['content_type'], + content: msg.content, + attachment_urls: msg.attachment_urls || [], + created_at: msg.created_at, + }, + ] nextTick(() => scrollBottom()) + } - // 不是自己发送的消息才发送通知 - if (!isSelf) { - desktopNotification.notify(event, active.value.id) - } + loadConversations(false) + + // 不是自己发送的消息才发送通知;当前正在看的会话且页面前台时会在 notify 内部静默。 + if (!isSelf) { + desktopNotification.notify(event, active.value?.id ?? null) } } } @@ -124,7 +136,9 @@ async function loadConversations(showLoading = true) { async function loadQuickReplies() { try { quickReplies.value = await fetchQuickReplies() - } catch { /* ignore */ } + } catch { + /* ignore */ + } } async function openConversation(item: ChatConversation) { @@ -212,7 +226,10 @@ async function uploadImages(files: File[]) { uploading.value = true try { for (const file of files.slice(0, slots)) { - if (!['image/jpeg', 'image/png', 'image/webp'].includes(file.type) || file.size > 25 * 1024 * 1024) { + if ( + !['image/jpeg', 'image/png', 'image/webp'].includes(file.type) || + file.size > 25 * 1024 * 1024 + ) { ElMessage.warning(`${file.name} 不符合图片规则`) continue } @@ -324,6 +341,7 @@ function getSupportName(item: ChatConversation) {

处理订单三方沟通和平台咨询

+ 快捷回复管理 刷新
@@ -350,7 +368,12 @@ function getSupportName(item: ChatConversation) { {{ getConversationTitle(item) }} {{ formatDateMinute(item.last_message_at || item.created_at) }} -

{{ item.last_message_preview || (item.type === 'general_support' ? '客服会话已创建' : '订单群聊已创建') }}

+

+ {{ + item.last_message_preview || + (item.type === 'general_support' ? '客服会话已创建' : '订单群聊已创建') + }} +

{{ getSupportName(item) }} {{ item.unread_count }} @@ -375,7 +398,10 @@ function getSupportName(item: ChatConversation) { 取消
@@ -395,7 +421,7 @@ function getSupportName(item: ChatConversation) { :class="{ self: item.is_self, system: item.sender_type === 'system', - admin: item.sender_role === 'admin' + admin: item.sender_role === 'admin', }" > @@ -484,10 +526,7 @@ function getSupportName(item: ChatConversation) { @success="handleTransferSuccess" /> - + @@ -519,6 +558,8 @@ function getSupportName(item: ChatConversation) { .head-right { display: flex; + align-items: center; + flex-wrap: wrap; gap: 8px; } diff --git a/frontend/src/features/chats/components/NotificationSettings.vue b/frontend/src/features/chats/components/NotificationSettings.vue index 762a30a..eefe336 100644 --- a/frontend/src/features/chats/components/NotificationSettings.vue +++ b/frontend/src/features/chats/components/NotificationSettings.vue @@ -22,6 +22,11 @@ const statusType = computed(() => { return 'info' }) +const voiceStatusText = computed(() => { + if (!notification.isSpeechSupported) return '语音不支持' + return notification.voiceEnabled.value ? '语音已开' : '语音关闭' +}) + async function toggleNotification() { if (!notification.isSupported) return @@ -32,36 +37,59 @@ async function toggleNotification() { alert('通知权限已被拒绝,请在浏览器设置中手动开启') } } + +function toggleVoice() { + if (!notification.isSpeechSupported) return + notification.setVoiceEnabled(!notification.voiceEnabled.value) +}