!579 【功能完善】商城: 客服缓存

Merge pull request !579 from puhui999/dev-crm
pull/584/MERGE
芋道源码 2024-11-09 11:04:32 +00:00 committed by Gitee
commit e4b57bd8c0
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
5 changed files with 119 additions and 41 deletions

View File

@ -21,6 +21,10 @@ export const KeFuConversationApi = {
getConversationList: async () => { getConversationList: async () => {
return await request.get({ url: '/promotion/kefu-conversation/list' }) 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) => { updateConversationPinned: async (data: any) => {
return await request.put({ return await request.put({
@ -30,6 +34,6 @@ export const KeFuConversationApi = {
}, },
// 删除客服会话 // 删除客服会话
deleteConversation: async (id: number) => { 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}` })
} }
} }

View File

@ -2,8 +2,8 @@ import { store } from '@/store'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { KeFuConversationApi, KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation' import { KeFuConversationApi, KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
import { KeFuMessageRespVO } from '@/api/mall/promotion/kefu/message' import { KeFuMessageRespVO } from '@/api/mall/promotion/kefu/message'
import { isEmpty } from '@/utils/is'
// TODO puhui999: 待优化完善
interface MallKefuInfoVO { interface MallKefuInfoVO {
conversationList: KeFuConversationRespVO[] // 会话列表 conversationList: KeFuConversationRespVO[] // 会话列表
conversationMessageList: Map<number, KeFuMessageRespVO[]> // 会话消息 conversationMessageList: Map<number, KeFuMessageRespVO[]> // 会话消息
@ -18,20 +18,92 @@ export const useMallKefuStore = defineStore('mall-kefu', {
getConversationList(): KeFuConversationRespVO[] { getConversationList(): KeFuConversationRespVO[] {
return this.conversationList return this.conversationList
}, },
getConversationMessageList(): Map<number, KeFuMessageRespVO[]> { getConversationMessageList(): (conversationId: number) => KeFuMessageRespVO[] | undefined {
return this.conversationMessageList return (conversationId: number) => this.conversationMessageList.get(conversationId)
} }
}, },
actions: { actions: {
//======================= 会话消息相关 =======================
/** 缓存历史消息 */
saveMessageList(conversationId: number, messageList: KeFuMessageRespVO[]) {
this.conversationMessageList.set(conversationId, messageList)
},
//======================= 会话相关 =======================
/** 加载会话缓存列表 */
async setConversationList() { async setConversationList() {
const list = await KeFuConversationApi.getConversationList() this.conversationList = await KeFuConversationApi.getConversationList()
list.sort((a: KeFuConversationRespVO, _) => (a.adminPinned ? -1 : 1)) this.conversationSort()
this.conversationList = list },
/** 更新会话缓存已读 */
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 为 trueobj2.adminPinned 为 falseobj1 应该排在前面
if (obj1.adminPinned && !obj2.adminPinned) return -1
// 如果 obj1.adminPinned 为 falseobj2.adminPinned 为 trueobj2 应该排在前面
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 为 trueobj2.adminPinned 为 true且 b 都大于 0比较 adminUnreadMessageCount 的值
if (
obj1.adminPinned &&
obj2.adminPinned &&
obj1.adminUnreadMessageCount > 0 &&
obj2.adminUnreadMessageCount > 0
) {
return obj1.adminUnreadMessageCount - obj2.adminUnreadMessageCount
}
// 如果 obj1.adminPinned 为 falseobj2.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) return useMallKefuStore(store)
} }

View File

@ -1,8 +1,10 @@
<template> <template>
<el-aside class="kefu p-5px h-100%" width="260px"> <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 <div
v-for="item in conversationList" v-for="item in kefuStore.getConversationList"
:key="item.id" :key="item.id"
:class="{ active: item.id === activeConversationId, pinned: item.adminPinned }" :class="{ active: item.id === activeConversationId, pinned: item.adminPinned }"
class="kefu-conversation flex items-center" class="kefu-conversation flex items-center"
@ -75,29 +77,26 @@ import { useEmoji } from './tools/emoji'
import { formatPast } from '@/utils/formatTime' import { formatPast } from '@/utils/formatTime'
import { KeFuMessageContentTypeEnum } from './tools/constants' import { KeFuMessageContentTypeEnum } from './tools/constants'
import { useAppStore } from '@/store/modules/app' import { useAppStore } from '@/store/modules/app'
import { useMallKefuStore } from '@/store/modules/mall/kefu'
defineOptions({ name: 'KeFuConversationList' }) defineOptions({ name: 'KeFuConversationList' })
const message = useMessage() // const message = useMessage() //
const appStore = useAppStore() const appStore = useAppStore()
const kefuStore = useMallKefuStore() //
const { replaceEmoji } = useEmoji() const { replaceEmoji } = useEmoji()
const conversationList = ref<KeFuConversationRespVO[]>([]) //
const activeConversationId = ref(-1) // const activeConversationId = ref(-1) //
const collapse = computed(() => appStore.getCollapse) // 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<{ const emits = defineEmits<{
(e: 'change', v: KeFuConversationRespVO): void (e: 'change', v: KeFuConversationRespVO): void
}>() }>()
const openRightMessage = (item: KeFuConversationRespVO) => { const openRightMessage = (item: KeFuConversationRespVO) => {
//
if (activeConversationId.value === item.id) {
return
}
activeConversationId.value = item.id activeConversationId.value = item.id
emits('change', item) emits('change', item)
} }
@ -156,7 +155,7 @@ const updateConversationPinned = async (adminPinned: boolean) => {
message.notifySuccess(adminPinned ? '置顶成功' : '取消置顶成功') message.notifySuccess(adminPinned ? '置顶成功' : '取消置顶成功')
// 2. // 2.
closeRightMenu() closeRightMenu()
await getConversationList() await kefuStore.updateConversation(rightClickConversation.value.id)
} }
/** 删除会话 */ /** 删除会话 */
@ -166,7 +165,7 @@ const deleteConversation = async () => {
await KeFuConversationApi.deleteConversation(rightClickConversation.value.id) await KeFuConversationApi.deleteConversation(rightClickConversation.value.id)
// 2. // 2.
closeRightMenu() closeRightMenu()
await getConversationList() kefuStore.deleteConversation(rightClickConversation.value.id)
} }
/** 监听右键菜单的显示状态,添加点击事件监听器 */ /** 监听右键菜单的显示状态,添加点击事件监听器 */

View File

@ -152,6 +152,7 @@ import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime' import relativeTime from 'dayjs/plugin/relativeTime'
import { debounce } from 'lodash-es' import { debounce } from 'lodash-es'
import { jsonParse } from '@/utils' import { jsonParse } from '@/utils'
import { useMallKefuStore } from '@/store/modules/mall/kefu'
dayjs.extend(relativeTime) dayjs.extend(relativeTime)
@ -169,6 +170,7 @@ const queryParams = reactive({
}) })
const total = ref(0) // const total = ref(0) //
const refreshContent = ref(false) // , const refreshContent = ref(false) // ,
const kefuStore = useMallKefuStore() //
/** 获悉消息内容 */ /** 获悉消息内容 */
const getMessageContent = computed(() => (item: any) => jsonParse(item.content)) const getMessageContent = computed(() => (item: any) => jsonParse(item.content))
@ -234,19 +236,20 @@ const refreshMessageList = async (message?: any) => {
} }
} }
/** 获得新会话的消息列表 */ /** 获得新会话的消息列表, 点击切换时读取缓存然后异步获取新消息merge 下; */
// TODO @puhui999 list merge
const getNewMessageList = async (val: KeFuConversationRespVO) => { const getNewMessageList = async (val: KeFuConversationRespVO) => {
// , // 1.
messageList.value = [] kefuStore.saveMessageList(conversation.value.id, messageList.value)
total.value = 0 // 2.1 ,
messageList.value = kefuStore.getConversationMessageList(val.id) || []
total.value = messageList.value.length || 0
loadHistory.value = false loadHistory.value = false
refreshContent.value = false refreshContent.value = false
// // 2.2
conversation.value = val conversation.value = val
queryParams.conversationId = val.id queryParams.conversationId = val.id
queryParams.createTime = undefined queryParams.createTime = undefined
// // 3.
await refreshMessageList() await refreshMessageList()
} }
defineExpose({ getNewMessageList, refreshMessageList }) defineExpose({ getNewMessageList, refreshMessageList })
@ -297,6 +300,8 @@ const sendMessage = async (msg: any) => {
message.value = '' message.value = ''
// //
await refreshMessageList() await refreshMessageList()
//
await kefuStore.updateConversation(conversation.value.id)
} }
/** 滚动到底部 */ /** 滚动到底部 */

View File

@ -3,7 +3,7 @@
<!-- 会话列表 --> <!-- 会话列表 -->
<KeFuConversationList ref="keFuConversationRef" @change="handleChange" /> <KeFuConversationList ref="keFuConversationRef" @change="handleChange" />
<!-- 会话详情选中会话的消息列表 --> <!-- 会话详情选中会话的消息列表 -->
<KeFuMessageList ref="keFuChatBoxRef" @change="getConversationList" /> <KeFuMessageList ref="keFuChatBoxRef" />
<!-- 会员信息选中会话的会员信息 --> <!-- 会员信息选中会话的会员信息 -->
<MemberInfo ref="memberInfoRef" /> <MemberInfo ref="memberInfoRef" />
</el-container> </el-container>
@ -15,10 +15,12 @@ import { WebSocketMessageTypeConstants } from './components/tools/constants'
import { KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation' import { KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
import { getRefreshToken } from '@/utils/auth' import { getRefreshToken } from '@/utils/auth'
import { useWebSocket } from '@vueuse/core' import { useWebSocket } from '@vueuse/core'
import { useMallKefuStore } from '@/store/modules/mall/kefu'
defineOptions({ name: 'KeFu' }) defineOptions({ name: 'KeFu' })
const message = useMessage() // const message = useMessage() //
const kefuStore = useMallKefuStore() //
// ======================= WebSocket start ======================= // ======================= WebSocket start =======================
const server = ref( const server = ref(
@ -53,29 +55,24 @@ watchEffect(() => {
} }
// 2.2 KEFU_MESSAGE_TYPE // 2.2 KEFU_MESSAGE_TYPE
if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_TYPE) { if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_TYPE) {
const message = JSON.parse(jsonMessage.content)
// //
// TODO @puhui999 update // TODO @puhui999 update
getConversationList() kefuStore.updateConversation(message.conversationId)
// //
keFuChatBoxRef.value?.refreshMessageList(JSON.parse(jsonMessage.content)) keFuChatBoxRef.value?.refreshMessageList(message)
return return
} }
// 2.3 KEFU_MESSAGE_ADMIN_READ // 2.3 KEFU_MESSAGE_ADMIN_READ
if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_ADMIN_READ) { if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_ADMIN_READ) {
// //
// TODO @puhui999 update kefuStore.updateConversationStatus(JSON.parse(jsonMessage.content)?.id)
getConversationList()
} }
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} }
}) })
// ======================= WebSocket end ======================= // ======================= WebSocket end =======================
/** 加载会话列表 */
const keFuConversationRef = ref<InstanceType<typeof KeFuConversationList>>()
const getConversationList = () => {
keFuConversationRef.value?.getConversationList()
}
/** 加载指定会话的消息列表 */ /** 加载指定会话的消息列表 */
const keFuChatBoxRef = ref<InstanceType<typeof KeFuMessageList>>() const keFuChatBoxRef = ref<InstanceType<typeof KeFuMessageList>>()
@ -87,7 +84,8 @@ const handleChange = (conversation: KeFuConversationRespVO) => {
/** 初始化 */ /** 初始化 */
onMounted(() => { onMounted(() => {
getConversationList() /** 加载会话列表 */
kefuStore.setConversationList()
// websocket // websocket
open() open()
}) })