Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
admin
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
8d73d741
authored
Jul 06, 2024
by
芋道源码
Committed by
Gitee
Jul 06, 2024
Browse files
Options
Browse Files
Download
Plain Diff
!471 mall 客服会话相关
Merge pull request !471 from puhui999/dev-crm
parents
5fd81777
d3c596dc
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
197 additions
and
114 deletions
+197
-114
src/api/mall/promotion/kefu/conversation/index.ts
+12
-49
src/api/mall/promotion/kefu/message/index.ts
+13
-47
src/views/mall/promotion/kefu/components/KeFuChatBox.vue
+7
-3
src/views/mall/promotion/kefu/components/KeFuConversationBox.vue
+155
-6
src/views/mall/promotion/kefu/components/index.ts
+1
-2
src/views/mall/promotion/kefu/index.vue
+9
-7
No files found.
src/api/mall/promotion/kefu/conversation/index.ts
View file @
8d73d741
import
request
from
'@/config/axios'
// TODO @puhui999:注释要不放在属性后面,避免太长哈
export
interface
KeFuConversationRespVO
{
/**
* 编号
*/
id
:
number
/**
* 会话所属用户
*/
userId
:
number
/**
* 会话所属用户头像
*/
userAvatar
:
string
/**
* 会话所属用户昵称
*/
userNickname
:
string
/**
* 最后聊天时间
*/
lastMessageTime
:
Date
/**
* 最后聊天内容
*/
lastMessageContent
:
string
/**
* 最后发送的消息类型
*/
lastMessageContentType
:
number
/**
* 管理端置顶
*/
adminPinned
:
boolean
/**
* 用户是否可见
*/
userDeleted
:
boolean
/**
* 管理员是否可见
*/
adminDeleted
:
boolean
/**
* 管理员未读消息数
*/
adminUnreadMessageCount
:
number
/**
* 创建时间
*/
createTime
?:
string
id
:
number
// 编号
userId
:
number
// 会话所属用户
userAvatar
:
string
// 会话所属用户头像
userNickname
:
string
// 会话所属用户昵称
lastMessageTime
:
Date
// 最后聊天时间
lastMessageContent
:
string
// 最后聊天内容
lastMessageContentType
:
number
// 最后发送的消息类型
adminPinned
:
boolean
// 管理端置顶
userDeleted
:
boolean
// 用户是否可见
adminDeleted
:
boolean
// 管理员是否可见
adminUnreadMessageCount
:
number
// 管理员未读消息数
createTime
?:
string
// 创建时间
}
// 客服会话 API
...
...
src/api/mall/promotion/kefu/message/index.ts
View file @
8d73d741
import
request
from
'@/config/axios'
export
interface
KeFuMessageRespVO
{
/**
* 编号
*/
id
:
number
/**
* 会话编号
*/
conversationId
:
number
/**
* 发送人编号
*/
senderId
:
number
/**
* 发送人头像
*/
senderAvatar
:
string
/**
* 发送人类型
*/
senderType
:
number
/**
* 接收人编号
*/
receiverId
:
number
/**
* 接收人类型
*/
receiverType
:
number
/**
* 消息类型
*/
contentType
:
number
/**
* 消息
*/
content
:
string
/**
* 是否已读
*/
readStatus
:
boolean
/**
* 创建时间
*/
createTime
:
Date
id
:
number
// 编号
conversationId
:
number
// 会话编号
senderId
:
number
// 发送人编号
senderAvatar
:
string
// 发送人头像
senderType
:
number
// 发送人类型
receiverId
:
number
// 接收人编号
receiverType
:
number
// 接收人类型
contentType
:
number
// 消息类型
content
:
string
// 消息
readStatus
:
boolean
// 是否已读
createTime
:
Date
// 创建时间
}
// 客服会话 API
...
...
@@ -57,10 +24,9 @@ export const KeFuMessageApi = {
})
},
// 更新客服消息已读状态
updateKeFuMessageReadStatus
:
async
(
data
:
any
)
=>
{
updateKeFuMessageReadStatus
:
async
(
conversationId
:
number
)
=>
{
return
await
request
.
put
({
url
:
'/promotion/kefu-message/update-read-status'
,
data
url
:
'/promotion/kefu-message/update-read-status?conversationId='
+
conversationId
})
},
// 获得消息分页数据
...
...
src/views/mall/promotion/kefu/components/KeFuChatBox.vue
View file @
8d73d741
...
...
@@ -18,12 +18,12 @@
{{
formatDate
(
item
.
createTime
)
}}
</div>
<!-- 系统消息 -->
<
view
<
div
v-if=
"item.contentType === KeFuMessageContentTypeEnum.SYSTEM"
class=
"system-message"
>
{{
item
.
content
}}
</
view
>
</
div
>
</div>
<div
:class=
"[
...
...
@@ -154,9 +154,10 @@ const handleSendMessage = async () => {
// 发送消息 【共用】
const
sendMessage
=
async
(
msg
:
any
)
=>
{
// 发送消息
await
KeFuMessageApi
.
sendKeFuMessage
(
msg
)
message
.
value
=
''
//
3.
加载消息列表
// 加载消息列表
await
getMessageList
(
keFuConversation
.
value
)
// 滚动到最新消息处
await
scrollToBottom
()
...
...
@@ -166,8 +167,11 @@ const innerRef = ref<HTMLDivElement>()
const
scrollbarRef
=
ref
<
InstanceType
<
typeof
ElScrollbarType
>>
()
// 滚动到底部
const
scrollToBottom
=
async
()
=>
{
// 1. 滚动到最新消息
await
nextTick
()
scrollbarRef
.
value
!
.
setScrollTop
(
innerRef
.
value
!
.
clientHeight
)
// 2. 消息已读
await
KeFuMessageApi
.
updateKeFuMessageReadStatus
(
keFuConversation
.
value
.
id
)
}
/**
* 是否显示时间
...
...
src/views/mall/promotion/kefu/components/KeFuConversationBox.vue
View file @
8d73d741
...
...
@@ -3,12 +3,21 @@
<div
v-for=
"(item, index) in conversationList"
:key=
"item.id"
:class=
"
{ active: index === activeConversationIndex }"
class="kefu-conversation flex
justify-between
items-center"
:class=
"
{ active: index === activeConversationIndex
, pinned: item.adminPinned
}"
class="kefu-conversation flex items-center"
@click="openRightMessage(item, index)"
@contextmenu.prevent="rightClick($event as PointerEvent, item)"
>
<div
class=
"flex justify-center items-center w-100%"
>
<el-avatar
:src=
"item.userAvatar"
alt=
"avatar"
/>
<div
class=
"flex justify-center items-center"
style=
"width: 50px; height: 50px"
>
<el-badge
:hidden=
"item.adminUnreadMessageCount === 0"
:max=
"99"
:value=
"item.adminUnreadMessageCount"
>
<el-avatar
:src=
"item.userAvatar"
alt=
"avatar"
/>
</el-badge>
</div>
<div
class=
"ml-10px w-100%"
>
<div
class=
"flex justify-between items-center w-100%"
>
<span>
{{
item
.
userNickname
}}
</span>
...
...
@@ -24,27 +33,75 @@
></div>
</
template
>
<!-- 图片消息 -->
<
template
v-if=
"KeFuMessageContentTypeEnum.IMAGE === item.lastMessageContentType"
>
<div
class=
"last-message flex items-center color-[#989EA6]"
>
【图片消息】
</div>
<
template
v-else
>
<div
class=
"last-message flex items-center color-[#989EA6]"
>
{{
getContentType
(
item
.
lastMessageContentType
)
}}
</div>
</
template
>
</div>
</div>
</div>
<!-- 通过右击获取到的坐标定位 -->
<ul
v-show=
"showRightMenu"
:style=
"rightMenuStyle"
class=
"right-menu-ul"
>
<li
v-show=
"!selectedConversation.adminPinned"
class=
"flex items-center"
@
click
.
stop=
"updateConversationPinned(true)"
>
<Icon
class=
"mr-5px"
icon=
"ep:top"
/>
置顶会话
</li>
<li
v-show=
"selectedConversation.adminPinned"
class=
"flex items-center"
@
click
.
stop=
"updateConversationPinned(false)"
>
<Icon
class=
"mr-5px"
icon=
"ep:bottom"
/>
取消置顶
</li>
<li
class=
"flex items-center"
@
click
.
stop=
"deleteConversation"
>
<Icon
class=
"mr-5px"
color=
"red"
icon=
"ep:delete"
/>
删除会话
</li>
<li
class=
"flex items-center"
@
click
.
stop=
"closeRightMenu"
>
<Icon
class=
"mr-5px"
color=
"red"
icon=
"ep:close"
/>
取消
</li>
</ul>
</div>
</template>
<
script
lang=
"ts"
setup
>
import
{
KeFuConversationApi
,
KeFuConversationRespVO
}
from
'@/api/mall/promotion/kefu/conversation'
import
{
useEmoji
}
from
'./tools/emoji'
import
{
formatDate
}
from
'@/utils/formatTime'
import
{
formatDate
,
getNowDateTime
}
from
'@/utils/formatTime'
import
{
KeFuMessageContentTypeEnum
}
from
'./tools/constants'
defineOptions
({
name
:
'KeFuConversationBox'
})
const
message
=
useMessage
()
const
{
replaceEmoji
}
=
useEmoji
()
const
activeConversationIndex
=
ref
(
-
1
)
// 选中的会话
const
conversationList
=
ref
<
KeFuConversationRespVO
[]
>
([])
// 会话列表
const
getConversationList
=
async
()
=>
{
conversationList
.
value
=
await
KeFuConversationApi
.
getConversationList
()
// 测试数据
for
(
let
i
=
0
;
i
<
5
;
i
++
)
{
conversationList
.
value
.
push
({
id
:
1
,
userId
:
283
,
userAvatar
:
'https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTKMezSxtOImrC9lbhwHiazYwck3xwrEcO7VJfG6WQo260whaeVNoByE5RreiaGsGfOMlIiaDhSaA991w/132'
,
userNickname
:
'辉辉鸭'
+
i
,
lastMessageTime
:
getNowDateTime
(),
lastMessageContent
:
'[爱心][爱心]你好哇你好哇你好哇你好哇你好哇你好哇你好哇你好哇你好哇你好哇你好哇你好哇你好哇你好哇你好哇你好哇你好哇你好哇'
,
lastMessageContentType
:
1
,
adminPinned
:
false
,
userDeleted
:
false
,
adminDeleted
:
false
,
adminUnreadMessageCount
:
i
})
}
}
defineExpose
({
getConversationList
})
const
emits
=
defineEmits
<
{
...
...
@@ -55,6 +112,73 @@ const openRightMessage = (item: KeFuConversationRespVO, index: number) => {
activeConversationIndex
.
value
=
index
emits
(
'change'
,
item
)
}
// 获得消息类型
const
getContentType
=
computed
(()
=>
(
lastMessageContentType
:
number
)
=>
{
switch
(
lastMessageContentType
)
{
case
KeFuMessageContentTypeEnum
.
SYSTEM
:
return
'[系统消息]'
case
KeFuMessageContentTypeEnum
.
VIDEO
:
return
'[视频消息]'
case
KeFuMessageContentTypeEnum
.
IMAGE
:
return
'[图片消息]'
case
KeFuMessageContentTypeEnum
.
PRODUCT
:
return
'[商品消息]'
case
KeFuMessageContentTypeEnum
.
ORDER
:
return
'[订单消息]'
case
KeFuMessageContentTypeEnum
.
VOICE
:
return
'[语音消息]'
default
:
return
''
}
})
//======================= 右键菜单 =======================
const
showRightMenu
=
ref
(
false
)
// 显示右键菜单
const
rightMenuStyle
=
ref
<
any
>
({})
// 右键菜单 Style
const
selectedConversation
=
ref
<
KeFuConversationRespVO
>
({}
as
KeFuConversationRespVO
)
// 右键选中的会话对象
// 右键菜单
const
rightClick
=
(
mouseEvent
:
PointerEvent
,
item
:
KeFuConversationRespVO
)
=>
{
selectedConversation
.
value
=
item
// 显示右键菜单
showRightMenu
.
value
=
true
rightMenuStyle
.
value
=
{
top
:
mouseEvent
.
clientY
-
110
+
'px'
,
left
:
mouseEvent
.
clientX
-
80
+
'px'
}
}
// 关闭菜单
const
closeRightMenu
=
()
=>
{
showRightMenu
.
value
=
false
}
// 置顶会话
const
updateConversationPinned
=
async
(
adminPinned
:
boolean
)
=>
{
// 1. 会话置顶/取消置顶
await
KeFuConversationApi
.
updateConversationPinned
({
id
:
selectedConversation
.
value
.
id
,
adminPinned
})
// TODO puhui999: 快速操作两次提示只会提示一次看看怎么优雅解决
message
.
success
(
adminPinned
?
'置顶成功'
:
'取消置顶成功'
)
// 2. 关闭右键菜单,更新会话列表
closeRightMenu
()
await
getConversationList
()
}
// 删除会话
const
deleteConversation
=
async
()
=>
{
// 1. 删除会话
await
message
.
confirm
(
'您确定要删除该会话吗?'
)
await
KeFuConversationApi
.
deleteConversation
(
selectedConversation
.
value
.
id
)
// 2. 关闭右键菜单,更新会话列表
closeRightMenu
()
await
getConversationList
()
}
watch
(
showRightMenu
,
(
val
)
=>
{
if
(
val
)
{
document
.
body
.
addEventListener
(
'click'
,
closeRightMenu
)
}
else
{
document
.
body
.
removeEventListener
(
'click'
,
closeRightMenu
)
}
})
</
script
>
<
style
lang=
"scss"
scoped
>
...
...
@@ -77,5 +201,30 @@ const openRightMessage = (item: KeFuConversationRespVO, index: number) => {
border-left
:
5px
#3271ff
solid
;
background-color
:
#eff0f1
;
}
.pinned
{
background-color
:
#eff0f1
;
}
.right-menu-ul
{
position
:
absolute
;
background-color
:
#fff
;
padding
:
10px
;
margin
:
0
;
list-style-type
:
none
;
/* 移除默认的项目符号 */
border-radius
:
12px
;
box-shadow
:
0
2px
4px
rgba
(
0
,
0
,
0
,
0.1
);
/* 阴影效果 */
width
:
130px
;
li
{
padding
:
8px
16px
;
cursor
:
pointer
;
border-radius
:
12px
;
transition
:
background-color
0.3s
;
/* 平滑过渡 */
&:hover
{
background-color
:
#e0e0e0
;
/* 悬停时的背景颜色 */
}
}
}
}
</
style
>
src/views/mall/promotion/kefu/components/index.ts
View file @
8d73d741
import
KeFuConversationBox
from
'./KeFuConversationBox.vue'
import
KeFuChatBox
from
'./KeFuChatBox.vue'
import
*
as
Constants
from
'./tools/constants'
export
{
KeFuConversationBox
,
KeFuChatBox
,
Constants
}
export
{
KeFuConversationBox
,
KeFuChatBox
}
src/views/mall/promotion/kefu/index.vue
View file @
8d73d741
...
...
@@ -7,7 +7,7 @@
</el-col>
<el-col
:span=
"16"
>
<ContentWrap>
<KeFuChatBox
ref=
"keFuChatBoxRef"
/>
<KeFuChatBox
ref=
"keFuChatBoxRef"
@
change=
"getConversationList"
/>
</ContentWrap>
</el-col>
</el-row>
...
...
@@ -15,13 +15,14 @@
<
script
lang=
"ts"
setup
>
import
{
KeFuChatBox
,
KeFuConversationBox
}
from
'./components'
import
{
WebSocketMessageTypeConstants
}
from
'./components/tools/constants'
import
{
KeFuConversationRespVO
}
from
'@/api/mall/promotion/kefu/conversation'
import
{
getAccessToken
}
from
'@/utils/auth'
import
{
useWebSocket
}
from
'@vueuse/core'
import
{
WebSocketMessageTypeConstants
}
from
'@/views/mall/promotion/kefu/components/tools/constants'
defineOptions
({
name
:
'KeFu'
})
const
message
=
useMessage
()
// 加载消息
const
keFuChatBoxRef
=
ref
<
InstanceType
<
typeof
KeFuChatBox
>>
()
const
handleChange
=
(
conversation
:
KeFuConversationRespVO
)
=>
{
...
...
@@ -47,10 +48,6 @@ watchEffect(() => {
try
{
// 1. 收到心跳
if
(
data
.
value
===
'pong'
)
{
// state.recordList.push({
// text: '【心跳】',
// time: new Date().getTime()
// })
return
}
...
...
@@ -63,12 +60,17 @@ watchEffect(() => {
}
// 2.2 消息类型:KEFU_MESSAGE_TYPE
if
(
type
===
WebSocketMessageTypeConstants
.
KEFU_MESSAGE_TYPE
)
{
// 刷新列表
// 刷新
会话
列表
getConversationList
()
// 刷新消息列表
keFuChatBoxRef
.
value
?.
refreshMessageList
()
return
}
// 2.3 消息类型:KEFU_MESSAGE_ADMIN_READ
if
(
type
===
WebSocketMessageTypeConstants
.
KEFU_MESSAGE_ADMIN_READ
)
{
// 刷新会话列表
getConversationList
()
}
}
catch
(
error
)
{
console
.
error
(
error
)
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment