zhangyaowen 1 month ago
parent
commit
bbd340e044

+ 328 - 0
pages/chat/index copy.vue

@@ -0,0 +1,328 @@
+<template>
+  <s-layout class="chat-wrap" :title="!isReconnecting ? '连接客服成功' : '会话重连中'" navbar="inner">
+    <!-- <view class="dropdownClass">
+      <u-dropdown>
+        <u-dropdown-item v-model="value1" :title="options1[value1 - 1].label" :options="options1"></u-dropdown-item>
+      </u-dropdown>
+    </view> -->
+    <!--  覆盖头部导航栏背景颜色  -->
+    <view class="page-bg" :style="{ height: sys_navBar + 'px' }"></view>
+    <!--  聊天区域  -->
+    <MessageList ref="messageListRef">
+      <template #bottom>
+        <message-input :loading="loadingInput" v-model="chat.msg" @on-tools="onTools"
+          @send-message="onSendMessage"></message-input>
+      </template>
+    </MessageList>
+    <!--  聊天工具  -->
+    <tools-popup :show-tools="chat.showTools" :tools-mode="chat.toolsMode" @close="handleToolsClose" @on-emoji="onEmoji"
+      @image-select="onSelect" @on-show-select="onShowSelect">
+      <message-input v-model="chat.msg" @on-tools="onTools" @send-message="onSendMessage"></message-input>
+    </tools-popup>
+    <!--  产品订单选择  -->
+    <SelectPopup :mode="chat.selectMode" :show="chat.showSelect" @select="onSelect" @close="chat.showSelect = false" />
+    <EventSource ref="EventSourceRef" :url="eventSourceUrl" :options="eventSourceOptions" @callback="handleCallback" />
+  </s-layout>
+</template>
+
+<script setup>
+import MessageList from '@/pages/chat/components/messageList.vue';
+import EventSource from '@/pages/chat/components/eventSource.vue';
+import { reactive, ref, toRefs, computed, provide } from 'vue';
+import sheep from '@/sheep';
+import ToolsPopup from '@/pages/chat/components/toolsPopup.vue';
+import MessageInput from '@/pages/chat/components/messageInput.vue';
+import SelectPopup from '@/pages/chat/components/select-popup.vue';
+import {
+  KeFuMessageContentTypeEnum,
+  WebSocketMessageTypeConstants,
+} from '@/pages/chat/util/constants';
+import FileApi from '@/sheep/api/infra/file';
+import KeFuApi from '@/sheep/api/promotion/kefu';
+import { useWebSocket } from '@/sheep/hooks/useWebSocket';
+import { jsonParse } from '@/sheep/util';
+import { onLoad } from '@dcloudio/uni-app';
+const EventSourceRef = ref(null); //AIref
+const chatMsgData = ref({});
+const eventSourceUrl = import.meta.env.SHOPRO_BASE_URL + "/app-api/promotion/kefu-message/sendStream"; //ai客服URL
+// const eventSourceUrl = "https://192.168.10.17:9095/app-api/infra/ai-dify/chat-messages-stream"; //ai客服URL
+// ai客服流式上传参数
+const eventSourceOptions = computed(() => {
+  return {
+    headers: {
+      "content-type": "application/json",
+      Accept: "text/event-stream",
+      "tenant-id": 1,
+      Authorization: "Bearer " + uni.getStorageSync('token'),
+    },
+    method: "POST",
+    body: JSON.stringify({
+      "contentType": 1,
+      "content": chatMsgData.value.content || chat.msg,
+      "relUserId": route.value.relUserId
+      // type: "律师咨询",
+      // query: chat.msg
+    }), // 请求体
+  };
+});
+const answerArr = ref('');
+const loadingInput = ref(false); // 加载状态
+provide('loadingInput', loadingInput); // 依赖注入加载状态
+const EventSourceFun = async (data, is = true) => {
+  chatMsgData.value = data;
+  const avatarObj = messageListRef.value.getAvatar() || {}
+  const params = {
+    id: 1,
+    conversationId: route.value.conversationId,
+    senderId: userInfo.value.id,
+    senderAvatar: avatarObj.senderAvatar || '',
+    senderType: 1,
+    receiverId: route.value.relUserId,
+    receiverAvatar: avatarObj.receiverAvatar || '',
+    receiverType: null,
+    contentType: 1,
+    content: is ? (JSON.stringify({ text: chat.msg })) : data.content,
+    readStatus: true,
+    createTime: 1745546275000
+  }
+  await messageListRef.value.refreshMessageList(params);
+  const params1 = {
+    ids: 1,
+    isAi: true,
+    isLoading: true,
+    conversationId: route.value.conversationId,
+    senderId: route.value.relUserId,
+    senderAvatar: avatarObj.receiverAvatar || '',
+    senderType: 1,
+    receiverId: userInfo.value.id,
+    receiverAvatar: avatarObj.senderAvatar || '',
+    receiverType: null,
+    contentType: 22,
+    content: '',
+    readStatus: true,
+    createTime: 1745546275000
+  }
+  await messageListRef.value.refreshMessageList(params1);
+  console.log(data, 22222);
+  await EventSourceRef.value.send(data);
+}
+provide('EventSourceFun', EventSourceFun); // 依赖注入加载状态
+
+const loadingId = ref(""); // 加载的id
+// ai客服发送消息接收回调
+const handleCallback = async (e) => {
+  const { type, msg, data } = e || {};
+  if (type == "onmessage") {
+    const datas = JSON.parse(data);
+    console.log("张耀文", datas)
+    answerArr.value += datas.content;
+    loadingId.value = datas.messageId
+    await messageListRef.value.updateMessage(datas);
+  }
+  if (type == "onclose") {
+    loadingInput.value = false;
+    await messageListRef.value.updateMessage({}, loadingId.value);
+    answerArr.value = ""
+  }
+};
+const sys_navBar = sheep.$platform.navbar;
+const options1 = [{
+  label: 'ai',
+  value: 1,
+},
+{
+  label: '律师咨询',
+  value: 2,
+},
+{
+  label: '金融相关',
+  value: 3,
+}
+]
+const value1 = ref(1)
+const chat = reactive({
+  msg: '',
+  scrollInto: '',
+  showTools: false,
+  toolsMode: '',
+  showSelect: false,
+  selectMode: '',
+});
+const route = ref({})
+onLoad((options) => {
+  route.value = options
+});
+const userInfo = computed(() => sheep.$store('user').userInfo);
+// 发送消息
+async function onSendMessage() {
+  if (!chat.msg) return;
+  try {
+    loadingInput.value = true;
+    const idArr = ['1', '2', '3', '4']
+    const data = {
+      conversationId: route.value.conversationId,
+      contentType: KeFuMessageContentTypeEnum.TEXT,
+      content: JSON.stringify({ text: chat.msg }),
+      relUserId: route.value.relUserId
+    };
+    // 如果在线就走直接发消息  如果不在线就走ai回复
+    const res = await KeFuApi.checkUserId({
+      userId: route.value.relUserId
+    })
+    if (res.data) {
+      await KeFuApi.sendKefuMessageNew(data);
+    } else {
+      if (idArr.includes(route.value.relUserId)) {
+        await EventSourceFun(data);
+      } else {
+        await KeFuApi.sendKefuMessageNew(data);
+      }
+    }
+    chat.msg = '';
+  } finally {
+    chat.showTools = false;
+  }
+
+}
+
+const messageListRef = ref();
+
+//======================= 聊天工具相关 start =======================
+
+function handleToolsClose() {
+  chat.showTools = false;
+  chat.toolsMode = '';
+}
+
+function onEmoji(item) {
+  chat.msg += item.name;
+}
+
+// 点击工具栏开关
+function onTools(mode) {
+  if (isReconnecting.value) {
+    sheep.$helper.toast('您已掉线!请返回重试');
+    return;
+  }
+
+  if (!chat.toolsMode || chat.toolsMode === mode) {
+    chat.showTools = !chat.showTools;
+  }
+  chat.toolsMode = mode;
+  if (!chat.showTools) {
+    chat.toolsMode = '';
+  }
+}
+
+function onShowSelect(mode) {
+  chat.showTools = false;
+  chat.showSelect = true;
+  chat.selectMode = mode;
+}
+
+async function onSelect({ type, data }) {
+  console.log(data, 555222233)
+  let msg;
+  switch (type) {
+    case 'image':
+      const res = await FileApi.uploadFile(data.tempFiles[0].path);
+      msg = {
+        contentType: KeFuMessageContentTypeEnum.IMAGE,
+        content: JSON.stringify({ picUrl: res.data }),
+        conversationId: route.value.conversationId,
+      };
+      break;
+    case 'goods':
+      msg = {
+        contentType: KeFuMessageContentTypeEnum.PRODUCT,
+        content: JSON.stringify(data),
+        conversationId: route.value.conversationId,
+      };
+      break;
+    case 'order':
+      msg = {
+        contentType: KeFuMessageContentTypeEnum.ORDER,
+        content: JSON.stringify(data),
+        conversationId: route.value.conversationId,
+      };
+      break;
+  }
+  if (msg) {
+    // 发送消息
+    // scrollBottom();
+    // await KeFuApi.sendKefuMessage(msg);
+    await KeFuApi.listSendNew(msg);
+    await messageListRef.value.refreshMessageList();
+    chat.showTools = false;
+    chat.showSelect = false;
+    chat.selectMode = '';
+  }
+}
+
+//======================= 聊天工具相关 end =======================
+const { options } = useWebSocket({
+  // 连接成功
+  onConnected: async () => { },
+  // 收到消息
+  onMessage: async (data) => {
+    console.log(data, 'data');
+    const type = data.type;
+    if (!type) {
+      console.error('未知的消息类型:' + data);
+      return;
+    }
+    // 2.2 消息类型:KEFU_MESSAGE_TYPE
+    if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_IM) {
+      console.log('客服消息IM');
+      // 刷新消息列表
+      await messageListRef.value.refreshMessageList(jsonParse(data.content));
+      return;
+    }
+    // 2.3 消息类型:KEFU_MESSAGE_ADMIN_READ
+    if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_ADMIN_READ) {
+      console.log('管理员已读消息');
+    }
+  },
+});
+const isReconnecting = toRefs(options).isReconnecting; // 重连状态
+</script>
+
+<style scoped lang="scss">
+:deep(.z-paging-content-fixed) {
+  // background: red;
+  // top: 40px;
+}
+
+:deep(.zp-paging-container-content) {
+  padding: 0px 10px;
+}
+
+.dropdownClass {
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+}
+
+.chat-wrap {
+  .page-bg {
+    width: 100%;
+    position: absolute;
+    top: 0;
+    left: 0;
+    background-color: #3A74F2;
+    z-index: 1;
+  }
+
+  .status {
+    position: relative;
+    box-sizing: border-box;
+    z-index: 3;
+    height: 70rpx;
+    padding: 0 30rpx;
+    background: var(--ui-BG-Main-opacity-1);
+    display: flex;
+    align-items: center;
+    font-size: 30rpx;
+    font-weight: 400;
+    color: var(--ui-BG-Main);
+  }
+}
+</style>

+ 27 - 160
pages/chat/index.vue

@@ -1,34 +1,28 @@
 <template>
   <s-layout class="chat-wrap" :title="!isReconnecting ? '连接客服成功' : '会话重连中'" navbar="inner">
-    <!-- <view class="dropdownClass">
-      <u-dropdown>
-        <u-dropdown-item v-model="value1" :title="options1[value1 - 1].label" :options="options1"></u-dropdown-item>
-      </u-dropdown>
-    </view> -->
     <!--  覆盖头部导航栏背景颜色  -->
     <view class="page-bg" :style="{ height: sys_navBar + 'px' }"></view>
     <!--  聊天区域  -->
     <MessageList ref="messageListRef">
       <template #bottom>
-        <message-input :loading="loadingInput" v-model="chat.msg" @on-tools="onTools"
-          @send-message="onSendMessage"></message-input>
+        <message-input v-model="chat.msg" @on-tools="onTools" @send-message="onSendMessage" :auto-focus="false"
+          :show-char-count="true" :max-length="500"></message-input>
       </template>
     </MessageList>
     <!--  聊天工具  -->
     <tools-popup :show-tools="chat.showTools" :tools-mode="chat.toolsMode" @close="handleToolsClose" @on-emoji="onEmoji"
       @image-select="onSelect" @on-show-select="onShowSelect">
-      <message-input v-model="chat.msg" @on-tools="onTools" @send-message="onSendMessage"></message-input>
+      <message-input v-model="chat.msg" @on-tools="onTools" @send-message="onSendMessage" :auto-focus="false"
+        :show-char-count="true" :max-length="500"></message-input>
     </tools-popup>
-    <!--  品订单选择  -->
+    <!--  品订单选择  -->
     <SelectPopup :mode="chat.selectMode" :show="chat.showSelect" @select="onSelect" @close="chat.showSelect = false" />
-    <EventSource ref="EventSourceRef" :url="eventSourceUrl" :options="eventSourceOptions" @callback="handleCallback" />
   </s-layout>
 </template>
 
 <script setup>
 import MessageList from '@/pages/chat/components/messageList.vue';
-import EventSource from '@/pages/chat/components/eventSource.vue';
-import { reactive, ref, toRefs, computed, provide } from 'vue';
+import { reactive, ref, toRefs } from 'vue';
 import sheep from '@/sheep';
 import ToolsPopup from '@/pages/chat/components/toolsPopup.vue';
 import MessageInput from '@/pages/chat/components/messageInput.vue';
@@ -41,105 +35,9 @@ import FileApi from '@/sheep/api/infra/file';
 import KeFuApi from '@/sheep/api/promotion/kefu';
 import { useWebSocket } from '@/sheep/hooks/useWebSocket';
 import { jsonParse } from '@/sheep/util';
-import { onLoad } from '@dcloudio/uni-app';
-const EventSourceRef = ref(null); //AIref
-const chatMsgData = ref({});
-const eventSourceUrl = import.meta.env.SHOPRO_BASE_URL + "/app-api/promotion/kefu-message/sendStream"; //ai客服URL
-// const eventSourceUrl = "https://192.168.10.17:9095/app-api/infra/ai-dify/chat-messages-stream"; //ai客服URL
-// ai客服流式上传参数
-const eventSourceOptions = computed(() => {
-  return {
-    headers: {
-      "content-type": "application/json",
-      Accept: "text/event-stream",
-      "tenant-id": 1,
-      Authorization: "Bearer " + uni.getStorageSync('token'),
-    },
-    method: "POST",
-    body: JSON.stringify({
-      "contentType": 1,
-      "content": chatMsgData.value.content || chat.msg,
-      "relUserId": route.value.relUserId
-      // type: "律师咨询",
-      // query: chat.msg
-    }), // 请求体
-  };
-});
-const answerArr = ref('');
-const loadingInput = ref(false); // 加载状态
-provide('loadingInput', loadingInput); // 依赖注入加载状态
-const EventSourceFun = async (data, is = true) => {
-  chatMsgData.value = data;
-  const avatarObj = messageListRef.value.getAvatar() || {}
-  const params = {
-    id: 1,
-    conversationId: route.value.conversationId,
-    senderId: userInfo.value.id,
-    senderAvatar: avatarObj.senderAvatar || '',
-    senderType: 1,
-    receiverId: route.value.relUserId,
-    receiverAvatar: avatarObj.receiverAvatar || '',
-    receiverType: null,
-    contentType: 1,
-    content: is ? (JSON.stringify({ text: chat.msg })) : data.content,
-    readStatus: true,
-    createTime: 1745546275000
-  }
-  await messageListRef.value.refreshMessageList(params);
-  const params1 = {
-    ids: 1,
-    isAi: true,
-    isLoading: true,
-    conversationId: route.value.conversationId,
-    senderId: route.value.relUserId,
-    senderAvatar: avatarObj.receiverAvatar || '',
-    senderType: 1,
-    receiverId: userInfo.value.id,
-    receiverAvatar: avatarObj.senderAvatar || '',
-    receiverType: null,
-    contentType: 22,
-    content: '',
-    readStatus: true,
-    createTime: 1745546275000
-  }
-  await messageListRef.value.refreshMessageList(params1);
-  console.log(data, 22222);
-  await EventSourceRef.value.send(data);
-}
-provide('EventSourceFun', EventSourceFun); // 依赖注入加载状态
 
-const loadingId = ref(""); // 加载的id
-// ai客服发送消息接收回调
-const handleCallback = async (e) => {
-  const { type, msg, data } = e || {};
-  if (type == "onmessage") {
-    const datas = JSON.parse(data);
-    console.log("张耀文", datas)
-    answerArr.value += datas.content;
-    loadingId.value = datas.messageId
-    await messageListRef.value.updateMessage(datas);
-  }
-  if (type == "onclose") {
-    loadingInput.value = false;
-    await messageListRef.value.updateMessage({}, loadingId.value);
-    answerArr.value = ""
-  }
-};
 const sys_navBar = sheep.$platform.navbar;
-const options1 = [{
-  label: 'ai',
-  value: 1,
-},
-{
-  label: '律师咨询',
-  value: 2,
-},
-{
-  label: '金融相关',
-  value: 3,
-}
-]
-const value1 = ref(1)
+
 const chat = reactive({
   msg: '',
   scrollInto: '',
@@ -148,41 +46,20 @@ const chat = reactive({
   showSelect: false,
   selectMode: '',
 });
-const route = ref({})
-onLoad((options) => {
-  route.value = options
-});
-const userInfo = computed(() => sheep.$store('user').userInfo);
+
 // 发送消息
 async function onSendMessage() {
   if (!chat.msg) return;
   try {
-    loadingInput.value = true;
-    const idArr = ['1', '2', '3', '4']
     const data = {
-      conversationId: route.value.conversationId,
       contentType: KeFuMessageContentTypeEnum.TEXT,
       content: JSON.stringify({ text: chat.msg }),
-      relUserId: route.value.relUserId
     };
-    // 如果在线就走直接发消息  如果不在线就走ai回复
-    const res = await KeFuApi.checkUserId({
-      userId: route.value.relUserId
-    })
-    if (res.data) {
-      await KeFuApi.sendKefuMessageNew(data);
-    } else {
-      if (idArr.includes(route.value.relUserId)) {
-        await EventSourceFun(data);
-      } else {
-        await KeFuApi.sendKefuMessageNew(data);
-      }
-    }
+    await KeFuApi.sendKefuMessage(data);
     chat.msg = '';
   } finally {
     chat.showTools = false;
   }
-
 }
 
 const messageListRef = ref();
@@ -205,13 +82,21 @@ function onTools(mode) {
     return;
   }
 
-  if (!chat.toolsMode || chat.toolsMode === mode) {
-    chat.showTools = !chat.showTools;
+  // 第二次点击关闭
+  if (chat.showTools && chat.toolsMode === mode) {
+    handleToolsClose();
+    return;
   }
-  chat.toolsMode = mode;
-  if (!chat.showTools) {
+  // 切换工具栏
+  if (chat.showTools && chat.toolsMode !== mode) {
+    chat.showTools = false;
     chat.toolsMode = '';
   }
+  // 延迟打开等一下过度效果
+  setTimeout(() => {
+    chat.toolsMode = mode;
+    chat.showTools = true;
+  }, 200);
 }
 
 function onShowSelect(mode) {
@@ -221,7 +106,6 @@ function onShowSelect(mode) {
 }
 
 async function onSelect({ type, data }) {
-  console.log(data, 555222233)
   let msg;
   switch (type) {
     case 'image':
@@ -229,29 +113,25 @@ async function onSelect({ type, data }) {
       msg = {
         contentType: KeFuMessageContentTypeEnum.IMAGE,
         content: JSON.stringify({ picUrl: res.data }),
-        conversationId: route.value.conversationId,
       };
       break;
     case 'goods':
       msg = {
         contentType: KeFuMessageContentTypeEnum.PRODUCT,
         content: JSON.stringify(data),
-        conversationId: route.value.conversationId,
       };
       break;
     case 'order':
       msg = {
         contentType: KeFuMessageContentTypeEnum.ORDER,
         content: JSON.stringify(data),
-        conversationId: route.value.conversationId,
       };
       break;
   }
   if (msg) {
     // 发送消息
     // scrollBottom();
-    // await KeFuApi.sendKefuMessage(msg);
-    await KeFuApi.listSendNew(msg);
+    await KeFuApi.sendKefuMessage(msg);
     await messageListRef.value.refreshMessageList();
     chat.showTools = false;
     chat.showSelect = false;
@@ -265,15 +145,13 @@ const { options } = useWebSocket({
   onConnected: async () => { },
   // 收到消息
   onMessage: async (data) => {
-    console.log(data, 'data');
     const type = data.type;
     if (!type) {
       console.error('未知的消息类型:' + data);
       return;
     }
     // 2.2 消息类型:KEFU_MESSAGE_TYPE
-    if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_IM) {
-      console.log('客服消息IM');
+    if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_TYPE) {
       // 刷新消息列表
       await messageListRef.value.refreshMessageList(jsonParse(data.content));
       return;
@@ -281,6 +159,8 @@ const { options } = useWebSocket({
     // 2.3 消息类型:KEFU_MESSAGE_ADMIN_READ
     if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_ADMIN_READ) {
       console.log('管理员已读消息');
+      // 更新消息已读状态
+      sheep.$helper.toast('客服已读您的消息');
     }
   },
 });
@@ -288,26 +168,13 @@ const isReconnecting = toRefs(options).isReconnecting; // 重连状态
 </script>
 
 <style scoped lang="scss">
-:deep(.z-paging-content-fixed) {
-  // background: red;
-  // top: 40px;
-}
-
-:deep(.zp-paging-container-content) {
-  padding: 0px 10px;
-}
-
-.dropdownClass {
-  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
-}
-
 .chat-wrap {
   .page-bg {
     width: 100%;
     position: absolute;
     top: 0;
     left: 0;
-    background-color: #3A74F2;
+    background-color: var(--ui-BG-Main);
     z-index: 1;
   }
 
@@ -325,4 +192,4 @@ const isReconnecting = toRefs(options).isReconnecting; // 重连状态
     color: var(--ui-BG-Main);
   }
 }
-</style>
+</style>

+ 266 - 320
pages/goods/list.vue

@@ -1,22 +1,11 @@
 <template>
-  <s-layout
-    navbar="normal"
-    :leftWidth="0"
-    :rightWidth="0"
-    tools="search"
-    :defaultSearch="state.keyword"
-    @search="onSearch"
-  >
+  <s-layout navbar="normal" :leftWidth="0" :rightWidth="0" tools="search" :defaultSearch="state.keyword"
+    @search="onSearch">
     <!-- 筛选 -->
     <su-sticky bgColor="#fff">
       <view class="ss-flex">
         <view class="ss-flex-1">
-          <su-tabs
-            :list="state.tabList"
-            :scrollable="false"
-            @change="onTabsChange"
-            :current="state.currentTab"
-          />
+          <su-tabs :list="state.tabList" :scrollable="false" @change="onTabsChange" :current="state.currentTab" />
         </view>
         <view class="list-icon" @tap="state.iconStatus = !state.iconStatus">
           <text v-if="state.iconStatus" class="sicon-goods-list" />
@@ -26,23 +15,11 @@
     </su-sticky>
 
     <!-- 弹窗 -->
-    <su-popup
-      :show="state.showFilter"
-      type="top"
-      round="10"
-      :space="sys_navBar + 38"
-      backgroundColor="#F6F6F6"
-      :zIndex="10"
-      @close="state.showFilter = false"
-    >
+    <su-popup :show="state.showFilter" type="top" round="10" :space="sys_navBar + 38" backgroundColor="#F6F6F6"
+      :zIndex="10" @close="state.showFilter = false">
       <view class="filter-list-box">
-        <view
-          class="filter-item"
-          v-for="(item, index) in state.tabList[state.currentTab].list"
-          :key="item.value"
-          :class="[{ 'filter-item-active': index === state.curFilter }]"
-          @tap="onFilterItem(index)"
-        >
+        <view class="filter-item" v-for="(item, index) in state.tabList[state.currentTab].list" :key="item.value"
+          :class="[{ 'filter-item-active': index === state.curFilter }]" @tap="onFilterItem(index)">
           {{ item.label }}
         </view>
       </view>
@@ -50,37 +27,18 @@
 
     <!-- 情况一:单列布局 -->
     <view v-if="state.iconStatus && state.pagination.total > 0" class="goods-list ss-m-t-20">
-      <view
-        class="ss-p-l-20 ss-p-r-20 ss-m-b-20"
-        v-for="item in state.pagination.list"
-        :key="item.id"
-      >
-        <s-goods-column
-          class=""
-          size="lg"
-          :data="item"
-          :topRadius="10"
-          :bottomRadius="10"
-          @click="sheep.$router.go('/pages/goods/index', { id: item.id })"
-        />
+      <view class="ss-p-l-20 ss-p-r-20 ss-m-b-20" v-for="item in state.pagination.list" :key="item.id">
+        <s-goods-column class="" size="lg" :data="item" :topRadius="10" :bottomRadius="10"
+          @click="sheep.$router.go('/pages/goods/index', { id: item.id })" />
       </view>
     </view>
     <!-- 情况二:双列布局 -->
-    <view
-      v-if="!state.iconStatus && state.pagination.total > 0"
-      class="ss-flex ss-flex-wrap ss-p-x-20 ss-m-t-20 ss-col-top"
-    >
+    <view v-if="!state.iconStatus && state.pagination.total > 0"
+      class="ss-flex ss-flex-wrap ss-p-x-20 ss-m-t-20 ss-col-top">
       <view class="goods-list-box">
         <view class="left-list" v-for="item in state.leftGoodsList" :key="item.id">
-          <s-goods-column
-            class="goods-md-box"
-            size="md"
-            :data="item"
-            :topRadius="10"
-            :bottomRadius="10"
-            @click="sheep.$router.go('/pages/goods/index', { id: item.id })"
-            @getHeight="mountMasonry($event, 'left')"
-          >
+          <s-goods-column class="goods-md-box" size="md" :data="item" :topRadius="10" :bottomRadius="10"
+            @click="sheep.$router.go('/pages/goods/index', { id: item.id })" @getHeight="mountMasonry($event, 'left')">
             <template v-slot:cart>
               <button class="ss-reset-button cart-btn" />
             </template>
@@ -89,15 +47,8 @@
       </view>
       <view class="goods-list-box">
         <view class="right-list" v-for="item in state.rightGoodsList" :key="item.id">
-          <s-goods-column
-            class="goods-md-box"
-            size="md"
-            :topRadius="10"
-            :bottomRadius="10"
-            :data="item"
-            @click="sheep.$router.go('/pages/goods/index', { id: item.id })"
-            @getHeight="mountMasonry($event, 'right')"
-          >
+          <s-goods-column class="goods-md-box" size="md" :topRadius="10" :bottomRadius="10" :data="item"
+            @click="sheep.$router.go('/pages/goods/index', { id: item.id })" @getHeight="mountMasonry($event, 'right')">
             <template v-slot:cart>
               <button class="ss-reset-button cart-btn" />
             </template>
@@ -105,303 +56,298 @@
         </view>
       </view>
     </view>
-    <uni-load-more
-      v-if="state.pagination.total > 0"
-      :status="state.loadStatus"
-      :content-text="{
-        contentdown: '上拉加载更多',
-      }"
-      @tap="loadMore"
-    />
+    <uni-load-more v-if="state.pagination.total > 0" :status="state.loadStatus" :content-text="{
+      contentdown: '上拉加载更多',
+    }" @tap="loadMore" />
     <s-empty v-if="state.pagination.total === 0" icon="/static/soldout-empty.png" text="暂无产品" />
   </s-layout>
 </template>
 
 <script setup>
-  import { reactive, ref } from 'vue';
-  import { onLoad, onReachBottom } from '@dcloudio/uni-app';
-  import sheep from '@/sheep';
-  import _ from 'lodash-es';
-  import { resetPagination } from '@/sheep/util';
-  import SpuApi from '@/sheep/api/product/spu';
-  import OrderApi from '@/sheep/api/trade/order';
-  import { appendSettlementProduct } from '@/sheep/hooks/useGoods';
-
-  const sys_navBar = sheep.$platform.navbar;
-  const emits = defineEmits(['close', 'change']);
-
-  const state = reactive({
-    pagination: {
-      list: [],
-      total: 0,
-      pageNo: 1,
-      pageSize: 6,
+import { reactive, ref } from 'vue';
+import { onLoad, onReachBottom } from '@dcloudio/uni-app';
+import sheep from '@/sheep';
+import _ from 'lodash-es';
+import { resetPagination } from '@/sheep/util';
+import SpuApi from '@/sheep/api/product/spu';
+import OrderApi from '@/sheep/api/trade/order';
+import { appendSettlementProduct } from '@/sheep/hooks/useGoods';
+
+const sys_navBar = sheep.$platform.navbar;
+const emits = defineEmits(['close', 'change']);
+
+const state = reactive({
+  pagination: {
+    list: [],
+    total: 0,
+    pageNo: 1,
+    pageSize: 15,
+  },
+  currentSort: undefined,
+  currentOrder: undefined,
+  currentTab: 0, // 当前选中的 tab
+  curFilter: 0, // 当前选中的 list 筛选项
+  showFilter: false,
+  iconStatus: false, // true - 单列布局;false - 双列布局
+  keyword: '',
+  categoryId: 0,
+  tabList: [
+    {
+      name: '综合推荐',
+      list: [
+        {
+          label: '综合推荐',
+        },
+        {
+          label: '价格升序',
+          sort: 'price',
+          order: true,
+        },
+        {
+          label: '价格降序',
+          sort: 'price',
+          order: false,
+        },
+      ],
     },
-    currentSort: undefined,
-    currentOrder: undefined,
-    currentTab: 0, // 当前选中的 tab
-    curFilter: 0, // 当前选中的 list 筛选项
-    showFilter: false,
-    iconStatus: false, // true - 单列布局;false - 双列布局
-    keyword: '',
-    categoryId: 0,
-    tabList: [
-      {
-        name: '综合推荐',
-        list: [
-          {
-            label: '综合推荐',
-          },
-          {
-            label: '价格升序',
-            sort: 'price',
-            order: true,
-          },
-          {
-            label: '价格降序',
-            sort: 'price',
-            order: false,
-          },
-        ],
-      },
-      {
-        name: '销量',
-        sort: 'salesCount',
-        order: false,
-      },
-      {
-        name: '新品优先',
-        value: 'createTime',
-        order: false,
-      },
-    ],
-    loadStatus: '',
-    leftGoodsList: [], // 双列布局 - 左侧产品
-    rightGoodsList: [], // 双列布局 - 右侧产品
-  });
-
-  // 加载瀑布流
-  let count = 0;
-  let leftHeight = 0;
-  let rightHeight = 0;
-
-  // 处理双列布局 leftGoodsList + rightGoodsList
-  function mountMasonry(height = 0, where = 'left') {
-    if (!state.pagination.list[count]) {
-      return;
-    }
-
-    if (where === 'left') {
-      leftHeight += height;
-    } else {
-      rightHeight += height;
-    }
-    if (leftHeight <= rightHeight) {
-      state.leftGoodsList.push(state.pagination.list[count]);
-    } else {
-      state.rightGoodsList.push(state.pagination.list[count]);
-    }
-    count++;
+    {
+      name: '销量',
+      sort: 'salesCount',
+      order: false,
+    },
+    {
+      name: '新品优先',
+      value: 'createTime',
+      order: false,
+    },
+  ],
+  loadStatus: '',
+  leftGoodsList: [], // 双列布局 - 左侧产品
+  rightGoodsList: [], // 双列布局 - 右侧产品
+});
+
+// 加载瀑布流
+let count = 0;
+let leftHeight = 0;
+let rightHeight = 0;
+
+// 处理双列布局 leftGoodsList + rightGoodsList
+function mountMasonry(height = 0, where = 'left') {
+  if (!state.pagination.list[count]) {
+    return;
   }
 
-  // 清空列表
-  function emptyList() {
-    resetPagination(state.pagination);
-    state.leftGoodsList = [];
-    state.rightGoodsList = [];
-    count = 0;
-    leftHeight = 0;
-    rightHeight = 0;
+  if (where === 'left') {
+    leftHeight += height;
+  } else {
+    rightHeight += height;
   }
-
-  // 搜索
-  function onSearch(e) {
-    state.keyword = e;
-    emptyList();
-    getList(state.currentSort, state.currentOrder);
+  if (leftHeight <= rightHeight) {
+    state.leftGoodsList.push(state.pagination.list[count]);
+  } else {
+    state.rightGoodsList.push(state.pagination.list[count]);
   }
-
-  // 点击
-  function onTabsChange(e) {
-    // 如果点击的是【综合推荐】,则直接展开或者收起筛选项
-    if (state.tabList[e.index].list) {
-      state.currentTab = e.index;
-      state.showFilter = !state.showFilter;
-      return;
-    }
-    state.showFilter = false;
-
-    // 如果点击的是【销量】或者【新品优先】,则直接切换 tab
-    if (e.index === state.currentTab) {
-      return;
-    }
-
+  count++;
+}
+
+// 清空列表
+function emptyList() {
+  resetPagination(state.pagination);
+  state.leftGoodsList = [];
+  state.rightGoodsList = [];
+  count = 0;
+  leftHeight = 0;
+  rightHeight = 0;
+}
+
+// 搜索
+function onSearch(e) {
+  state.keyword = e;
+  emptyList();
+  getList(state.currentSort, state.currentOrder);
+}
+
+// 点击
+function onTabsChange(e) {
+  // 如果点击的是【综合推荐】,则直接展开或者收起筛选项
+  if (state.tabList[e.index].list) {
     state.currentTab = e.index;
-    state.currentSort = e.sort;
-    state.currentOrder = e.order;
-    emptyList();
-    getList(e.sort, e.order);
+    state.showFilter = !state.showFilter;
+    return;
   }
+  state.showFilter = false;
 
-  // 点击 tab 的 list 筛选项
-  const onFilterItem = (val) => {
-    // 如果点击的是当前的筛选项,则直接收起筛选项,不要加载数据
-    // 这里选择 tabList[0] 的原因,是目前只有它有 list
-    if (
-      state.currentSort === state.tabList[0].list[val].sort &&
-      state.currentOrder === state.tabList[0].list[val].order
-    ) {
-      state.showFilter = false;
-      return;
-    }
-    state.showFilter = false;
-
-    // 设置筛选条件
-    state.curFilter = val;
-    state.tabList[0].name = state.tabList[0].list[val].label;
-    state.currentSort = state.tabList[0].list[val].sort;
-    state.currentOrder = state.tabList[0].list[val].order;
-    // 清空 + 加载数据
-    emptyList();
-    getList();
-  };
-
-  async function getList() {
-    state.loadStatus = 'loading';
-    const { code, data } = await SpuApi.getSpuPage({
-      pageNo: state.pagination.pageNo,
-      pageSize: state.pagination.pageSize,
-      sortField: state.currentSort,
-      sortAsc: state.currentOrder,
-      categoryId: state.categoryId,
-      keyword: state.keyword,
-    });
-    if (code !== 0) {
-      return;
-    }
-    // 拼接结算信息(营销)
-    await OrderApi.getSettlementProduct(data.list.map((item) => item.id).join(',')).then((res) => {
-      if (res.code !== 0) {
-        return;
-      }
-      appendSettlementProduct(data.list, res.data);
-    });
-    state.pagination.list = _.concat(state.pagination.list, data.list);
-    state.pagination.total = data.total;
-    state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';
-    mountMasonry();
+  // 如果点击的是【销量】或者【新品优先】,则直接切换 tab
+  if (e.index === state.currentTab) {
+    return;
   }
 
-  // 加载更多
-  function loadMore() {
-    if (state.loadStatus === 'noMore') {
-      return;
-    }
-    state.pagination.pageNo++;
-    getList(state.currentSort, state.currentOrder);
+  state.currentTab = e.index;
+  state.currentSort = e.sort;
+  state.currentOrder = e.order;
+  emptyList();
+  getList(e.sort, e.order);
+}
+
+// 点击 tab 的 list 筛选项
+const onFilterItem = (val) => {
+  // 如果点击的是当前的筛选项,则直接收起筛选项,不要加载数据
+  // 这里选择 tabList[0] 的原因,是目前只有它有 list
+  if (
+    state.currentSort === state.tabList[0].list[val].sort &&
+    state.currentOrder === state.tabList[0].list[val].order
+  ) {
+    state.showFilter = false;
+    return;
   }
-
-  onLoad((options) => {
-    state.categoryId = options.categoryId;
-    state.keyword = options.keyword;
-    getList(state.currentSort, state.currentOrder);
+  state.showFilter = false;
+
+  // 设置筛选条件
+  state.curFilter = val;
+  state.tabList[0].name = state.tabList[0].list[val].label;
+  state.currentSort = state.tabList[0].list[val].sort;
+  state.currentOrder = state.tabList[0].list[val].order;
+  // 清空 + 加载数据
+  emptyList();
+  getList();
+};
+
+async function getList() {
+  state.loadStatus = 'loading';
+  const { code, data } = await SpuApi.getSpuPage({
+    pageNo: state.pagination.pageNo,
+    pageSize: state.pagination.pageSize,
+    sortField: state.currentSort,
+    sortAsc: state.currentOrder,
+    categoryId: state.categoryId,
+    keyword: state.keyword,
   });
-
-  // 上拉加载更多
-  onReachBottom(() => {
-    loadMore();
+  if (code !== 0) {
+    return;
+  }
+  // 拼接结算信息(营销)
+  await OrderApi.getSettlementProduct(data.list.map((item) => item.id).join(',')).then((res) => {
+    if (res.code !== 0) {
+      return;
+    }
+    appendSettlementProduct(data.list, res.data);
   });
+  state.pagination.list = _.concat(state.pagination.list, data.list);
+  state.pagination.total = data.total;
+  state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';
+  mountMasonry();
+}
+
+// 加载更多
+function loadMore() {
+  if (state.loadStatus === 'noMore') {
+    return;
+  }
+  state.pagination.pageNo++;
+  getList(state.currentSort, state.currentOrder);
+}
+
+onLoad((options) => {
+  state.categoryId = options.categoryId;
+  state.keyword = options.keyword;
+  getList(state.currentSort, state.currentOrder);
+});
+
+// 上拉加载更多
+onReachBottom(() => {
+  loadMore();
+});
 </script>
 
 <style lang="scss" scoped>
-  .goods-list-box {
-    width: 50%;
-    box-sizing: border-box;
-
-    .left-list {
-      margin-right: 10rpx;
-      margin-bottom: 20rpx;
-    }
+.goods-list-box {
+  width: 50%;
+  box-sizing: border-box;
 
-    .right-list {
-      margin-left: 10rpx;
-      margin-bottom: 20rpx;
-    }
+  .left-list {
+    margin-right: 10rpx;
+    margin-bottom: 20rpx;
   }
 
-  .goods-box {
-    &:nth-last-of-type(1) {
-      margin-bottom: 0 !important;
-    }
+  .right-list {
+    margin-left: 10rpx;
+    margin-bottom: 20rpx;
+  }
+}
 
-    &:nth-child(2n) {
-      margin-right: 0;
-    }
+.goods-box {
+  &:nth-last-of-type(1) {
+    margin-bottom: 0 !important;
   }
 
-  .list-icon {
-    width: 80rpx;
+  &:nth-child(2n) {
+    margin-right: 0;
+  }
+}
 
-    .sicon-goods-card {
-      font-size: 40rpx;
-    }
+.list-icon {
+  width: 80rpx;
 
-    .sicon-goods-list {
-      font-size: 40rpx;
-    }
+  .sicon-goods-card {
+    font-size: 40rpx;
   }
 
-  .goods-card {
-    margin-left: 20rpx;
+  .sicon-goods-list {
+    font-size: 40rpx;
   }
+}
 
-  .list-filter-tabs {
-    background-color: #fff;
-  }
+.goods-card {
+  margin-left: 20rpx;
+}
 
-  .filter-list-box {
-    padding: 28rpx 52rpx;
+.list-filter-tabs {
+  background-color: #fff;
+}
 
-    .filter-item {
-      font-size: 28rpx;
-      font-weight: 500;
-      color: #333333;
-      line-height: normal;
-      margin-bottom: 24rpx;
+.filter-list-box {
+  padding: 28rpx 52rpx;
 
-      &:nth-last-child(1) {
-        margin-bottom: 0;
-      }
-    }
+  .filter-item {
+    font-size: 28rpx;
+    font-weight: 500;
+    color: #333333;
+    line-height: normal;
+    margin-bottom: 24rpx;
 
-    .filter-item-active {
-      color: var(--ui-BG-Main);
+    &:nth-last-child(1) {
+      margin-bottom: 0;
     }
   }
 
-  .tab-item {
-    height: 50px;
-    position: relative;
-    z-index: 11;
+  .filter-item-active {
+    color: var(--ui-BG-Main);
+  }
+}
 
-    .tab-title {
-      font-size: 30rpx;
-    }
+.tab-item {
+  height: 50px;
+  position: relative;
+  z-index: 11;
 
-    .cur-tab-title {
-      font-weight: $font-weight-bold;
-    }
+  .tab-title {
+    font-size: 30rpx;
+  }
 
-    .tab-line {
-      width: 60rpx;
-      height: 6rpx;
-      border-radius: 6rpx;
-      position: absolute;
-      left: 50%;
-      transform: translateX(-50%);
-      bottom: 10rpx;
-      background-color: var(--ui-BG-Main);
-      z-index: 12;
-    }
+  .cur-tab-title {
+    font-weight: $font-weight-bold;
+  }
+
+  .tab-line {
+    width: 60rpx;
+    height: 6rpx;
+    border-radius: 6rpx;
+    position: absolute;
+    left: 50%;
+    transform: translateX(-50%);
+    bottom: 10rpx;
+    background-color: var(--ui-BG-Main);
+    z-index: 12;
   }
+}
 </style>

+ 8 - 7
pages/index/category.vue

@@ -4,7 +4,7 @@
     <view class="s-category">
       <view :style="[{ height: pageHeight + 'px' }]" class="three-level-wrap ss-flex ss-col-top">
         <!-- 产品大纲(左) -->
-        <scroll-view :style="[{ height: pageHeight + 'px' }]" class="side-menu-wrap" scroll-y>
+        <!-- <scroll-view :style="[{ height: pageHeight + 'px' }]" class="side-menu-wrap" scroll-y>
           <view v-for="(item, index) in state.categoryList" :key="item.id"
             :class="[{ 'menu-item-active': index === state.activeMenu }]" class="menu-item ss-flex"
             @tap="onMenu(index)">
@@ -12,14 +12,14 @@
               {{ item.name }}
             </view>
           </view>
-        </scroll-view>
+        </scroll-view> -->
         <!-- 产品大纲(右) -->
         <scroll-view v-if="state.categoryList?.length" :style="[{ height: pageHeight + 'px' }]" class="goods-list-box"
           scroll-y @scrolltolower="handleScrollToLower">
-          <image v-if="state.categoryList[state.activeMenu].picUrl"
-            :src="sheep.$url.cdn(state.categoryList[state.activeMenu].picUrl)" class="banner-img" mode="widthFix" />
-          <first-one v-if="state.style === 'first_one'" :pagination="state.pagination" />
-          <first-two v-if="state.style === 'first_two'" :pagination="state.pagination" />
+          <!-- <image v-if="state.categoryList[state.activeMenu].picUrl"
+            :src="sheep.$url.cdn(state.categoryList[state.activeMenu].picUrl)" class="banner-img" mode="widthFix" /> -->
+          <!-- <first-one v-if="state.style === 'first_one'" :pagination="state.pagination" /> -->
+          <!-- <first-two v-if="state.style === 'first_two'" :pagination="state.pagination" /> -->
           <second-one v-if="state.style === 'second_one'" :activeMenu="state.activeMenu" :data="state.categoryList" />
           <uni-load-more v-if="
             (state.style === 'first_one' || state.style === 'first_two') &&
@@ -203,7 +203,8 @@ function handleScrollToLower() {
 
     .goods-list-box {
       background-color: #fff;
-      width: calc(100vw - 100px);
+      width: 100%;
+      // width: calc(100vw - 100px);
       padding: 10px;
     }
 

+ 27 - 15
pages/index/components/second-one.vue

@@ -1,17 +1,19 @@
 <!-- 分类展示:second-one 风格  -->
 <template>
-  <view>
+  <view v-for="item in props.data" :key="item.id">
     <!-- 一级分类的名字 -->
-    <view class="title-box ss-flex ss-col-center ss-row-center ss-p-b-30">
+    <!-- <view class="title-box ss-flex ss-col-center ss-row-center ss-p-b-30">
       <view class="title-line-left" />
-      <view class="title-text ss-p-x-20">{{ props.data[activeMenu].name }}</view>
+      <view class="title-text ss-p-x-20">{{ item.name }}</view>
       <view class="title-line-right" />
-    </view>
+    </view> -->
+    <titleCom :title="item.name" />
     <!-- 二级分类的名字 -->
     <view class="goods-item-box ss-flex ss-flex-wrap ss-p-b-20">
-      <view class="goods-item" v-for="item in props.data[activeMenu].children" :key="item.id" @tap="
+      <view class="goods-item" v-for="item in item.children" :key="item.id" @tap="
         sheep.$router.go('/pages/goods/list', {
           categoryId: item.id,
+          type: props.activeMenu,
         })
         ">
         <image class="goods-img" :src="item.picUrl" mode="aspectFill" />
@@ -25,7 +27,7 @@
 
 <script setup>
 import sheep from '@/sheep';
-
+import titleCom from '@/pages/index/com/title.vue'
 const props = defineProps({
   data: {
     type: Object,
@@ -46,29 +48,39 @@ const props = defineProps({
   }
 }
 
+.goods-item-box {
+  padding: 10px;
+}
+
 .goods-item {
-  width: calc((100% - 50px) / 3);
-  margin-right: 10px;
-  margin-bottom: 10px;
-  background: #eee;
+  width: calc((100% - 20px) / 3);
+  margin-right: 8px;
+  margin-bottom: 16px;
+  background: #fff;
   box-shadow: 0px 0px 20rpx 8rpx rgba(199, 199, 199, 0.22);
   border-radius: 20rpx;
-  padding: 10rpx;
+  padding: 16rpx 10px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
 
   &:nth-of-type(3n) {
     margin-right: 0;
   }
 
   .goods-img {
-    width: calc((100vw - 180px) / 3);
-    height: calc((100vw - 180px) / 3);
+    // width: calc((100vw - 180px) / 3);
+    // height: calc((100vw - 180px) / 3);
+    width: 24px;
+    height: 24px;
+    flex-shrink: 0;
     border-radius: 20rpx;
 
   }
 
   .goods-title {
-    font-size: 26rpx;
-    font-weight: bold;
+    font-size: 28rpx;
+    // font-weight: bold;
     color: #333333;
     line-height: 40rpx;
     text-align: center;

+ 2 - 1
sheep/components/s-tabbar/s-tabbar.vue

@@ -5,7 +5,8 @@
       :customStyle="tabbarStyle">
       <su-tabbar-item v-for="(item, index) in tabbar.items" :key="item.text" :text="item.text" :name="item.url"
         :badge="item.badge" :dot="item.dot" :badgeStyle="{ ...tabbar.badgeStyle, paddingTop: '20px' }"
-        :isCenter="getTabbarCenter(index)" :centerImage="sheep.$url.cdn(item.iconUrl)" @tap="routerGo(item)">
+        :isCenter="getTabbarCenter(index)" :centerImage="sheep.$url.cdn(item.iconUrl)"
+        @tap="sheep.$router.go(item.url)">
         <template v-slot:active-icon>
           <image class="u-page__item__slot-icon" :src="sheep.$url.cdn(item.activeIconUrl)"></image>
         </template>

+ 5 - 5
sheep/hooks/useWebSocket.js

@@ -20,9 +20,9 @@ export function useWebSocket(opt) {
     destroy: false, // 是否销毁
     pingTimeout: null, // 心跳检测定时器
     reconnectTimeout: null, // 重连定时器ID的属性
-    onConnected: () => {}, // 连接成功时触发
-    onClosed: () => {}, // 连接关闭时触发
-    onMessage: (data) => {}, // 收到消息
+    onConnected: () => { }, // 连接成功时触发
+    onClosed: () => { }, // 连接关闭时触发
+    onMessage: (data) => { }, // 收到消息
   });
   const SocketTask = ref(null); // SocketTask 由 uni.connectSocket() 接口创建
 
@@ -136,8 +136,8 @@ export function useWebSocket(opt) {
     copyValueToTarget(options, opt);
     SocketTask.value = uni.connectSocket({
       url: options.url,
-      complete: () => {},
-      success: () => {},
+      complete: () => { },
+      success: () => { },
     });
     initEventListeners();
   };