mirror of
https://gitee.com/hhyykk/ipms-sjy-ui.git
synced 2025-06-19 23:02:00 +08:00
commit
d3f3044655
@ -29,8 +29,8 @@ export const KeFuMessageApi = {
|
||||
url: '/promotion/kefu-message/update-read-status?conversationId=' + conversationId
|
||||
})
|
||||
},
|
||||
// 获得消息分页数据
|
||||
getKeFuMessagePage: async (params: any) => {
|
||||
return await request.get({ url: '/promotion/kefu-message/page', params })
|
||||
// 获得消息数据
|
||||
getKeFuMessageList: async (params: any) => {
|
||||
return await request.get({ url: '/promotion/kefu-message/list', params })
|
||||
}
|
||||
}
|
||||
|
37
src/store/modules/mall/kefu.ts
Normal file
37
src/store/modules/mall/kefu.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { store } from '@/store'
|
||||
import { defineStore } from 'pinia'
|
||||
import { KeFuConversationApi, KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
|
||||
import { KeFuMessageRespVO } from '@/api/mall/promotion/kefu/message'
|
||||
|
||||
// TODO puhui999: 待优化完善
|
||||
interface MallKefuInfoVO {
|
||||
conversationList: KeFuConversationRespVO[] // 会话列表
|
||||
conversationMessageList: Map<number, KeFuMessageRespVO[]> // 会话消息
|
||||
}
|
||||
|
||||
export const useMallKefuStore = defineStore('mall-kefu', {
|
||||
state: (): MallKefuInfoVO => ({
|
||||
conversationList: [],
|
||||
conversationMessageList: new Map<number, KeFuMessageRespVO[]>() // key 会话,value 会话消息列表
|
||||
}),
|
||||
getters: {
|
||||
getConversationList(): KeFuConversationRespVO[] {
|
||||
return this.conversationList
|
||||
},
|
||||
getConversationMessageList(): Map<number, KeFuMessageRespVO[]> {
|
||||
return this.conversationMessageList
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
async setConversationList() {
|
||||
const list = await KeFuConversationApi.getConversationList()
|
||||
list.sort((a: KeFuConversationRespVO, _) => (a.adminPinned ? -1 : 1))
|
||||
this.conversationList = list
|
||||
}
|
||||
// async setConversationMessageList(conversationId: number) {}
|
||||
}
|
||||
})
|
||||
|
||||
export const useUserStoreWithOut = () => {
|
||||
return useMallKefuStore(store)
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="kefu">
|
||||
<el-aside class="kefu p-5px h-100%" width="260px">
|
||||
<div class="color-[#999] font-bold my-10px">会话记录({{ conversationList.length }})</div>
|
||||
<div
|
||||
v-for="item in conversationList"
|
||||
:key="item.id"
|
||||
@ -22,7 +23,7 @@
|
||||
<div class="ml-10px w-100%">
|
||||
<div class="flex justify-between items-center w-100%">
|
||||
<span class="username">{{ item.userNickname }}</span>
|
||||
<span class="color-[var(--left-menu-text-color)]" style="font-size: 13px">
|
||||
<span class="color-[#999]" style="font-size: 13px">
|
||||
{{ formatPast(item.lastMessageTime, 'YYYY-MM-DD') }}
|
||||
</span>
|
||||
</div>
|
||||
@ -31,7 +32,7 @@
|
||||
v-dompurify-html="
|
||||
getConversationDisplayText(item.lastMessageContentType, item.lastMessageContent)
|
||||
"
|
||||
class="last-message flex items-center color-[var(--left-menu-text-color)]"
|
||||
class="last-message flex items-center color-[#999]"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@ -65,7 +66,7 @@
|
||||
取消
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</el-aside>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@ -180,11 +181,12 @@ watch(showRightMenu, (val) => {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.kefu {
|
||||
background-color: #fff;
|
||||
|
||||
&-conversation {
|
||||
height: 60px;
|
||||
padding: 10px;
|
||||
//background-color: #fff;
|
||||
transition: border-left 0.05s ease-in-out; /* 设置过渡效果 */
|
||||
//transition: border-left 0.05s ease-in-out; /* 设置过渡效果 */
|
||||
|
||||
.username {
|
||||
min-width: 0;
|
||||
@ -205,13 +207,10 @@ watch(showRightMenu, (val) => {
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
border-left: 5px #3271ff solid;
|
||||
background-color: var(--login-bg-color);
|
||||
}
|
||||
|
||||
.active,
|
||||
.pinned {
|
||||
background-color: var(--left-menu-bg-active-color);
|
||||
background-color: rgba(128, 128, 128, 0.5); // 透明色,暗黑模式下也能体现
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.right-menu-ul {
|
||||
|
@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<el-container v-if="showKeFuMessageList" class="kefu">
|
||||
<el-header>
|
||||
<el-header class="kefu-header">
|
||||
<div class="kefu-title">{{ conversation.userNickname }}</div>
|
||||
</el-header>
|
||||
<el-main class="kefu-content overflow-visible">
|
||||
<el-scrollbar ref="scrollbarRef" always height="calc(100vh - 495px)" @scroll="handleScroll">
|
||||
<div v-if="refreshContent" ref="innerRef" class="w-[100%] pb-3px">
|
||||
<el-scrollbar ref="scrollbarRef" always @scroll="handleScroll">
|
||||
<div v-if="refreshContent" ref="innerRef" class="w-[100%] px-10px">
|
||||
<!-- 消息列表 -->
|
||||
<div v-for="(item, index) in getMessageList0" :key="item.id" class="w-[100%]">
|
||||
<div class="flex justify-center items-center mb-20px">
|
||||
@ -43,7 +43,9 @@
|
||||
class="w-60px h-60px"
|
||||
/>
|
||||
<div
|
||||
:class="{ 'kefu-message': KeFuMessageContentTypeEnum.TEXT === item.contentType }"
|
||||
:class="{
|
||||
'kefu-message': KeFuMessageContentTypeEnum.TEXT === item.contentType
|
||||
}"
|
||||
class="p-10px"
|
||||
>
|
||||
<!-- 文本消息 -->
|
||||
@ -71,10 +73,10 @@
|
||||
<MessageItem :message="item">
|
||||
<ProductItem
|
||||
v-if="KeFuMessageContentTypeEnum.PRODUCT === item.contentType"
|
||||
:spuId="getMessageContent(item).spuId"
|
||||
:picUrl="getMessageContent(item).picUrl"
|
||||
:price="getMessageContent(item).price"
|
||||
:skuText="getMessageContent(item).introduction"
|
||||
:spuId="getMessageContent(item).spuId"
|
||||
:title="getMessageContent(item).spuName"
|
||||
:titleWidth="400"
|
||||
class="max-w-70%"
|
||||
@ -108,8 +110,7 @@
|
||||
<Icon class="ml-5px" icon="ep:bottom" />
|
||||
</div>
|
||||
</el-main>
|
||||
<el-footer height="230px">
|
||||
<div class="h-[100%]">
|
||||
<el-footer class="kefu-footer">
|
||||
<div class="chat-tools flex items-center">
|
||||
<EmojiSelectPopover @select-emoji="handleEmojiSelect" />
|
||||
<PictureSelectUpload
|
||||
@ -117,14 +118,21 @@
|
||||
@send-picture="handleSendPicture"
|
||||
/>
|
||||
</div>
|
||||
<el-input v-model="message" :rows="6" style="border-style: none" type="textarea" />
|
||||
<div class="h-45px flex justify-end">
|
||||
<el-button class="mt-10px" type="primary" @click="handleSendMessage">发送</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-input
|
||||
v-model="message"
|
||||
:rows="6"
|
||||
placeholder="输入消息,Enter发送,Shift+Enter换行"
|
||||
style="border-style: none"
|
||||
type="textarea"
|
||||
@keyup.enter.prevent="handleSendMessage"
|
||||
/>
|
||||
</el-footer>
|
||||
</el-container>
|
||||
<el-empty v-else description="请选择左侧的一个会话后开始" />
|
||||
<el-container v-else class="kefu">
|
||||
<el-main>
|
||||
<el-empty description="请选择左侧的一个会话后开始" />
|
||||
</el-main>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@ -156,9 +164,8 @@ const messageList = ref<KeFuMessageRespVO[]>([]) // 消息列表
|
||||
const conversation = ref<KeFuConversationRespVO>({} as KeFuConversationRespVO) // 用户会话
|
||||
const showNewMessageTip = ref(false) // 显示有新消息提示
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
conversationId: 0
|
||||
conversationId: 0,
|
||||
createTime: undefined
|
||||
})
|
||||
const total = ref(0) // 消息总条数
|
||||
const refreshContent = ref(false) // 内容刷新,主要解决会话消息页面高度不一致导致的滚动功能精度失效
|
||||
@ -167,14 +174,20 @@ const refreshContent = ref(false) // 内容刷新,主要解决会话消息页面
|
||||
const getMessageContent = computed(() => (item: any) => jsonParse(item.content))
|
||||
/** 获得消息列表 */
|
||||
const getMessageList = async () => {
|
||||
const res = await KeFuMessageApi.getKeFuMessagePage(queryParams)
|
||||
total.value = res.total
|
||||
const res = await KeFuMessageApi.getKeFuMessageList(queryParams)
|
||||
if (isEmpty(res)) {
|
||||
// 当返回的是空列表说明没有消息或者已经查询完了历史消息
|
||||
skipGetMessageList.value = true
|
||||
return
|
||||
}
|
||||
queryParams.createTime = formatDate(res.at(-1).createTime) as any
|
||||
|
||||
// 情况一:加载最新消息
|
||||
if (queryParams.pageNo === 1) {
|
||||
messageList.value = res.list
|
||||
if (!queryParams.createTime) {
|
||||
messageList.value = res
|
||||
} else {
|
||||
// 情况二:加载历史消息
|
||||
for (const item of res.list) {
|
||||
for (const item of res) {
|
||||
pushMessage(item)
|
||||
}
|
||||
}
|
||||
@ -208,8 +221,7 @@ const refreshMessageList = async (message?: any) => {
|
||||
}
|
||||
pushMessage(message)
|
||||
} else {
|
||||
// TODO @puhui999:不基于 page 做。而是流式分页;通过 createTime 排序查询;
|
||||
queryParams.pageNo = 1
|
||||
queryParams.createTime = undefined
|
||||
await getMessageList()
|
||||
}
|
||||
|
||||
@ -226,7 +238,6 @@ const refreshMessageList = async (message?: any) => {
|
||||
// TODO @puhui999:可优化:可以考虑本地做每个会话的消息 list 缓存;然后点击切换时,读取缓存;然后异步获取新消息,merge 下;
|
||||
const getNewMessageList = async (val: KeFuConversationRespVO) => {
|
||||
// 会话切换,重置相关参数
|
||||
queryParams.pageNo = 1
|
||||
messageList.value = []
|
||||
total.value = 0
|
||||
loadHistory.value = false
|
||||
@ -234,16 +245,14 @@ const getNewMessageList = async (val: KeFuConversationRespVO) => {
|
||||
// 设置会话相关属性
|
||||
conversation.value = val
|
||||
queryParams.conversationId = val.id
|
||||
queryParams.createTime = undefined
|
||||
// 获取消息
|
||||
await refreshMessageList()
|
||||
}
|
||||
defineExpose({ getNewMessageList, refreshMessageList })
|
||||
|
||||
const showKeFuMessageList = computed(() => !isEmpty(conversation.value)) // 是否显示聊天区域
|
||||
const skipGetMessageList = computed(() => {
|
||||
// 已加载到最后一页的话则不触发新的消息获取
|
||||
return total.value > 0 && Math.ceil(total.value / queryParams.pageSize) === queryParams.pageNo
|
||||
}) // 跳过消息获取
|
||||
const skipGetMessageList = ref(false) // 跳过消息获取
|
||||
|
||||
/** 处理表情选择 */
|
||||
const handleEmojiSelect = (item: Emoji) => {
|
||||
@ -262,7 +271,11 @@ const handleSendPicture = async (picUrl: string) => {
|
||||
}
|
||||
|
||||
/** 发送文本消息 */
|
||||
const handleSendMessage = async () => {
|
||||
const handleSendMessage = async (event: any) => {
|
||||
// shift 不发送
|
||||
if (event.shiftKey) {
|
||||
return
|
||||
}
|
||||
// 1. 校验消息是否为空
|
||||
if (isEmpty(unref(message.value))) {
|
||||
messageTool.notifyWarning('请输入消息后再发送哦!')
|
||||
@ -333,8 +346,6 @@ const handleOldMessage = async () => {
|
||||
return
|
||||
}
|
||||
loadHistory.value = true
|
||||
// 加载消息列表
|
||||
queryParams.pageNo += 1
|
||||
await getMessageList()
|
||||
// 等页面加载完后,获得上一页最后一条消息的位置,控制滚动到它所在位置
|
||||
scrollbarRef.value!.setScrollTop(innerRef.value!.clientHeight - oldPageHeight)
|
||||
@ -357,14 +368,29 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.kefu {
|
||||
background-color: #fff;
|
||||
width: calc(100% - 300px - 260px);
|
||||
border-left: var(--el-border-color) solid 1px;
|
||||
|
||||
.kefu-header {
|
||||
background: #fbfbfb;
|
||||
box-shadow: 0 0 0 0 #dcdfe6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&-title {
|
||||
border-bottom: #e4e0e0 solid 1px;
|
||||
height: 60px;
|
||||
line-height: 60px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
.newMessageTip {
|
||||
position: absolute;
|
||||
@ -381,21 +407,12 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
|
||||
justify-content: flex-start;
|
||||
|
||||
.kefu-message {
|
||||
margin-left: 20px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
left: -19px;
|
||||
top: calc(50% - 10px);
|
||||
position: absolute;
|
||||
border-left: 5px solid transparent;
|
||||
border-bottom: 5px solid transparent;
|
||||
border-top: 5px solid transparent;
|
||||
border-right: 5px solid var(--app-content-bg-color);
|
||||
}
|
||||
background-color: rgb(245, 245, 245);
|
||||
margin-left: 10px;
|
||||
margin-top: 18px;
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
border-bottom-left-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -403,37 +420,25 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
|
||||
justify-content: flex-end;
|
||||
|
||||
.kefu-message {
|
||||
margin-right: 20px;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
right: -19px;
|
||||
top: calc(50% - 10px);
|
||||
position: absolute;
|
||||
border-left: 5px solid var(--app-content-bg-color);
|
||||
border-bottom: 5px solid transparent;
|
||||
border-top: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
}
|
||||
background-color: rgb(206, 223, 255);
|
||||
margin-right: 10px;
|
||||
margin-top: 18px;
|
||||
border-top-left-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
border-bottom-left-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
// 消息气泡
|
||||
.kefu-message {
|
||||
color: #a9a9a9;
|
||||
border-radius: 5px;
|
||||
box-shadow: 3px 3px 5px rgba(220, 220, 220, 0.1);
|
||||
color: #414141;
|
||||
font-weight: 500;
|
||||
padding: 5px 10px;
|
||||
width: auto;
|
||||
max-width: 50%;
|
||||
text-align: left;
|
||||
display: inline-block !important;
|
||||
position: relative;
|
||||
word-break: break-all;
|
||||
background-color: var(--app-content-bg-color);
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
@ -447,21 +452,36 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
|
||||
border-radius: 12rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
//background-color: #e8e8e8;
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.kefu-footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-top: var(--el-border-color) solid 1px;
|
||||
|
||||
.chat-tools {
|
||||
width: 100%;
|
||||
border: var(--el-border-color) solid 1px;
|
||||
border-radius: 10px;
|
||||
height: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep(textarea) {
|
||||
resize: none;
|
||||
}
|
||||
|
||||
:deep(.el-input__wrapper) {
|
||||
box-shadow: none !important;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
::v-deep(.el-textarea__inner) {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,97 +0,0 @@
|
||||
<!-- 目录是不是叫 member 好点。然后这个组件是 MemberInfo,里面有浏览足迹 -->
|
||||
<template>
|
||||
<div v-show="!isEmpty(conversation)" class="kefu">
|
||||
<div class="header-title h-60px flex justify-center items-center">他的足迹</div>
|
||||
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
|
||||
<el-tab-pane label="最近浏览" name="a" />
|
||||
<el-tab-pane label="订单列表" name="b" />
|
||||
</el-tabs>
|
||||
<div>
|
||||
<el-scrollbar ref="scrollbarRef" always height="calc(115vh - 400px)" @scroll="handleScroll">
|
||||
<!-- 最近浏览 -->
|
||||
<ProductBrowsingHistory v-if="activeName === 'a'" ref="productBrowsingHistoryRef" />
|
||||
<!-- 订单列表 -->
|
||||
<OrderBrowsingHistory v-if="activeName === 'b'" ref="orderBrowsingHistoryRef" />
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-show="isEmpty(conversation)" description="请选择左侧的一个会话后开始" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { TabsPaneContext } from 'element-plus'
|
||||
import ProductBrowsingHistory from './ProductBrowsingHistory.vue'
|
||||
import OrderBrowsingHistory from './OrderBrowsingHistory.vue'
|
||||
import { KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import { debounce } from 'lodash-es'
|
||||
import { ElScrollbar as ElScrollbarType } from 'element-plus/es/components/scrollbar/index'
|
||||
|
||||
defineOptions({ name: 'MemberBrowsingHistory' })
|
||||
|
||||
const activeName = ref('a')
|
||||
|
||||
/** tab 切换 */
|
||||
const productBrowsingHistoryRef = ref<InstanceType<typeof ProductBrowsingHistory>>()
|
||||
const orderBrowsingHistoryRef = ref<InstanceType<typeof OrderBrowsingHistory>>()
|
||||
const handleClick = async (tab: TabsPaneContext) => {
|
||||
activeName.value = tab.paneName as string
|
||||
await nextTick()
|
||||
await getHistoryList()
|
||||
}
|
||||
|
||||
/** 获得历史数据 */
|
||||
// TODO @puhui:不要用 a、b 哈。就订单列表、浏览列表这种噶
|
||||
const getHistoryList = async () => {
|
||||
switch (activeName.value) {
|
||||
case 'a':
|
||||
await productBrowsingHistoryRef.value?.getHistoryList(conversation.value)
|
||||
break
|
||||
case 'b':
|
||||
await orderBrowsingHistoryRef.value?.getHistoryList(conversation.value)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/** 加载下一页数据 */
|
||||
const loadMore = async () => {
|
||||
switch (activeName.value) {
|
||||
case 'a':
|
||||
await productBrowsingHistoryRef.value?.loadMore()
|
||||
break
|
||||
case 'b':
|
||||
await orderBrowsingHistoryRef.value?.loadMore()
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/** 浏览历史初始化 */
|
||||
const conversation = ref<KeFuConversationRespVO>({} as KeFuConversationRespVO) // 用户会话
|
||||
const initHistory = async (val: KeFuConversationRespVO) => {
|
||||
activeName.value = 'a'
|
||||
conversation.value = val
|
||||
await nextTick()
|
||||
await getHistoryList()
|
||||
}
|
||||
defineExpose({ initHistory })
|
||||
|
||||
/** 处理消息列表滚动事件(debounce 限流) */
|
||||
const scrollbarRef = ref<InstanceType<typeof ElScrollbarType>>()
|
||||
const handleScroll = debounce(() => {
|
||||
const wrap = scrollbarRef.value?.wrapRef
|
||||
// 触底重置
|
||||
if (Math.abs(wrap!.scrollHeight - wrap!.clientHeight - wrap!.scrollTop) < 1) {
|
||||
loadMore()
|
||||
}
|
||||
}, 200)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.header-title {
|
||||
border-bottom: #e4e0e0 solid 1px;
|
||||
}
|
||||
</style>
|
@ -1,5 +1,5 @@
|
||||
import KeFuConversationList from './KeFuConversationList.vue'
|
||||
import KeFuMessageList from './KeFuMessageList.vue'
|
||||
import MemberBrowsingHistory from './history/MemberBrowsingHistory.vue'
|
||||
import MemberInfo from './member/MemberInfo.vue'
|
||||
|
||||
export { KeFuConversationList, KeFuMessageList, MemberBrowsingHistory }
|
||||
export { KeFuConversationList, KeFuMessageList, MemberInfo }
|
||||
|
179
src/views/mall/promotion/kefu/components/member/MemberInfo.vue
Normal file
179
src/views/mall/promotion/kefu/components/member/MemberInfo.vue
Normal file
@ -0,0 +1,179 @@
|
||||
<!-- 目录是不是叫 member 好点。然后这个组件是 MemberInfo,里面有浏览足迹 -->
|
||||
<template>
|
||||
<el-container class="kefu">
|
||||
<el-header class="kefu-header">
|
||||
<div
|
||||
:class="{ 'kefu-header-item-activation': tabActivation('会员信息') }"
|
||||
class="kefu-header-item cursor-pointer flex items-center justify-center"
|
||||
@click="handleClick('会员信息')"
|
||||
>
|
||||
会员信息
|
||||
</div>
|
||||
<div
|
||||
:class="{ 'kefu-header-item-activation': tabActivation('最近浏览') }"
|
||||
class="kefu-header-item cursor-pointer flex items-center justify-center"
|
||||
@click="handleClick('最近浏览')"
|
||||
>
|
||||
最近浏览
|
||||
</div>
|
||||
<div
|
||||
:class="{ 'kefu-header-item-activation': tabActivation('交易订单') }"
|
||||
class="kefu-header-item cursor-pointer flex items-center justify-center"
|
||||
@click="handleClick('交易订单')"
|
||||
>
|
||||
交易订单
|
||||
</div>
|
||||
</el-header>
|
||||
<el-main class="kefu-content">
|
||||
<div v-show="!isEmpty(conversation)">
|
||||
<el-scrollbar ref="scrollbarRef" always @scroll="handleScroll">
|
||||
<!-- 最近浏览 -->
|
||||
<ProductBrowsingHistory v-if="activeTab === '最近浏览'" ref="productBrowsingHistoryRef" />
|
||||
<!-- 交易订单 -->
|
||||
<OrderBrowsingHistory v-if="activeTab === '交易订单'" ref="orderBrowsingHistoryRef" />
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<el-empty v-show="isEmpty(conversation)" description="请选择左侧的一个会话后开始" />
|
||||
</el-main>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ProductBrowsingHistory from './ProductBrowsingHistory.vue'
|
||||
import OrderBrowsingHistory from './OrderBrowsingHistory.vue'
|
||||
import { KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import { debounce } from 'lodash-es'
|
||||
import { ElScrollbar as ElScrollbarType } from 'element-plus/es/components/scrollbar/index'
|
||||
|
||||
defineOptions({ name: 'MemberBrowsingHistory' })
|
||||
|
||||
const activeTab = ref('会员信息')
|
||||
const tabActivation = computed(() => (tab: string) => activeTab.value === tab)
|
||||
/** tab 切换 */
|
||||
const productBrowsingHistoryRef = ref<InstanceType<typeof ProductBrowsingHistory>>()
|
||||
const orderBrowsingHistoryRef = ref<InstanceType<typeof OrderBrowsingHistory>>()
|
||||
const handleClick = async (tab: string) => {
|
||||
activeTab.value = tab
|
||||
await nextTick()
|
||||
await getHistoryList()
|
||||
}
|
||||
|
||||
/** 获得历史数据 */
|
||||
const getHistoryList = async () => {
|
||||
switch (activeTab.value) {
|
||||
case '会员信息':
|
||||
break
|
||||
case '最近浏览':
|
||||
await productBrowsingHistoryRef.value?.getHistoryList(conversation.value)
|
||||
break
|
||||
case '交易订单':
|
||||
await orderBrowsingHistoryRef.value?.getHistoryList(conversation.value)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/** 加载下一页数据 */
|
||||
const loadMore = async () => {
|
||||
switch (activeTab.value) {
|
||||
case '会员信息':
|
||||
break
|
||||
case '最近浏览':
|
||||
await productBrowsingHistoryRef.value?.loadMore()
|
||||
break
|
||||
case '交易订单':
|
||||
await orderBrowsingHistoryRef.value?.loadMore()
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/** 浏览历史初始化 */
|
||||
const conversation = ref<KeFuConversationRespVO>({} as KeFuConversationRespVO) // 用户会话
|
||||
const initHistory = async (val: KeFuConversationRespVO) => {
|
||||
activeTab.value = '会员信息'
|
||||
conversation.value = val
|
||||
await nextTick()
|
||||
await getHistoryList()
|
||||
}
|
||||
defineExpose({ initHistory })
|
||||
|
||||
/** 处理消息列表滚动事件(debounce 限流) */
|
||||
const scrollbarRef = ref<InstanceType<typeof ElScrollbarType>>()
|
||||
const handleScroll = debounce(() => {
|
||||
const wrap = scrollbarRef.value?.wrapRef
|
||||
// 触底重置
|
||||
if (Math.abs(wrap!.scrollHeight - wrap!.clientHeight - wrap!.scrollTop) < 1) {
|
||||
loadMore()
|
||||
}
|
||||
}, 200)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.kefu {
|
||||
width: 300px !important;
|
||||
background-color: #fff;
|
||||
border-left: var(--el-border-color) solid 1px;
|
||||
|
||||
&-header {
|
||||
background: #fbfbfb;
|
||||
box-shadow: 0 0 0 0 #dcdfe6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
|
||||
&-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&-item {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
&-activation::before {
|
||||
content: '';
|
||||
position: absolute; /* 绝对定位 */
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0; /* 覆盖整个元素 */
|
||||
border-bottom: 2px solid rgba(128, 128, 128, 0.5); /* 边框样式 */
|
||||
pointer-events: none; /* 确保点击事件不会被伪元素拦截 */
|
||||
}
|
||||
|
||||
&:hover::before {
|
||||
content: '';
|
||||
position: absolute; /* 绝对定位 */
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0; /* 覆盖整个元素 */
|
||||
border-bottom: 2px solid rgba(128, 128, 128, 0.5); /* 边框样式 */
|
||||
pointer-events: none; /* 确保点击事件不会被伪元素拦截 */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-tabs {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.header-title {
|
||||
border-bottom: #e4e0e0 solid 1px;
|
||||
}
|
||||
</style>
|
@ -1,28 +1,16 @@
|
||||
<template>
|
||||
<el-row :gutter="10">
|
||||
<el-container class="kefu-layout">
|
||||
<!-- 会话列表 -->
|
||||
<el-col :span="6">
|
||||
<ContentWrap>
|
||||
<KeFuConversationList ref="keFuConversationRef" @change="handleChange" />
|
||||
</ContentWrap>
|
||||
</el-col>
|
||||
<!-- 会话详情(选中会话的消息列表) -->
|
||||
<el-col :span="12">
|
||||
<ContentWrap>
|
||||
<KeFuMessageList ref="keFuChatBoxRef" @change="getConversationList" />
|
||||
</ContentWrap>
|
||||
</el-col>
|
||||
<!-- 会员足迹(选中会话的会员足迹) -->
|
||||
<el-col :span="6">
|
||||
<ContentWrap>
|
||||
<MemberBrowsingHistory ref="memberBrowsingHistoryRef" />
|
||||
</ContentWrap>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<MemberInfo ref="memberInfoRef" />
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { KeFuConversationList, KeFuMessageList, MemberBrowsingHistory } from './components'
|
||||
import { KeFuConversationList, KeFuMessageList, MemberInfo } from './components'
|
||||
import { WebSocketMessageTypeConstants } from './components/tools/constants'
|
||||
import { KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
|
||||
import { getRefreshToken } from '@/utils/auth'
|
||||
@ -91,10 +79,10 @@ const getConversationList = () => {
|
||||
|
||||
/** 加载指定会话的消息列表 */
|
||||
const keFuChatBoxRef = ref<InstanceType<typeof KeFuMessageList>>()
|
||||
const memberBrowsingHistoryRef = ref<InstanceType<typeof MemberBrowsingHistory>>()
|
||||
const memberInfoRef = ref<InstanceType<typeof MemberInfo>>()
|
||||
const handleChange = (conversation: KeFuConversationRespVO) => {
|
||||
keFuChatBoxRef.value?.getNewMessageList(conversation)
|
||||
memberBrowsingHistoryRef.value?.initHistory(conversation)
|
||||
memberInfoRef.value?.initHistory(conversation)
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
@ -112,9 +100,13 @@ onBeforeUnmount(() => {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.kefu {
|
||||
height: calc(100vh - 165px);
|
||||
overflow: auto; /* 确保内容可滚动 */
|
||||
.kefu-layout {
|
||||
position: absolute;
|
||||
flex: 1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 定义滚动条样式 */
|
||||
|
Loading…
x
Reference in New Issue
Block a user