优化客服消息通知提醒
This commit is contained in:
@@ -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) {
|
||||
<p>处理订单三方沟通和平台咨询</p>
|
||||
</div>
|
||||
<div class="head-right">
|
||||
<NotificationSettings scope="admin" />
|
||||
<el-button @click="quickReplyVisible = true">快捷回复管理</el-button>
|
||||
<el-button :loading="loading" @click="loadConversations()">刷新</el-button>
|
||||
</div>
|
||||
@@ -350,7 +368,12 @@ function getSupportName(item: ChatConversation) {
|
||||
<strong>{{ getConversationTitle(item) }}</strong>
|
||||
<span>{{ formatDateMinute(item.last_message_at || item.created_at) }}</span>
|
||||
</div>
|
||||
<p>{{ item.last_message_preview || (item.type === 'general_support' ? '客服会话已创建' : '订单群聊已创建') }}</p>
|
||||
<p>
|
||||
{{
|
||||
item.last_message_preview ||
|
||||
(item.type === 'general_support' ? '客服会话已创建' : '订单群聊已创建')
|
||||
}}
|
||||
</p>
|
||||
<div class="row-meta">
|
||||
<span class="support-name">{{ getSupportName(item) }}</span>
|
||||
<em v-if="item.unread_count > 0">{{ item.unread_count }}</em>
|
||||
@@ -375,7 +398,10 @@ function getSupportName(item: ChatConversation) {
|
||||
<el-button size="small" @click="remarkEditing = false">取消</el-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<h2>{{ getConversationTitle(active) }} <el-button link size="small" @click="startEditRemark">编辑备注</el-button></h2>
|
||||
<h2>
|
||||
{{ getConversationTitle(active) }}
|
||||
<el-button link size="small" @click="startEditRemark">编辑备注</el-button>
|
||||
</h2>
|
||||
<p>{{ activeMembers }}</p>
|
||||
</template>
|
||||
</div>
|
||||
@@ -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',
|
||||
}"
|
||||
>
|
||||
<template v-if="item.sender_type === 'system'">
|
||||
@@ -427,7 +453,7 @@ function getSupportName(item: ChatConversation) {
|
||||
accept="image/jpeg,image/png,image/webp"
|
||||
multiple
|
||||
@change="handleImageChange"
|
||||
>
|
||||
/>
|
||||
<el-dropdown trigger="click" @command="handleQuickReplySelect">
|
||||
<el-button size="small" text>快捷回复</el-button>
|
||||
<template #dropdown>
|
||||
@@ -438,7 +464,10 @@ function getSupportName(item: ChatConversation) {
|
||||
:command="reply"
|
||||
>
|
||||
<span class="reply-title">{{ reply.title }}</span>
|
||||
<span class="reply-preview">{{ reply.content.slice(0, 30) }}{{ reply.content.length > 30 ? '...' : '' }}</span>
|
||||
<span class="reply-preview"
|
||||
>{{ reply.content.slice(0, 30)
|
||||
}}{{ reply.content.length > 30 ? '...' : '' }}</span
|
||||
>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="quickReplies.length === 0" disabled>
|
||||
暂无快捷回复
|
||||
@@ -446,7 +475,14 @@ function getSupportName(item: ChatConversation) {
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button size="small" text :icon="Picture" :loading="uploading" :disabled="attachments.length >= 9" @click="pickImages">
|
||||
<el-button
|
||||
size="small"
|
||||
text
|
||||
:icon="Picture"
|
||||
:loading="uploading"
|
||||
:disabled="attachments.length >= 9"
|
||||
@click="pickImages"
|
||||
>
|
||||
图片
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -469,7 +505,13 @@ function getSupportName(item: ChatConversation) {
|
||||
@keydown.enter.exact.prevent="handleSend"
|
||||
@paste="handlePaste"
|
||||
/>
|
||||
<el-button type="primary" :loading="sending" :disabled="!canSend || uploading" @click="handleSend">发送</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="sending"
|
||||
:disabled="!canSend || uploading"
|
||||
@click="handleSend"
|
||||
>发送</el-button
|
||||
>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
@@ -484,10 +526,7 @@ function getSupportName(item: ChatConversation) {
|
||||
@success="handleTransferSuccess"
|
||||
/>
|
||||
|
||||
<QuickReplyDialog
|
||||
v-model="quickReplyVisible"
|
||||
@success="loadQuickReplies"
|
||||
/>
|
||||
<QuickReplyDialog v-model="quickReplyVisible" @success="loadQuickReplies" />
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -519,6 +558,8 @@ function getSupportName(item: ChatConversation) {
|
||||
|
||||
.head-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user