commit
4abd246d96
|
@ -0,0 +1,9 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
/**
|
||||
* 获得商品浏览记录分页
|
||||
* @param params 请求参数
|
||||
*/
|
||||
export const getBrowseHistoryPage = (params: any) => {
|
||||
return request.get({ url: '/product/browse-history/page', params })
|
||||
}
|
|
@ -21,9 +21,9 @@
|
|||
</div>
|
||||
<div class="ml-10px w-100%">
|
||||
<div class="flex justify-between items-center w-100%">
|
||||
<span>{{ item.userNickname }}</span>
|
||||
<span class="username">{{ item.userNickname }}</span>
|
||||
<span class="color-[#989EA6]">
|
||||
{{ formatDate(item.lastMessageTime) }}
|
||||
{{ formatPast(item.lastMessageTime, 'YYYY-mm-dd') }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- 最后聊天内容 -->
|
||||
|
@ -70,7 +70,7 @@
|
|||
<script lang="ts" setup>
|
||||
import { KeFuConversationApi, KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
|
||||
import { useEmoji } from './tools/emoji'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { formatPast } from '@/utils/formatTime'
|
||||
import { KeFuMessageContentTypeEnum } from './tools/constants'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
|
||||
|
@ -185,6 +185,16 @@ watch(showRightMenu, (val) => {
|
|||
background-color: #fff;
|
||||
transition: border-left 0.05s ease-in-out; /* 设置过渡效果 */
|
||||
|
||||
.username {
|
||||
min-width: 0;
|
||||
max-width: 60%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
}
|
||||
|
||||
.last-message {
|
||||
width: 200px;
|
||||
overflow: hidden; // 隐藏超出的文本
|
||||
|
|
|
@ -40,19 +40,54 @@
|
|||
v-if="item.senderType === UserTypeEnum.MEMBER"
|
||||
:src="conversation.userAvatar"
|
||||
alt="avatar"
|
||||
class="w-60px h-60px"
|
||||
/>
|
||||
<div
|
||||
:class="{ 'kefu-message': KeFuMessageContentTypeEnum.TEXT === item.contentType }"
|
||||
class="p-10px"
|
||||
>
|
||||
<!-- 文本消息 -->
|
||||
<TextMessageItem :message="item" />
|
||||
<MessageItem :message="item">
|
||||
<template v-if="KeFuMessageContentTypeEnum.TEXT === item.contentType">
|
||||
<div
|
||||
v-dompurify-html="replaceEmoji(item.content)"
|
||||
class="flex items-center"
|
||||
></div>
|
||||
</template>
|
||||
</MessageItem>
|
||||
<!-- 图片消息 -->
|
||||
<ImageMessageItem :message="item" />
|
||||
<MessageItem :message="item">
|
||||
<el-image
|
||||
v-if="KeFuMessageContentTypeEnum.IMAGE === item.contentType"
|
||||
:initial-index="0"
|
||||
:preview-src-list="[item.content]"
|
||||
:src="item.content"
|
||||
class="w-200px"
|
||||
fit="contain"
|
||||
preview-teleported
|
||||
/>
|
||||
</MessageItem>
|
||||
<!-- 商品消息 -->
|
||||
<ProductMessageItem :message="item" />
|
||||
<MessageItem :message="item">
|
||||
<ProductItem
|
||||
v-if="KeFuMessageContentTypeEnum.PRODUCT === item.contentType"
|
||||
:picUrl="getMessageContent(item).picUrl"
|
||||
:price="getMessageContent(item).price"
|
||||
:skuText="getMessageContent(item).introduction"
|
||||
:title="getMessageContent(item).spuName"
|
||||
:titleWidth="400"
|
||||
class="max-w-70%"
|
||||
priceColor="#FF3000"
|
||||
/>
|
||||
</MessageItem>
|
||||
<!-- 订单消息 -->
|
||||
<OrderMessageItem :message="item" />
|
||||
<MessageItem :message="item">
|
||||
<OrderItem
|
||||
v-if="KeFuMessageContentTypeEnum.ORDER === item.contentType"
|
||||
:message="item"
|
||||
class="max-w-70%"
|
||||
/>
|
||||
</MessageItem>
|
||||
</div>
|
||||
<el-avatar
|
||||
v-if="item.senderType === UserTypeEnum.ADMIN"
|
||||
|
@ -97,24 +132,24 @@ import { KeFuMessageApi, KeFuMessageRespVO } from '@/api/mall/promotion/kefu/mes
|
|||
import { KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
|
||||
import EmojiSelectPopover from './tools/EmojiSelectPopover.vue'
|
||||
import PictureSelectUpload from './tools/PictureSelectUpload.vue'
|
||||
import TextMessageItem from './message/TextMessageItem.vue'
|
||||
import ImageMessageItem from './message/ImageMessageItem.vue'
|
||||
import ProductMessageItem from './message/ProductMessageItem.vue'
|
||||
import OrderMessageItem from './message/OrderMessageItem.vue'
|
||||
import { Emoji } from './tools/emoji'
|
||||
import ProductItem from './message/ProductItem.vue'
|
||||
import OrderItem from './message/OrderItem.vue'
|
||||
import { Emoji, useEmoji } from './tools/emoji'
|
||||
import { KeFuMessageContentTypeEnum } from './tools/constants'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import { UserTypeEnum } from '@/utils/constants'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import { debounce } from 'lodash-es'
|
||||
import { jsonParse } from '@/utils'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
defineOptions({ name: 'KeFuMessageList' })
|
||||
|
||||
const message = ref('') // 消息弹窗
|
||||
|
||||
const { replaceEmoji } = useEmoji()
|
||||
const messageTool = useMessage()
|
||||
const messageList = ref<KeFuMessageRespVO[]>([]) // 消息列表
|
||||
const conversation = ref<KeFuConversationRespVO>({} as KeFuConversationRespVO) // 用户会话
|
||||
|
@ -126,18 +161,10 @@ const queryParams = reactive({
|
|||
})
|
||||
const total = ref(0) // 消息总条数
|
||||
const refreshContent = ref(false) // 内容刷新,主要解决会话消息页面高度不一致导致的滚动功能精度失效
|
||||
/** 获悉消息内容 */
|
||||
const getMessageContent = computed(() => (item: any) => jsonParse(item.content))
|
||||
/** 获得消息列表 */
|
||||
const getMessageList = async (val: KeFuConversationRespVO, conversationChange: boolean) => {
|
||||
// 会话切换,重置相关参数
|
||||
if (conversationChange) {
|
||||
queryParams.pageNo = 1
|
||||
messageList.value = []
|
||||
total.value = 0
|
||||
loadHistory.value = false
|
||||
refreshContent.value = false
|
||||
}
|
||||
conversation.value = val
|
||||
queryParams.conversationId = val.id
|
||||
const getMessageList = async () => {
|
||||
const res = await KeFuMessageApi.getKeFuMessagePage(queryParams)
|
||||
total.value = res.total
|
||||
// 情况一:加载最新消息
|
||||
|
@ -146,14 +173,17 @@ const getMessageList = async (val: KeFuConversationRespVO, conversationChange: b
|
|||
} else {
|
||||
// 情况二:加载历史消息
|
||||
for (const item of res.list) {
|
||||
if (messageList.value.some((val) => val.id === item.id)) {
|
||||
continue
|
||||
}
|
||||
messageList.value.push(item)
|
||||
pushMessage(item)
|
||||
}
|
||||
}
|
||||
refreshContent.value = true
|
||||
await scrollToBottom()
|
||||
}
|
||||
/** 添加消息 */
|
||||
const pushMessage = (message: any) => {
|
||||
if (messageList.value.some((val) => val.id === message.id)) {
|
||||
return
|
||||
}
|
||||
messageList.value.push(message)
|
||||
}
|
||||
|
||||
/** 按照时间倒序,获取消息列表 */
|
||||
|
@ -163,20 +193,44 @@ const getMessageList0 = computed(() => {
|
|||
})
|
||||
|
||||
/** 刷新消息列表 */
|
||||
const refreshMessageList = async () => {
|
||||
const refreshMessageList = async (message?: any) => {
|
||||
if (!conversation.value) {
|
||||
return
|
||||
}
|
||||
|
||||
queryParams.pageNo = 1
|
||||
await getMessageList(conversation.value, false)
|
||||
if (typeof message !== 'undefined') {
|
||||
// 当前查询会话与消息所属会话不一致则不做处理
|
||||
if (message.conversationId !== conversation.value.id) {
|
||||
return
|
||||
}
|
||||
pushMessage(message)
|
||||
} else {
|
||||
queryParams.pageNo = 1
|
||||
await getMessageList()
|
||||
}
|
||||
|
||||
if (loadHistory.value) {
|
||||
// 右下角显示有新消息提示
|
||||
showNewMessageTip.value = true
|
||||
} else {
|
||||
// 滚动到最新消息处
|
||||
await handleToNewMessage()
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ getMessageList, refreshMessageList })
|
||||
const getNewMessageList = async (val: KeFuConversationRespVO) => {
|
||||
// 会话切换,重置相关参数
|
||||
queryParams.pageNo = 1
|
||||
messageList.value = []
|
||||
total.value = 0
|
||||
loadHistory.value = false
|
||||
refreshContent.value = false
|
||||
// 设置会话相关属性
|
||||
conversation.value = val
|
||||
queryParams.conversationId = val.id
|
||||
// 获取消息
|
||||
await refreshMessageList()
|
||||
}
|
||||
defineExpose({ getNewMessageList, refreshMessageList })
|
||||
const showKeFuMessageList = computed(() => !isEmpty(conversation.value)) // 是否显示聊天区域
|
||||
const skipGetMessageList = computed(() => {
|
||||
// 已加载到最后一页的话则不触发新的消息获取
|
||||
|
@ -221,9 +275,7 @@ const sendMessage = async (msg: any) => {
|
|||
await KeFuMessageApi.sendKeFuMessage(msg)
|
||||
message.value = ''
|
||||
// 加载消息列表
|
||||
await getMessageList(conversation.value, false)
|
||||
// 滚动到最新消息处
|
||||
await scrollToBottom()
|
||||
await refreshMessageList()
|
||||
}
|
||||
|
||||
/** 滚动到底部 */
|
||||
|
@ -248,17 +300,24 @@ const handleToNewMessage = async () => {
|
|||
await scrollToBottom()
|
||||
}
|
||||
|
||||
/** 加载历史消息 */
|
||||
const loadHistory = ref(false) // 加载历史消息
|
||||
const handleScroll = async ({ scrollTop }) => {
|
||||
/** 处理消息列表滚动事件(debounce 限流) */
|
||||
const handleScroll = debounce(({ scrollTop }) => {
|
||||
if (skipGetMessageList.value) {
|
||||
return
|
||||
}
|
||||
// 触顶自动加载下一页数据
|
||||
if (scrollTop === 0) {
|
||||
await handleOldMessage()
|
||||
if (Math.floor(scrollTop) === 0) {
|
||||
handleOldMessage()
|
||||
}
|
||||
}
|
||||
const wrap = scrollbarRef.value?.wrapRef
|
||||
// 触底重置
|
||||
if (Math.abs(wrap!.scrollHeight - wrap!.clientHeight - wrap!.scrollTop) < 1) {
|
||||
loadHistory.value = false
|
||||
refreshMessageList()
|
||||
}
|
||||
}, 200)
|
||||
/** 加载历史消息 */
|
||||
const handleOldMessage = async () => {
|
||||
// 记录已有页面高度
|
||||
const oldPageHeight = innerRef.value?.clientHeight
|
||||
|
@ -268,7 +327,7 @@ const handleOldMessage = async () => {
|
|||
loadHistory.value = true
|
||||
// 加载消息列表
|
||||
queryParams.pageNo += 1
|
||||
await getMessageList(conversation.value, false)
|
||||
await getMessageList()
|
||||
// 等页面加载完后,获得上一页最后一条消息的位置,控制滚动到它所在位置
|
||||
scrollbarRef.value!.setScrollTop(innerRef.value!.clientHeight - oldPageHeight)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
<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(100vh - 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'
|
||||
|
||||
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()
|
||||
}
|
||||
/** 获得历史数据 */
|
||||
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>
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<OrderItem v-for="item in list" :key="item.id" :order="item" class="mb-10px" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import OrderItem from '@/views/mall/promotion/kefu/components/message/OrderItem.vue'
|
||||
import { KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
|
||||
import { getOrderPage } from '@/api/mall/trade/order'
|
||||
import { concat } from 'lodash-es'
|
||||
|
||||
defineOptions({ name: 'OrderBrowsingHistory' })
|
||||
|
||||
const list = ref<any>([]) // 列表
|
||||
const total = ref(0) // 总数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
userId: 0
|
||||
})
|
||||
const skipGetMessageList = computed(() => {
|
||||
// 已加载到最后一页的话则不触发新的消息获取
|
||||
return total.value > 0 && Math.ceil(total.value / queryParams.pageSize) === queryParams.pageNo
|
||||
}) // 跳过消息获取
|
||||
/** 获得浏览记录 */
|
||||
const getHistoryList = async (val: KeFuConversationRespVO) => {
|
||||
queryParams.userId = val.userId
|
||||
const res = await getOrderPage(queryParams)
|
||||
total.value = res.total
|
||||
list.value = res.list
|
||||
}
|
||||
/** 加载下一页数据 */
|
||||
const loadMore = async () => {
|
||||
if (skipGetMessageList.value) {
|
||||
return
|
||||
}
|
||||
queryParams.pageNo += 1
|
||||
const res = await getOrderPage(queryParams)
|
||||
total.value = res.total
|
||||
concat(list.value, res.list)
|
||||
}
|
||||
defineExpose({ getHistoryList, loadMore })
|
||||
</script>
|
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<ProductItem
|
||||
v-for="item in list"
|
||||
:key="item.id"
|
||||
:picUrl="item.picUrl"
|
||||
:price="item.price"
|
||||
:skuText="item.introduction"
|
||||
:title="item.spuName"
|
||||
:titleWidth="400"
|
||||
class="mb-10px"
|
||||
priceColor="#FF3000"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getBrowseHistoryPage } from '@/api/mall/product/history'
|
||||
import ProductItem from '@/views/mall/promotion/kefu/components/message/ProductItem.vue'
|
||||
import { KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
|
||||
import { concat } from 'lodash-es'
|
||||
|
||||
defineOptions({ name: 'ProductBrowsingHistory' })
|
||||
|
||||
const list = ref<any>([]) // 列表
|
||||
const total = ref(0) // 总数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
userId: 0,
|
||||
userDeleted: false
|
||||
})
|
||||
const skipGetMessageList = computed(() => {
|
||||
// 已加载到最后一页的话则不触发新的消息获取
|
||||
return total.value > 0 && Math.ceil(total.value / queryParams.pageSize) === queryParams.pageNo
|
||||
}) // 跳过消息获取
|
||||
/** 获得浏览记录 */
|
||||
const getHistoryList = async (val: KeFuConversationRespVO) => {
|
||||
queryParams.userId = val.userId
|
||||
const res = await getBrowseHistoryPage(queryParams)
|
||||
total.value = res.total
|
||||
list.value = res.list
|
||||
}
|
||||
/** 加载下一页数据 */
|
||||
const loadMore = async () => {
|
||||
if (skipGetMessageList.value) {
|
||||
return
|
||||
}
|
||||
queryParams.pageNo += 1
|
||||
const res = await getBrowseHistoryPage(queryParams)
|
||||
total.value = res.total
|
||||
concat(list.value, res.list)
|
||||
}
|
||||
defineExpose({ getHistoryList, loadMore })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
|
@ -1,4 +1,5 @@
|
|||
import KeFuConversationList from './KeFuConversationList.vue'
|
||||
import KeFuMessageList from './KeFuMessageList.vue'
|
||||
import MemberBrowsingHistory from './history/MemberBrowsingHistory.vue'
|
||||
|
||||
export { KeFuConversationList, KeFuMessageList }
|
||||
export { KeFuConversationList, KeFuMessageList, MemberBrowsingHistory }
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
<template>
|
||||
<!-- 图片消息 -->
|
||||
<template v-if="KeFuMessageContentTypeEnum.IMAGE === message.contentType">
|
||||
<div
|
||||
:class="[
|
||||
message.senderType === UserTypeEnum.MEMBER
|
||||
? `ml-10px`
|
||||
: message.senderType === UserTypeEnum.ADMIN
|
||||
? `mr-10px`
|
||||
: ''
|
||||
]"
|
||||
>
|
||||
<el-image
|
||||
:initial-index="0"
|
||||
:preview-src-list="[message.content]"
|
||||
:src="message.content"
|
||||
class="w-200px"
|
||||
fit="contain"
|
||||
preview-teleported
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { KeFuMessageContentTypeEnum } from '../tools/constants'
|
||||
import { UserTypeEnum } from '@/utils/constants'
|
||||
import { KeFuMessageRespVO } from '@/api/mall/promotion/kefu/message'
|
||||
|
||||
defineOptions({ name: 'ImageMessageItem' })
|
||||
defineProps<{
|
||||
message: KeFuMessageRespVO
|
||||
}>()
|
||||
</script>
|
|
@ -0,0 +1,24 @@
|
|||
<template>
|
||||
<!-- 消息组件 -->
|
||||
<div
|
||||
:class="[
|
||||
message.senderType === UserTypeEnum.MEMBER
|
||||
? `ml-10px`
|
||||
: message.senderType === UserTypeEnum.ADMIN
|
||||
? `mr-10px`
|
||||
: ''
|
||||
]"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { UserTypeEnum } from '@/utils/constants'
|
||||
import { KeFuMessageRespVO } from '@/api/mall/promotion/kefu/message'
|
||||
|
||||
defineOptions({ name: 'MessageItem' })
|
||||
defineProps<{
|
||||
message: KeFuMessageRespVO
|
||||
}>()
|
||||
</script>
|
|
@ -0,0 +1,146 @@
|
|||
<template>
|
||||
<div v-if="isObject(getMessageContent)">
|
||||
<div :key="getMessageContent.id" class="order-list-card-box mt-14px">
|
||||
<div class="order-card-header flex items-center justify-between p-x-20px">
|
||||
<div class="order-no">订单号:{{ getMessageContent.no }}</div>
|
||||
<div :class="formatOrderColor(getMessageContent)" class="order-state font-16">
|
||||
{{ formatOrderStatus(getMessageContent) }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="item in getMessageContent.items" :key="item.id" class="border-bottom">
|
||||
<ProductItem
|
||||
:num="item.count"
|
||||
:picUrl="item.picUrl"
|
||||
:price="item.price"
|
||||
:skuText="item.properties.map((property: any) => property.valueName).join(' ')"
|
||||
:title="item.spuName"
|
||||
/>
|
||||
</div>
|
||||
<div class="pay-box flex justify-end pr-20px">
|
||||
<div class="flex items-center">
|
||||
<div class="discounts-title pay-color"
|
||||
>共 {{ getMessageContent?.productCount }} 件商品,总金额:
|
||||
</div>
|
||||
<div class="discounts-money pay-color">
|
||||
¥{{ fenToYuan(getMessageContent?.payPrice) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { fenToYuan, jsonParse } from '@/utils'
|
||||
import { KeFuMessageRespVO } from '@/api/mall/promotion/kefu/message'
|
||||
import { isObject } from '@/utils/is'
|
||||
import ProductItem from '@/views/mall/promotion/kefu/components/message/ProductItem.vue'
|
||||
|
||||
defineOptions({ name: 'OrderItem' })
|
||||
const props = defineProps<{
|
||||
message?: KeFuMessageRespVO
|
||||
order?: any
|
||||
}>()
|
||||
|
||||
const getMessageContent = computed(() =>
|
||||
typeof props.message !== 'undefined' ? jsonParse(props!.message!.content) : props.order
|
||||
)
|
||||
|
||||
/**
|
||||
* 格式化订单状态的颜色
|
||||
*
|
||||
* @param order 订单
|
||||
* @return {string} 颜色的 class 名称
|
||||
*/
|
||||
function formatOrderColor(order: any) {
|
||||
if (order.status === 0) {
|
||||
return 'info-color'
|
||||
}
|
||||
if (order.status === 10 || order.status === 20 || (order.status === 30 && !order.commentStatus)) {
|
||||
return 'warning-color'
|
||||
}
|
||||
if (order.status === 30 && order.commentStatus) {
|
||||
return 'success-color'
|
||||
}
|
||||
return 'danger-color'
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化订单状态
|
||||
*
|
||||
* @param order 订单
|
||||
*/
|
||||
function formatOrderStatus(order: any) {
|
||||
if (order.status === 0) {
|
||||
return '待付款'
|
||||
}
|
||||
if (order.status === 10 && order.deliveryType === 1) {
|
||||
return '待发货'
|
||||
}
|
||||
if (order.status === 10 && order.deliveryType === 2) {
|
||||
return '待核销'
|
||||
}
|
||||
if (order.status === 20) {
|
||||
return '待收货'
|
||||
}
|
||||
if (order.status === 30 && !order.commentStatus) {
|
||||
return '待评价'
|
||||
}
|
||||
if (order.status === 30 && order.commentStatus) {
|
||||
return '已完成'
|
||||
}
|
||||
return '已关闭'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.order-list-card-box {
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
background-color: #e2e2e2;
|
||||
|
||||
.order-card-header {
|
||||
height: 28px;
|
||||
|
||||
.order-no {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.pay-box {
|
||||
.discounts-title {
|
||||
font-size: 16px;
|
||||
line-height: normal;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.discounts-money {
|
||||
font-size: 16px;
|
||||
line-height: normal;
|
||||
color: #999;
|
||||
font-family: OPPOSANS;
|
||||
}
|
||||
|
||||
.pay-color {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.warning-color {
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
.danger-color {
|
||||
color: #ff3000;
|
||||
}
|
||||
|
||||
.success-color {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.info-color {
|
||||
color: #999999;
|
||||
}
|
||||
</style>
|
|
@ -1,182 +0,0 @@
|
|||
<template>
|
||||
<!-- 图片消息 -->
|
||||
<template v-if="KeFuMessageContentTypeEnum.ORDER === message.contentType">
|
||||
<div
|
||||
:class="[
|
||||
message.senderType === UserTypeEnum.MEMBER
|
||||
? `ml-10px`
|
||||
: message.senderType === UserTypeEnum.ADMIN
|
||||
? `mr-10px`
|
||||
: ''
|
||||
]"
|
||||
>
|
||||
<div :key="getMessageContent.id" class="order-list-card-box mt-14px">
|
||||
<div class="order-card-header flex items-center justify-between p-x-20px">
|
||||
<div class="order-no">订单号:{{ getMessageContent.no }}</div>
|
||||
<div :class="formatOrderColor(getMessageContent)" class="order-state font-26">
|
||||
{{ formatOrderStatus(getMessageContent) }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="item in getMessageContent.items" :key="item.id" class="border-bottom">
|
||||
<ProductItem
|
||||
:num="item.count"
|
||||
:picUrl="item.picUrl"
|
||||
:price="item.price"
|
||||
:skuText="item.properties.map((property: any) => property.valueName).join(' ')"
|
||||
:title="item.spuName"
|
||||
/>
|
||||
</div>
|
||||
<div class="pay-box mt-30px flex justify-end pr-20px">
|
||||
<div class="flex items-center">
|
||||
<div class="discounts-title pay-color"
|
||||
>共 {{ getMessageContent?.productCount }} 件商品,总金额:
|
||||
</div>
|
||||
<div class="discounts-money pay-color">
|
||||
¥{{ fenToYuan(getMessageContent?.payPrice) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { KeFuMessageContentTypeEnum } from '../tools/constants'
|
||||
import ProductItem from './ProductItem.vue'
|
||||
import { UserTypeEnum } from '@/utils/constants'
|
||||
import { KeFuMessageRespVO } from '@/api/mall/promotion/kefu/message'
|
||||
import { fenToYuan } from '@/utils'
|
||||
|
||||
defineOptions({ name: 'OrderMessageItem' })
|
||||
const props = defineProps<{
|
||||
message: KeFuMessageRespVO
|
||||
}>()
|
||||
const getMessageContent = computed(() => JSON.parse(props.message.content))
|
||||
|
||||
/**
|
||||
* 格式化订单状态的颜色
|
||||
*
|
||||
* @param order 订单
|
||||
* @return {string} 颜色的 class 名称
|
||||
*/
|
||||
function formatOrderColor(order: any) {
|
||||
if (order.status === 0) {
|
||||
return 'info-color'
|
||||
}
|
||||
if (order.status === 10 || order.status === 20 || (order.status === 30 && !order.commentStatus)) {
|
||||
return 'warning-color'
|
||||
}
|
||||
if (order.status === 30 && order.commentStatus) {
|
||||
return 'success-color'
|
||||
}
|
||||
return 'danger-color'
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化订单状态
|
||||
*
|
||||
* @param order 订单
|
||||
*/
|
||||
function formatOrderStatus(order: any) {
|
||||
if (order.status === 0) {
|
||||
return '待付款'
|
||||
}
|
||||
if (order.status === 10 && order.deliveryType === 1) {
|
||||
return '待发货'
|
||||
}
|
||||
if (order.status === 10 && order.deliveryType === 2) {
|
||||
return '待核销'
|
||||
}
|
||||
if (order.status === 20) {
|
||||
return '待收货'
|
||||
}
|
||||
if (order.status === 30 && !order.commentStatus) {
|
||||
return '待评价'
|
||||
}
|
||||
if (order.status === 30 && order.commentStatus) {
|
||||
return '已完成'
|
||||
}
|
||||
return '已关闭'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.order-list-card-box {
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
background-color: #e2e2e2;
|
||||
|
||||
.order-card-header {
|
||||
height: 80px;
|
||||
|
||||
.order-no {
|
||||
font-size: 26px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.pay-box {
|
||||
.discounts-title {
|
||||
font-size: 24px;
|
||||
line-height: normal;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.discounts-money {
|
||||
font-size: 24px;
|
||||
line-height: normal;
|
||||
color: #999;
|
||||
font-family: OPPOSANS;
|
||||
}
|
||||
|
||||
.pay-color {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.order-card-footer {
|
||||
height: 100px;
|
||||
|
||||
.more-item-box {
|
||||
padding: 20px;
|
||||
|
||||
.more-item {
|
||||
height: 60px;
|
||||
|
||||
.title {
|
||||
font-size: 26px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.more-btn {
|
||||
color: #999999;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 154px;
|
||||
color: #333333;
|
||||
font-size: 26px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.warning-color {
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
.danger-color {
|
||||
color: #ff3000;
|
||||
}
|
||||
|
||||
.success-color {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.info-color {
|
||||
color: #999999;
|
||||
}
|
||||
</style>
|
|
@ -110,33 +110,25 @@ const skuString = computed(() => {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.score-img {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.ss-order-card-warp {
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
background-color: #e2e2e2;
|
||||
|
||||
.img-box {
|
||||
width: 164px;
|
||||
height: 164px;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
|
||||
.order-img {
|
||||
width: 164px;
|
||||
height: 164px;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.box-right {
|
||||
flex: 1;
|
||||
// width: 500px;
|
||||
// height: 164px;
|
||||
position: relative;
|
||||
|
||||
.tool-box {
|
||||
|
@ -147,13 +139,13 @@ const skuString = computed(() => {
|
|||
}
|
||||
|
||||
.title-text {
|
||||
font-size: 28px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 40px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.spec-text {
|
||||
font-size: 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #999999;
|
||||
min-width: 0;
|
||||
|
@ -165,15 +157,15 @@ const skuString = computed(() => {
|
|||
}
|
||||
|
||||
.price-text {
|
||||
font-size: 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
font-family: OPPOSANS;
|
||||
}
|
||||
|
||||
.total-text {
|
||||
font-size: 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
line-height: 16px;
|
||||
color: #999999;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
<template>
|
||||
<!-- 图片消息 -->
|
||||
<template v-if="KeFuMessageContentTypeEnum.PRODUCT === message.contentType">
|
||||
<div
|
||||
:class="[
|
||||
message.senderType === UserTypeEnum.MEMBER
|
||||
? `ml-10px`
|
||||
: message.senderType === UserTypeEnum.ADMIN
|
||||
? `mr-10px`
|
||||
: ''
|
||||
]"
|
||||
>
|
||||
<ProductItem
|
||||
:picUrl="getMessageContent.picUrl"
|
||||
:price="getMessageContent.price"
|
||||
:skuText="getMessageContent.introduction"
|
||||
:title="getMessageContent.spuName"
|
||||
:titleWidth="400"
|
||||
priceColor="#FF3000"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { KeFuMessageContentTypeEnum } from '../tools/constants'
|
||||
import ProductItem from './ProductItem.vue'
|
||||
import { UserTypeEnum } from '@/utils/constants'
|
||||
import { KeFuMessageRespVO } from '@/api/mall/promotion/kefu/message'
|
||||
|
||||
defineOptions({ name: 'ProductMessageItem' })
|
||||
const props = defineProps<{
|
||||
message: KeFuMessageRespVO
|
||||
}>()
|
||||
|
||||
/** 获悉消息内容 */
|
||||
const getMessageContent = computed(() => JSON.parse(props.message.content))
|
||||
</script>
|
|
@ -1,29 +0,0 @@
|
|||
<template>
|
||||
<!-- 文本消息 -->
|
||||
<template v-if="KeFuMessageContentTypeEnum.TEXT === message.contentType">
|
||||
<div
|
||||
v-dompurify-html="replaceEmoji(message.content)"
|
||||
:class="[
|
||||
message.senderType === UserTypeEnum.MEMBER
|
||||
? `ml-10px`
|
||||
: message.senderType === UserTypeEnum.ADMIN
|
||||
? `mr-10px`
|
||||
: ''
|
||||
]"
|
||||
class="flex items-center"
|
||||
></div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { KeFuMessageContentTypeEnum } from '../tools/constants'
|
||||
import { UserTypeEnum } from '@/utils/constants'
|
||||
import { useEmoji } from '../tools/emoji'
|
||||
import { KeFuMessageRespVO } from '@/api/mall/promotion/kefu/message'
|
||||
|
||||
defineOptions({ name: 'TextMessageItem' })
|
||||
defineProps<{
|
||||
message: KeFuMessageRespVO
|
||||
}>()
|
||||
const { replaceEmoji } = useEmoji()
|
||||
</script>
|
|
@ -1,22 +1,28 @@
|
|||
<template>
|
||||
<el-row :gutter="10">
|
||||
<!-- 会话列表 -->
|
||||
<el-col :span="8">
|
||||
<el-col :span="6">
|
||||
<ContentWrap>
|
||||
<KeFuConversationList ref="keFuConversationRef" @change="handleChange" />
|
||||
</ContentWrap>
|
||||
</el-col>
|
||||
<!-- 会话详情(选中会话的消息列表) -->
|
||||
<el-col :span="16">
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { KeFuConversationList, KeFuMessageList } from './components'
|
||||
import { KeFuConversationList, KeFuMessageList, MemberBrowsingHistory } from './components'
|
||||
import { WebSocketMessageTypeConstants } from './components/tools/constants'
|
||||
import { KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
|
||||
import { getAccessToken } from '@/utils/auth'
|
||||
|
@ -60,7 +66,7 @@ watchEffect(() => {
|
|||
// 刷新会话列表
|
||||
getConversationList()
|
||||
// 刷新消息列表
|
||||
keFuChatBoxRef.value?.refreshMessageList()
|
||||
keFuChatBoxRef.value?.refreshMessageList(JSON.parse(jsonMessage.content))
|
||||
return
|
||||
}
|
||||
// 2.3 消息类型:KEFU_MESSAGE_ADMIN_READ
|
||||
|
@ -81,8 +87,10 @@ const getConversationList = () => {
|
|||
|
||||
/** 加载指定会话的消息列表 */
|
||||
const keFuChatBoxRef = ref<InstanceType<typeof KeFuMessageList>>()
|
||||
const memberBrowsingHistoryRef = ref<InstanceType<typeof MemberBrowsingHistory>>()
|
||||
const handleChange = (conversation: KeFuConversationRespVO) => {
|
||||
keFuChatBoxRef.value?.getMessageList(conversation, true)
|
||||
keFuChatBoxRef.value?.getNewMessageList(conversation)
|
||||
memberBrowsingHistoryRef.value?.initHistory(conversation)
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
|
|
Loading…
Reference in New Issue