commit
e4b57bd8c0
|
@ -21,6 +21,10 @@ export const KeFuConversationApi = {
|
|||
getConversationList: async () => {
|
||||
return await request.get({ url: '/promotion/kefu-conversation/list' })
|
||||
},
|
||||
// 获得客服会话
|
||||
getConversation: async (id: number) => {
|
||||
return await request.get({ url: `/promotion/kefu-conversation/get?id=` + id })
|
||||
},
|
||||
// 客服会话置顶
|
||||
updateConversationPinned: async (data: any) => {
|
||||
return await request.put({
|
||||
|
@ -30,6 +34,6 @@ export const KeFuConversationApi = {
|
|||
},
|
||||
// 删除客服会话
|
||||
deleteConversation: async (id: number) => {
|
||||
return await request.delete({ url: `/promotion/kefu-conversation/delete?id=${id}`})
|
||||
return await request.delete({ url: `/promotion/kefu-conversation/delete?id=${id}` })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ import { store } from '@/store'
|
|||
import { defineStore } from 'pinia'
|
||||
import { KeFuConversationApi, KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
|
||||
import { KeFuMessageRespVO } from '@/api/mall/promotion/kefu/message'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
|
||||
// TODO puhui999: 待优化完善
|
||||
interface MallKefuInfoVO {
|
||||
conversationList: KeFuConversationRespVO[] // 会话列表
|
||||
conversationMessageList: Map<number, KeFuMessageRespVO[]> // 会话消息
|
||||
|
@ -18,20 +18,92 @@ export const useMallKefuStore = defineStore('mall-kefu', {
|
|||
getConversationList(): KeFuConversationRespVO[] {
|
||||
return this.conversationList
|
||||
},
|
||||
getConversationMessageList(): Map<number, KeFuMessageRespVO[]> {
|
||||
return this.conversationMessageList
|
||||
getConversationMessageList(): (conversationId: number) => KeFuMessageRespVO[] | undefined {
|
||||
return (conversationId: number) => this.conversationMessageList.get(conversationId)
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
//======================= 会话消息相关 =======================
|
||||
/** 缓存历史消息 */
|
||||
saveMessageList(conversationId: number, messageList: KeFuMessageRespVO[]) {
|
||||
this.conversationMessageList.set(conversationId, messageList)
|
||||
},
|
||||
//======================= 会话相关 =======================
|
||||
/** 加载会话缓存列表 */
|
||||
async setConversationList() {
|
||||
const list = await KeFuConversationApi.getConversationList()
|
||||
list.sort((a: KeFuConversationRespVO, _) => (a.adminPinned ? -1 : 1))
|
||||
this.conversationList = list
|
||||
this.conversationList = await KeFuConversationApi.getConversationList()
|
||||
this.conversationSort()
|
||||
},
|
||||
/** 更新会话缓存已读 */
|
||||
async updateConversationStatus(conversationId: number) {
|
||||
if (isEmpty(this.conversationList)) {
|
||||
return
|
||||
}
|
||||
const conversation = this.conversationList.find((item) => item.id === conversationId)
|
||||
conversation && (conversation.adminUnreadMessageCount = 0)
|
||||
},
|
||||
/** 更新会话缓存 */
|
||||
async updateConversation(conversationId: number) {
|
||||
if (isEmpty(this.conversationList)) {
|
||||
return
|
||||
}
|
||||
|
||||
const conversation = await KeFuConversationApi.getConversation(conversationId)
|
||||
this.deleteConversation(conversationId)
|
||||
conversation && this.conversationList.push(conversation)
|
||||
this.conversationSort()
|
||||
},
|
||||
/** 删除会话缓存 */
|
||||
deleteConversation(conversationId: number) {
|
||||
const index = this.conversationList.findIndex((item) => item.id === conversationId)
|
||||
// 存在则删除
|
||||
if (index > -1) {
|
||||
this.conversationList.splice(index, 1)
|
||||
}
|
||||
},
|
||||
conversationSort() {
|
||||
this.conversationList.sort((obj1, obj2) => {
|
||||
// 如果 obj1.adminPinned 为 true,obj2.adminPinned 为 false,obj1 应该排在前面
|
||||
if (obj1.adminPinned && !obj2.adminPinned) return -1
|
||||
// 如果 obj1.adminPinned 为 false,obj2.adminPinned 为 true,obj2 应该排在前面
|
||||
if (!obj1.adminPinned && obj2.adminPinned) return 1
|
||||
|
||||
// 如果 obj1.adminPinned 和 obj2.adminPinned 都为 true,比较 adminUnreadMessageCount 的值
|
||||
if (obj1.adminPinned && obj2.adminPinned) {
|
||||
return obj1.adminUnreadMessageCount - obj2.adminUnreadMessageCount
|
||||
}
|
||||
|
||||
// 如果 obj1.adminPinned 和 obj2.adminPinned 都为 false,比较 adminUnreadMessageCount 的值
|
||||
if (!obj1.adminPinned && !obj2.adminPinned) {
|
||||
return obj1.adminUnreadMessageCount - obj2.adminUnreadMessageCount
|
||||
}
|
||||
|
||||
// 如果 obj1.adminPinned 为 true,obj2.adminPinned 为 true,且 b 都大于 0,比较 adminUnreadMessageCount 的值
|
||||
if (
|
||||
obj1.adminPinned &&
|
||||
obj2.adminPinned &&
|
||||
obj1.adminUnreadMessageCount > 0 &&
|
||||
obj2.adminUnreadMessageCount > 0
|
||||
) {
|
||||
return obj1.adminUnreadMessageCount - obj2.adminUnreadMessageCount
|
||||
}
|
||||
|
||||
// 如果 obj1.adminPinned 为 false,obj2.adminPinned 为 false,且 b 都大于 0,比较 adminUnreadMessageCount 的值
|
||||
if (
|
||||
!obj1.adminPinned &&
|
||||
!obj2.adminPinned &&
|
||||
obj1.adminUnreadMessageCount > 0 &&
|
||||
obj2.adminUnreadMessageCount > 0
|
||||
) {
|
||||
return obj1.adminUnreadMessageCount - obj2.adminUnreadMessageCount
|
||||
}
|
||||
|
||||
return 0
|
||||
})
|
||||
}
|
||||
// async setConversationMessageList(conversationId: number) {}
|
||||
}
|
||||
})
|
||||
|
||||
export const useUserStoreWithOut = () => {
|
||||
export const useMallKefuStoreWithOut = () => {
|
||||
return useMallKefuStore(store)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
<template>
|
||||
<el-aside class="kefu p-5px h-100%" width="260px">
|
||||
<div class="color-[#999] font-bold my-10px">会话记录({{ conversationList.length }})</div>
|
||||
<div class="color-[#999] font-bold my-10px"
|
||||
>会话记录({{ kefuStore.getConversationList.length }})
|
||||
</div>
|
||||
<div
|
||||
v-for="item in conversationList"
|
||||
v-for="item in kefuStore.getConversationList"
|
||||
:key="item.id"
|
||||
:class="{ active: item.id === activeConversationId, pinned: item.adminPinned }"
|
||||
class="kefu-conversation flex items-center"
|
||||
|
@ -75,29 +77,26 @@ import { useEmoji } from './tools/emoji'
|
|||
import { formatPast } from '@/utils/formatTime'
|
||||
import { KeFuMessageContentTypeEnum } from './tools/constants'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { useMallKefuStore } from '@/store/modules/mall/kefu'
|
||||
|
||||
defineOptions({ name: 'KeFuConversationList' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const appStore = useAppStore()
|
||||
const kefuStore = useMallKefuStore() // 客服缓存
|
||||
const { replaceEmoji } = useEmoji()
|
||||
const conversationList = ref<KeFuConversationRespVO[]>([]) // 会话列表
|
||||
const activeConversationId = ref(-1) // 选中的会话
|
||||
const collapse = computed(() => appStore.getCollapse) // 折叠菜单
|
||||
|
||||
/** 加载会话列表 */
|
||||
const getConversationList = async () => {
|
||||
const list = await KeFuConversationApi.getConversationList()
|
||||
list.sort((a: KeFuConversationRespVO, _) => (a.adminPinned ? -1 : 1))
|
||||
conversationList.value = list
|
||||
}
|
||||
defineExpose({ getConversationList })
|
||||
|
||||
/** 打开右侧的消息列表 */
|
||||
const emits = defineEmits<{
|
||||
(e: 'change', v: KeFuConversationRespVO): void
|
||||
}>()
|
||||
const openRightMessage = (item: KeFuConversationRespVO) => {
|
||||
// 同一个会话则不处理
|
||||
if (activeConversationId.value === item.id) {
|
||||
return
|
||||
}
|
||||
activeConversationId.value = item.id
|
||||
emits('change', item)
|
||||
}
|
||||
|
@ -156,7 +155,7 @@ const updateConversationPinned = async (adminPinned: boolean) => {
|
|||
message.notifySuccess(adminPinned ? '置顶成功' : '取消置顶成功')
|
||||
// 2. 关闭右键菜单,更新会话列表
|
||||
closeRightMenu()
|
||||
await getConversationList()
|
||||
await kefuStore.updateConversation(rightClickConversation.value.id)
|
||||
}
|
||||
|
||||
/** 删除会话 */
|
||||
|
@ -166,7 +165,7 @@ const deleteConversation = async () => {
|
|||
await KeFuConversationApi.deleteConversation(rightClickConversation.value.id)
|
||||
// 2. 关闭右键菜单,更新会话列表
|
||||
closeRightMenu()
|
||||
await getConversationList()
|
||||
kefuStore.deleteConversation(rightClickConversation.value.id)
|
||||
}
|
||||
|
||||
/** 监听右键菜单的显示状态,添加点击事件监听器 */
|
||||
|
|
|
@ -152,6 +152,7 @@ import dayjs from 'dayjs'
|
|||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import { debounce } from 'lodash-es'
|
||||
import { jsonParse } from '@/utils'
|
||||
import { useMallKefuStore } from '@/store/modules/mall/kefu'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
|
@ -169,6 +170,7 @@ const queryParams = reactive({
|
|||
})
|
||||
const total = ref(0) // 消息总条数
|
||||
const refreshContent = ref(false) // 内容刷新,主要解决会话消息页面高度不一致导致的滚动功能精度失效
|
||||
const kefuStore = useMallKefuStore() // 客服缓存
|
||||
|
||||
/** 获悉消息内容 */
|
||||
const getMessageContent = computed(() => (item: any) => jsonParse(item.content))
|
||||
|
@ -234,19 +236,20 @@ const refreshMessageList = async (message?: any) => {
|
|||
}
|
||||
}
|
||||
|
||||
/** 获得新会话的消息列表 */
|
||||
// TODO @puhui999:可优化:可以考虑本地做每个会话的消息 list 缓存;然后点击切换时,读取缓存;然后异步获取新消息,merge 下;
|
||||
/** 获得新会话的消息列表, 点击切换时,读取缓存;然后异步获取新消息,merge 下; */
|
||||
const getNewMessageList = async (val: KeFuConversationRespVO) => {
|
||||
// 会话切换,重置相关参数
|
||||
messageList.value = []
|
||||
total.value = 0
|
||||
// 1. 缓存当前会话消息列表
|
||||
kefuStore.saveMessageList(conversation.value.id, messageList.value)
|
||||
// 2.1 会话切换,重置相关参数
|
||||
messageList.value = kefuStore.getConversationMessageList(val.id) || []
|
||||
total.value = messageList.value.length || 0
|
||||
loadHistory.value = false
|
||||
refreshContent.value = false
|
||||
// 设置会话相关属性
|
||||
// 2.2 设置会话相关属性
|
||||
conversation.value = val
|
||||
queryParams.conversationId = val.id
|
||||
queryParams.createTime = undefined
|
||||
// 获取消息
|
||||
// 3. 获取消息
|
||||
await refreshMessageList()
|
||||
}
|
||||
defineExpose({ getNewMessageList, refreshMessageList })
|
||||
|
@ -297,6 +300,8 @@ const sendMessage = async (msg: any) => {
|
|||
message.value = ''
|
||||
// 加载消息列表
|
||||
await refreshMessageList()
|
||||
// 更新会话缓存
|
||||
await kefuStore.updateConversation(conversation.value.id)
|
||||
}
|
||||
|
||||
/** 滚动到底部 */
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<!-- 会话列表 -->
|
||||
<KeFuConversationList ref="keFuConversationRef" @change="handleChange" />
|
||||
<!-- 会话详情(选中会话的消息列表) -->
|
||||
<KeFuMessageList ref="keFuChatBoxRef" @change="getConversationList" />
|
||||
<KeFuMessageList ref="keFuChatBoxRef" />
|
||||
<!-- 会员信息(选中会话的会员信息) -->
|
||||
<MemberInfo ref="memberInfoRef" />
|
||||
</el-container>
|
||||
|
@ -15,10 +15,12 @@ import { WebSocketMessageTypeConstants } from './components/tools/constants'
|
|||
import { KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
|
||||
import { getRefreshToken } from '@/utils/auth'
|
||||
import { useWebSocket } from '@vueuse/core'
|
||||
import { useMallKefuStore } from '@/store/modules/mall/kefu'
|
||||
|
||||
defineOptions({ name: 'KeFu' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const kefuStore = useMallKefuStore() // 客服缓存
|
||||
|
||||
// ======================= WebSocket start =======================
|
||||
const server = ref(
|
||||
|
@ -53,29 +55,24 @@ watchEffect(() => {
|
|||
}
|
||||
// 2.2 消息类型:KEFU_MESSAGE_TYPE
|
||||
if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_TYPE) {
|
||||
const message = JSON.parse(jsonMessage.content)
|
||||
// 刷新会话列表
|
||||
// TODO @puhui999:不应该刷新列表,而是根据消息,本地 update 列表的数据;
|
||||
getConversationList()
|
||||
kefuStore.updateConversation(message.conversationId)
|
||||
// 刷新消息列表
|
||||
keFuChatBoxRef.value?.refreshMessageList(JSON.parse(jsonMessage.content))
|
||||
keFuChatBoxRef.value?.refreshMessageList(message)
|
||||
return
|
||||
}
|
||||
// 2.3 消息类型:KEFU_MESSAGE_ADMIN_READ
|
||||
if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_ADMIN_READ) {
|
||||
// 刷新会话列表
|
||||
// TODO @puhui999:不应该刷新列表,而是根据消息,本地 update 列表的数据;
|
||||
getConversationList()
|
||||
// 更新会话已读
|
||||
kefuStore.updateConversationStatus(JSON.parse(jsonMessage.content)?.id)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
// ======================= WebSocket end =======================
|
||||
/** 加载会话列表 */
|
||||
const keFuConversationRef = ref<InstanceType<typeof KeFuConversationList>>()
|
||||
const getConversationList = () => {
|
||||
keFuConversationRef.value?.getConversationList()
|
||||
}
|
||||
|
||||
/** 加载指定会话的消息列表 */
|
||||
const keFuChatBoxRef = ref<InstanceType<typeof KeFuMessageList>>()
|
||||
|
@ -87,7 +84,8 @@ const handleChange = (conversation: KeFuConversationRespVO) => {
|
|||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getConversationList()
|
||||
/** 加载会话列表 */
|
||||
kefuStore.setConversationList()
|
||||
// 打开 websocket 连接
|
||||
open()
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue