Browse Source

!483 完善 mall 客服
Merge pull request !483 from puhui999/dev-crm

芋道源码 9 tháng trước cách đây
mục cha
commit
4abd246d96

+ 9 - 0
src/api/mall/product/history.ts

@@ -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 })
+}

+ 13 - 3
src/views/mall/promotion/kefu/components/KeFuConversationList.vue

@@ -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; // 隐藏超出的文本

+ 99 - 40
src/views/mall/promotion/kefu/components/KeFuMessageList.vue

@@ -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)
 }

+ 91 - 0
src/views/mall/promotion/kefu/components/history/MemberBrowsingHistory.vue

@@ -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>

+ 42 - 0
src/views/mall/promotion/kefu/components/history/OrderBrowsingHistory.vue

@@ -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>

+ 55 - 0
src/views/mall/promotion/kefu/components/history/ProductBrowsingHistory.vue

@@ -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>

+ 2 - 1
src/views/mall/promotion/kefu/components/index.ts

@@ -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 }

+ 0 - 34
src/views/mall/promotion/kefu/components/message/ImageMessageItem.vue

@@ -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>

+ 24 - 0
src/views/mall/promotion/kefu/components/message/MessageItem.vue

@@ -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>

+ 146 - 0
src/views/mall/promotion/kefu/components/message/OrderItem.vue

@@ -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>

+ 0 - 182
src/views/mall/promotion/kefu/components/message/OrderMessageItem.vue

@@ -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>

+ 10 - 18
src/views/mall/promotion/kefu/components/message/ProductItem.vue

@@ -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;
   }

+ 0 - 38
src/views/mall/promotion/kefu/components/message/ProductMessageItem.vue

@@ -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>

+ 0 - 29
src/views/mall/promotion/kefu/components/message/TextMessageItem.vue

@@ -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>

+ 13 - 5
src/views/mall/promotion/kefu/index.vue

@@ -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)
 }
 
 /** 初始化 */