Skip to content

Commit 8d73d74

Browse files
YunaiVgitee-org
authored andcommitted
!471 mall 客服会话相关
Merge pull request !471 from puhui999/dev-crm
2 parents 5fd8177 + d3c596d commit 8d73d74

File tree

6 files changed

+197
-114
lines changed

6 files changed

+197
-114
lines changed

src/api/mall/promotion/kefu/conversation/index.ts

Lines changed: 12 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,18 @@
11
import request from '@/config/axios'
22

3-
// TODO @puhui999:注释要不放在属性后面,避免太长哈
43
export interface KeFuConversationRespVO {
5-
/**
6-
* 编号
7-
*/
8-
id: number
9-
/**
10-
* 会话所属用户
11-
*/
12-
userId: number
13-
/**
14-
* 会话所属用户头像
15-
*/
16-
userAvatar: string
17-
/**
18-
* 会话所属用户昵称
19-
*/
20-
userNickname: string
21-
/**
22-
* 最后聊天时间
23-
*/
24-
lastMessageTime: Date
25-
/**
26-
* 最后聊天内容
27-
*/
28-
lastMessageContent: string
29-
/**
30-
* 最后发送的消息类型
31-
*/
32-
lastMessageContentType: number
33-
/**
34-
* 管理端置顶
35-
*/
36-
adminPinned: boolean
37-
/**
38-
* 用户是否可见
39-
*/
40-
userDeleted: boolean
41-
/**
42-
* 管理员是否可见
43-
*/
44-
adminDeleted: boolean
45-
/**
46-
* 管理员未读消息数
47-
*/
48-
adminUnreadMessageCount: number
49-
/**
50-
* 创建时间
51-
*/
52-
createTime?: string
4+
id: number // 编号
5+
userId: number // 会话所属用户
6+
userAvatar: string // 会话所属用户头像
7+
userNickname: string // 会话所属用户昵称
8+
lastMessageTime: Date // 最后聊天时间
9+
lastMessageContent: string // 最后聊天内容
10+
lastMessageContentType: number // 最后发送的消息类型
11+
adminPinned: boolean // 管理端置顶
12+
userDeleted: boolean // 用户是否可见
13+
adminDeleted: boolean // 管理员是否可见
14+
adminUnreadMessageCount: number // 管理员未读消息数
15+
createTime?: string // 创建时间
5316
}
5417

5518
// 客服会话 API

src/api/mall/promotion/kefu/message/index.ts

Lines changed: 13 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,17 @@
11
import request from '@/config/axios'
22

33
export interface KeFuMessageRespVO {
4-
/**
5-
* 编号
6-
*/
7-
id: number
8-
/**
9-
* 会话编号
10-
*/
11-
conversationId: number
12-
/**
13-
* 发送人编号
14-
*/
15-
senderId: number
16-
/**
17-
* 发送人头像
18-
*/
19-
senderAvatar: string
20-
/**
21-
* 发送人类型
22-
*/
23-
senderType: number
24-
/**
25-
* 接收人编号
26-
*/
27-
receiverId: number
28-
/**
29-
* 接收人类型
30-
*/
31-
receiverType: number
32-
/**
33-
* 消息类型
34-
*/
35-
contentType: number
36-
/**
37-
* 消息
38-
*/
39-
content: string
40-
/**
41-
* 是否已读
42-
*/
43-
readStatus: boolean
44-
/**
45-
* 创建时间
46-
*/
47-
createTime: Date
4+
id: number // 编号
5+
conversationId: number // 会话编号
6+
senderId: number // 发送人编号
7+
senderAvatar: string // 发送人头像
8+
senderType: number // 发送人类型
9+
receiverId: number // 接收人编号
10+
receiverType: number // 接收人类型
11+
contentType: number // 消息类型
12+
content: string // 消息
13+
readStatus: boolean // 是否已读
14+
createTime: Date // 创建时间
4815
}
4916

5017
// 客服会话 API
@@ -57,10 +24,9 @@ export const KeFuMessageApi = {
5724
})
5825
},
5926
// 更新客服消息已读状态
60-
updateKeFuMessageReadStatus: async (data: any) => {
27+
updateKeFuMessageReadStatus: async (conversationId: number) => {
6128
return await request.put({
62-
url: '/promotion/kefu-message/update-read-status',
63-
data
29+
url: '/promotion/kefu-message/update-read-status?conversationId=' + conversationId
6430
})
6531
},
6632
// 获得消息分页数据

src/views/mall/promotion/kefu/components/KeFuChatBox.vue

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
{{ formatDate(item.createTime) }}
1919
</div>
2020
<!-- 系统消息 -->
21-
<view
21+
<div
2222
v-if="item.contentType === KeFuMessageContentTypeEnum.SYSTEM"
2323
class="system-message"
2424
>
2525
{{ item.content }}
26-
</view>
26+
</div>
2727
</div>
2828
<div
2929
:class="[
@@ -154,9 +154,10 @@ const handleSendMessage = async () => {
154154
155155
// 发送消息 【共用】
156156
const sendMessage = async (msg: any) => {
157+
// 发送消息
157158
await KeFuMessageApi.sendKeFuMessage(msg)
158159
message.value = ''
159-
// 3. 加载消息列表
160+
// 加载消息列表
160161
await getMessageList(keFuConversation.value)
161162
// 滚动到最新消息处
162163
await scrollToBottom()
@@ -166,8 +167,11 @@ const innerRef = ref<HTMLDivElement>()
166167
const scrollbarRef = ref<InstanceType<typeof ElScrollbarType>>()
167168
// 滚动到底部
168169
const scrollToBottom = async () => {
170+
// 1. 滚动到最新消息
169171
await nextTick()
170172
scrollbarRef.value!.setScrollTop(innerRef.value!.clientHeight)
173+
// 2. 消息已读
174+
await KeFuMessageApi.updateKeFuMessageReadStatus(keFuConversation.value.id)
171175
}
172176
/**
173177
* 是否显示时间

src/views/mall/promotion/kefu/components/KeFuConversationBox.vue

Lines changed: 155 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,21 @@
33
<div
44
v-for="(item, index) in conversationList"
55
:key="item.id"
6-
:class="{ active: index === activeConversationIndex }"
7-
class="kefu-conversation flex justify-between items-center"
6+
:class="{ active: index === activeConversationIndex, pinned: item.adminPinned }"
7+
class="kefu-conversation flex items-center"
88
@click="openRightMessage(item, index)"
9+
@contextmenu.prevent="rightClick($event as PointerEvent, item)"
910
>
1011
<div class="flex justify-center items-center w-100%">
11-
<el-avatar :src="item.userAvatar" alt="avatar" />
12+
<div class="flex justify-center items-center" style="width: 50px; height: 50px">
13+
<el-badge
14+
:hidden="item.adminUnreadMessageCount === 0"
15+
:max="99"
16+
:value="item.adminUnreadMessageCount"
17+
>
18+
<el-avatar :src="item.userAvatar" alt="avatar" />
19+
</el-badge>
20+
</div>
1221
<div class="ml-10px w-100%">
1322
<div class="flex justify-between items-center w-100%">
1423
<span>{{ item.userNickname }}</span>
@@ -24,27 +33,75 @@
2433
></div>
2534
</template>
2635
<!-- 图片消息 -->
27-
<template v-if="KeFuMessageContentTypeEnum.IMAGE === item.lastMessageContentType">
28-
<div class="last-message flex items-center color-[#989EA6]">【图片消息】</div>
36+
<template v-else>
37+
<div class="last-message flex items-center color-[#989EA6]">
38+
{{ getContentType(item.lastMessageContentType) }}
39+
</div>
2940
</template>
3041
</div>
3142
</div>
3243
</div>
44+
<!-- 通过右击获取到的坐标定位 -->
45+
<ul v-show="showRightMenu" :style="rightMenuStyle" class="right-menu-ul">
46+
<li
47+
v-show="!selectedConversation.adminPinned"
48+
class="flex items-center"
49+
@click.stop="updateConversationPinned(true)"
50+
>
51+
<Icon class="mr-5px" icon="ep:top" />
52+
置顶会话
53+
</li>
54+
<li
55+
v-show="selectedConversation.adminPinned"
56+
class="flex items-center"
57+
@click.stop="updateConversationPinned(false)"
58+
>
59+
<Icon class="mr-5px" icon="ep:bottom" />
60+
取消置顶
61+
</li>
62+
<li class="flex items-center" @click.stop="deleteConversation">
63+
<Icon class="mr-5px" color="red" icon="ep:delete" />
64+
删除会话
65+
</li>
66+
<li class="flex items-center" @click.stop="closeRightMenu">
67+
<Icon class="mr-5px" color="red" icon="ep:close" />
68+
取消
69+
</li>
70+
</ul>
3371
</div>
3472
</template>
3573

3674
<script lang="ts" setup>
3775
import { KeFuConversationApi, KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
3876
import { useEmoji } from './tools/emoji'
39-
import { formatDate } from '@/utils/formatTime'
77+
import { formatDate, getNowDateTime } from '@/utils/formatTime'
4078
import { KeFuMessageContentTypeEnum } from './tools/constants'
4179
4280
defineOptions({ name: 'KeFuConversationBox' })
81+
const message = useMessage()
4382
const { replaceEmoji } = useEmoji()
4483
const activeConversationIndex = ref(-1) // 选中的会话
4584
const conversationList = ref<KeFuConversationRespVO[]>([]) // 会话列表
4685
const getConversationList = async () => {
4786
conversationList.value = await KeFuConversationApi.getConversationList()
87+
// 测试数据
88+
for (let i = 0; i < 5; i++) {
89+
conversationList.value.push({
90+
id: 1,
91+
userId: 283,
92+
userAvatar:
93+
'https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTKMezSxtOImrC9lbhwHiazYwck3xwrEcO7VJfG6WQo260whaeVNoByE5RreiaGsGfOMlIiaDhSaA991w/132',
94+
userNickname: '辉辉鸭' + i,
95+
lastMessageTime: getNowDateTime(),
96+
lastMessageContent:
97+
'[爱心][爱心]你好哇你好哇你好哇你好哇你好哇你好哇你好哇你好哇你好哇你好哇你好哇你好哇你好哇你好哇你好哇你好哇你好哇你好哇',
98+
lastMessageContentType: 1,
99+
adminPinned: false,
100+
userDeleted: false,
101+
adminDeleted: false,
102+
adminUnreadMessageCount: i
103+
})
104+
}
48105
}
49106
defineExpose({ getConversationList })
50107
const emits = defineEmits<{
@@ -55,6 +112,73 @@ const openRightMessage = (item: KeFuConversationRespVO, index: number) => {
55112
activeConversationIndex.value = index
56113
emits('change', item)
57114
}
115+
// 获得消息类型
116+
const getContentType = computed(() => (lastMessageContentType: number) => {
117+
switch (lastMessageContentType) {
118+
case KeFuMessageContentTypeEnum.SYSTEM:
119+
return '[系统消息]'
120+
case KeFuMessageContentTypeEnum.VIDEO:
121+
return '[视频消息]'
122+
case KeFuMessageContentTypeEnum.IMAGE:
123+
return '[图片消息]'
124+
case KeFuMessageContentTypeEnum.PRODUCT:
125+
return '[商品消息]'
126+
case KeFuMessageContentTypeEnum.ORDER:
127+
return '[订单消息]'
128+
case KeFuMessageContentTypeEnum.VOICE:
129+
return '[语音消息]'
130+
default:
131+
return ''
132+
}
133+
})
134+
135+
//======================= 右键菜单 =======================
136+
const showRightMenu = ref(false) // 显示右键菜单
137+
const rightMenuStyle = ref<any>({}) // 右键菜单 Style
138+
const selectedConversation = ref<KeFuConversationRespVO>({} as KeFuConversationRespVO) // 右键选中的会话对象
139+
// 右键菜单
140+
const rightClick = (mouseEvent: PointerEvent, item: KeFuConversationRespVO) => {
141+
selectedConversation.value = item
142+
// 显示右键菜单
143+
showRightMenu.value = true
144+
rightMenuStyle.value = {
145+
top: mouseEvent.clientY - 110 + 'px',
146+
left: mouseEvent.clientX - 80 + 'px'
147+
}
148+
}
149+
// 关闭菜单
150+
const closeRightMenu = () => {
151+
showRightMenu.value = false
152+
}
153+
// 置顶会话
154+
const updateConversationPinned = async (adminPinned: boolean) => {
155+
// 1. 会话置顶/取消置顶
156+
await KeFuConversationApi.updateConversationPinned({
157+
id: selectedConversation.value.id,
158+
adminPinned
159+
})
160+
// TODO puhui999: 快速操作两次提示只会提示一次看看怎么优雅解决
161+
message.success(adminPinned ? '置顶成功' : '取消置顶成功')
162+
// 2. 关闭右键菜单,更新会话列表
163+
closeRightMenu()
164+
await getConversationList()
165+
}
166+
// 删除会话
167+
const deleteConversation = async () => {
168+
// 1. 删除会话
169+
await message.confirm('您确定要删除该会话吗?')
170+
await KeFuConversationApi.deleteConversation(selectedConversation.value.id)
171+
// 2. 关闭右键菜单,更新会话列表
172+
closeRightMenu()
173+
await getConversationList()
174+
}
175+
watch(showRightMenu, (val) => {
176+
if (val) {
177+
document.body.addEventListener('click', closeRightMenu)
178+
} else {
179+
document.body.removeEventListener('click', closeRightMenu)
180+
}
181+
})
58182
</script>
59183

60184
<style lang="scss" scoped>
@@ -77,5 +201,30 @@ const openRightMessage = (item: KeFuConversationRespVO, index: number) => {
77201
border-left: 5px #3271ff solid;
78202
background-color: #eff0f1;
79203
}
204+
205+
.pinned {
206+
background-color: #eff0f1;
207+
}
208+
209+
.right-menu-ul {
210+
position: absolute;
211+
background-color: #fff;
212+
padding: 10px;
213+
margin: 0;
214+
list-style-type: none; /* 移除默认的项目符号 */
215+
border-radius: 12px;
216+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 阴影效果 */
217+
width: 130px;
218+
219+
li {
220+
padding: 8px 16px;
221+
cursor: pointer;
222+
border-radius: 12px;
223+
transition: background-color 0.3s; /* 平滑过渡 */
224+
&:hover {
225+
background-color: #e0e0e0; /* 悬停时的背景颜色 */
226+
}
227+
}
228+
}
80229
}
81230
</style>
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import KeFuConversationBox from './KeFuConversationBox.vue'
22
import KeFuChatBox from './KeFuChatBox.vue'
3-
import * as Constants from './tools/constants'
43

5-
export { KeFuConversationBox, KeFuChatBox, Constants }
4+
export { KeFuConversationBox, KeFuChatBox }

0 commit comments

Comments
 (0)