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
Commit
ec61670b
authored
Jul 10, 2024
by
YunaiV
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【代码评审】Mall:客服的会话列表
parent
65509834
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
72 additions
and
26 deletions
+72
-26
src/views/mall/promotion/kefu/components/KeFuChatBox.vue
+27
-11
src/views/mall/promotion/kefu/components/KeFuConversationBox.vue
+0
-1
src/views/mall/promotion/kefu/components/message/ImageMessageItem.vue
+2
-0
src/views/mall/promotion/kefu/components/message/OrderMessageItem.vue
+3
-2
src/views/mall/promotion/kefu/components/message/ProductItem.vue
+4
-0
src/views/mall/promotion/kefu/components/message/ProductMessageItem.vue
+2
-0
src/views/mall/promotion/kefu/components/tools/EmojiSelectPopover.vue
+2
-0
src/views/mall/promotion/kefu/components/tools/PictureSelectUpload.vue
+12
-8
src/views/mall/promotion/kefu/components/tools/emoji.ts
+20
-4
No files found.
src/views/mall/promotion/kefu/components/KeFuChatBox.vue
View file @
ec61670b
<
template
>
<el-container
v-if=
"showChatBox"
class=
"kefu"
>
<el-header>
<!-- TODO @puhui999:keFuConversation => conversation -->
<div
class=
"kefu-title"
>
{{
keFuConversation
.
userNickname
}}
</div>
</el-header>
<!-- TODO @puhui999:unocss -->
<el-main
class=
"kefu-content"
style=
"overflow: visible"
>
<!-- 加载历史消息 -->
<div
v-show=
"loadingMore"
class=
"loadingMore flex justify-center items-center cursor-pointer"
...
...
@@ -13,6 +16,7 @@
</div>
<el-scrollbar
ref=
"scrollbarRef"
always
height=
"calc(100vh - 495px)"
@
scroll=
"handleScroll"
>
<div
ref=
"innerRef"
class=
"w-[100%] pb-3px"
>
<!-- 消息列表 -->
<div
v-for=
"(item, index) in getMessageList0"
:key=
"item.id"
class=
"w-[100%]"
>
<div
class=
"flex justify-center items-center mb-20px"
>
<!-- 日期 -->
...
...
@@ -118,8 +122,10 @@ import relativeTime from 'dayjs/plugin/relativeTime'
dayjs
.
extend
(
relativeTime
)
defineOptions
({
name
:
'KeFuMessageBox'
})
const
message
=
ref
(
''
)
// 消息弹窗
const
messageTool
=
useMessage
()
const
message
=
ref
(
''
)
// 消息
const
messageList
=
ref
<
KeFuMessageRespVO
[]
>
([])
// 消息列表
const
keFuConversation
=
ref
<
KeFuConversationRespVO
>
({}
as
KeFuConversationRespVO
)
// 用户会话
const
showNewMessageTip
=
ref
(
false
)
// 显示有新消息提示
...
...
@@ -128,7 +134,8 @@ const queryParams = reactive({
conversationId
:
0
})
const
total
=
ref
(
0
)
// 消息总条数
// 获得消息
/** 获得消息列表 */
const
getMessageList
=
async
(
conversation
:
KeFuConversationRespVO
)
=>
{
keFuConversation
.
value
=
conversation
queryParams
.
conversationId
=
conversation
.
id
...
...
@@ -146,12 +153,14 @@ const getMessageList = async (conversation: KeFuConversationRespVO) => {
}
await
scrollToBottom
()
}
/** 按照时间倒序,获取消息列表 */
const
getMessageList0
=
computed
(()
=>
{
messageList
.
value
.
sort
((
a
:
any
,
b
:
any
)
=>
a
.
createTime
-
b
.
createTime
)
return
messageList
.
value
})
/
/ 刷新消息列表
/
** 刷新消息列表 */
const
refreshMessageList
=
async
()
=>
{
if
(
!
keFuConversation
.
value
)
{
return
...
...
@@ -164,14 +173,16 @@ const refreshMessageList = async () => {
showNewMessageTip
.
value
=
true
}
}
defineExpose
({
getMessageList
,
refreshMessageList
})
// 是否显示聊天区域
const
showChatBox
=
computed
(()
=>
!
isEmpty
(
keFuConversation
.
value
))
/
/ 处理表情选择
const
showChatBox
=
computed
(()
=>
!
isEmpty
(
keFuConversation
.
value
))
// 是否显示聊天区域
/
** 处理表情选择 */
const
handleEmojiSelect
=
(
item
:
Emoji
)
=>
{
message
.
value
+=
item
.
name
}
// 处理图片发送
/** 处理图片发送 */
const
handleSendPicture
=
async
(
picUrl
:
string
)
=>
{
// 组织发送消息
const
msg
=
{
...
...
@@ -181,7 +192,8 @@ const handleSendPicture = async (picUrl: string) => {
}
await
sendMessage
(
msg
)
}
// 发送消息
/** 发送文本消息 */
const
handleSendMessage
=
async
()
=>
{
// 1. 校验消息是否为空
if
(
isEmpty
(
unref
(
message
.
value
)))
{
...
...
@@ -197,7 +209,7 @@ const handleSendMessage = async () => {
await
sendMessage
(
msg
)
}
/
/ 发送消息 【共用】
/
** 真正发送消息 【共用】*/
const
sendMessage
=
async
(
msg
:
any
)
=>
{
// 发送消息
await
KeFuMessageApi
.
sendKeFuMessage
(
msg
)
...
...
@@ -208,9 +220,9 @@ const sendMessage = async (msg: any) => {
await
scrollToBottom
()
}
/** 滚动到底部 */
const
innerRef
=
ref
<
HTMLDivElement
>
()
const
scrollbarRef
=
ref
<
InstanceType
<
typeof
ElScrollbarType
>>
()
// 滚动到底部
const
scrollToBottom
=
async
()
=>
{
// 1. 首次加载时滚动到最新消息,如果加载的是历史消息则不滚动
if
(
loadHistory
.
value
)
{
...
...
@@ -223,12 +235,14 @@ const scrollToBottom = async () => {
// 2.2 消息已读
await
KeFuMessageApi
.
updateKeFuMessageReadStatus
(
keFuConversation
.
value
.
id
)
}
// 查看新消息
/** 查看新消息 */
const
handleToNewMessage
=
async
()
=>
{
loadHistory
.
value
=
false
await
scrollToBottom
()
}
/** 加载历史消息 */
const
loadingMore
=
ref
(
false
)
// 滚动到顶部加载更多
const
loadHistory
=
ref
(
false
)
// 加载历史消息
const
handleScroll
=
async
({
scrollTop
})
=>
{
...
...
@@ -247,8 +261,10 @@ const handleOldMessage = async () => {
loadingMore
.
value
=
false
// TODO puhui999: 等页面加载完后,获得上一页最后一条消息的位置,控制滚动到它所在位置
}
/**
* 是否显示时间
*
* @param {*} item - 数据
* @param {*} index - 索引
*/
...
...
src/views/mall/promotion/kefu/components/KeFuConversationBox.vue
View file @
ec61670b
<
template
>
<div
class=
"kefu"
>
<!-- TODO @puhui999:item => conversation 会不会更容易理解 -->
<div
v-for=
"(item, index) in conversationList"
:key=
"item.id"
...
...
src/views/mall/promotion/kefu/components/message/ImageMessageItem.vue
View file @
ec61670b
...
...
@@ -10,6 +10,7 @@
: ''
]"
>
<!-- TODO @puhui999:unocss -->
<el-image
:src=
"message.content"
fit=
"contain"
...
...
@@ -30,6 +31,7 @@ defineOptions({ name: 'ImageMessageItem' })
defineProps
<
{
message
:
KeFuMessageRespVO
}
>
()
/** 图预览 */
const
imagePreview
=
(
imgUrl
:
string
)
=>
{
createImageViewer
({
...
...
src/views/mall/promotion/kefu/components/message/OrderMessageItem.vue
View file @
ec61670b
...
...
@@ -18,6 +18,7 @@
</div>
</div>
<div
v-for=
"item in getMessageContent.items"
:key=
"item.id"
class=
"border-bottom"
>
<!-- TODO @puhui999:要不把 img => picUrl 类似这种,搞的更匹配一点 -->
<ProductItem
:img=
"item.picUrl"
:num=
"item.count"
...
...
@@ -29,10 +30,10 @@
<div
class=
"pay-box mt-30px flex justify-end pr-20px"
>
<div
class=
"flex items-center"
>
<div
class=
"discounts-title pay-color"
>
共
{{
getMessageContent
.
productCount
}}
件商品,总金额:
>
共
{{
getMessageContent
?
.
productCount
}}
件商品,总金额:
</div>
<div
class=
"discounts-money pay-color"
>
¥
{{
fenToYuan
(
getMessageContent
.
payPrice
)
}}
¥
{{
fenToYuan
(
getMessageContent
?
.
payPrice
)
}}
</div>
</div>
</div>
...
...
src/views/mall/promotion/kefu/components/message/ProductItem.vue
View file @
ec61670b
...
...
@@ -90,6 +90,8 @@ const props = defineProps({
default
:
''
}
})
/** SKU 展示字符串 */
const
skuString
=
computed
(()
=>
{
if
(
!
props
.
skuText
)
{
return
''
...
...
@@ -99,6 +101,8 @@ const skuString = computed(() => {
}
return
props
.
skuText
})
// TODO @puhui999:可以使用 preview-teleported
/** 图预览 */
const
imagePrediv
=
(
imgUrl
:
string
)
=>
{
createImageViewer
({
...
...
src/views/mall/promotion/kefu/components/message/ProductMessageItem.vue
View file @
ec61670b
...
...
@@ -32,5 +32,7 @@ defineOptions({ name: 'ProductMessageItem' })
const
props
=
defineProps
<
{
message
:
KeFuMessageRespVO
}
>
()
/** 获悉消息内容 */
const
getMessageContent
=
computed
(()
=>
JSON
.
parse
(
props
.
message
.
content
))
</
script
>
src/views/mall/promotion/kefu/components/tools/EmojiSelectPopover.vue
View file @
ec61670b
...
...
@@ -17,6 +17,7 @@
class=
"icon-item mr-2 mt-1 w-1/10 flex cursor-pointer items-center justify-center border border-solid p-2"
@
click=
"handleSelect(item)"
>
<!-- TODO @puhui999:换成 unocss -->
<img
:src=
"item.url"
style=
"width: 24px; height: 24px"
/>
</li>
</ul>
...
...
@@ -31,6 +32,7 @@ import { Emoji, useEmoji } from './emoji'
const
{
getEmojiList
}
=
useEmoji
()
const
emojiList
=
computed
(()
=>
getEmojiList
())
/** 选择 emoji 表情 */
const
emits
=
defineEmits
<
{
(
e
:
'select-emoji'
,
v
:
Emoji
)
}
>
()
...
...
src/views/mall/promotion/kefu/components/tools/PictureSelectUpload.vue
View file @
ec61670b
<!-- 图片选择 -->
<
template
>
<div>
<!-- TODO @puhui999:unocss -->
<img
:src=
"Picture"
style=
"width: 35px; height: 35px"
@
click=
"selectAndUpload"
/>
</div>
</
template
>
<
script
lang=
"ts"
setup
>
// TODO @puhui999:images 换成 asserts
import
Picture
from
'@/views/mall/promotion/kefu/components/images/picture.svg'
import
*
as
FileApi
from
'@/api/infra/file'
defineOptions
({
name
:
'PictureSelectUpload'
})
const
message
=
useMessage
()
const
message
=
useMessage
()
// 消息弹窗
/** 选择并上传文件 */
const
emits
=
defineEmits
<
{
(
e
:
'send-picture'
,
v
:
string
):
void
}
>
()
// 选择并上传文件
const
selectAndUpload
=
async
()
=>
{
const
files
:
any
=
await
getFiles
()
message
.
success
(
'图片发送中请稍等。。。'
)
...
...
@@ -23,6 +28,7 @@ const selectAndUpload = async () => {
/**
* 唤起文件选择窗口,并获取选择的文件
*
* @param {Object} options - 配置选项
* @param {boolean} [options.multiple=true] - 是否支持多选
* @param {string} [options.accept=''] - 文件上传格式限制
...
...
@@ -54,7 +60,7 @@ async function getFiles(options = {}) {
// 等待文件选择元素的 change 事件
try
{
const
files
=
await
new
Promise
((
resolve
,
reject
)
=>
{
return
await
new
Promise
((
resolve
,
reject
)
=>
{
input
.
addEventListener
(
'change'
,
(
event
:
any
)
=>
{
const
filesArray
=
Array
.
from
(
event
?.
target
?.
files
||
[])
...
...
@@ -68,9 +74,9 @@ async function getFiles(options = {}) {
}
// 判断是否超出上传文件大小限制
const
over
s
izedFiles
=
filesArray
.
filter
((
file
:
File
)
=>
file
.
size
/
1024
**
2
>
fileSize
)
if
(
over
s
izedFiles
.
length
>
0
)
{
reject
({
errorType
:
'fileSize'
,
files
:
over
s
izedFiles
})
const
over
S
izedFiles
=
filesArray
.
filter
((
file
:
File
)
=>
file
.
size
/
1024
**
2
>
fileSize
)
if
(
over
S
izedFiles
.
length
>
0
)
{
reject
({
errorType
:
'fileSize'
,
files
:
over
S
izedFiles
})
return
}
...
...
@@ -79,8 +85,6 @@ async function getFiles(options = {}) {
resolve
(
fileList
)
})
})
return
files
}
catch
(
error
)
{
console
.
error
(
'选择文件出错:'
,
error
)
throw
error
...
...
src/views/mall/promotion/kefu/components/tools/emoji.ts
View file @
ec61670b
...
...
@@ -58,8 +58,11 @@ export interface Emoji {
export
const
useEmoji
=
()
=>
{
const
emojiPathList
=
ref
<
any
[]
>
([])
// 加载本地图片
// TODO @puhui999:initStaticEmoji 会不会更好
/** 加载本地图片 */
const
getStaticEmojiPath
=
async
()
=>
{
// TODO @puhui999:images 改成 asserts 更合适哈。
const
pathList
=
import
.
meta
.
glob
(
'@/views/mall/promotion/kefu/components/images/*.{png,jpg,jpeg,svg}'
)
...
...
@@ -68,17 +71,25 @@ export const useEmoji = () => {
emojiPathList
.
value
.
push
(
imageModule
.
default
)
}
}
// 初始化
/** 初始化 */
onMounted
(
async
()
=>
{
if
(
isEmpty
(
emojiPathList
.
value
))
{
await
getStaticEmojiPath
()
}
})
// 处理表情
// TODO @puhui999:建议 function 都改成 const 这种来定义哈。保持统一风格
/**
* 将文本中的表情替换成图片
*
* @param data 文本 TODO @puhui999:data => content
* @return 替换后的文本
*/
function
replaceEmoji
(
data
:
string
)
{
let
newData
=
data
if
(
typeof
newData
!==
'object'
)
{
// TODO @puhui999: \] 是不是可以简化成 ]。我看 idea 提示了哈
const
reg
=
/
\[(
.+
?)\]
/g
// [] 中括号
const
zhEmojiName
=
newData
.
match
(
reg
)
if
(
zhEmojiName
)
{
...
...
@@ -94,7 +105,11 @@ export const useEmoji = () => {
return
newData
}
// 获得所有表情
/**
* 获得所有表情
*
* @return 表情列表
*/
function
getEmojiList
():
Emoji
[]
{
return
emojiList
.
map
((
item
)
=>
({
url
:
selEmojiFile
(
item
.
name
),
...
...
@@ -102,6 +117,7 @@ export const useEmoji = () => {
}))
as
Emoji
[]
}
// TODO @puhui999:getEmojiFileByName 会不会更容易理解哈
function
selEmojiFile
(
name
:
string
)
{
for
(
const
emoji
of
emojiList
)
{
if
(
emoji
.
name
===
name
)
{
...
...
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