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