Przeglądaj źródła

聊天功能
1. 聊天用户列表
2. 聊天界面

shizhongqi 11 miesięcy temu
rodzic
commit
76274c04a1

+ 688 - 0
hdApp/src/admin/chat/chatPage.vue

@@ -0,0 +1,688 @@
+<template>
+  <view class="chat-container">
+    <!-- 连接状态指示器 -->
+    <view class="connection-status" v-if="!isConnected">
+      <text class="status-text">{{ reconnectCount > 0 ? '重连中...' : '连接中...' }}</text>
+    </view>
+
+    <!-- 聊天消息列表 -->
+    <scroll-view class="message-list" :scroll-top="scrollTop" scroll-y :scroll-into-view="scrollIntoView" scroll-with-animation>
+      <view class="message-item" v-for="(message, index) in messageList" :key="index" :id="'message-' + index">
+        <!-- 左侧消息(商家/其他用户) -->
+        <view class="message-left" v-if="!message.isMine && !message.shopId">
+          <view class="avatar-wrapper">
+            <image class="avatar" :src="message.avatar" mode="aspectFill" />
+          </view>
+          <view class="message-content-wrapper">
+            <view class="username" v-if="isMultiUser">{{ message.username }}</view>
+            <view class="message-bubble left-bubble">
+              <text class="message-text">{{ message.message }}</text>
+            </view>
+            <view class="message-time">{{ message.time }}</view>
+          </view>
+        </view>
+
+        <!-- 右侧消息(我的) -->
+        <view class="message-right" v-if="message.isMine || message.shopId">
+          <view class="message-content-wrapper">
+            <view class="username align-right" v-if="isMultiUser">{{ message.username }}</view>
+            <view class="message-bubble right-bubble">
+              <text class="message-text">{{ message.message }}</text>
+            </view>
+            <view class="message-time align-right">{{ message.time }}</view>
+          </view>
+          <view class="avatar-wrapper">
+            <image class="avatar" :src="message.avatar" mode="aspectFill" />
+          </view>
+        </view>
+      </view>
+
+      <!-- 空状态 -->
+      <view class="empty-state" v-if="messageList.length === 0">
+        <text class="empty-text">暂无聊天记录</text>
+      </view>
+    </scroll-view>
+
+    <!-- 输入区域 -->
+    <view class="input-area">
+      <view class="input-wrapper">
+        <input
+          class="message-input"
+          v-model="inputText"
+          placeholder="请输入消息..."
+          :placeholder-style="placeholderStyle"
+          @confirm="sendMessage"
+          confirm-type="send"
+        />
+        <button class="send-button" :class="{ 'send-active': inputText.trim() }" @click="sendMessage" :disabled="!inputText.trim()">发送</button>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getChatHistory } from "@/api/chat";
+export default {
+  data() {
+    return {
+      inputText: '',
+      scrollTop: 0,
+      scrollIntoView: '',
+      placeholderStyle: 'color: #ccc; font-size: 28upx;',
+      // 是否多人聊天
+      isMultiUser: false,
+      // 当前用户信息
+      currentUser: {
+        customId: 0,
+        shopId: 0,
+        id: 0,
+        username: '',
+        avatar: ''
+      },
+      // 聊天消息列表
+      messageList: [],
+      // WebSocket相关
+      socket: null,
+      isConnected: false,
+      room: '',
+      reconnectCount: 0,
+      maxReconnectCount: 3,
+      isManualDisconnect: false // 添加标志位:是否主动断开连接
+    };
+  },
+  onLoad(options) {
+    console.log('---------- options: ', options);
+    // 根据传入参数判断是否多人聊天
+    if (options.isMultiUser) {
+      this.isMultiUser = options.isMultiUser === 'true';
+    }
+
+    // 如果传入了用户信息,更新当前用户
+    if (options.customId) {
+      this.currentUser.customId = options.customId;
+    }
+    if (options.shopId) {
+      this.currentUser.shopId = options.shopId;
+    }
+    if (options.name) {
+      this.currentUser.username = options.name;
+    }
+    if (options.shopId) {
+      // 是门店,则使用shopId
+      this.currentUser.id = options.shopId;
+    }
+    if (options.avatar) {
+      this.currentUser.avatar = options.avatar;
+    }
+
+    // 获取房间号
+    if (options.room) {
+      this.room = options.room;
+    } else {
+      if (this.currentUser.customId && this.currentUser.shopId) {
+        this.room = 'custom_id-' + this.currentUser.customId + 'AND' + 'shop_id-' + this.currentUser.shopId;
+      } else {
+        // 如果没有传入房间号,使用默认值或生成一个
+        this.room = 'default_room';
+      }
+    }
+    console.log('---------- this.room: ', this.room);
+
+    // 加载聊天记录
+    this.loadChatHistory();
+
+    // 连接WebSocket
+    this.connectWebSocket();
+  },
+  onReady() {
+    // 页面加载完成后滚动到底部
+    this.$nextTick(() => {
+      this.scrollToBottom();
+    });
+  },
+  onUnload() {
+    // 页面卸载时关闭WebSocket连接
+    this.isManualDisconnect = true; // 标记为主动断开
+    this.disconnectWebSocket();
+  },
+  onHide() {
+    // 页面隐藏时可以选择性地关闭连接(根据业务需求)
+    // this.disconnectWebSocket()
+  },
+  onShow() {
+    // 页面显示时重新连接(如果之前断开了)
+    // if (!this.isConnected && this.room) {
+    //   this.connectWebSocket();
+    // }
+  },
+  methods: {
+    init() {},
+    // 加载聊天历史记录
+    loadChatHistory() {
+      // 在实际应用中,这里应该从服务器加载历史聊天记录
+      // 可以通过API请求获取该房间的历史消息
+      getChatHistory({
+        roomName: this.room,
+        userId: this.currentUser.id,
+      }).then((res) => {
+        if (res.code == 1 && res.data) {
+          this.messageList = res.data;
+          this.$nextTick(() => {
+            this.scrollToBottom();
+          });
+        }
+      });
+
+      // 临时保留一些示例数据用于开发测试
+      // if (process.env.NODE_ENV === "development") {
+      //   const sampleMessages = [
+      //     {
+      //       id: "msg_001",
+      //       content: "您好,请问有什么可以帮助您的吗?",
+      //       time: "14:30",
+      //       isMine: false,
+      //       username: "花店客服",
+      //       avatar: "https://cdn.uviewui.com/uview/demo/user/2.jpg",
+      //     },
+      //     {
+      //       id: "msg_002",
+      //       content: "我想订购一束玫瑰花",
+      //       time: "14:31",
+      //       isMine: true,
+      //       username: "我",
+      //       avatar: "https://cdn.uviewui.com/uview/demo/user/1.jpg",
+      //     },
+      //   ];
+      //   this.messageList = sampleMessages;
+      // }
+    },
+
+    // 获取主机地址
+    getHost() {
+      // 在实际项目中,这里应该返回你的WebSocket服务器地址
+      // 可以从配置文件中读取
+
+      // #ifdef H5
+      return window.location.host;
+      // #endif
+
+      // #ifdef MP-WEIXIN || APP-PLUS
+      // 小程序和App环境下,需要配置具体的服务器地址
+      return '192.168.10.57:8080'; // 替换为实际的服务器地址
+      // #endif
+    },
+
+    // 连接WebSocket
+    connectWebSocket() {
+      if (!this.room) {
+        console.error('房间号不能为空');
+        uni.showToast({
+          title: '房间号不能为空',
+          icon: 'none'
+        });
+        return;
+      }
+
+      try {
+        // 构建WebSocket URL
+        const wsUrl = `ws://${this.getHost()}/room?room=${this.room}`;
+
+        // 创建WebSocket连接
+        this.socket = uni.connectSocket({
+          url: wsUrl,
+          success: () => {
+            console.log('WebSocket连接创建成功');
+          },
+          fail: (error) => {
+            console.error('WebSocket连接创建失败:', error);
+            this.handleConnectionError();
+          }
+        });
+
+        // 监听WebSocket连接打开
+        this.socket.onOpen(() => {
+          console.log('WebSocket连接已打开');
+          this.isConnected = true;
+          this.reconnectCount = 0;
+          this.isManualDisconnect = false; // 连接成功时重置标志位
+
+          // uni.showToast({
+          //   title: "连接成功",
+          //   icon: "success",
+          //   duration: 1500,
+          // });
+        });
+
+        // 监听WebSocket消息
+        this.socket.onMessage((event) => {
+          this.handleWebSocketMessage(event.data);
+        });
+
+        // 监听WebSocket连接关闭
+        this.socket.onClose(() => {
+          console.log('WebSocket连接已关闭');
+          this.isConnected = false;
+
+          // 只有在非主动断开的情况下才尝试重连
+          if (!this.isManualDisconnect && this.reconnectCount < this.maxReconnectCount) {
+            setTimeout(() => {
+              this.reconnectWebSocket();
+            }, 3000);
+          }
+        });
+
+        // 监听WebSocket错误
+        this.socket.onError((error) => {
+          console.error('WebSocket连接错误:', error);
+          this.isConnected = false;
+          this.handleConnectionError();
+        });
+      } catch (error) {
+        console.error('创建WebSocket连接异常:', error);
+        this.handleConnectionError();
+      }
+    },
+
+    // 断开WebSocket连接
+    disconnectWebSocket() {
+      if (this.socket) {
+        this.isManualDisconnect = true; // 标记为主动断开
+        this.socket.close();
+        this.socket = null;
+        this.isConnected = false;
+      }
+    },
+
+    // 重连WebSocket
+    reconnectWebSocket() {
+      if (this.reconnectCount < this.maxReconnectCount) {
+        this.reconnectCount++;
+        console.log(`正在尝试第${this.reconnectCount}次重连...`);
+
+        // uni.showToast({
+        //   title: `重连中(${this.reconnectCount}/${this.maxReconnectCount})`,
+        //   icon: 'loading',
+        //   duration: 1500
+        // });
+
+        this.connectWebSocket();
+      } else {
+        uni.showToast({
+          title: '连接失败,请检查网络',
+          icon: 'none',
+          duration: 3000
+        });
+      }
+    },
+
+    // 处理WebSocket消息
+    handleWebSocketMessage(data) {
+      console.log('---------- data: ', data);
+      try {
+        // 尝试解析JSON,如果失败则认为是纯文本消息
+        let messageData;
+        try {
+          const temp = JSON.parse(data);
+          const msg = temp.message ? temp.message : temp; // TODO 测试兼容
+          messageData = JSON.parse(msg);
+        } catch (parseError) {
+          // 如果不是JSON格式,可能是纯文本消息,暂时忽略或创建简单消息对象
+          console.log('收到非JSON格式消息:', data);
+          try {
+            messageData = JSON.parse(data);
+          } catch (error) {
+            console.error('解析WebSocket消息失败:', error, data);
+          }
+        }
+
+        // 检查是否是自己发送的消息,避免重复显示
+        //  const isMyMessage = messageData.username === this.currentUser.username && messageData.userId === this.currentUser.id;
+        // 如果是自己发送的消息,不重复添加(因为发送时已经添加了)
+        //   if (isMyMessage) {
+        //     console.log("忽略自己发送的消息回显");
+        //     return;
+        //   }
+
+        // 创建消息对象
+        const newMessage = {
+          id: 'msg_' + Date.now() + '_' + Math.random(),
+          message: messageData.message || messageData || '',
+          time: this.getCurrentTime(),
+          isMine: false, // 既然不是自己的消息,就标记为false
+          username: messageData.name || messageData.username || '未知用户',
+          avatar: messageData.avatar || this.$constant.imgUrl + '/retail/default-img.png'
+        };
+
+        console.log('收到 ---------- newMessage: ', newMessage);
+        // 添加到消息列表
+        this.messageList.push(newMessage);
+
+        // 滚动到底部
+        this.$nextTick(() => {
+          this.scrollToBottom();
+        });
+      } catch (error) {
+        console.error('解析WebSocket消息失败:', error, data);
+      }
+    },
+
+    // 发送消息
+    sendMessage() {
+      if (!this.inputText.trim()) {
+        return;
+      }
+
+      if (!this.isConnected) {
+        uni.showToast({
+          title: 'WebSocket未连接',
+          icon: 'none'
+        });
+        return;
+      }
+
+      const messageContent = this.inputText.trim();
+
+      try {
+        // 立即在本地显示发送的消息(右侧)
+        const myMessage = {
+          id: 'msg_' + Date.now() + '_local',
+          message: messageContent,
+          time: this.getCurrentTime(),
+          isMine: true,
+          username: this.currentUser.username,
+          avatar: this.currentUser.avatar
+        };
+
+        // 添加到消息列表
+        this.messageList.push(myMessage);
+
+        // 清空输入框
+        this.inputText = '';
+
+        // 滚动到底部
+        this.$nextTick(() => {
+          this.scrollToBottom();
+        });
+
+        // 构建要发送的消息数据
+        const messageData = {
+          message: messageContent,
+          username: this.currentUser.username,
+          shopId: this.currentUser.shopId, // 门店使用shopId,客户使用customId
+          avatar: this.currentUser.avatar,
+          timestamp: Date.now()
+        };
+
+        const json = JSON.stringify(messageData);
+        console.log('send ---------- json: ', json);
+        // 发送到WebSocket服务器
+        this.socket.send({
+          data: json,
+          success: () => {
+            console.log('消息发送成功');
+          },
+          fail: (error) => {
+            console.error('消息发送失败:', error);
+            uni.showToast({
+              title: '发送失败',
+              icon: 'none'
+            });
+            // 发送失败时,可以考虑移除刚添加的消息或标记为失败
+          }
+        });
+      } catch (error) {
+        console.error('发送消息异常:', error);
+        uni.showToast({
+          title: '发送异常',
+          icon: 'none'
+        });
+      }
+    },
+
+    // 处理连接错误
+    handleConnectionError() {
+      uni.showToast({
+        title: '连接失败',
+        icon: 'none',
+        duration: 2000
+      });
+    },
+
+    // 滚动到底部
+    scrollToBottom() {
+      if (this.messageList.length > 0) {
+        this.scrollIntoView = 'message-' + (this.messageList.length - 1);
+      }
+    },
+
+    // 获取当前时间
+    getCurrentTime() {
+      const now = new Date();
+      const hours = String(now.getHours()).padStart(2, '0');
+      const minutes = String(now.getMinutes()).padStart(2, '0');
+      return `${hours}:${minutes}`;
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.chat-container {
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+  background-color: #f5f5f5;
+}
+
+.connection-status {
+  background-color: #ff9800;
+  color: #ffffff;
+  text-align: center;
+  padding: 16upx;
+  font-size: 24upx;
+
+  .status-text {
+    color: #ffffff;
+  }
+}
+
+.message-list {
+  flex: 1;
+  padding: 20upx;
+  overflow-y: auto;
+}
+
+.message-item {
+  margin-bottom: 30upx;
+
+  &:last-child {
+    margin-bottom: 0;
+  }
+}
+
+.message-left {
+  display: flex;
+  justify-content: flex-start;
+  align-items: flex-start;
+}
+
+.message-right {
+  display: flex;
+  justify-content: flex-end;
+  align-items: flex-start;
+}
+
+.avatar-wrapper {
+  flex-shrink: 0;
+  margin: 0 20upx;
+}
+
+.avatar {
+  width: 80upx;
+  height: 80upx;
+  border-radius: 50%;
+  background-color: #e0e0e0;
+}
+
+.message-content-wrapper {
+  max-width: 520upx;
+  min-width: 100upx;
+}
+
+.username {
+  font-size: 24upx;
+  color: $fontColor3;
+  margin-bottom: 8upx;
+
+  &.align-right {
+    text-align: right;
+  }
+}
+
+.message-bubble {
+  padding: 20upx 24upx;
+  border-radius: 16upx;
+  position: relative;
+  word-wrap: break-word;
+  word-break: break-all;
+
+  &.left-bubble {
+    background-color: #ffffff;
+    margin-left: 0;
+    border-top-left-radius: 8upx;
+
+    &::before {
+      content: '';
+      position: absolute;
+      left: -15upx;
+      top: 30upx;
+      width: 0;
+      height: 0;
+      border: 6upx solid transparent;
+      border-right-color: #ffffff;
+    }
+  }
+
+  &.right-bubble {
+    background-color: rgb(27, 193, 66);
+    margin-right: 0;
+    border-top-right-radius: 8upx;
+
+    &::before {
+      content: '';
+      position: absolute;
+      right: -15upx;
+      top: 30upx;
+      width: 0;
+      height: 0;
+      border: 6upx solid transparent;
+      border-left-color: rgb(27, 193, 66);
+    }
+
+    .message-text {
+      color: #ffffff;
+    }
+  }
+}
+
+.message-text {
+  font-size: 28upx;
+  line-height: 1.4;
+  color: $fontColorMain;
+}
+
+.message-time {
+  font-size: 20upx;
+  color: $fontColor3;
+  margin-top: 8upx;
+
+  &.align-right {
+    text-align: right;
+  }
+}
+
+.empty-state {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 300upx;
+}
+
+.empty-text {
+  font-size: 28upx;
+  color: $fontColor3;
+}
+
+.input-area {
+  background-color: #ffffff;
+  padding: 20upx;
+  border-top: 1upx solid $borderColor;
+  // 适配安全区域
+  padding-bottom: calc(20upx + env(safe-area-inset-bottom));
+}
+
+.input-wrapper {
+  display: flex;
+  align-items: center;
+  background-color: #f8f8f8;
+  border-radius: 50upx;
+  padding: 16upx 20upx;
+}
+
+.message-input {
+  flex: 1;
+  font-size: 28upx;
+  border: none;
+  background-color: transparent;
+  outline: none;
+
+  &::placeholder {
+    color: #ccc;
+  }
+}
+
+.send-button {
+  margin-left: 20upx;
+  padding: 12upx 32upx;
+  background-color: $fontColor4;
+  color: #ffffff;
+  border: none;
+  border-radius: 50upx;
+  font-size: 26upx;
+  transition: all 0.3s ease;
+
+  &.send-active {
+    background-color: $mainColor;
+  }
+
+  &:disabled {
+    background-color: $fontColor4;
+    color: #ffffff;
+  }
+
+  &::after {
+    border: none;
+  }
+}
+
+// 适配不同设备
+/* #ifdef H5 */
+.chat-container {
+  height: calc(100vh - 44px); // 减去导航栏高度
+}
+/* #endif */
+
+/* #ifdef MP-WEIXIN */
+.input-area {
+  // 微信小程序底部安全区域
+  padding-bottom: calc(20upx + env(safe-area-inset-bottom));
+}
+/* #endif */
+
+/* #ifdef APP-PLUS */
+.input-area {
+  // App端底部安全区域
+  padding-bottom: calc(20upx + env(safe-area-inset-bottom));
+}
+/* #endif */
+</style>

+ 11 - 2
hdApp/src/admin/chat/list.vue

@@ -12,8 +12,8 @@
 
     <view class="list-wrap">
       <block v-if="$util.isEmpty(list.data) === false">
-        <block v-for="(item, index) in list.data" :key="item.id || index">
-          <tui-list-cell :arrow="true" @click="pageTo({ url: '/admin/chat/chatPage', query: { id: item.id } })">
+        <block v-for="(item, index) in list.data" :key="index">
+          <tui-list-cell :arrow="true" @click="pageTo({ url: '/admin/chat/chatPage', query: {customId: item.id, shopId: getLoginInfo.shopId, name: getLoginInfo.name, account: getLoginInfo.shopId, avatar: shopAvatar } })">
             <view class="user-card">
               <view class="user-avatar">
                 <image :src="item.avatar" class="avatar-img" mode="aspectFill" />
@@ -101,6 +101,7 @@ import AppSearchModule from "@/components/module/app-search";
 import { list } from "@/mixins";
 import { getList } from "@/api/member";
 import NotLogin from "@/components/not-login";
+import {mapGetters } from "vuex";
 export default {
   name: "memebr",
   components: {
@@ -114,6 +115,7 @@ export default {
   data() {
     return {
       tabIndex: 0,
+      shopAvatar: '',
       tabs: [
         {
           name: "全部",
@@ -156,6 +158,13 @@ export default {
       uni.stopPullDownRefresh();
     }
   },
+  computed: {
+    ...mapGetters(["getLoginInfo"]),
+  },
+  onLoad(options) {
+    console.log("---------- getLoginInfo: ", this.getLoginInfo.avatar);
+    this.shopAvatar = this.getLoginInfo.avatar != '' ? this.getLoginInfo.avatar : this.$constant.imgUrl + '/retail/default-img.png';
+  },
   methods: {
     change(e) {
       if (this.tabIndex == e.index) {

+ 3 - 0
hdApp/src/api/chat/index.js

@@ -0,0 +1,3 @@
+import https from '@/plugins/luch-request_0.0.7/request'
+
+export const getChatHistory = param => https.post('/chat/chat-history', param)

+ 3 - 0
mallApp/src/api/chat/index.js

@@ -0,0 +1,3 @@
+import https from '@/plugins/luch-request_0.0.7/request'
+
+export const getChatHistory = param => https.post('/chat/chat-history', param)

+ 57 - 88
mallApp/src/pages/chat/chatPage.vue

@@ -22,7 +22,7 @@
         :id="'message-' + index"
       >
         <!-- 左侧消息(商家/其他用户) -->
-        <view class="message-left" v-if="!message.isMine">
+        <view class="message-left" v-if="!message.isMine && !message.customId">
           <view class="avatar-wrapper">
             <image class="avatar" :src="message.avatar" mode="aspectFill" />
           </view>
@@ -31,20 +31,20 @@
               message.username
             }}</view>
             <view class="message-bubble left-bubble">
-              <text class="message-text">{{ message.content }}</text>
-            </view>
+                <text class="message-text">{{ message.message }}</text>
+              </view>
             <view class="message-time">{{ message.time }}</view>
           </view>
         </view>
 
         <!-- 右侧消息(我的) -->
-        <view class="message-right" v-else>
+        <view class="message-right" v-if="message.isMine || message.customId">
           <view class="message-content-wrapper">
             <view class="username align-right" v-if="isMultiUser">{{
               message.username
             }}</view>
             <view class="message-bubble right-bubble">
-              <text class="message-text">{{ message.content }}</text>
+              <text class="message-text">{{ message.message }}</text>
             </view>
             <view class="message-time align-right">{{ message.time }}</view>
           </view>
@@ -85,6 +85,7 @@
 </template>
 
 <script>
+import { getChatHistory } from "@/api/chat";
 export default {
   data() {
     return {
@@ -109,7 +110,8 @@ export default {
       isConnected: false,
       room: "",
       reconnectCount: 0,
-      maxReconnectCount: 5,
+      maxReconnectCount: 3,
+      isManualDisconnect: false, // 添加标志位:是否主动断开连接
     };
   },
   onLoad(options) {
@@ -129,8 +131,9 @@ export default {
     if (options.name) {
       this.currentUser.username = options.name;
     }
-    if (options.account) { // 1. 如果是门店,则使用shopId
-      this.currentUser.id = options.account;
+    if (options.customId) {
+      // 如果是客户,则使用customId
+      this.currentUser.id = options.customId;
     }
     if (options.avatar) {
       this.currentUser.avatar = options.avatar;
@@ -141,7 +144,12 @@ export default {
       this.room = options.room;
     } else {
       if (this.currentUser.customId && this.currentUser.shopId) {
-        this.room = this.currentUser.customId + "_" + this.currentUser.shopId;
+        this.room =
+          "custom_id-" +
+          this.currentUser.customId +
+          "AND" +
+          "shop_id-" +
+          this.currentUser.shopId;
       } else {
         // 如果没有传入房间号,使用默认值或生成一个
         this.room = "default_room";
@@ -163,6 +171,7 @@ export default {
   },
   onUnload() {
     // 页面卸载时关闭WebSocket连接
+    this.isManualDisconnect = true; // 标记为主动断开
     this.disconnectWebSocket();
   },
   onHide() {
@@ -176,51 +185,22 @@ export default {
     // }
   },
   methods: {
-    init(){},
+    init() {},
     // 加载聊天历史记录
     loadChatHistory() {
       // 在实际应用中,这里应该从服务器加载历史聊天记录
       // 可以通过API请求获取该房间的历史消息
-
-      // 示例:
-      // uni.request({
-      //   url: `${API_BASE_URL}/chat/history`,
-      //   data: {
-      //     room: this.room,
-      //     userId: this.currentUser.id
-      //   },
-      //   success: (res) => {
-      //     if (res.data && res.data.messages) {
-      //       this.messageList = res.data.messages
-      //       this.$nextTick(() => {
-      //         this.scrollToBottom()
-      //       })
-      //     }
-      //   }
-      // })
-
-      // 临时保留一些示例数据用于开发测试
-      if (process.env.NODE_ENV === "development") {
-        const sampleMessages = [
-          {
-            id: "msg_001",
-            content: "您好,请问有什么可以帮助您的吗?",
-            time: "14:30",
-            isMine: false,
-            username: "花店客服",
-            avatar: "https://cdn.uviewui.com/uview/demo/user/2.jpg",
-          },
-          {
-            id: "msg_002",
-            content: "我想订购一束玫瑰花",
-            time: "14:31",
-            isMine: true,
-            username: "我",
-            avatar: "https://cdn.uviewui.com/uview/demo/user/1.jpg",
-          },
-        ];
-        this.messageList = sampleMessages;
-      }
+      getChatHistory({
+        roomName: this.room,
+        userId: this.currentUser.id,
+      }).then((res) => {
+        if (res.code == 1 && res.data) {
+          this.messageList = res.data;
+          this.$nextTick(() => {
+            this.scrollToBottom();
+          });
+        }
+      });
     },
 
     // 获取主机地址
@@ -270,12 +250,7 @@ export default {
           console.log("WebSocket连接已打开");
           this.isConnected = true;
           this.reconnectCount = 0;
-
-          uni.showToast({
-            title: "连接成功",
-            icon: "success",
-            duration: 1500,
-          });
+          this.isManualDisconnect = false; // 连接成功时重置标志位
         });
 
         // 监听WebSocket消息
@@ -288,8 +263,8 @@ export default {
           console.log("WebSocket连接已关闭");
           this.isConnected = false;
 
-          // 尝试重连
-          if (this.reconnectCount < this.maxReconnectCount) {
+          // 只有在非主动断开的情况下才尝试重连
+          if (!this.isManualDisconnect && this.reconnectCount < this.maxReconnectCount) {
             setTimeout(() => {
               this.reconnectWebSocket();
             }, 3000);
@@ -311,6 +286,7 @@ export default {
     // 断开WebSocket连接
     disconnectWebSocket() {
       if (this.socket) {
+        this.isManualDisconnect = true; // 标记为主动断开
         this.socket.close();
         this.socket = null;
         this.isConnected = false;
@@ -323,11 +299,11 @@ export default {
         this.reconnectCount++;
         console.log(`正在尝试第${this.reconnectCount}次重连...`);
 
-        uni.showToast({
-          title: `重连中(${this.reconnectCount}/${this.maxReconnectCount})`,
-          icon: "loading",
-          duration: 1500,
-        });
+        // uni.showToast({
+        //   title: `重连中(${this.reconnectCount}/${this.maxReconnectCount})`,
+        //   icon: "loading",
+        //   duration: 1500,
+        // });
 
         this.connectWebSocket();
       } else {
@@ -347,8 +323,6 @@ export default {
         let messageData;
         try {
           const temp = JSON.parse(data);
-          //const msg = temp.message;
-          console.log("---------- temp: ", temp);
           const msg = temp.message ? temp.message : temp; // TODO 测试兼容
           messageData = JSON.parse(msg);
         } catch (parseError) {
@@ -360,33 +334,27 @@ export default {
             console.error("解析WebSocket消息失败:", error, data);
           }
         }
-
-        console.log("---------- messageData: ", messageData);
-        console.log("---------- this.currentUser: ", this.currentUser);
         // 检查是否是自己发送的消息,避免重复显示
-        const isMyMessage =
-          messageData.username === this.currentUser.username &&
-          messageData.userId === this.currentUser.id;
-
+        // const isMyMessage = messageData.username === this.currentUser.username && messageData.userId === this.currentUser.id;
         // 如果是自己发送的消息,不重复添加(因为发送时已经添加了)
-        if (isMyMessage) {
-          console.log("忽略自己发送的消息回显");
-          return;
-        }
+        // if (isMyMessage) {
+        //   console.log("忽略自己发送的消息回显");
+        //   return;
+        // }
 
         // 创建消息对象
         const newMessage = {
           id: "msg_" + Date.now() + "_" + Math.random(),
-          content: messageData.message || messageData || "",
+          message: messageData.message || messageData || "",
           time: this.getCurrentTime(),
           isMine: false, // 既然不是自己的消息,就标记为false
           username: messageData.name || messageData.username || "未知用户",
           avatar:
             messageData.avatar ||
-            "https://cdn.uviewui.com/uview/demo/user/2.jpg",
+            this.$constant.imgUrl + "/retail/default-img.png",
         };
 
-        console.log("---------- newMessage: ", newMessage);
+        console.log("收到 ---------- newMessage: ", newMessage);
         // 添加到消息列表
         this.messageList.push(newMessage);
 
@@ -419,7 +387,7 @@ export default {
         // 立即在本地显示发送的消息(右侧)
         const myMessage = {
           id: "msg_" + Date.now() + "_local",
-          content: messageContent,
+          message: messageContent,
           time: this.getCurrentTime(),
           isMine: true,
           username: this.currentUser.username,
@@ -440,9 +408,8 @@ export default {
         // 构建要发送的消息数据
         const messageData = {
           message: messageContent,
-          name: this.currentUser.username,
           username: this.currentUser.username,
-          userId: this.currentUser.id,
+          customId: this.currentUser.customId, // 客户使用customId,门店使用shopId
           avatar: this.currentUser.avatar,
           timestamp: Date.now(),
         };
@@ -480,6 +447,8 @@ export default {
         icon: "none",
         duration: 2000,
       });
+      // 清除所有连接
+      this.disconnectWebSocket();
     },
 
     // 滚动到底部
@@ -583,13 +552,13 @@ export default {
   &.left-bubble {
     background-color: #ffffff;
     margin-left: 0;
-    border-top-left-radius: 4upx;
+    border-top-left-radius: 8upx;
 
     &::before {
       content: "";
       position: absolute;
-      left: -12upx;
-      top: 20upx;
+      left: -15upx;
+      top: 30upx;
       width: 0;
       height: 0;
       border: 6upx solid transparent;
@@ -600,13 +569,13 @@ export default {
   &.right-bubble {
     background-color: rgb(27, 193, 66);
     margin-right: 0;
-    border-top-right-radius: 4upx;
+    border-top-right-radius: 8upx;
 
     &::before {
       content: "";
       position: absolute;
-      right: -12upx;
-      top: 20upx;
+      right: -15upx;
+      top: 30upx;
       width: 0;
       height: 0;
       border: 6upx solid transparent;

+ 7 - 2
mallApp/src/pages/goods/detail.vue

@@ -118,11 +118,16 @@ export default {
       previewContent: [],
       common_title: "",
       common_previewContent: [],
+      shopInfo: {},
     };
   },
   onLoad(option) {
     this.hdId = this.option.hdId ? this.option.hdId : 0;
     this.account = this.option.account?this.option.account:0
+    if (option.shopId) {
+      console.log("---------- option: ", option);
+      this.shopInfo = option
+    }
   },
   onPageScroll(e) {
     // console.log('Scroll position:', parseInt(e.scrollTop))
@@ -135,7 +140,7 @@ export default {
     }
   },
   computed: {
-    ...mapGetters({ shopUser: "getShopUser" }),
+    ...mapGetters({ shopUser: "getShopUser", loginInfo: "getLoginInfo" }),
   },
   methods: {
     init() {
@@ -208,7 +213,7 @@ export default {
       const account = uni.getStorageSync('account') || this.account || (this.shopUser && this.shopUser.account) || '';
       this.$util.pageTo({
         url: "/pages/chat/chatPage",
-        query: { account }
+        query: { customId: this.loginInfo.id, shopId: this.shopInfo.shopId, name: this.shopInfo.name, account: this.shopInfo.account, avatar: this.shopInfo.avatar }
       });
     }
   },

+ 9 - 1
mallApp/src/pages/home/mall.vue

@@ -125,6 +125,7 @@ export default {
       categoryId: '',
       currentTabIndex: 1,
       myClass: 'fadeIn',
+      shopInfo: {},
 			shopName:'',
 			shopImg:'',
       hdId:0,
@@ -174,8 +175,14 @@ export default {
 	},
   methods: {
     toBuy(item){
+      const shopName = this.shopInfo.shopName != '首店' ? this.shopInfo.shopName || this.shopInfo.merchantName : this.shopInfo.merchantName;
+      const params = { shopId: this.shopInfo.id, name: shopName, avatar: this.shopInfo.avatar }
+      console.log("---------- params: ", params);
       let account = this.option.account?this.option.account:0
-      this.$util.pageTo({url:"/pages/goods/detail?id="+item.id+'&hdId='+this.hdId+'&categoryId='+this.categoryId+'&account='+account})
+      this.$util.pageTo({
+        url:"/pages/goods/detail",//?id="+item.id+'&hdId='+this.hdId+'&categoryId='+this.categoryId+'&account='+account // 与 query 不能同时存在
+        query: { id: item.id, hdId: this.hdId, categoryId: this.categoryId, account: account, shopId: this.shopInfo.id, name: shopName, avatar: this.shopInfo.avatar } 
+      })
     },
     buyProduct(){
       let account = this.option.account?this.option.account:0
@@ -200,6 +207,7 @@ export default {
 						let name = shopName == '首店' ? sjName : sjName+' '+shopName
 						this.shopName = name
 						this.shopImg = res.data.info.avatar
+            this.shopInfo = res.data.info
 					}
 
 					if(res.data.hd && Number(res.data.hd.id) > 0){

+ 3 - 3
mallApp/src/pages/home/recent.vue

@@ -54,7 +54,7 @@
                 </view>
                 <view class="box_4 flex-col"></view>
                 <view class="box_9">
-                  <view class="tag_2" @click="pageTo({ url: '/pages/chat/chatPage', query: {customId: loginInfo.id, shopId: item.shopId, name: item.name, account: item.shopId, avatar: item.smallAvatar } })"> <text>联系商家</text> </view>
+                  <view class="tag_2" @click="pageTo({ url: '/pages/chat/chatPage', query: {customId: item.customId, shopId: item.shopId, name: item.name, account: item.shopId, avatar: item.smallAvatar } })"> <text>联系商家</text> </view>
                   <view class="tag_2" @click="getBalanceChange(item)"> <text>余额变动</text> </view>
                   <view class="tag_2" @click="getSettleList(item)"> <text>结账记录</text> </view>
                   <view class="tag_2" @click="getBuyList(item)"> <text>买花记录</text> </view>
@@ -206,8 +206,8 @@ export default {
       this.pageTo({ url:'/pages/home/category?id='+item.id+'&account='+item.shopId})
     },
     tj(item){
-      //this.pageTo({ url:'/pages/goods/mall?id='+item.id+'&account='+item.shopId})
-      this.pageTo({ url:'/pages/goods/picGoods?id='+item.id+'&account='+item.shopId})
+      this.pageTo({ url:'/pages/home/mall?id='+item.id+'&account='+item.shopId})
+      //this.pageTo({ url:'/pages/goods/picGoods?id='+item.id+'&account='+item.shopId})
     },
     init () {
       this.getMyHdList()