Selaa lähdekoodia

聊天页面修改与优化

shizhongqi 11 kuukautta sitten
vanhempi
commit
8e316589c1

+ 224 - 148
hdApp/src/admin/chat/chatPage.vue

@@ -2,7 +2,7 @@
   <view class="chat-container">
     <!-- 连接状态指示器 -->
     <view class="connection-status" v-if="!isConnected && !isManualDisconnect">
-      <text class="status-text">{{ '连接中...' }}</text>
+      <text class="status-text">连接已断开,请退出重新进入</text>
     </view>
 
     <!-- 聊天消息列表 -->
@@ -18,19 +18,14 @@
       <view class="load-more" v-if="showLoadMore" @click="loadMoreMessages">
         <text class="load-more-text">加载更多</text>
       </view>
-      <view
-        class="message-item"
-        v-for="(message, index) in messageList"
-        :key="index"
-        :id="'message-' + message.id"
-      >
-        <!-- 左侧消息(商家/其他用户) -->
-        <view class="message-left" v-if="!message.isMine && !message.shopId">
+      <view class="message-item" v-for="(message, index) in messageList" :key="index" :id="'message-' + message.id">
+        <!-- 左侧消息(客户) -->
+        <view class="message-left" v-if="!message.isMine">
           <view class="avatar-wrapper">
             <image class="avatar" :src="message.avatar" mode="aspectFill" />
           </view>
 
-          <view v-if="message.messageType == 'text'|| message.messageType != 'goods'" class="message-content-wrapper">
+          <view v-if="message.messageType == 'text' || message.messageType != 'goods'" 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>
@@ -39,25 +34,33 @@
           </view>
 
           <view v-if="message.messageType == 'goods'" class="goods-content-wrapper">
-            <view class="username align-right" v-if="isMultiUser">{{message.username}}</view>
+            <view class="username align-right" v-if="isMultiUser">{{ message.username }}</view>
             <view class="goods-info left-bubble">
-              <view class="goods-main">
+              <view class="goods-main" @click="toGoodsDetail(message.goodsInfo)">
                 <image class="goods-image" :src="message.goodsInfo.goodsImg" mode="aspectFill"></image>
                 <view class="goods-details">
                   <view class="goods-name-wrapper">
-                    <text class="goods-name">{{message.goodsInfo.goodsName}}</text>
+                    <text class="goods-name">{{ message.goodsInfo.goodsName }}</text>
                   </view>
                   <!-- <view class="sales-info">
                     <text class="delivery-time">48小时发</text>
                   </view> -->
-                  <text v-if="message.goodsInfo.goodsPriceType == 1" class="goods-price">¥{{ parseFloat(message.goodsInfo.goodsPrice).toFixed(2) }}</text>
+                  <text v-if="message.goodsInfo.goodsPriceType == 1 || message.goodsInfo.goodsPriceType == 2" class="goods-price"
+                    >¥{{ parseFloat(message.goodsInfo.goodsPrice).toFixed(2) }}</text
+                  >
                 </view>
               </view>
-              <view class="goods-footer">
-              </view>
+              <view class="goods-footer" v-if="message.goodsInfo.goodsPriceType != 1"> </view>
               <view v-if="message.goodsInfo.goodsPriceType == 0 && isExpired(message.timestamp)" class="action-buttons">
                 <form @submit.prevent="createQrotedPrice(message)">
-                  <input class="price-input" type="number" v-model="message.quotedPrice" placeholder="输入价格" />
+                  <input 
+                    class="price-input"
+                    type="number" 
+                    v-model="message.quotedPrice"
+                    placeholder="输入价格"
+                    @focus="hideKeyboard"
+                    @blur="showKeyboard"
+                  />
                   <button class="action-btn buy-btn" form-type="submit">报价</button>
                 </form>
               </view>
@@ -67,8 +70,17 @@
               <!-- 报价类型显示重新报价 -->
               <view v-if="message.goodsInfo.goodsPriceType == 2 && isExpired(message.timestamp)" class="action-buttons">
                 <form @submit.prevent="createQrotedPrice(message)">
-                  <text class="goods-price" v-if="message.goodsInfo.lastPrice || message.goodsInfo.goodsPrice">¥{{parseFloat(message.goodsInfo.lastPrice || message.goodsInfo.goodsPrice).toFixed(2)}}</text>
-                  <input class="price-input" type="number" v-model="message.quotedPrice" placeholder="输入价格" />
+                  <text class="goods-price" v-if="message.goodsInfo.lastPrice || message.goodsInfo.goodsPrice"
+                    >¥{{ parseFloat(message.goodsInfo.lastPrice || message.goodsInfo.goodsPrice).toFixed(2) }}</text
+                  >
+                  <input
+                    class="price-input"
+                    type="number"
+                    v-model="message.quotedPrice"
+                    placeholder="输入价格"
+                    @focus="hideKeyboard"
+                    @blur="showKeyboard"
+                  />
                   <button class="action-btn buy-btn" form-type="submit">重新报价</button>
                 </form>
               </view>
@@ -78,9 +90,8 @@
         </view>
 
         <!-- 右侧消息(我的) -->
-        <view class="message-right" v-if="message.isMine || message.shopId">
-          
-          <view v-if="message.messageType == 'text'|| message.messageType != 'goods'" class="message-content-wrapper">
+        <view class="message-right" v-if="message.isMine">
+          <view v-if="message.messageType == 'text' || message.messageType != 'goods'" 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>
@@ -89,34 +100,35 @@
           </view>
 
           <view v-if="message.messageType == 'goods'" class="goods-content-wrapper">
-            <view class="username align-right" v-if="isMultiUser">{{message.username}}</view>
+            <view class="username align-right" v-if="isMultiUser">{{ message.username }}</view>
             <view class="goods-info right-bubble">
-              <view class="goods-main">
-                <image
-                  class="goods-image"
-                  :src="message.goodsInfo.goodsImg"
-                  mode="aspectFill"
-                />
+              <view class="goods-main" @click="toGoodsDetail(message.goodsInfo)">
+                <image class="goods-image" :src="message.goodsInfo.goodsImg" mode="aspectFill" />
                 <view class="goods-details">
                   <view class="goods-name-wrapper">
                     <text class="goods-name">{{ message.goodsInfo.goodsName }}</text>
                   </view>
+                  <text v-if="message.goodsInfo.goodsPriceType == 1 || message.goodsInfo.goodsPriceType == 2" class="goods-price"
+                    >¥{{ parseFloat(message.goodsInfo.goodsPrice).toFixed(2) }}</text
+                  >
                   <view class="sales-info">
                     <!-- <text class="delivery-time">48小时发</text> -->
                   </view>
-                  <text v-if="message.goodsInfo.goodsPriceType == 1" class="goods-price">¥{{ parseFloat(message.goodsInfo.goodsPrice).toFixed(2) }}</text>
+                  <text v-if="message.goodsInfo.goodsPriceType == 1" class="goods-price"
+                    >¥{{ parseFloat(message.goodsInfo.goodsPrice).toFixed(2) }}</text
+                  >
                 </view>
               </view>
-              <view class="goods-footer">
-                <!-- <text class="footer-text">破损包退·极速退款·7天无理由退货</text>
-                <text class="arrow">></text> -->
-              </view>
-              <view class="action-buttons">
+              <!-- <view class="goods-footer">
+                <text class="footer-text">破损包退·极速退款·7天无理由退货</text>
+                <text class="arrow">></text>
+              </view> -->
+              <!--<view class="action-buttons">
                 <button class="action-btn spec-btn" @click="toGoodsDetail(message.goodsInfo)">查看商品</button>
-                <!-- <button v-if="message.goodsInfo.goodsPriceType == 1" class="action-btn buy-btn" @click="toBuy">去购买</button> -->
-                <!-- 报价类型显示去购买 -->
-                <!-- <button v-if="message.goodsInfo.goodsPriceType == 2" class="action-btn buy-btn" @click="toBuy">去购买</button> -->
-              </view>
+                <button v-if="message.goodsInfo.goodsPriceType == 1" class="action-btn buy-btn" @click="toBuy">去购买</button>
+                //报价类型显示去购买
+                <button v-if="message.goodsInfo.goodsPriceType == 2" class="action-btn buy-btn" @click="toBuy">去购买</button>
+              </view> -->
             </view>
             <view class="message-time align-right">{{ message.time }}</view>
           </view>
@@ -133,7 +145,7 @@
     </scroll-view>
 
     <!-- 输入区域 -->
-    <view class="input-area" :style="{ 'padding-bottom': keyboardHeight > 0 ? keyboardHeight + 'px' : 'calc(20upx + env(safe-area-inset-bottom))' }">
+    <view v-show="msgInputShow" class="input-area" :style="{ 'padding-bottom': keyboardHeight > 0 ? keyboardHeight + 'px' : 'calc(20upx + env(safe-area-inset-bottom))' }">
       <view class="input-wrapper">
         <textarea
           class="message-input"
@@ -149,13 +161,9 @@
           :maxlength="500"
           :show-confirm-bar="false"
           :focus="inputFocus"
-          /><!-- @blur="onInputBlur" -->
-        <button
-          class="send-button"
-          :class="{ 'send-active': inputText.trim() }"
-          @click="sendMessage"
-          :disabled="!inputText.trim()"
-        > <!-- @touchstart.prevent="sendMessage" -->
+        /><!-- @blur="onInputBlur" -->
+        <button class="send-button" :class="{ 'send-active': inputText.trim() }" @click="sendMessage" :disabled="!inputText.trim()">
+          <!-- @touchstart.prevent="sendMessage" -->
           发送
         </button>
       </view>
@@ -164,7 +172,7 @@
 </template>
 
 <script>
-import { getChatHistory, createQuotedPrice } from '@/api/chat';
+import { getChatHistory, createQuotedPrice, getQuotedPrice } from '@/api/chat';
 import { WSSHOST } from '@/config.js';
 export default {
   data() {
@@ -182,7 +190,7 @@ export default {
         customId: 0, //客户id
         shopId: 0, //门店id
         username: '', //门店名称
-        avatar: ''
+        avatar: ''  //门店头像
       },
       // 聊天消息列表
       messageList: [],
@@ -197,7 +205,7 @@ export default {
       // 分页加载
       allMessages: [], // 存储所有从API获取的消息
       initialLoadSize: 50, // 首次加载数量
-      loadMoreSize: 100, // 每次加载更多的数量
+      loadMoreSize: 50, // 每次加载更多的数量
       showLoadMore: false, // 是否显示“加载更多”
       // 键盘相关
       keyboardHeight: 0, // 键盘高度
@@ -205,6 +213,7 @@ export default {
       inputFocus: false, // 输入框焦点
       isInputFocused: false, // 标记输入框是否聚焦
       expireTime: 23 * 60 * 60, // 商品报价有效窗口时长 -- 23小时
+      msgInputShow: true, // 消息输入框是否显示
     };
   },
   onLoad(options) {
@@ -213,7 +222,7 @@ export default {
       // 设置聊天人名称
       uni.setNavigationBarTitle({
         title: options.chatPerson
-      })
+      });
     }
 
     // 根据传入参数判断是否多人聊天
@@ -222,19 +231,28 @@ export default {
     }
 
     if (options.userId) {
-      this.currentUser.userId = options.userId;
+      this.currentUser.userId = parseInt(options.userId);
+      if(this.currentUser.userId == 0) {
+        console.error('userId 出错 ---------- this.currentUser.userId: ', this.currentUser.userId);
+      }
     }
     if (options.customId) {
-      this.currentUser.customId = options.customId;
+      this.currentUser.customId = parseInt(options.customId);
+      if(this.currentUser.customId == 0) {
+        console.error('customId 出错 ---------- this.currentUser.customId: ', this.currentUser.customId);
+      }
     }
     if (options.shopId) {
-      this.currentUser.shopId = options.shopId;
+      this.currentUser.shopId = parseInt(options.shopId);
+      if(this.currentUser.shopId == 0) {
+        console.error('shopId 出错 ---------- this.currentUser.shopId: ', this.currentUser.shopId);
+      }
     }
     if (options.name) {
       this.currentUser.username = options.name;
     }
     if (options.staffId) {
-      this.currentUser.staffId = options.staffId;
+      this.currentUser.staffId = parseInt(options.staffId);
     }
     if (options.avatar) {
       this.currentUser.avatar = options.avatar;
@@ -246,6 +264,7 @@ export default {
     } else {
       // 如果没有传入房间号,使用默认值或生成一个
       this.room = 'default_room';
+      console.error('房间号为默认房间 ---------- this.room: ', this.room);
     }
     // console.log('---------- this.room: ', this.room);
 
@@ -259,12 +278,12 @@ export default {
     this.$nextTick(() => {
       this.scrollToBottom();
     });
-     
+
     // 监听键盘弹起事件
     uni.onKeyboardHeightChange((res) => {
       this.keyboardHeight = res.height;
       this.isKeyboardShow = res.height > 0;
-      
+
       // 键盘弹起时,延迟滚动到底部
       if (this.isKeyboardShow) {
         this.$nextTick(() => {
@@ -276,11 +295,11 @@ export default {
     });
   },
   onUnload() {
-    // 页面卸载时关闭WebSocket连接
+    // 页面卸载时关闭 WebSocket连接
     this.disconnectWebSocket();
   },
   onHide() {
-    // 页面隐藏时可以选择性地关闭连接(根据业务需求)
+    // 页面隐藏时关闭 WebSocket连接(根据业务需求)
     this.disconnectWebSocket();
   },
   onShow() {
@@ -294,26 +313,29 @@ export default {
     // 加载聊天历史记录
     loadChatHistory() {
       getChatHistory({
-        roomName: this.room,
-        userId: this.currentUser.id
+        roomName: this.room
       }).then((res) => {
         if (res.code == 1 && res.data) {
           this.allMessages = res.data.map((item, index) => {
+            // 判断是否是我(本店)发送的消息
+            const isMine = item.shopId ? parseInt(item.shopId) == this.currentUser.shopId : false;
+            //console.log("---isMine---: ", isMine);
+
             const newItem = {
               ...item,
-              id: `message-${index}`, // 为每条消息添加唯一ID
+              id: `message-${index}` // 为每条消息添加唯一ID // 'msg_local_' + timestamp + '_' + Math.floor(Math.random() * 100000) + 1,
             };
             //如果消息类型为 'goods',就调用 parseGoodsMessage 并将结果存入新属性 goodsInfo
             if (newItem.messageType === 'goods') {
               newItem.goodsInfo = this.parseGoodsMessage(newItem.message);
             }
+            newItem.isMine = isMine;
+
             return newItem;
           });
 
           if (this.allMessages.length > this.initialLoadSize) {
-            this.messageList = this.allMessages.slice(
-              this.allMessages.length - this.initialLoadSize
-            );
+            this.messageList = this.allMessages.slice(this.allMessages.length - this.initialLoadSize);
             this.showLoadMore = true;
           } else {
             this.messageList = this.allMessages;
@@ -338,10 +360,7 @@ export default {
         return;
       }
       const countToLoad = Math.min(this.loadMoreSize, remainingMessagesCount);
-      const moreMessages = this.allMessages.slice(
-        remainingMessagesCount - countToLoad,
-        remainingMessagesCount
-      );
+      const moreMessages = this.allMessages.slice(remainingMessagesCount - countToLoad, remainingMessagesCount);
       const oldTopMessageId = this.messageList.length > 0 ? this.messageList[0].id : null;
       this.messageList = [...moreMessages, ...this.messageList];
       if (this.allMessages.length - (currentLoadedCount + countToLoad) <= 0) {
@@ -350,7 +369,7 @@ export default {
       if (oldTopMessageId) {
         this.$nextTick(() => {
           this.scrollWithAnimation = false; // 加载更多时不使用动画
-          this.scrollIntoView = "message-" + oldTopMessageId;
+          this.scrollIntoView = 'message-' + oldTopMessageId;
         });
       }
     },
@@ -421,7 +440,7 @@ export default {
               }
             }, 3000);
           }
-          
+
           // 尝试重新连接超过 maxReconnectCount 次才标记为未连接状态
           if (!this.isManualDisconnect && this.reconnectCount >= this.maxReconnectCount) {
             this.isConnected = false;
@@ -443,39 +462,42 @@ export default {
     // 构建WebSocket子协议参数(模拟POST body传输)
     buildWebSocketProtocols() {
       try {
-        const userId = parseInt(this.currentUser.userId); //客户登录帐户的id(不是 customId,是customer登录帐户的userId)
+        const userId = this.currentUser.userId; //客户登录帐户的id(不是 customId,是customer登录帐户的userId)
         if (isNaN(userId) || userId <= 0) {
-          this.$msg("当前用户的id出错");
+          this.$msg('当前用户的id出错');
           return;
         }
-        
-        const customId = parseInt(this.currentUser.customId);
+
+        const customId = this.currentUser.customId;
         if (isNaN(customId) || customId <= 0) {
-          this.$msg("当前用户的customId出错");
+          this.$msg('当前用户的customId出错');
           return;
         }
-        
-        const shopId = parseInt(this.currentUser.shopId);
+
+        const shopId = this.currentUser.shopId;
         if (isNaN(shopId) || shopId <= 0) {
-          this.$msg("当前用户的shopId出错");
+          this.$msg('当前用户的shopId出错');
           return;
         }
 
-        const staffId = parseInt(this.currentUser.staffId);
+        const staffId = this.currentUser.staffId;
         if (isNaN(staffId) || staffId <= 0) {
-          this.$msg("当前用户的staffId出错");
+          this.$msg('当前用户的staffId出错');
           return;
         }
 
         // 创建房间参数对象
         const roomParams = {
           u_id: userId, //不是 customId,是customer的: userId
-          c_id: parseInt(this.currentUser.customId),
-          s_id: parseInt(this.currentUser.shopId),
-          u_name: this.currentUser.username && this.currentUser.username.length > 30 ? this.currentUser.username.substring(0, 30) : this.currentUser.username,
+          c_id: this.currentUser.customId,
+          s_id: this.currentUser.shopId,
+          u_name:
+            this.currentUser.username && this.currentUser.username.length > 30
+              ? this.currentUser.username.substring(0, 30)
+              : this.currentUser.username,
           platform: this.getPlatformInfo(),
           type: 'shop', // 客户端类型:shop:门店端, customer:客户端
-          staff_id: staffId, //门店员工id
+          staff_id: staffId //门店员工id
         };
 
         // 分段传输(如果参数过长)
@@ -606,8 +628,8 @@ export default {
 
       // 增加一个保护
       if (typeof base64 !== 'string') {
-          console.error('Base64编码失败,返回非字符串值:', base64);
-          return ''; // 返回一个空字符串或其他默认值
+        console.error('Base64编码失败,返回非字符串值:', base64);
+        return ''; // 返回一个空字符串或其他默认值
       }
 
       // 转换为URL安全格式:替换 +/= 字符
@@ -727,19 +749,20 @@ export default {
           isMine = true;
         }
 
-        const messageType = messageData.messageType || "text";
+        const messageType = messageData.messageType || 'text';
         // console.log('messageType -- ', messageType);
 
         // 创建消息对象
         const newMessage = {
           id: 'msg_receive_' + Math.floor(Date.now() / 1000) + '_' + Math.floor(Math.random() * 100000) + 1,
           isMine: isMine, // 标记是否是本店发送的消息
+          //--------------------------------
           message: messageData.message || messageData || '',
           username: messageData.name || messageData.username || '未知用户',
           avatar: messageData.avatar || this.$constant.imgUrl + '/retail/default-img.png',
           time: this.getCurrentTime(), // 收到消息时间点
           timestamp: messageData.timestamp, // 消息发送时间点
-          messageType: messageType,
+          messageType: messageType
         };
 
         if (newMessage.messageType === 'goods') {
@@ -785,12 +808,13 @@ export default {
         const myMessage = {
           id: 'msg_local_' + timestamp + '_' + Math.floor(Math.random() * 100000) + 1,
           isMine: true,
+          //--------------------------------
           message: messageContent,
           username: this.currentUser.username,
           avatar: avatar,
           time: this.getCurrentTime(timestamp), // 收到消息时间点
           timestamp: timestamp, // 消息发送时间点
-          messageType: "text",
+          messageType: 'text'
         };
 
         // 添加到消息列表
@@ -814,7 +838,7 @@ export default {
           avatar: avatar,
           shopId: this.currentUser.shopId, // 发送者:门店使用shopId,客户使用customId
           timestamp: timestamp,
-          messageType: "text",
+          messageType: 'text'
         };
 
         const json = JSON.stringify(messageData);
@@ -865,20 +889,20 @@ export default {
     scrollToBottom(enableAnimation = true) {
       this.scrollWithAnimation = enableAnimation;
       if (this.messageList.length > 0) {
-        this.scrollIntoView = "message-" + this.messageList[this.messageList.length - 1].id;
+        this.scrollIntoView = 'message-' + this.messageList[this.messageList.length - 1].id;
       }
     },
 
     // 获取当前时间
-    getCurrentTime(timestamp=null) {
+    getCurrentTime(timestamp = null) {
       let now;
       if (timestamp) {
         now = new Date(timestamp * 1000);
       } else {
         now = new Date();
       }
-      const hours = String(now.getHours()).padStart(2, "0");
-      const minutes = String(now.getMinutes()).padStart(2, "0");
+      const hours = String(now.getHours()).padStart(2, '0');
+      const minutes = String(now.getMinutes()).padStart(2, '0');
       return `${hours}:${minutes}`;
     },
     // 输入框聚焦事件
@@ -888,21 +912,21 @@ export default {
       setTimeout(() => {
         this.scrollToBottom();
       }, 450);
-      this.isInputFocused = true
-      this.inputFocus = true
+      this.isInputFocused = true;
+      this.inputFocus = true;
       //this.hideExtraPanels() // 聚焦时隐藏表情和更多面板
     },
 
     // 输入框失焦事件
     onInputBlur() {
       // console.log('输入框失焦');
-      this.isInputFocused = false
+      this.isInputFocused = false;
       // this.inputFocus = false
     },
 
     // 输入内容变化事件
     onInputChange(event) {
-      this.inputText = event.detail.value
+      this.inputText = event.detail.value;
       // 可以在这里处理输入内容的变化,比如限制行数等
       const value = event.detail.value;
       // 限制最大行数为5行,避免输入框过高
@@ -916,23 +940,60 @@ export default {
     parseGoodsMessage(messageStr) {
       try {
         // 首先检查 messageStr 是否已经是对象
-        if (typeof messageStr === "object" && messageStr !== null) {
+        if (typeof messageStr === 'object' && messageStr !== null) {
           return messageStr;
         }
         // 如果是字符串,则尝试解析
-        if (typeof messageStr === "string") {
-          const parsed = JSON.parse(messageStr);
+        if (typeof messageStr === 'string') {
+          let parsed = JSON.parse(messageStr);
+          if(parsed.goodsPriceType == 0) { // 无价格类型,则要请求后端获取报价,再根据结果展示
+            // 如果没有过期,请求后端获取报价
+            const now = Math.floor(Date.now()/1000);
+            const timeDiff = now - parsed.timestamp;
+            const expireTime = 24 * 60 * 60;
+            if (timeDiff > expireTime) {
+              // console.log("parsed 过期:", parsed);
+            } else {
+              // console.log("parsed 无价格:", parsed);
+              const key = parsed.key || '';
+
+              if (key != '') {
+                getQuotedPrice({
+                  key: key,
+                }).then(res => {
+                  console.log("res", res)
+                  if (res.code == 1) {
+                    if (res.data.price != -1) {
+                      parsed.goodsPriceType = 2; // 报价类型
+                      parsed.goodsPrice = parseFloat(res.data.price).toFixed(2);
+                    }
+                  }
+                }).catch(err => {
+                  console.log("getQuotedPrice error: ", key, err)
+                })
+              }
+            }
+          } else if(parsed.goodsPriceType == 1) {
+            // console.log("parsed 有价格:", parsed);
+          } else if(parsed.goodsPriceType == 2) {
+            // console.log("parsed 重新报价:", parsed);
+          } else {
+            console.error("parsed 其他类型:", parsed);
+            return {};
+          }
           return parsed;
         }
+
+        console.error("解析商品消息失败:", messageStr);
         // 如果两者都不是,返回空对象
         return {};
       } catch (error) {
-        // console.error("解析商品消息失败:", error, messageStr);
+        console.error("解析商品消息失败:", error, messageStr);
         // 在解析失败时返回一个包含默认值的对象,以避免模板渲染错误
         return {
-          goodsImg: "",
-          goodsName: "商品信息解析失败",
-          goodsPrice: "0.00",
+          goodsImg: '',
+          goodsName: '商品信息解析失败',
+          goodsPrice: '0.00'
         };
       }
     },
@@ -941,32 +1002,32 @@ export default {
     sendMessageOfGoodsPrice(currentGoodsInfo) {
       if (!this.isConnected) {
         uni.showToast({
-          title: "WebSocket未连接",
-          icon: "none",
+          title: 'WebSocket未连接',
+          icon: 'none'
         });
         return;
       }
-      const avatar = this.currentUser.avatar || this.$constant.imgUrl + "/retail/default-img.png";
+      const avatar = this.currentUser.avatar || this.$constant.imgUrl + '/retail/default-img.png';
       const timestamp = Math.floor(Date.now() / 1000);
 
       // 构建商品信息消息体 -------------------------------- 所看到的消息体 -------------------------------
       const goodsMessage = {
-        ...currentGoodsInfo,
+        ...currentGoodsInfo
       };
       const messageContent = JSON.stringify(goodsMessage); // 所看到的消息体
 
       try {
         // 立即在本地显示发送的消息(右侧)
         const myMessage = {
-          id: "msg_local_" + timestamp + "_" + (Math.floor(Math.random() * 100000) + 1),
+          id: 'msg_local_' + timestamp + '_' + (Math.floor(Math.random() * 100000) + 1),
           isMine: true,
           // message: `[商品] ${this.goodsInfo.goodsName}`, // 显示简化信息
           message: messageContent,
           username: this.currentUser.username,
           avatar: avatar,
           time: this.getCurrentTime(timestamp),
-          messageType: "goods", // 附加一个字段,用于渲染时区分商品消息
-          goodsInfo: goodsMessage,
+          messageType: 'goods', // 附加一个字段,用于渲染时区分商品消息
+          goodsInfo: goodsMessage
         };
 
         // 添加到消息列表
@@ -987,7 +1048,7 @@ export default {
           avatar: avatar,
           shopId: this.currentUser.shopId, // 发送者:门店使用shopId,客户使用customId
           timestamp: timestamp,
-          messageType: "goods",
+          messageType: 'goods'
         };
 
         const json = JSON.stringify(messageData);
@@ -998,18 +1059,18 @@ export default {
             // console.log("商品消息发送成功");
           },
           fail: (error) => {
-            console.error("商品消息发送失败:", error);
+            console.error('商品消息发送失败:', error);
             uni.showToast({
-              title: "发送失败",
-              icon: "none",
+              title: '发送失败',
+              icon: 'none'
             });
-          },
+          }
         });
       } catch (error) {
-        console.error("发送商品消息异常:", error);
+        console.error('发送商品消息异常:', error);
         uni.showToast({
-          title: "发送异常",
-          icon: "none",
+          title: '发送异常',
+          icon: 'none'
         });
       }
     },
@@ -1017,26 +1078,28 @@ export default {
       // console.log("createPrice", message)
       createQuotedPrice({
         key: message.goodsInfo.key,
-        price: parseFloat(message.quotedPrice).toFixed(2),
-      }).then(res => {
-        // console.log("createQuotedPrice", res)
-        if (res.code == 1) {
-          this.$msg("报价成功")
-          message.goodsInfo.goodsPriceType = 2;
-          message.goodsInfo.goodsPrice = parseFloat(message.quotedPrice).toFixed(2);
-          message.goodsInfo.lastPrice = parseFloat(message.quotedPrice).toFixed(2);
-          message.quotedPrice = '';
-          this.sendMessageOfGoodsPrice(message.goodsInfo)
-        }
-      }).catch(err => {
-        console.log("createQuotedPrice error", err)
+        price: parseFloat(message.quotedPrice).toFixed(2)
       })
+        .then((res) => {
+          // console.log("createQuotedPrice", res)
+          if (res.code == 1) {
+            this.$msg('报价成功');
+            message.goodsInfo.goodsPriceType = 2;
+            message.goodsInfo.goodsPrice = parseFloat(message.quotedPrice).toFixed(2);
+            message.goodsInfo.lastPrice = parseFloat(message.quotedPrice).toFixed(2);
+            message.quotedPrice = '';
+            this.sendMessageOfGoodsPrice(message.goodsInfo);
+          }
+        })
+        .catch((err) => {
+          console.log('createQuotedPrice error', err);
+        });
     },
     isExpired(timestamp) {
       if (isNaN(timestamp)) {
         return false;
       }
-      const now = Math.floor(Date.now()/1000);
+      const now = Math.floor(Date.now() / 1000);
       const timeDiff = now - timestamp;
       return timeDiff < this.expireTime;
     },
@@ -1048,25 +1111,33 @@ export default {
       const hdId = parseInt(goodsInfo.hdId);
       const account = parseInt(goodsInfo.account);
       if (isNaN(goodsId) || isNaN(categoryId) || isNaN(hdId) || isNaN(account)) {
-        this.$msg("商品数据异常");
+        this.$msg('商品数据异常');
         return;
       }
 
       this.$util.pageTo({
-        url: "/admin/goods/detail",
-        query: { // id=2020&categoryId=1402&hdId=229&account=36548&shopId=36548&name=花仙子&avatar=https://img.theflorist.cn/hhb_small.png
+        url: '/admin/goods/detail',
+        query: {
+          // id=2020&categoryId=1402&hdId=229&account=36548&shopId=36548&name=花仙子&avatar=https://img.theflorist.cn/hhb_small.png
           id: goodsId,
           account: goodsInfo.account,
           hdId: goodsInfo.hdId,
           categoryId: categoryId,
           shopId: goodsInfo.shopId,
           name: goodsInfo.name,
-          avatar: goodsInfo.avatar,
-        },
+          avatar: goodsInfo.avatar
+        }
       });
       this.disconnectWebSocket();
+    },
+    hideKeyboard() {
+      this.keyboardHeight = 0;
+      this.msgInputShow = false;
+    },
+    showKeyboard() {
+      this.msgInputShow = true;
     }
-  },
+  }
 };
 </script>
 
@@ -1246,7 +1317,7 @@ export default {
       height: 80rpx;
     }
     .goods-name {
-      font-size: 28rpx;
+      font-size: 30rpx;
       color: #333;
       font-weight: bold;
       // 多行省略
@@ -1271,10 +1342,10 @@ export default {
       border-radius: 4rpx;
     }
     .goods-price {
-      font-size: 36rpx;
+      font-size: 28rpx;
       color: #ff5050;
       font-weight: bold;
-      margin-top: 10rpx;
+      margin-top: 40rpx;
     }
   }
 
@@ -1306,6 +1377,11 @@ export default {
       padding: 0 15rpx;
       font-size: 28rpx;
       text-align: center;
+      color: #666666;
+    }
+
+    .price-input::placeholder {
+      color: #aaaaaa;
     }
     .action-btn {
       display: inline-block;
@@ -1329,10 +1405,10 @@ export default {
       color: white;
     }
     .goods-price {
-      font-size: 26rpx;
-      color: #ff5050;
+      font-size: 28rpx;
+      color: green;
       font-weight: bold;
-      margin-right: 10rpx;
+      margin-right: 12rpx;
     }
   }
 }
@@ -1397,7 +1473,7 @@ export default {
   resize: none;
 
   &::placeholder {
-    color: #ccc;
+    color: #cccccc;
   }
 }
 

+ 1 - 5
hdApp/src/admin/chat/list.vue

@@ -29,8 +29,6 @@
         <app-wrapper-empty :is-empty="true" />
       </block>
     </view>
-
-    <NotLogin></NotLogin>
   </view>
 </template>
 <script>
@@ -38,14 +36,12 @@ import TuiListCell from "@/components/plugin/list-cell";
 import AppWrapperEmpty from "@/components/app-wrapper-empty";
 import { list } from "@/mixins";
 import { getLatestUsers } from "@/api/chat";
-import NotLogin from "@/components/not-login";
 import {mapGetters } from "vuex";
 export default {
   name: "member",
   components: {
     TuiListCell,
-    AppWrapperEmpty,
-    NotLogin
+    AppWrapperEmpty
   },
   mixins: [list],
   data() {

+ 1 - 1
hdApp/src/admin/goods/add.vue

@@ -93,7 +93,7 @@
         </tui-list-cell>
 
         <tui-list-cell class="line-cell" :hover="false">
-          <view class="tui-title">重量(公斤)</view>
+          <view class="tui-title required">重量(公斤)</view>
           <input v-model="form.weight" placeholder-class="phcolor" @focus="onWeightFocus" @blur="onWeightBlur" class="tui-input" name="weight" placeholder="公斤,可输入小数" maxlength="10" type="digit" />
         </tui-list-cell>
 

+ 4 - 1
hdApp/src/api/chat/index.js

@@ -7,4 +7,7 @@ export const getChatHistory = param => https.post('/chat/chat-history', param)
 export const unReadMsgCount = param => https.post('/chat/un-read-msg-count', param)
 
 // 报价
-export const createQuotedPrice = param => https.post('/chat/create-quoted-price', param)
+export const createQuotedPrice = param => https.post('/chat/create-quoted-price', param)
+
+// 获取商品报价
+export const getQuotedPrice = param => https.post('/chat/get-quoted-price', param)

+ 3 - 1
hdApp/src/utils/util.js

@@ -337,8 +337,9 @@ export const hitRemind = () =>{
 //#ifdef APP-PLUS
 const CLDialog=uni.requireNativePlugin("CL-Dialog")
 //#endif
-export const confirmModal = (item,okCallback=null,cancelCallback=null) => {
 
+// 确认弹框
+export const confirmModal = (item,okCallback=null,cancelCallback=null) => {
 	let title = item.title || '提示'
 	let content = item.content || '确认操作?'
 	let okText = item.okText || '确认'
@@ -418,6 +419,7 @@ export const confirmModal = (item,okCallback=null,cancelCallback=null) => {
 	})
 	//#endif
 }
+
 //判断是否有扫码的条件
 export const isScanEnv = () =>{
 	let isScanEnv = false

+ 46 - 39
mallApp/src/pages/chat/chatPage.vue

@@ -2,7 +2,7 @@
   <view class="chat-container">
     <!-- 连接状态指示器 -->
     <view class="connection-status" v-if="!isConnected && !isManualDisconnect">
-      <text class="status-text">"连接中...</text>
+      <text class="status-text">连接已断开,请退出重新进入</text>
     </view>
 
     <!-- 聊天消息列表 -->
@@ -25,7 +25,7 @@
         :id="'message-' + message.id"
       >
         <!-- 左侧消息(商家/其他用户) -->
-        <view class="message-left" v-if="!message.isMine && !message.customId">
+        <view class="message-left" v-if="!message.isMine">
           <view class="avatar-wrapper">
             <image class="avatar" :src="message.avatar" mode="aspectFill" />
           </view>
@@ -43,7 +43,7 @@
               message.username
             }}</view>
             <view class="goods-info left-bubble">
-              <view class="goods-main">
+              <view class="goods-main" @click="toGoodsDetail(message.goodsInfo)">
                 <image class="goods-image" :src="message.goodsInfo.goodsImg" mode="aspectFill"></image>
                 <view class="goods-details">
                   <view class="goods-name-wrapper">
@@ -53,11 +53,11 @@
                     <text class="delivery-time">48小时发</text>
                   </view> -->
                   <!-- 2 是报价类型,专门为报价类型使用 -->
-                  <text v-if="message.goodsInfo.goodsPriceType == 2" class="goods-price">¥{{ parseFloat(message.goodsInfo.goodsPrice).toFixed(2) }}</text>
+                  <text v-if="message.goodsInfo.goodsPriceType != 0" class="goods-price">¥{{ parseFloat(message.goodsInfo.goodsPrice).toFixed(2) }}</text>
                 </view>
               </view>
 
-              <view class="goods-footer">
+              <view class="goods-footer" v-if="message.goodsInfo.goodsPriceType == 1 || message.goodsInfo.goodsPriceType == 2">
               </view>
               
               <view v-if="message.goodsInfo.goodsPriceType == 2" class="action-buttons">
@@ -74,7 +74,7 @@
         </view>
 
         <!-- 右侧消息(我的) -->
-        <view class="message-right" v-if="message.isMine || message.customId">
+        <view class="message-right" v-if="message.isMine">
           <view v-if="message.messageType == 'text'|| message.messageType != 'goods'" class="message-content-wrapper">
             <view class="username align-right" v-if="isMultiUser">{{ message.username}}</view>
             <view class="message-bubble right-bubble">
@@ -83,11 +83,11 @@
             <view class="message-time align-right">{{ message.time }}</view>
           </view>
           <view v-if="message.messageType == 'goods'" class="goods-content-wrapper">
-            <view class="username align-right" v-if="isMultiUser">{{
-              message.username
-            }}</view>
+            <view class="username align-right" v-if="isMultiUser">
+              {{ message.username }}
+            </view>
             <view class="goods-info right-bubble">
-              <view class="goods-main">
+              <view class="goods-main" @click="toGoodsDetail(message.goodsInfo)">
                 <image
                   class="goods-image"
                   :src="message.goodsInfo.goodsImg"
@@ -97,21 +97,19 @@
                   <view class="goods-name-wrapper">
                     <text class="goods-name">{{ message.goodsInfo.goodsName }}</text>
                   </view>
-                  <view class="sales-info">
-                    <!-- <text class="delivery-time">48小时发</text> -->
+                  <view class="sales-info" v-if="message.goodsInfo.goodsPriceType == 0">
+                    <text class="delivery-time">等待商家标价</text>
                   </view>
-                  <text v-if="message.goodsInfo.goodsPriceType == 1" class="goods-price">¥{{ parseFloat(message.goodsInfo.goodsPrice).toFixed(2) }}</text>
+                  <text v-if="message.goodsInfo.goodsPriceType != 0" class="goods-price">¥{{ parseFloat(message.goodsInfo.goodsPrice).toFixed(2) }}</text>
                 </view>
               </view>
-              <view class="goods-footer">
+              <view class="goods-footer" v-if="message.goodsInfo.goodsPriceType != 0">
                 <!-- <text class="footer-text">破损包退·极速退款·7天无理由退货</text>
                 <text class="arrow">></text> -->
               </view>
-              <view class="action-buttons">
-                <button class="action-btn spec-btn" @click="toBuy(message.goodsInfo)">查看商品</button>
-                <button v-if="message.goodsInfo.goodsPriceType == 1" class="action-btn buy-btn" @click="toGoodsDetail(message.goodsInfo)">去购买</button>
-                <!-- 报价类型显示去购买 -->
-                <button v-if="message.goodsInfo.goodsPriceType == 2" class="action-btn buy-btn" @click="toBuy(message.goodsInfo)">去购买</button>
+              <view class="action-buttons" v-if="message.goodsInfo.goodsPriceType != 0">
+                <button class="action-btn spec-btn" @click="toGoodsDetail(message.goodsInfo)">查看商品</button>
+                <button class="action-btn buy-btn" @click="toBuy(message.goodsInfo)">去购买</button>
               </view>
             </view>
             <view class="message-time align-right">{{ message.time }}</view>
@@ -220,7 +218,7 @@ export default {
       // 分页加载
       allMessages: [], // 存储所有从API获取的消息
       initialLoadSize: 50, // 首次加载数量
-      loadMoreSize: 100, // 每次加载更多的数量
+      loadMoreSize: 50, // 每次加载更多的数量
       showLoadMore: false, // 是否显示“加载更多”
       // 键盘相关
       keyboardHeight: 0, // 键盘高度
@@ -230,7 +228,6 @@ export default {
   },
   onLoad(options) {
     // console.log("---------- options: ", options);
-
     if (options.goodsId) {
       this.currentGoodsInfo = {
         ...options
@@ -250,13 +247,22 @@ export default {
     }
 
     if (this.loginInfo.id) {
-      this.currentUser.userId = this.loginInfo.id;
+      this.currentUser.userId = parseInt(this.loginInfo.id);
+      if(this.currentUser.userId == 0) {
+        console.error('userId 出错 ---------- this.currentUser.userId: ', this.currentUser.userId);
+      }
     }
     if (options.customId) {
-      this.currentUser.customId = options.customId;
+      this.currentUser.customId = parseInt(options.customId);
+      if(this.currentUser.customId == 0) {
+        console.error('customId 出错 ---------- this.currentUser.customId: ', this.currentUser.customId);
+      }
     }
     if (options.shopId) {
-      this.currentUser.shopId = options.shopId;
+      this.currentUser.shopId = parseInt(options.shopId);
+      if(this.currentUser.shopId == 0) {
+        console.error('shopId 出错 ---------- this.currentUser.shopId: ', this.currentUser.shopId);
+      }
     }
     if (options.name) {
       this.currentUser.username = options.name;
@@ -267,17 +273,11 @@ export default {
 
     // 获取房间号
     if (this.currentUser.customId && this.currentUser.shopId) {
-      this.room =
-        "custom_id-" +
-        this.currentUser.customId +
-        "AND" +
-        "shop_id-" +
-        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);
 
     // 加载聊天记录
@@ -324,7 +324,7 @@ export default {
     init() {},
     // 加载聊天历史记录
     loadChatHistory() {
-      const userId = parseInt(this.currentUser.userId);
+      const userId = this.currentUser.userId;
       if (isNaN(userId) || userId <= 0) {
         this.$msg("当前用户的id出错");
         return;
@@ -335,6 +335,10 @@ export default {
       }).then((res) => {
         if (res.code == 1 && res.data) {
           this.allMessages = res.data.map((item, index) => {
+            // 判断是否是我(本客户)发送的消息
+            const isMine = item.customId ? parseInt(item.customId) == this.currentUser.customId : false;
+            //console.log("---isMine---: ", isMine);
+
             const newItem = {
               ...item,
               id: `message-${index}`, // 为每条消息添加唯一ID
@@ -344,6 +348,8 @@ export default {
             if (newItem.messageType === 'goods') {
               newItem.goodsInfo = this.parseGoodsMessage(newItem.message, newItem.timestamp);
             }
+            newItem.isMine = isMine;
+
             return newItem;
           });
 
@@ -499,19 +505,19 @@ export default {
     // 构建WebSocket子协议参数(模拟POST body传输)
     buildWebSocketProtocols() {
       try {
-        const userId = parseInt(this.loginInfo.id); //当前用户的id(不是 customId,是customer登录帐户的userId)
+        const userId = this.currentUser.userId; //当前用户的id(不是 customId,是customer登录帐户的userId)
         if (isNaN(userId) || userId <= 0) {
           this.$msg("当前用户的id出错");
           return;
         }
         
-        const customId = parseInt(this.currentUser.customId);
+        const customId = this.currentUser.customId;
         if (isNaN(customId) || customId <= 0) {
           this.$msg("当前用户的customId出错");
           return;
         }
         
-        const shopId = parseInt(this.currentUser.shopId);
+        const shopId = this.currentUser.shopId;
         if (isNaN(shopId) || shopId <= 0) {
           this.$msg("当前用户的shopId出错");
           return;
@@ -524,7 +530,7 @@ export default {
           s_id: shopId,
           u_name: this.currentUser.username && this.currentUser.username.length > 30 ? this.currentUser.username.substring(0, 30) : this.currentUser.username,
           platform: this.getPlatformInfo(),
-          type: "customer", // 客户端类型:shop:门店端, customer:客户端
+          type: "customer", // 客户端类型:shop(门店端), customer(客户端)
           staff_id: parseInt(0),
         };
 
@@ -1054,6 +1060,7 @@ export default {
         // 如果是字符串,则尝试解析
         if (typeof messageStr === "string") {
           const parsed = JSON.parse(messageStr);
+
           if(parsed.goodsPriceType == 0) {
             const key = parsed.key || '';
             if (key == '') {
@@ -1225,7 +1232,7 @@ export default {
     }
 
     .goods-price {
-      font-size: 30upx;
+      font-size: 28upx;
       font-weight: bold;
       color: #ff5050;
       margin-top: 4upx;
@@ -1429,7 +1436,7 @@ export default {
       height: 80rpx;
     }
     .goods-name {
-      font-size: 28rpx;
+      font-size: 32rpx;
       color: #333;
       font-weight: bold;
       // 多行省略
@@ -1454,7 +1461,7 @@ export default {
       border-radius: 4rpx;
     }
     .goods-price {
-      font-size: 36rpx;
+      font-size: 28rpx;
       color: #ff5050;
       font-weight: bold;
       margin-top: 12rpx;