6
6
<el-main class =" kefu-content" style =" overflow : visible " >
7
7
<el-scrollbar ref =" scrollbarRef" always height =" calc(100vh - 495px)" >
8
8
<div ref =" innerRef" class =" w-[100%] pb-3px" >
9
- <div
10
- v-for =" item in messageList"
11
- :key =" item.id"
12
- :class =" [
13
- item.senderType === UserTypeEnum.MEMBER
14
- ? `ss-row-left`
15
- : item.senderType === UserTypeEnum.ADMIN
16
- ? `ss-row-right`
17
- : ''
18
- ]"
19
- class =" flex mb-20px w-[100%]"
20
- >
21
- <el-avatar
22
- v-show =" item.senderType === UserTypeEnum.MEMBER"
23
- :src =" keFuConversation.userAvatar"
24
- alt =" avatar"
25
- />
26
- <div class =" kefu-message p-10px" >
27
- <!-- TODO puhui999: 消息相关等后续完成后统一抽离封装 -->
28
- <!-- 文本消息 -->
29
- <template v-if =" KeFuMessageContentTypeEnum .TEXT === item .contentType " >
30
- <div
31
- v-dompurify-html =" replaceEmoji(item.content)"
32
- :class =" [
33
- item.senderType === UserTypeEnum.MEMBER
34
- ? `ml-10px`
35
- : item.senderType === UserTypeEnum.ADMIN
36
- ? `mr-10px`
37
- : ''
38
- ]"
39
- class =" flex items-center"
40
- ></div >
41
- </template >
42
- <!-- 图片消息 -->
43
- <template v-if =" KeFuMessageContentTypeEnum .IMAGE === item .contentType " >
44
- <div
45
- :class =" [
46
- item.senderType === UserTypeEnum.MEMBER
47
- ? `ml-10px`
48
- : item.senderType === UserTypeEnum.ADMIN
49
- ? `mr-10px`
50
- : ''
51
- ]"
52
- class =" flex items-center"
53
- >
54
- <el-image
55
- :src =" item.content"
56
- fit =" contain"
57
- style =" width : 200px ; height : 200px "
58
- @click =" imagePreview(item.content)"
59
- />
60
- </div >
61
- </template >
9
+ <div v-for =" (item, index) in messageList" :key =" item.id" class =" w-[100%]" >
10
+ <div class =" flex justify-center items-center mb-20px" >
11
+ <!-- 日期 -->
12
+ <div
13
+ v-if ="
14
+ item.contentType !== KeFuMessageContentTypeEnum.SYSTEM && showTime(item, index)
15
+ "
16
+ class =" date-message"
17
+ >
18
+ {{ formatDate(item.createTime) }}
19
+ </div >
20
+ <!-- 系统消息 -->
21
+ <view
22
+ v-if =" item.contentType === KeFuMessageContentTypeEnum.SYSTEM"
23
+ class =" system-message"
24
+ >
25
+ {{ item.content }}
26
+ </view >
27
+ </div >
28
+ <div
29
+ :class =" [
30
+ item.senderType === UserTypeEnum.MEMBER
31
+ ? `ss-row-left`
32
+ : item.senderType === UserTypeEnum.ADMIN
33
+ ? `ss-row-right`
34
+ : ''
35
+ ]"
36
+ class =" flex mb-20px w-[100%]"
37
+ >
38
+ <el-avatar
39
+ v-if =" item.senderType === UserTypeEnum.MEMBER"
40
+ :src =" keFuConversation.userAvatar"
41
+ alt =" avatar"
42
+ />
43
+ <div
44
+ :class =" { 'kefu-message': KeFuMessageContentTypeEnum.TEXT === item.contentType }"
45
+ class =" p-10px"
46
+ >
47
+ <!-- 文本消息 -->
48
+ <TextMessageItem :message =" item" />
49
+ <!-- 图片消息 -->
50
+ <ImageMessageItem :message =" item" />
51
+ </div >
52
+ <el-avatar
53
+ v-if =" item.senderType === UserTypeEnum.ADMIN"
54
+ :src =" item.senderAvatar"
55
+ alt =" avatar"
56
+ />
62
57
</div >
63
- <el-avatar
64
- v-show =" item.senderType === UserTypeEnum.ADMIN"
65
- :src =" item.senderAvatar"
66
- alt =" avatar"
67
- />
68
58
</div >
69
59
</div >
70
60
</el-scrollbar >
71
61
</el-main >
72
62
<el-footer height =" 230px" >
73
63
<div class =" h-[100%]" >
74
- <div class =" chat-tools" >
64
+ <div class =" chat-tools flex items-center " >
75
65
<EmojiSelectPopover @select-emoji =" handleEmojiSelect" />
66
+ <PictureSelectUpload
67
+ class =" ml-15px mt-3px cursor-pointer"
68
+ @send-picture =" handleSendPicture"
69
+ />
76
70
</div >
77
- <el-input v-model =" message" :rows =" 6" type =" textarea" />
71
+ <el-input v-model =" message" :rows =" 6" style = " border-style : none " type =" textarea" />
78
72
<div class =" h-45px flex justify-end" >
79
73
<el-button class =" mt-10px" type =" primary" @click =" handleSendMessage" >发送</el-button >
80
74
</div >
88
82
import { ElScrollbar as ElScrollbarType } from ' element-plus'
89
83
import { KeFuMessageApi , KeFuMessageRespVO } from ' @/api/mall/promotion/kefu/message'
90
84
import { KeFuConversationRespVO } from ' @/api/mall/promotion/kefu/conversation'
91
- import EmojiSelectPopover from ' ./EmojiSelectPopover.vue'
92
- import { Emoji , replaceEmoji } from ' ./emoji'
93
- import { KeFuMessageContentTypeEnum } from ' ./constants'
85
+ import EmojiSelectPopover from ' ./tools/EmojiSelectPopover.vue'
86
+ import PictureSelectUpload from ' ./tools/PictureSelectUpload.vue'
87
+ import TextMessageItem from ' ./message/TextMessageItem.vue'
88
+ import ImageMessageItem from ' ./message/ImageMessageItem.vue'
89
+ import { Emoji } from ' ./tools/emoji'
90
+ import { KeFuMessageContentTypeEnum } from ' ./tools/constants'
94
91
import { isEmpty } from ' @/utils/is'
95
92
import { UserTypeEnum } from ' @/utils/constants'
96
- import { createImageViewer } from ' @/components/ImageViewer'
93
+ import { formatDate } from ' @/utils/formatTime'
94
+ import dayjs from ' dayjs'
95
+ import relativeTime from ' dayjs/plugin/relativeTime'
96
+
97
+ dayjs .extend (relativeTime )
97
98
98
99
defineOptions ({ name: ' KeFuMessageBox' })
99
100
const messageTool = useMessage ()
100
101
const message = ref (' ' ) // 消息
101
102
const messageList = ref <KeFuMessageRespVO []>([]) // 消息列表
102
103
const keFuConversation = ref <KeFuConversationRespVO >({} as KeFuConversationRespVO ) // 用户会话
103
- const poller = ref <any >(null ) // TODO puhui999: 轮训定时器,暂时模拟 websocket
104
104
// 获得消息 TODO puhui999: 先不考虑下拉加载历史消息
105
105
const getMessageList = async (conversation : KeFuConversationRespVO ) => {
106
106
keFuConversation .value = conversation
@@ -111,32 +111,49 @@ const getMessageList = async (conversation: KeFuConversationRespVO) => {
111
111
messageList .value = list .reverse ()
112
112
// TODO puhui999: 首次加载时滚动到最新消息,如果加载的是历史消息则不滚动
113
113
await scrollToBottom ()
114
- // TODO puhui999: 轮训相关,功能完善后移除
115
- if ( ! poller . value ) {
116
- poller . value = setInterval ( () => {
117
- getMessageList ( conversation )
118
- }, 1000 )
114
+ }
115
+ // 刷新消息列表
116
+ const refreshMessageList = () => {
117
+ if ( ! keFuConversation . value ) {
118
+ return
119
119
}
120
+ getMessageList (keFuConversation .value )
120
121
}
121
- defineExpose ({ getMessageList })
122
+ defineExpose ({ getMessageList , refreshMessageList })
122
123
// 是否显示聊天区域
123
124
const showChatBox = computed (() => ! isEmpty (keFuConversation .value ))
124
125
// 处理表情选择
125
126
const handleEmojiSelect = (item : Emoji ) => {
126
127
message .value += item .name
127
128
}
129
+ // 处理图片发送
130
+ const handleSendPicture = async (picUrl : string ) => {
131
+ // 组织发送消息
132
+ const msg = {
133
+ conversationId: keFuConversation .value .id ,
134
+ contentType: KeFuMessageContentTypeEnum .IMAGE ,
135
+ content: picUrl
136
+ }
137
+ await sendMessage (msg )
138
+ }
128
139
// 发送消息
129
140
const handleSendMessage = async () => {
130
141
// 1. 校验消息是否为空
131
142
if (isEmpty (unref (message .value ))) {
132
143
messageTool .warning (' 请输入消息后再发送哦!' )
144
+ return
133
145
}
134
146
// 2. 组织发送消息
135
147
const msg = {
136
148
conversationId: keFuConversation .value .id ,
137
149
contentType: KeFuMessageContentTypeEnum .TEXT ,
138
150
content: message .value
139
151
}
152
+ await sendMessage (msg )
153
+ }
154
+
155
+ // 发送消息 【共用】
156
+ const sendMessage = async (msg : any ) => {
140
157
await KeFuMessageApi .sendKeFuMessage (msg )
141
158
message .value = ' '
142
159
// 3. 加载消息列表
@@ -152,20 +169,17 @@ const scrollToBottom = async () => {
152
169
await nextTick ()
153
170
scrollbarRef .value ! .setScrollTop (innerRef .value ! .clientHeight )
154
171
}
155
-
156
- /** 图预览 */
157
- const imagePreview = (imgUrl : string ) => {
158
- createImageViewer ({
159
- urlList: [imgUrl ]
160
- })
161
- }
162
-
163
- // TODO puhui999: 轮训相关,功能完善后移除
164
- onBeforeUnmount (() => {
165
- if (! poller .value ) {
166
- return
172
+ /**
173
+ * 是否显示时间
174
+ * @param {*} item - 数据
175
+ * @param {*} index - 索引
176
+ */
177
+ const showTime = computed (() => (item : KeFuMessageRespVO , index : number ) => {
178
+ if (unref (messageList .value )[index + 1 ]) {
179
+ let dateString = dayjs (unref (messageList .value )[index + 1 ].createTime ).fromNow ()
180
+ return dateString !== dayjs (unref (item ).createTime ).fromNow ()
167
181
}
168
- clearInterval ( poller . value )
182
+ return false
169
183
})
170
184
</script >
171
185
@@ -241,14 +255,24 @@ onBeforeUnmount(() => {
241
255
transform : scale (1.03 );
242
256
}
243
257
}
258
+
259
+ .date-message ,
260
+ .system-message {
261
+ width : fit-content ;
262
+ border-radius : 12 rpx;
263
+ padding : 8 rpx 16 rpx;
264
+ margin-bottom : 16 rpx;
265
+ background-color : #e8e8e8 ;
266
+ color : #999 ;
267
+ font-size : 24 rpx;
268
+ }
244
269
}
245
270
246
271
.chat-tools {
247
272
width : 100% ;
248
273
border : #e4e0e0 solid 1px ;
274
+ border-radius : 10px ;
249
275
height : 44px ;
250
- display : flex ;
251
- align-items : center ;
252
276
}
253
277
254
278
::v-deep (textarea ) {
0 commit comments