Browse Source

接收兽医消息实时,个人中心聊天列表

master
ZhaoYang 1 month ago
parent
commit
ab4f559ae0
  1. 3
      app.json
  2. 2
      pages/personal/personal.js
  3. 115
      pagesA/pages/chatting/chatting.js
  4. 4
      pagesA/pages/chatting/chatting.json
  5. 34
      pagesA/pages/chatting/chatting.wxml
  6. 111
      pagesA/pages/chatting/chatting.wxss
  7. 146
      pagesA/pages/expert/expert.js
  8. 600
      pagesA/pages/expertChat/expertChat.js
  9. 4
      pagesA/pages/expertChat/expertChat.wxml
  10. 2
      utils/api.js
  11. 6
      utils/tool.wxs

3
app.json

@ -23,7 +23,8 @@
"pages/noticeListDetails/noticeListDetails", "pages/noticeListDetails/noticeListDetails",
"pages/todayInquiry/todayInquiry", "pages/todayInquiry/todayInquiry",
"pages/carouselDetail/carouselDetail", "pages/carouselDetail/carouselDetail",
"pages/releaseSuffer/releaseSuffer"
"pages/releaseSuffer/releaseSuffer",
"pages/chatting/chatting"
] ]
}, },
{ {

2
pages/personal/personal.js

@ -342,7 +342,7 @@ Page({
// 查看问答消息 // 查看问答消息
goToQA() { goToQA() {
wx.navigateTo({ wx.navigateTo({
url: '' // 请填写实际的问答消息页面路径
url: '/pagesA/pages/chatting/chatting'
}) })
}, },

115
pagesA/pages/chatting/chatting.js

@ -0,0 +1,115 @@
import http from '../../../utils/api';
const baseUrl = require('../../../utils/baseUrl');
Page({
/**
* 页面的初始数据
*/
data: {
baseUrl: baseUrl,
chatSessions: [] // 存储聊天列表数据
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.getsessions();
},
// 获取聊天列表
getsessions() {
http.sessions({
data: {},
success: (res) => {
console.log('接口返回数据:', res);
if (res.code === 200 && Array.isArray(res.data)) {
// 预处理数据:为每条数据添加格式化后的时间字段
const formattedSessions = res.data.map(item => {
return {
...item,
formattedTime: this.formatTime(item.lastMessageTime) // 新增字段,在JS里直接格式化好
};
});
this.setData({
chatSessions: formattedSessions
});
} else {
console.error('数据格式错误或接口异常', res);
}
},
fail: (err) => {
console.error('请求失败', err);
}
});
},
// 格式化时间显示 - 兼容多种格式
formatTime(timeValue) {
console.log('时间原始值:', timeValue);
if (!timeValue) return '';
let date;
// 判断是否是时间戳(数字或数字字符串)
if (typeof timeValue === 'number' || (typeof timeValue === 'string' && /^\d+$/.test(timeValue))) {
// 如果是时间戳,直接使用
date = new Date(Number(timeValue));
}
// 判断是否是日期字符串
else if (typeof timeValue === 'string') {
// 尝试解析字符串日期
// 将 '2026-03-06 15:50:11' 替换为 '2026/03/06 15:50:11' (兼容 iOS)
const formattedStr = timeValue.replace(/-/g, '/');
date = new Date(formattedStr);
}
else {
console.log('未知的时间格式:', timeValue);
return '';
}
// 检查日期是否有效
if (isNaN(date.getTime())) {
console.log('无效的日期:', timeValue);
return '';
}
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
const yesterday = today - 24 * 60 * 60 * 1000;
const targetDate = new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
if (targetDate === today) {
// 今天: 显示 时:分
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
return `${hours}:${minutes}`;
} else if (targetDate === yesterday) {
return '昨天';
} else {
// 更早: 显示 月-日
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const day = date.getDate().toString().padStart(2, '0');
return `${month}-${day}`;
}
},
// 跳转到聊天详情页面
goToChat(e) {
console.log(e);
const id = e.currentTarget.dataset.id
wx.navigateTo({
url: `/pagesA/pages/expertChat/expertChat?id=${id}`
});
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
this.getsessions();
wx.stopPullDownRefresh();
}
});

4
pagesA/pages/chatting/chatting.json

@ -0,0 +1,4 @@
{
"navigationBarTitleText":"聊天列表",
"usingComponents": {}
}

34
pagesA/pages/chatting/chatting.wxml

@ -0,0 +1,34 @@
<view class="chat-list">
<block wx:for="{{chatSessions}}" wx:key="index">
<!-- 列表项 -->
<view class="list-item" bindtap="goToChat" data-id="{{item.otherUserId}}">
<!-- 头像区域 -->
<view class="avatar">
<image src="{{item.otherUserAvatar ? baseUrl + item.otherUserAvatar : '/pages/images/tx.png'}}" mode="aspectFill"></image>
</view>
<!-- 内容区域 -->
<view class="content">
<view class="info-row">
<text class="username">{{item.otherUserName || '未知用户'}}</text>
<text class="time">{{item.formattedTime}}</text>
</view>
<view class="message-row">
<view wx:if="{{!item.lastMessage || item.lastMessage === '[暂无消息]'}}"
class="last-message empty">
{{item.lastMessage || '[暂无消息]'}}
</view>
<view wx:else
class="last-message normal">
{{item.lastMessage}}
</view>
</view>
</view>
</view>
</block>
<!-- 空状态提示 -->
<view class="empty-state" wx:if="{{chatSessions.length === 0}}">
<text>暂无聊天记录</text>
</view>
</view>

111
pagesA/pages/chatting/chatting.wxss

@ -0,0 +1,111 @@
/* 页面背景 - 微信灰 */
.chat-list {
background-color: #ededed;
min-height: 100vh;
}
/* 列表项 - 微信白色卡片 */
.list-item {
display: flex;
padding: 10px 15px;
background-color: #ffffff;
position: relative;
align-items: center; /* 关键:垂直居中对齐 */
}
/* 微信没有点击变色效果,如果要加可以保留,不加可以删除 */
.list-item:active {
background-color: #f4f4f4;
}
/* 头像区域 - 微信45px,固定大小不变 */
.avatar {
width: 45px;
height: 45px;
margin-right: 12px;
flex-shrink: 0;
}
.avatar image {
width: 100%;
height: 100%;
border-radius: 6px;
background-color: #f0f0f0;
}
/* 内容区域 - 占据剩余空间,垂直居中 */
.content {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
justify-content: center; /* 内容垂直居中 */
height: 100%; /* 占满父容器高度 */
}
/* 第一行:用户名和时间 */
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
line-height: 1.2; /* 控制行高 */
}
/* 用户名 - 微信字体16px,粗体,不换行 */
.username {
font-size: 16px;
font-weight: bold;
color: #333333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1; /* 自动伸缩 */
min-width: 0; /* 防止溢出 */
}
/* 时间 - 微信浅灰色小字,固定宽度不换行 */
.time {
font-size: 12px;
color: #b3b3b3;
white-space: nowrap;
margin-left: 8px;
}
/* 第二行:最后一条消息 */
.message-row {
display: flex;
align-items: center;
width: 100%;
margin-top: 16rpx;
}
/* 消息基础样式 - 微信14px灰色 */
.last-message {
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1; /* 自动伸缩 */
min-width: 0; /* 防止溢出 */
}
/* 正常消息 - 微信灰色 #8a8a8a */
.last-message.normal {
color: #9E9E9E;
}
/* 空消息 - 微信红色 */
.last-message.empty {
color: #fa5151;
}
/* 空状态页面 */
.empty-state {
display: flex;
justify-content: center;
align-items: center;
height: 80vh;
color: #b3b3b3;
font-size: 15px;
}

146
pagesA/pages/expert/expert.js

@ -9,151 +9,7 @@ Page({
currentFilter: '全部', currentFilter: '全部',
// 所有专家数据 // 所有专家数据
allExperts: [{
id: 1,
name: '张建国',
title: '畜牧学博士',
expertise: '牛类养殖与疾病防治',
experience: 15,
online: true,
senior: true,
phone: '138-0013-8001',
email: 'zhangjg@agri-expert.com',
institution: '国家畜牧业研究所',
address: '北京市朝阳区农业科技大厦',
bio: '15年牛类养殖研究经验,发表SCI论文20余篇,擅长规模化养殖场管理与疾病防控。',
tags: ['肉牛养殖', '疾病预防', '饲料配方', '规模化养殖'],
successRate: 98,
responseTime: '15分钟内响应',
avatar: '/pagesA/images/1.png'
},
{
id: 2,
name: '李秀英',
title: '兽医学硕士',
expertise: '猪病诊断与治疗',
experience: 12,
online: true,
senior: true,
phone: '139-0013-9002',
email: 'lixy@vet-hospital.cn',
institution: '农业大学动物医学院',
address: '上海市徐汇区农业路58号',
bio: '资深猪病防治专家,参与多项国家级科研项目,成功解决多个大规模猪场疫情。',
tags: ['猪病诊疗', '疫苗管理', '养殖场规划', '传染病防治'],
successRate: 96,
responseTime: '20分钟内响应',
avatar: '/pagesA/images/2.png'
},
{
id: 3,
name: '王伟民',
title: '高级畜牧师',
expertise: '羊类繁殖与饲养管理',
experience: 20,
online: false,
senior: true,
phone: '137-0013-7003',
email: 'wangwm@livestock.cn',
institution: '草原畜牧业研究中心',
address: '内蒙古呼和浩特市牧业科技园',
bio: '羊类养殖领域权威专家,拥有20年草原牧区养殖经验,精通各类羊只繁殖技术。',
tags: ['绵羊养殖', '山羊繁殖', '牧草种植', '草原管理'],
successRate: 95,
responseTime: '1小时内响应',
avatar: '/pagesA/images/3.png'
},
{
id: 4,
name: '陈秀兰',
title: '家禽养殖专家',
expertise: '鸡鸭养殖技术',
experience: 18,
online: true,
senior: false,
phone: '136-0013-6004',
email: 'chenxl@poultry-expert.com',
institution: '家禽养殖技术中心',
address: '广东省广州市禽业科技园',
bio: '家禽养殖技术推广专家,帮助300+养殖户实现科学化、标准化养殖管理。',
tags: ['蛋鸡养殖', '肉鸭饲养', '疾病防控', '禽舍设计'],
successRate: 94,
responseTime: '30分钟内响应',
avatar: '/pagesA/images/1.png'
},
{
id: 5,
name: '刘志强',
title: '特种养殖专家',
expertise: '鹿、兔等特种动物养殖',
experience: 10,
online: false,
senior: false,
phone: '135-0013-5005',
email: 'liuzq@special-livestock.cn',
institution: '特种动物养殖研究所',
address: '四川省成都市农业科技园',
bio: '特种动物养殖新兴力量,专注于梅花鹿、肉兔等特种动物的现代化养殖技术。',
tags: ['梅花鹿养殖', '肉兔饲养', '市场分析', '特色养殖'],
successRate: 92,
responseTime: '2小时内响应',
avatar: '/pagesA/images/2.png'
},
{
id: 6,
name: '赵雪梅',
title: '畜牧营养学博士',
expertise: '牲畜饲料配方与营养',
experience: 14,
online: true,
senior: true,
phone: '134-0013-4006',
email: 'zhaoxm@feed-research.cn',
institution: '农业科学院饲料研究所',
address: '江苏省南京市科研路88号',
bio: '饲料营养学专家,研发多个高效饲料配方,帮助养殖户降低30%饲料成本。',
tags: ['饲料配方', '营养管理', '成本控制', '饲料安全'],
successRate: 97,
responseTime: '25分钟内响应',
avatar: '/pagesA/images/3.png'
},
{
id: 7,
name: '孙建国',
title: '养殖场管理专家',
expertise: '现代化养殖场设计与运营',
experience: 22,
online: true,
senior: true,
phone: '133-0013-3007',
email: 'sunjg@farm-management.com',
institution: '现代农业发展中心',
address: '浙江省杭州市创新产业园',
bio: '养殖场规划设计专家,参与设计200+现代化养殖场,精通自动化养殖设备应用。',
tags: ['场舍设计', '设备管理', '成本核算', '环保处理'],
successRate: 99,
responseTime: '10分钟内响应',
avatar: '/pagesA/images/2.png'
},
{
id: 8,
name: '周小芳',
title: '兽医病理学硕士',
expertise: '牲畜常见病与传染病防治',
experience: 11,
online: false,
senior: false,
phone: '132-0013-2008',
email: 'zhouxf@vet-pathology.cn',
institution: '动物疫病防控中心',
address: '湖北省武汉市动物防疫站',
bio: '专攻牲畜病理诊断与传染病防控,在快速诊断和精准治疗方面有丰富经验。',
tags: ['传染病防治', '病理诊断', '用药指导', '疫情处理'],
successRate: 93,
responseTime: '1.5小时内响应',
avatar: '/pagesA/images/3.png'
}
],
allExperts: [],
baseUr: baseUr, baseUr: baseUr,
formzj: { formzj: {

600
pagesA/pages/expertChat/expertChat.js

@ -55,6 +55,8 @@ Page({
pageSize: 20, pageSize: 20,
hasMore: true, hasMore: true,
total: 0, total: 0,
// 记录最早消息的时间戳,用于加载更早的消息
earliestMsgTime: 0,
// 时间显示间隔 // 时间显示间隔
timeInterval: 5, timeInterval: 5,
@ -66,7 +68,18 @@ Page({
// WebSocket状态 // WebSocket状态
socketConnected: false, socketConnected: false,
reconnectCount: 0, reconnectCount: 0,
maxReconnectCount: 5
maxReconnectCount: 5,
// 第一次加载完成标记
firstLoadComplete: false,
// 是否有新消息
hasNewMessage: false,
// 消息ID集合,用于去重
messageIds: new Set(),
// 正在发送中的消息ID集合
sendingMessages: new Set()
}, },
onLoad: function(options) { onLoad: function(options) {
@ -82,7 +95,15 @@ Page({
if (options.id) { if (options.id) {
this.setData({ this.setData({
otherUserId: options.id, otherUserId: options.id,
currentExpertId: options.id
currentExpertId: options.id,
// 初始页码设置为1
page: 1,
messageList: [],
hasMore: true,
earliestMsgTime: 0,
firstLoadComplete: false,
messageIds: new Set(),
sendingMessages: new Set()
}); });
// 先创建对话,然后获取聊天记录 // 先创建对话,然后获取聊天记录
@ -164,14 +185,21 @@ Page({
this.setData({ this.setData({
conversationId: conversationData.id || conversationData.conversationId || id, conversationId: conversationData.id || conversationData.conversationId || id,
conversation: conversationData, conversation: conversationData,
otherUserId: id // 确保对方用户ID正确
otherUserId: id,
// 重置分页参数
page: 1,
hasMore: true,
messageList: [],
earliestMsgTime: 0,
firstLoadComplete: false,
messageIds: new Set(),
sendingMessages: new Set()
}); });
// 获取聊天记录(从第一页开始)
this.getChatHistory(id, false);
// 获取聊天记录
this.getChatHistory(id);
// 连接WebSocket(确保用户ID已加载)
// 连接WebSocket
const userId = this.getCurrentUserId(); const userId = this.getCurrentUserId();
if (userId) { if (userId) {
console.log('用户ID已存在,立即连接WebSocket'); console.log('用户ID已存在,立即连接WebSocket');
@ -199,50 +227,83 @@ Page({
}, },
// 获取聊天记录 // 获取聊天记录
getChatHistory(id) {
getChatHistory(id, isLoadMore = false) {
// 如果是加载更多且没有更多数据,直接返回
if (isLoadMore && !this.data.hasMore) {
console.log('没有更多消息了');
this.setData({ loadingMore: false });
return;
}
// 如果不是加载更多,显示加载提示
if (!isLoadMore) {
wx.showLoading({ title: '加载中...' });
}
const params = {
otherUserId: id,
page: this.data.page,
pageSize: this.data.pageSize
};
console.log('请求聊天记录参数:', params);
http.direct({ http.direct({
data: {
otherUserId: id,
page: this.data.page,
pageSize: this.data.pageSize
},
data: params,
success: res => { success: res => {
console.log('获取聊天记录响应:', res); console.log('获取聊天记录响应:', res);
wx.hideLoading();
if (!isLoadMore) {
wx.hideLoading();
}
if (res && res.code === 200) { if (res && res.code === 200) {
// 处理返回的消息数据 // 处理返回的消息数据
this.processChatHistory(res);
this.processChatHistory(res, isLoadMore);
} else { } else {
// 如果没有聊天记录,显示空状态 // 如果没有聊天记录,显示空状态
this.setData({ this.setData({
messageList: [], messageList: [],
loading: false, loading: false,
hasMore: false
loadingMore: false,
hasMore: false,
firstLoadComplete: true
}); });
if (!isLoadMore) {
wx.showToast({
title: res?.msg || '获取聊天记录失败',
icon: 'none'
});
}
} }
}, },
fail: err => { fail: err => {
console.error('获取聊天记录失败:', err); console.error('获取聊天记录失败:', err);
wx.hideLoading();
if (!isLoadMore) {
wx.hideLoading();
}
wx.showToast({ wx.showToast({
title: '获取聊天记录失败',
title: '网络错误',
icon: 'none' icon: 'none'
}); });
this.setData({ this.setData({
messageList: [], messageList: [],
loading: false
loading: false,
loadingMore: false,
firstLoadComplete: true
}); });
} }
}); });
}, },
// 处理聊天历史数据 // 处理聊天历史数据
processChatHistory(response) {
processChatHistory(response, isLoadMore = false) {
let messages = []; let messages = [];
let total = 0; let total = 0;
let hasMore = false;
// 根据实际返回的数据结构调整这里 // 根据实际返回的数据结构调整这里
if (response.rows && Array.isArray(response.rows)) { if (response.rows && Array.isArray(response.rows)) {
@ -256,26 +317,79 @@ Page({
total = messages.length; total = messages.length;
} }
// 格式化消息数据,适配页面渲染
const formattedMessages = this.formatMessages(messages);
console.log('处理消息数据:', {
messageCount: messages.length,
total,
isLoadMore,
currentPage: this.data.page
});
// 处理消息时间显示
const processedMessages = this.processMessageTimes(formattedMessages);
// 格式化消息数据
const formattedMessages = this.formatMessages(messages);
// 判断是否还有更多数据 // 判断是否还有更多数据
hasMore = this.data.page * this.data.pageSize < total;
const hasMore = this.data.page * this.data.pageSize < total;
// 记录最早消息的时间戳(用于加载更多)
let earliestTime = this.data.earliestMsgTime;
if (formattedMessages.length > 0) {
// 因为接口返回的是正序,第一条就是最早的
earliestTime = formattedMessages[0].timestamp;
}
let newMessageList;
let targetScrollTop = 0;
// 构建消息ID集合
const messageIds = new Set(this.data.messageIds);
formattedMessages.forEach(msg => {
if (msg.id) {
messageIds.add(msg.id);
}
});
if (isLoadMore) {
// 加载更多:新加载的消息(更早的)应该放在现有消息列表的前面
newMessageList = [...formattedMessages, ...this.data.messageList];
// 记录新消息的总高度,用于滚动定位
targetScrollTop = formattedMessages.length * 120; // 估算每条消息高度
} else {
// 首次加载
newMessageList = formattedMessages;
}
// 重新处理消息时间显示
const processedMessages = this.processMessageTimes(newMessageList);
this.setData({ this.setData({
messageList: processedMessages, messageList: processedMessages,
messageIds: messageIds,
loading: false, loading: false,
loadingMore: false, loadingMore: false,
hasMore: hasMore, hasMore: hasMore,
total: total
total: total,
earliestMsgTime: earliestTime,
firstLoadComplete: true
}, () => { }, () => {
// 滚动到底部
setTimeout(() => {
this.scrollToBottom(true);
}, 100);
if (isLoadMore) {
// 加载更多后,调整滚动位置以保持在当前查看的位置
setTimeout(() => {
this.setData({
scrollAnimate: false,
scrollTop: targetScrollTop
}, () => {
setTimeout(() => {
this.setData({ scrollAnimate: true });
}, 100);
});
}, 50);
} else {
// 首次加载,滚动到底部(显示最新消息)
setTimeout(() => {
this.scrollToBottom(false);
}, 100);
}
}); });
}, },
@ -313,7 +427,43 @@ Page({
}); });
}, },
// 加载更多消息
loadMoreMessages: function() {
// 防止重复加载
if (this.data.loadingMore) {
console.log('正在加载中,请稍后');
return;
}
// 检查是否有更多数据
if (!this.data.hasMore) {
console.log('没有更多消息了');
return;
}
// 检查对方用户ID是否存在
if (!this.data.otherUserId) {
console.error('对方用户ID不存在');
return;
}
console.log('开始加载更多消息,当前页码:', this.data.page);
// 设置加载状态
this.setData({
loadingMore: true
}, () => {
// 页码+1,加载更早的消息
const nextPage = this.data.page + 1;
this.setData({
page: nextPage
}, () => {
// 调用获取聊天记录方法,传入true表示是加载更多操作
this.getChatHistory(this.data.otherUserId, true);
});
});
},
// 加载用户信息 // 加载用户信息
loadUserInfo: function() { loadUserInfo: function() {
@ -365,18 +515,6 @@ Page({
} }
}, },
// 加载更多消息
loadMoreMessages: function() {
if (this.data.loadingMore || !this.data.hasMore || !this.data.otherUserId) return;
this.setData({
loadingMore: true,
page: this.data.page + 1
}, () => {
this.getChatHistory(this.data.otherUserId);
});
},
// ========== WebSocket相关方法 ========== // ========== WebSocket相关方法 ==========
// 连接WebSocket // 连接WebSocket
@ -507,7 +645,7 @@ Page({
reconnectCount: this.data.reconnectCount + 1 reconnectCount: this.data.reconnectCount + 1
}); });
this.connectWebSocket(); this.connectWebSocket();
}, 3000); // 3秒后重连
}, 3000);
}, },
// 开始心跳 // 开始心跳
@ -525,7 +663,7 @@ Page({
timestamp: Date.now() timestamp: Date.now()
})); }));
} }
}, 30000); // 30秒发送一次心跳
}, 30000);
}, },
// 发送WebSocket消息 // 发送WebSocket消息
@ -538,16 +676,13 @@ Page({
}, },
fail: (err) => { fail: (err) => {
console.error('WebSocket消息发送失败:', err); console.error('WebSocket消息发送失败:', err);
// 加入队列,等待重发
socketMsgQueue.push(data); socketMsgQueue.push(data);
} }
}); });
} else { } else {
console.log('WebSocket未连接,消息加入队列'); console.log('WebSocket未连接,消息加入队列');
// 队列存储
socketMsgQueue.push(data); socketMsgQueue.push(data);
// 尝试重连
if (!isManualClose) { if (!isManualClose) {
this.connectWebSocket(); this.connectWebSocket();
} }
@ -559,29 +694,24 @@ Page({
console.log('处理消息类型:', data.type); console.log('处理消息类型:', data.type);
switch (data.type) { switch (data.type) {
case 'chat': // 聊天消息
case 'message': // 兼容其他类型
// 新消息
case 'chat':
case 'message':
this.handleNewMessage(data); this.handleNewMessage(data);
break; break;
case 'message_status': case 'message_status':
// 消息状态更新(已读、送达等)
this.handleMessageStatus(data); this.handleMessageStatus(data);
break; break;
case 'typing': case 'typing':
// 对方正在输入
this.handleTypingStatus(data); this.handleTypingStatus(data);
break; break;
case 'online_status': case 'online_status':
// 在线状态变更
this.handleOnlineStatus(data); this.handleOnlineStatus(data);
break; break;
case 'heartbeat_ack': case 'heartbeat_ack':
// 心跳响应
console.log('收到心跳响应'); console.log('收到心跳响应');
break; break;
@ -592,99 +722,127 @@ Page({
// 处理新消息 // 处理新消息
handleNewMessage(data) { handleNewMessage(data) {
console.log('处理新消息:', data);
console.log('处理新消息 - 完整数据:', data);
// 获取当前用户ID
const currentUserId = this.getCurrentUserId();
// 提取消息内容
let messageData = data.data || data;
// 检查消息是否属于当前对话
const conversationId = data.conversationId || data.chatId || data.roomId;
const senderId = data.senderId || data.fromUserId || data.userId;
const receiverId = data.receiverId || data.toUserId;
// 获取消息ID
const messageId = messageData.id || messageData.messageId || 'msg_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
console.log('消息归属检查:', {
currentConversationId: this.data.conversationId,
messageConversationId: conversationId,
currentUserId: currentUserId,
senderId: senderId,
receiverId: receiverId,
otherUserId: this.data.otherUserId
});
console.log('消息ID:', messageId);
console.log('当前发送中消息集合:', this.data.sendingMessages);
// 判断是否属于当前对话
let isCurrentConversation = false;
// 检查是否是自己发送的消息(通过sendingMessages集合判断)
if (this.data.sendingMessages.has(messageId)) {
console.log('收到自己发送的消息回执,跳过处理:', messageId);
if (conversationId && conversationId === this.data.conversationId) {
isCurrentConversation = true;
} else if (!conversationId) {
// 如果没有对话ID,则根据发送者和接收者判断
// 消息发送者是我,接收者是对方
if (senderId === currentUserId && receiverId === this.data.otherUserId) {
isCurrentConversation = true;
}
// 消息发送者是对方,接收者是我
else if (senderId === this.data.otherUserId && receiverId === currentUserId) {
isCurrentConversation = true;
}
// 从发送中集合移除
const sendingMessages = new Set(this.data.sendingMessages);
sendingMessages.delete(messageId);
this.setData({ sendingMessages: sendingMessages });
// 更新本地消息状态为成功
this.updateMessageStatus(messageId, 'success');
return;
} }
if (!isCurrentConversation) {
console.log('消息不属于当前对话,忽略');
// 检查消息是否已存在(去重)
if (this.data.messageIds.has(messageId)) {
console.log('消息已存在,跳过处理:', messageId);
return; return;
} }
// 判断发送者是否是当前用户
const isMe = senderId === currentUserId;
const userId = this.getCurrentUserId();
const senderId = messageData.senderId || messageData.fromUserId || messageData.userId;
console.log('消息归属判断结果:', { isMe, isCurrentConversation });
// 判断是否是自己发送的消息
const isMe = senderId === userId;
// 如果是自己发送的消息,检查是否已存在
console.log('消息归属判断:', {
senderId: senderId,
userId: userId,
isMe: isMe,
messageId: messageId
});
// 如果是自己发送的消息但不在sendingMessages中,说明是通过其他方式发送的
// 也跳过处理,避免重复
if (isMe) { if (isMe) {
// 检查是否已存在相同ID的消息
const exists = this.data.messageList.some(msg =>
msg.id === data.messageId || msg.id === data.id
);
if (exists) {
console.log('消息已存在,忽略重复添加');
return;
}
console.log('是自己发送的消息,但不在发送中集合,可能已存在,跳过处理');
return;
} }
// 创建新消息对象
const newMessage = { const newMessage = {
id: data.messageId || data.id || 'msg_' + Date.now() + Math.random(),
id: messageId,
isMe: isMe, isMe: isMe,
sender: isMe ? 'user' : 'expert', sender: isMe ? 'user' : 'expert',
senderId: senderId, senderId: senderId,
contentType: data.contentType || 'text',
type: data.contentType || 'text',
content: data.content,
timestamp: data.timestamp || Date.now(),
status: 'success'
contentType: messageData.contentType || messageData.type || 'text',
type: messageData.type || messageData.contentType || 'text',
content: messageData.content || '',
timestamp: messageData.timestamp || Date.now(),
status: 'success',
progress: 100
}; };
console.log('添加新消息到列表:', newMessage);
console.log('创建的新消息对象:', newMessage);
// 处理消息时间显示
const { messageList } = this.data;
const processedMessage = this.processSingleMessageTime(newMessage, messageList);
// 获取当前消息列表
const currentList = [...this.data.messageList];
console.log('当前消息列表长度:', currentList.length);
// 添加新消息
messageList.push(processedMessage);
// 添加新消息到列表末尾
currentList.push(newMessage);
this.setData({ messageList }, () => {
this.scrollToBottom();
// 更新消息ID集合
const messageIds = new Set(this.data.messageIds);
messageIds.add(messageId);
// 如果不是自己发的消息,发送已读回执
if (!newMessage.isMe) {
this.sendReadReceipt(newMessage.id);
// 重新处理时间显示
const processedMessages = this.processMessageTimes(currentList);
// 播放提示音
this.playNotificationSound();
console.log('处理后的消息列表长度:', processedMessages.length);
console.log('最后一条消息:', processedMessages[processedMessages.length - 1]);
// 震动提示
wx.vibrateShort({
type: 'light'
});
// 更新数据
this.setData({
messageList: processedMessages,
messageIds: messageIds,
hasNewMessage: !isMe // 如果不是自己发的,标记有新消息
}, () => {
console.log('消息列表已更新,当前列表:', this.data.messageList);
// 判断是否需要滚动到底部
const query = wx.createSelectorQuery();
query.select('#chatScroll').boundingClientRect();
query.select('.chat-bottom-space').boundingClientRect();
query.exec((res) => {
if (res[0] && res[1]) {
const scrollHeight = res[0].height;
const bottomOffset = res[1].top - res[0].top;
const scrollTop = this.data.lastScrollTop;
// 如果在底部附近(距离底部小于200px)或是自己发的消息,则滚动到底部
const shouldScroll = isMe || (scrollTop + scrollHeight >= bottomOffset - 200);
if (shouldScroll) {
this.scrollToBottom(true);
}
} else {
// 如果获取不到位置信息,默认滚动到底部
if (isMe) {
this.scrollToBottom(true);
}
}
});
// 如果不是自己发的消息,震动提示
if (!isMe) {
// wx.vibrateShort({ type: 'light' });
// 发送已读回执
this.sendReadReceipt(messageId);
} }
}); });
}, },
@ -699,21 +857,24 @@ Page({
messageList[index].status = data.status || 'success'; messageList[index].status = data.status || 'success';
this.setData({ messageList }); this.setData({ messageList });
} }
// 如果收到成功状态,从发送中集合移除
if (data.status === 'success') {
const sendingMessages = new Set(this.data.sendingMessages);
sendingMessages.delete(messageId);
this.setData({ sendingMessages: sendingMessages });
}
}, },
// 处理正在输入状态 // 处理正在输入状态
handleTypingStatus(data) { handleTypingStatus(data) {
const userId = data.userId || data.senderId; const userId = data.userId || data.senderId;
if (userId === this.data.otherUserId) { if (userId === this.data.otherUserId) {
this.setData({
'expertInfo.typing': true
});
this.setData({ 'expertInfo.typing': true });
clearTimeout(this.typingTimer); clearTimeout(this.typingTimer);
this.typingTimer = setTimeout(() => { this.typingTimer = setTimeout(() => {
this.setData({
'expertInfo.typing': false
});
this.setData({ 'expertInfo.typing': false });
}, 3000); }, 3000);
} }
}, },
@ -728,7 +889,7 @@ Page({
} }
}, },
// 发送消息 - 修复用户ID获取
// 发送消息到服务器
sendMessageToServer: function(content, type, messageId) { sendMessageToServer: function(content, type, messageId) {
const receiverId = this.data.otherUserId; const receiverId = this.data.otherUserId;
const senderId = this.getCurrentUserId(); const senderId = this.getCurrentUserId();
@ -747,16 +908,25 @@ Page({
title: '发送失败,用户信息错误', title: '发送失败,用户信息错误',
icon: 'none' icon: 'none'
}); });
// 发送失败,从发送中集合移除
const sendingMessages = new Set(this.data.sendingMessages);
sendingMessages.delete(messageId);
this.setData({ sendingMessages: sendingMessages });
// 更新消息状态为失败
this.updateMessageStatus(messageId, 'failed');
return; return;
} }
const message = { const message = {
type: 'chat', // 指定为chat类型
receiverId: receiverId, // 接收者ID,就是对方的ID
senderId: senderId, // 发送者ID
content: content, // 消息内容
contentType: type || 'text', // 内容类型
timestamp: Date.now()
type: 'chat',
receiverId: receiverId,
senderId: senderId,
content: content,
contentType: type || 'text',
timestamp: Date.now(),
messageId: messageId
}; };
const messageStr = JSON.stringify(message); const messageStr = JSON.stringify(message);
@ -769,10 +939,7 @@ Page({
icon: 'none' icon: 'none'
}); });
// 尝试重连
this.connectWebSocket(); this.connectWebSocket();
// 加入队列
socketMsgQueue.push(messageStr); socketMsgQueue.push(messageStr);
return; return;
} }
@ -819,6 +986,10 @@ Page({
console.log('发送文本消息,ID:', messageId); console.log('发送文本消息,ID:', messageId);
// 将消息ID添加到发送中集合
const sendingMessages = new Set(this.data.sendingMessages);
sendingMessages.add(messageId);
// 创建本地消息 // 创建本地消息
const newMessage = { const newMessage = {
id: messageId, id: messageId,
@ -835,18 +1006,32 @@ Page({
// 添加到列表 // 添加到列表
this.addMessageToList(newMessage); this.addMessageToList(newMessage);
// 更新发送中集合
this.setData({ sendingMessages: sendingMessages });
// 清空输入框 // 清空输入框
this.setData({
inputValue: ''
});
this.setData({ inputValue: '' });
// 通过WebSocket发送到服务器 // 通过WebSocket发送到服务器
this.sendMessageToServer(content, 'text', messageId); this.sendMessageToServer(content, 'text', messageId);
// 模拟发送成功回调(实际应该通过WebSocket接收消息状态)
// 设置超时,如果5秒后还没有收到回执,认为发送失败
setTimeout(() => { setTimeout(() => {
this.updateMessageStatus(messageId, 'success');
}, 500);
if (this.data.sendingMessages.has(messageId)) {
console.log('消息发送超时:', messageId);
this.updateMessageStatus(messageId, 'timeout');
// 从发送中集合移除
const sendingMessages = new Set(this.data.sendingMessages);
sendingMessages.delete(messageId);
this.setData({ sendingMessages: sendingMessages });
wx.showToast({
title: '发送超时',
icon: 'none'
});
}
}, 5000);
}, },
// 上传图片后发送 // 上传图片后发送
@ -866,6 +1051,11 @@ Page({
// 通用文件上传 // 通用文件上传
uploadFile: function(tempFilePath, fileName, fileSize = 0, type = 'file', thumbPath = '') { uploadFile: function(tempFilePath, fileName, fileSize = 0, type = 'file', thumbPath = '') {
const messageId = 'file_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); const messageId = 'file_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
// 将消息ID添加到发送中集合
const sendingMessages = new Set(this.data.sendingMessages);
sendingMessages.add(messageId);
const message = { const message = {
id: messageId, id: messageId,
isMe: true, isMe: true,
@ -883,10 +1073,11 @@ Page({
}; };
this.addMessageToList(message); this.addMessageToList(message);
this.setData({ sendingMessages: sendingMessages });
this.simulateUpload(messageId, type, tempFilePath); this.simulateUpload(messageId, type, tempFilePath);
}, },
// 上传
// 模拟上传
simulateUpload: function(messageId, type, tempFilePath) { simulateUpload: function(messageId, type, tempFilePath) {
let progress = 0; let progress = 0;
const uploadInterval = setInterval(() => { const uploadInterval = setInterval(() => {
@ -897,7 +1088,6 @@ Page({
clearInterval(uploadInterval); clearInterval(uploadInterval);
setTimeout(() => { setTimeout(() => {
// 上传完成,更新消息状态
this.updateMessageStatus(messageId, 'success'); this.updateMessageStatus(messageId, 'success');
const { messageList } = this.data; const { messageList } = this.data;
@ -906,9 +1096,7 @@ Page({
delete messageList[index].progress; delete messageList[index].progress;
this.setData({ messageList }); this.setData({ messageList });
// 上传完成后,通过WebSocket发送消息
const fileUrl = tempFilePath; // 上传后的URL
const fileUrl = tempFilePath;
this.sendMessageToServer(fileUrl, type, messageId); this.sendMessageToServer(fileUrl, type, messageId);
} }
}, 200); }, 200);
@ -925,12 +1113,27 @@ Page({
// 添加消息到列表 // 添加消息到列表
addMessageToList: function(message) { addMessageToList: function(message) {
const { messageList } = this.data;
const processedMessage = this.processSingleMessageTime(message, messageList);
const { messageList, messageIds } = this.data;
messageList.push(processedMessage);
// 检查是否已存在
if (messageIds.has(message.id)) {
console.log('消息已存在,不重复添加:', message.id);
return;
}
// 新消息添加到末尾
messageList.push(message);
this.setData({ messageList }, () => {
// 更新消息ID集合
messageIds.add(message.id);
// 重新处理时间显示
const processedMessages = this.processMessageTimes(messageList);
this.setData({
messageList: processedMessages,
messageIds: messageIds
}, () => {
this.scrollToBottom(); this.scrollToBottom();
}); });
}, },
@ -946,15 +1149,10 @@ Page({
} }
}, },
// 输入处理(带防抖)
// 输入处理
onInput: function(e) { onInput: function(e) {
this.setData({
inputValue: e.detail.value
});
this.setData({ inputValue: e.detail.value });
// 发送正在输入状态(防抖)
clearTimeout(this.typingDebounce); clearTimeout(this.typingDebounce);
this.typingDebounce = setTimeout(() => { this.typingDebounce = setTimeout(() => {
this.sendTypingStatus(); this.sendTypingStatus();
@ -963,9 +1161,7 @@ Page({
// 输入框获得焦点 // 输入框获得焦点
onInputFocus: function() { onInputFocus: function() {
this.setData({
inputFocus: true
}, () => {
this.setData({ inputFocus: true }, () => {
setTimeout(() => { setTimeout(() => {
this.scrollToBottom(); this.scrollToBottom();
}, 200); }, 200);
@ -974,9 +1170,7 @@ Page({
// 输入框失去焦点 // 输入框失去焦点
onInputBlur: function() { onInputBlur: function() {
this.setData({
inputFocus: false
});
this.setData({ inputFocus: false });
}, },
// 清除输入 // 清除输入
@ -1000,10 +1194,25 @@ Page({
this.setData({ isScrolling: false }); this.setData({ isScrolling: false });
}, 200); }, 200);
// 加载更多
if (scrollTop <= 100 && !this.data.loadingMore && this.data.hasMore) {
// 当滚动到顶部附近时加载更多
if (scrollTop <= 50 && !this.data.loadingMore && this.data.hasMore && this.data.firstLoadComplete) {
console.log('触发加载更多,当前滚动位置:', scrollTop);
this.loadMoreMessages(); this.loadMoreMessages();
} }
// 当滚动到底部时,清除新消息标记
const query = wx.createSelectorQuery();
query.select('#chatScroll').boundingClientRect();
query.select('.chat-bottom-space').boundingClientRect();
query.exec((res) => {
if (res[0] && res[1]) {
const scrollHeight = res[0].height;
const bottomOffset = res[1].top - res[0].top;
if (scrollTop + scrollHeight >= bottomOffset - 50) {
this.setData({ hasNewMessage: false });
}
}
});
}, },
// 滚动到底部 // 滚动到底部
@ -1011,25 +1220,21 @@ Page({
if (this.data.isScrolling) return; if (this.data.isScrolling) return;
this.setData({ this.setData({
scrollAnimate: animate
scrollAnimate: animate,
hasNewMessage: false
}, () => { }, () => {
setTimeout(() => { setTimeout(() => {
this.setData({
scrollTop: 999999
});
this.setData({ scrollTop: 999999 });
}, 50); }, 50);
}); });
}, },
// 键盘高度变化 // 键盘高度变化
onKeyboardHeightChange: function(res) { onKeyboardHeightChange: function(res) {
this.setData({
keyboardHeight: res.height
});
this.setData({ keyboardHeight: res.height });
if (res.height > 0) { if (res.height > 0) {
this.setData({ showMediaSheet: false }); this.setData({ showMediaSheet: false });
setTimeout(() => { setTimeout(() => {
this.scrollToBottom(); this.scrollToBottom();
}, 100); }, 100);
@ -1046,9 +1251,7 @@ Page({
// 隐藏多媒体选择面板 // 隐藏多媒体选择面板
hideMediaActionSheet: function() { hideMediaActionSheet: function() {
this.setData({
showMediaSheet: false
});
this.setData({ showMediaSheet: false });
}, },
// 选择图片 // 选择图片
@ -1115,12 +1318,11 @@ Page({
processMessageTimes: function(messages) { processMessageTimes: function(messages) {
if (!messages || messages.length === 0) return []; if (!messages || messages.length === 0) return [];
const sortedMessages = [...messages].sort((a, b) => a.timestamp - b.timestamp);
const processedMessages = []; const processedMessages = [];
let lastShowTime = null; let lastShowTime = null;
for (let i = 0; i < sortedMessages.length; i++) {
const msg = { ...sortedMessages[i] };
for (let i = 0; i < messages.length; i++) {
const msg = { ...messages[i] };
if (i === 0) { if (i === 0) {
msg.showTime = true; msg.showTime = true;
@ -1141,27 +1343,6 @@ Page({
return processedMessages; return processedMessages;
}, },
// 处理单条消息时间
processSingleMessageTime: function(message, messageList) {
const msg = { ...message };
if (messageList.length === 0) {
msg.showTime = true;
this.setData({ lastShowTimeStamp: msg.timestamp });
return msg;
}
const lastShowTime = this.data.lastShowTimeStamp;
const timeDiffMinutes = (msg.timestamp - lastShowTime) / (1000 * 60);
msg.showTime = timeDiffMinutes >= this.data.timeInterval;
if (msg.showTime) {
this.setData({ lastShowTimeStamp: msg.timestamp });
}
return msg;
},
// 格式化时间 // 格式化时间
formatTime: function(timestamp) { formatTime: function(timestamp) {
if (!timestamp) return ''; if (!timestamp) return '';
@ -1188,21 +1369,6 @@ Page({
return size.toFixed(1) + units[unitIndex]; return size.toFixed(1) + units[unitIndex];
}, },
// 播放提示音
playNotificationSound() {
try {
const innerAudioContext = wx.createInnerAudioContext();
innerAudioContext.src = '/assets/sounds/notification.mp3';
innerAudioContext.play();
innerAudioContext.onEnded(() => {
innerAudioContext.destroy();
});
} catch (e) {
console.log('播放提示音失败:', e);
}
},
// 阻止事件冒泡 // 阻止事件冒泡
stopPropagation: function() {} stopPropagation: function() {}
}); });

4
pagesA/pages/expertChat/expertChat.wxml

@ -6,7 +6,7 @@
<view class="expert-info"> <view class="expert-info">
<text class="expert-name">{{conversation.otherUserName}}</text> <text class="expert-name">{{conversation.otherUserName}}</text>
<view class="expert-status"> <view class="expert-status">
<view class="status-dot {{expertInfo.online ? 'online' : 'offline'}}"></view>
<view class="status-dot online"></view>
<text class="status-text">在线</text> <text class="status-text">在线</text>
</view> </view>
</view> </view>
@ -31,7 +31,7 @@
</view> </view>
<!-- 消息列表 --> <!-- 消息列表 -->
<block wx:for="{{messageList}}" wx:key="id">
<block wx:for="{{messageList}}" wx:key="index">
<!-- 对方消息 --> <!-- 对方消息 -->
<view class="message-item message-left" wx:if="{{!item.isMe}}"> <view class="message-item message-left" wx:if="{{!item.isMe}}">
<view class="message-avatar"> <view class="message-avatar">

2
utils/api.js

@ -255,7 +255,6 @@ function fkfw(params) {
http('/system/dict/data/list', 'get', params) http('/system/dict/data/list', 'get', params)
} }
// 获取聊天记录列表 // 获取聊天记录列表
function sessions(params) { function sessions(params) {
http('/system/chat/sessions', 'get', params) http('/system/chat/sessions', 'get', params)
@ -280,4 +279,5 @@ export default { // 暴露接口
areaChildren,userCode,UserInfo,videoList,videoZd,videoDetails,forumList,forumAdd,forumDetails, areaChildren,userCode,UserInfo,videoList,videoZd,videoDetails,forumList,forumAdd,forumDetails,
forumReply,commentReply,experience,experiencezd,experienceDetails,realName,revise,feedback, forumReply,commentReply,experience,experiencezd,experienceDetails,realName,revise,feedback,
warningType,disasterDetail,today,carouselDetail,planDetails,wzdDetails,create,direct,fkfw, warningType,disasterDetail,today,carouselDetail,planDetails,wzdDetails,create,direct,fkfw,
sessions
} }

6
utils/tool.wxs

@ -34,12 +34,8 @@ function medicineType(type) {
} }
module.exports = { module.exports = {
type:type, type:type,
ztLevel:ztLevel, ztLevel:ztLevel,
medicineType:medicineType
medicineType:medicineType,
} }
Loading…
Cancel
Save