Parcourir la source

【代码评审】Mall:客服的会话列表

YunaiV il y a 11 mois
Parent
commit
ec61670b75

+ 27 - 11
src/views/mall/promotion/kefu/components/KeFuChatBox.vue

@@ -1,9 +1,12 @@
 <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 - 索引
  */

+ 0 - 1
src/views/mall/promotion/kefu/components/KeFuConversationBox.vue

@@ -1,6 +1,5 @@
 <template>
   <div class="kefu">
-    <!-- TODO @puhui999:item => conversation 会不会更容易理解 -->
     <div
       v-for="(item, index) in conversationList"
       :key="item.id"

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

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

+ 3 - 2
src/views/mall/promotion/kefu/components/message/OrderMessageItem.vue

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

+ 4 - 0
src/views/mall/promotion/kefu/components/message/ProductItem.vue

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

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

@@ -32,5 +32,7 @@ defineOptions({ name: 'ProductMessageItem' })
 const props = defineProps<{
   message: KeFuMessageRespVO
 }>()
+
+/** 获悉消息内容 */
 const getMessageContent = computed(() => JSON.parse(props.message.content))
 </script>

+ 2 - 0
src/views/mall/promotion/kefu/components/tools/EmojiSelectPopover.vue

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

+ 12 - 8
src/views/mall/promotion/kefu/components/tools/PictureSelectUpload.vue

@@ -1,19 +1,24 @@
+<!-- 图片选择 -->
 <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 oversizedFiles = filesArray.filter((file: File) => file.size / 1024 ** 2 > fileSize)
-        if (oversizedFiles.length > 0) {
-          reject({ errorType: 'fileSize', files: oversizedFiles })
+        const overSizedFiles = filesArray.filter((file: File) => file.size / 1024 ** 2 > fileSize)
+        if (overSizedFiles.length > 0) {
+          reject({ errorType: 'fileSize', files: overSizedFiles })
           return
         }
 
@@ -79,8 +85,6 @@ async function getFiles(options = {}) {
         resolve(fileList)
       })
     })
-
-    return files
   } catch (error) {
     console.error('选择文件出错:', error)
     throw error

+ 20 - 4
src/views/mall/promotion/kefu/components/tools/emoji.ts

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