diff --git a/app.json b/app.json
index 215bdd4..c0775fc 100644
--- a/app.json
+++ b/app.json
@@ -2,7 +2,6 @@
"pages": [
"pages/home/home",
"pages/login/login",
- "pages/message/message",
"pages/personal/personal"
],
"subPackages": [
@@ -48,12 +47,6 @@
"iconPath": "pages/images/home.png",
"selectedIconPath": "pages/images/home1.png"
},
- {
- "pagePath": "pages/message/message",
- "text": "消息",
- "iconPath": "pages/images/xx.png",
- "selectedIconPath": "pages/images/xx1.png"
- },
{
"pagePath": "pages/personal/personal",
"text": "我的",
diff --git a/pages/images/xx.png b/pages/images/xx.png
deleted file mode 100644
index 62d55b9..0000000
Binary files a/pages/images/xx.png and /dev/null differ
diff --git a/pages/images/xx1.png b/pages/images/xx1.png
deleted file mode 100644
index 3aea74a..0000000
Binary files a/pages/images/xx1.png and /dev/null differ
diff --git a/pagesA/pages/advisory/advisory.js b/pagesA/pages/advisory/advisory.js
index 1bdf7a6..1bc91ac 100644
--- a/pagesA/pages/advisory/advisory.js
+++ b/pagesA/pages/advisory/advisory.js
@@ -1,142 +1,129 @@
+import http from '../../../utils/api'
+const baseUrl = require('../../../utils/baseUrl')
+
Page({
data: {
- // 专家信息(新增在线状态)
- expertInfo: {
- avatar: 'https://api.dicebear.com/7.x/avataaars/png?seed=expert_pro&size=140',
- name: '张振农',
- specialty: '作物栽培 · 土壤改良',
- experience: 15,
- online: true // 在线状态
- },
+ baseUrl: baseUrl,
+ user: {},
// 聊天申请列表数据
applyList: [],
// 分页相关
page: 1,
pageSize: 10,
- hasMore: true,
total: 0,
+ hasMore: true, // 是否还有更多数据
+ isLoading: false, // 是否正在加载
+ isFirstLoad: true, // 是否首次加载
},
onLoad() {
- this.loadApplyList(1);
+ this.getzj()
+ this.getsessions(true) // true 表示首次加载(重置列表)
},
- // 加载申请列表(增强模拟数据)
- loadApplyList(page) {
- const { pageSize } = this.data;
- wx.showLoading({ title: '加载中...', mask: true });
-
- setTimeout(() => {
- const mockData = this.generateMockData(page, pageSize);
-
- if (page === 1) {
- this.setData({
- applyList: mockData.list,
- total: mockData.total,
- hasMore: mockData.hasMore,
- page: page
- });
- } else {
- this.setData({
- applyList: this.data.applyList.concat(mockData.list),
- total: mockData.total,
- hasMore: mockData.hasMore,
- page: page
- });
- }
- wx.hideLoading();
- }, 600);
+ // 个人专家信息
+ getzj() {
+ const user = wx.getStorageSync('userInfo')
+ console.log(222, user)
+ this.setData({
+ user: user
+ })
},
- // 生成更丰富的模拟数据(含标签)
- generateMockData(page, pageSize) {
- const total = 23;
- const start = (page - 1) * pageSize;
- const end = start + pageSize;
- const hasMore = end < total;
- const list = [];
+ // 咨询列表 - 支持分页加载
+ getsessions(isRefresh = false) {
+ // 防止重复请求
+ if (this.data.isLoading) return
+ // 如果是加载更多但已无更多数据,直接返回
+ if (!isRefresh && !this.data.hasMore) return
- const seeds = ['farmer1', 'sheep', 'cattle', 'wheat', 'tractor', 'green', 'sun', 'rain', 'soil', 'seed'];
- const firstNames = ['李', '王', '张', '刘', '赵', '陈', '杨', '周', '吴', '黄'];
- const farmTypes = ['水稻种植', '牛羊养殖', '果树栽培', '蔬菜大棚', '有机农场', '蜂蜜养殖', '食用菌', '药材种植'];
- const tagPool = [
- ['玉米', '虫害'],
- ['羊羔', '腹泻'],
- ['施肥', '建议'],
- ['土壤', '检测'],
- ['大棚', '温度'],
- ['猪病', '防治'],
- ['果树', '修剪'],
- ['蜂蜜', '取蜜']
- ];
+ this.setData({ isLoading: true })
- for (let i = start; i < Math.min(end, total); i++) {
- const id = `apply_${i + 1}`;
- const userId = `user_${(i % 10) + 1}`;
- const seedIndex = i % seeds.length;
- const nameIndex = i % firstNames.length;
- const typeIndex = (i * 3) % farmTypes.length;
- const unread = i % 4 === 0 ? 2 : (i % 7 === 0 ? 1 : 0);
- const statuses = ['pending', 'accepted', 'completed'];
- const status = statuses[i % 3];
-
- // 随机标签
- const tags = tagPool[(i * 2) % tagPool.length];
+ // 准备请求参数
+ const params = {
+ page: isRefresh ? 1 : this.data.page,
+ pageSize: this.data.pageSize
+ }
- const time = new Date(Date.now() - (i * 7200000) - Math.floor(Math.random() * 5000000));
- const timeStr = `${time.getMonth()+1}.${time.getDate()} ${time.getHours()}:${time.getMinutes().toString().padStart(2,'0')}`;
+ http.sessions({
+ data: params,
+ success: (res) => {
+ console.log('接口返回数据:', res)
+
+ let newList = []
+ let total = 0
+
+ if (Array.isArray(res)) {
+ // 如果直接返回数组
+ newList = res
+ total = res.length
+ } else if (res.data && Array.isArray(res.data)) {
+ // 如果返回 { data: [], total: 100 }
+ newList = res.data
+ total = res.total || res.data.length
+ } else if (res.list && Array.isArray(res.list)) {
+ // 如果返回 { list: [], total: 100 }
+ newList = res.list
+ total = res.total || res.list.length
+ } else {
+ console.error('接口返回格式不正确', res)
+ newList = []
+ total = 0
+ }
+
+ // 计算是否有更多数据
+ const currentPage = isRefresh ? 1 : this.data.page
+ const pageSize = this.data.pageSize
+ const hasMore = currentPage * pageSize < total
- // 更真实的预览消息
- let lastMessage = '';
- if (i % 5 === 0) lastMessage = '专家您好,我家玉米出现黄叶,能帮看看吗?';
- else if (i % 3 === 0) lastMessage = '咨询一下羊羔腹泻的问题,急!';
- else if (i % 4 === 0) lastMessage = '请问什么时候方便通话?';
- else lastMessage = '请求添加您为咨询顾问';
+ this.setData({
+ applyList: isRefresh ? newList : [...this.data.applyList, ...newList],
+ total: total,
+ page: isRefresh ? 2 : this.data.page + 1, // 下次请求的页码
+ hasMore: hasMore,
+ isLoading: false,
+ isFirstLoad: false
+ })
+ },
+ fail: (err) => {
+ console.error('加载失败:', err)
+ wx.showToast({
+ title: '加载失败',
+ icon: 'none'
+ })
+ this.setData({
+ isLoading: false,
+ isFirstLoad: false
+ })
+ }
+ })
+ },
- list.push({
- id: id,
- user: {
- id: userId,
- name: firstNames[nameIndex] + (i % 2 === 0 ? '建国' : '秀英') + (i + 1),
- avatar: `https://api.dicebear.com/7.x/avataaars/png?seed=${seeds[seedIndex]}_${i}&size=96`,
- farmType: farmTypes[typeIndex],
- },
- applyTime: timeStr,
- lastMessage: lastMessage,
- unreadCount: unread,
- status: status,
- tags: tags,
- });
+ // 加载更多(上拉触底触发)
+ loadMore() {
+ // 如果有更多数据且不在加载中,则加载下一页
+ if (this.data.hasMore && !this.data.isLoading) {
+ this.getsessions(false)
}
-
- return {
- list,
- total,
- hasMore,
- };
},
// 处理点击申请项
handleApply(e) {
- console.log(111,e);
- const id = e.currentTarget.dataset.id
- wx.navigateTo({
- url: `/pagesA/pages/expertChat/expertChat?id=${id}`,
- })
- },
-
-
-
- loadMore() {
- const { hasMore, page } = this.data;
- if (hasMore) {
- this.loadApplyList(page + 1);
- }
+ console.log(111, e)
+ const id = e.currentTarget.dataset.id
+ wx.navigateTo({
+ url: `/pagesA/pages/expertChat/expertChat?id=${id}`,
+ })
},
+ // 可选:下拉刷新功能
onPullDownRefresh() {
- this.setData({ page: 1, hasMore: true });
- this.loadApplyList(1);
- wx.stopPullDownRefresh();
+ this.setData({
+ page: 1,
+ hasMore: true
+ })
+ this.getsessions(true)
+ // 停止下拉刷新
+ wx.stopPullDownRefresh()
}
-});
\ No newline at end of file
+})
\ No newline at end of file
diff --git a/pagesA/pages/advisory/advisory.wxml b/pagesA/pages/advisory/advisory.wxml
index 2674ac4..3f1fbdf 100644
--- a/pagesA/pages/advisory/advisory.wxml
+++ b/pagesA/pages/advisory/advisory.wxml
@@ -2,17 +2,18 @@
-
-
+
+
- {{expertInfo.name}}
- {{expertInfo.online ? '在线' : '离线'}}
+ {{user.user.nickName}}
+ {{user.vetInfo.expertType}}
+ 在线
- {{expertInfo.specialty}}
+ {{user.vetInfo.specialty}}
- 从业 {{expertInfo.experience}} 年
+ 从业 {{user.vetInfo.workExperience}}
@@ -25,28 +26,27 @@
-
+
-
-
+
+
{{item.lastMessage || '请求咨询...'}}
- {{item.unreadCount > 99 ? '99+' : item.unreadCount}}
-
+
暂无新的咨询申请
稍后刷新试试
diff --git a/pagesA/pages/advisory/advisory.wxss b/pagesA/pages/advisory/advisory.wxss
index 982b2dd..0cb0441 100644
--- a/pagesA/pages/advisory/advisory.wxss
+++ b/pagesA/pages/advisory/advisory.wxss
@@ -81,10 +81,11 @@
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
}
+
.online-text {
- font-size: 26rpx;
+ font-size: 24rpx;
background-color: rgba(255, 255, 255, 0.2);
- padding: 3rpx 20rpx;
+ padding: 3rpx 14rpx;
border-radius: 30rpx;
backdrop-filter: blur(4px);
border: 1rpx solid rgba(255, 255, 255, 0.15);
diff --git a/pagesA/pages/expertChat/expertChat.js b/pagesA/pages/expertChat/expertChat.js
index 742ccab..2527e73 100644
--- a/pagesA/pages/expertChat/expertChat.js
+++ b/pagesA/pages/expertChat/expertChat.js
@@ -1,389 +1,1316 @@
-// 专家端聊天页面 - 纯模拟数据,无真实接口
+import http from '../../../utils/api'
+const baseUrl = require('../../../utils/baseUrl')
+
+// WebSocket实例
+let socketOpen = false
+let socketMsgQueue = []
+let heartbeatInterval = null
+let reconnectTimer = null
+let isManualClose = false // 是否手动关闭
+let heartBeatTimer = null // 心跳定时器
+
Page({
data: {
+ baseUrl: baseUrl,
// 专家信息
- expertInfo: {
- id: 'expert_001',
- name: '张医生',
- avatar: '/pages/images/tx.png'
- },
- expertAvatar: '/pages/images/tx.png',
+ expertInfo: {},
- // 对话信息
- conversation: {
- userId: 'user_001',
- userName: '李华',
- userAvatar: '/pages/images/tx.png'
+ // 用户信息
+ userInfo: {
+ user: {} // 完整用户信息
},
- // 在线状态
- onlineStatus: true,
+ // 对话信息
+ conversation: {},
// 消息列表
messageList: [],
scrollTop: 0,
- scrollAnimate: true,
+ scrollAnimate: false,
// 输入相关
inputValue: '',
inputFocus: false,
- // 多媒体面板
+ // 多媒体
showMediaSheet: false,
-
// 页面状态
+ showDateDivider: true,
+ todayDate: '',
+ loading: false,
loadingMore: false,
- hasMore: true,
+
+ // 滚动相关
+ isScrolling: false,
+ lastScrollTop: 0,
+
+ // 当前对话ID和对方用户ID
+ currentExpertId: '',
+ conversationId: '',
+ otherUserId: '',
+
+ // 分页相关
page: 1,
+ pageSize: 20,
+ hasMore: true,
+ total: 0,
+ // 记录最早消息的时间戳,用于加载更早的消息
+ earliestMsgTime: 0,
- // 模拟数据
- mockMessages: [
- {
- id: 'msg_1',
- isMe: false,
- type: 'text',
- content: '医生您好,我想咨询一下皮肤问题',
- timestamp: Date.now() - 3600000,
- status: 'success',
- avatar: '/pages/images/tx.png'
- },
- {
- id: 'msg_2',
- isMe: true,
- type: 'text',
- content: '您好,请描述一下您的具体症状',
- timestamp: Date.now() - 3500000,
- status: 'success',
- avatar: '/pages/images/tx.png'
- },
- {
- id: 'msg_3',
- isMe: false,
- type: 'text',
- content: '脸上起了很多小红点,有点痒',
- timestamp: Date.now() - 3400000,
- status: 'success',
- avatar: '/pages/images/tx.png'
- },
- {
- id: 'msg_4',
- isMe: false,
- type: 'image',
- content: 'https://picsum.photos/200/200?random=1',
- timestamp: Date.now() - 3300000,
- status: 'success',
- avatar: '/pages/images/tx.png'
- },
- {
- id: 'msg_5',
- isMe: true,
- type: 'text',
- content: '看起来像是过敏反应,最近有接触什么新的东西吗?',
- timestamp: Date.now() - 3200000,
- status: 'success',
- avatar: '/pages/images/tx.png'
- }
- ],
+ // 时间显示间隔
+ timeInterval: 5,
+ lastShowTimeStamp: 0,
+
+ // 键盘高度
+ keyboardHeight: 0,
+
+ // WebSocket状态
+ socketConnected: false,
+ reconnectCount: 0,
+ maxReconnectCount: 5,
+
+ // 第一次加载完成标记
+ firstLoadComplete: false,
+ // 是否有新消息
+ hasNewMessage: false,
+
+ // 消息ID集合,用于去重
+ messageIds: new Set(),
+
+ // 正在发送中的消息ID集合
+ sendingMessages: new Set(),
+
+ // 显示发送中提示
+ showSendingTip: false,
+ // 发送中的消息数量
+ sendingCount: 0,
- // 定时器
- typingTimer: null,
- mockUserTimer: null
+ // 上传状态
+ isUploading: false
},
onLoad: function(options) {
- console.log('专家端聊天页面加载', options);
+ console.log('接收到的参数:', options);
- // 获取传递的参数
- if (options.userId) {
- this.setData({
- 'conversation.userId': options.userId,
- 'conversation.userName': options.userName || '用户'
- });
- }
+ // 设置今天日期
+ this.setTodayDate();
- // 加载模拟消息
- this.loadMockMessages();
+ // 加载用户信息
+ this.loadUserInfo();
- // 模拟用户在线状态变化
- this.simulateOnlineStatus();
+ // 获取对方用户ID
+ if (options.id) {
+ this.setData({
+ otherUserId: options.id,
+ currentExpertId: options.id,
+ // 初始页码设置为1
+ page: 1,
+ messageList: [],
+ hasMore: true,
+ earliestMsgTime: 0,
+ firstLoadComplete: false,
+ messageIds: new Set(),
+ sendingMessages: new Set(),
+ showSendingTip: false,
+ sendingCount: 0
+ });
+
+ // 先创建对话,然后获取聊天记录
+ this.createConversation(options.id);
+ }
- // 模拟用户发送消息
- this.startMockUserTyping();
+ // 监听键盘高度变化
+ wx.onKeyboardHeightChange(this.onKeyboardHeightChange.bind(this));
},
onShow: function() {
- // 滚动到底部
- setTimeout(() => {
- this.scrollToBottom(false);
- }, 200);
+ // 页面显示时连接WebSocket
+ isManualClose = false;
+
+ // 确保用户ID和对话ID都存在再连接
+ const userId = this.getCurrentUserId();
+ if (userId && this.data.conversationId) {
+ console.log('页面显示,准备连接WebSocket,用户ID:', userId);
+ this.connectWebSocket();
+ } else {
+ console.log('用户ID或对话ID不存在,稍后再连接', {
+ userId: userId,
+ conversationId: this.data.conversationId
+ });
+ }
+ },
+
+ onHide: function() {
+ // 页面隐藏时暂停心跳检查但不关闭连接
+ console.log('页面隐藏,暂停心跳检查');
+ if (heartBeatTimer) {
+ clearInterval(heartBeatTimer);
+ heartBeatTimer = null;
+ }
},
onUnload: function() {
+ // 页面卸载时关闭WebSocket并清理资源
+ isManualClose = true;
+ this.closeWebSocket();
+ wx.offKeyboardHeightChange();
+
// 清理定时器
- if (this.data.mockUserTimer) {
- clearInterval(this.data.mockUserTimer);
+ if (heartbeatInterval) {
+ clearInterval(heartbeatInterval);
+ heartbeatInterval = null;
}
- if (this.data.typingTimer) {
- clearTimeout(this.data.typingTimer);
+ if (reconnectTimer) {
+ clearTimeout(reconnectTimer);
+ reconnectTimer = null;
}
+ if (heartBeatTimer) {
+ clearInterval(heartBeatTimer);
+ heartBeatTimer = null;
+ }
+ },
+
+ // 获取当前用户ID的辅助方法
+ getCurrentUserId: function() {
+ const userId = this.data.userInfo?.user?.userId;
+ console.log('获取到的用户ID:', userId);
+ return userId;
},
- // 返回上一页
- goBack: function() {
- wx.navigateBack({
- delta: 1
+ // 创建一对一聊天
+ createConversation(id) {
+ wx.showLoading({ title: '加载中...' });
+
+ http.create({
+ data: {
+ otherUserId: id
+ },
+ success: res => {
+ console.log('创建对话响应:', res);
+
+ if (res && res.data) {
+ // 保存对话ID
+ const conversationData = res.data;
+ this.setData({
+ conversationId: conversationData.id || conversationData.conversationId || id,
+ conversation: conversationData,
+ otherUserId: id,
+ // 重置分页参数
+ page: 1,
+ hasMore: true,
+ messageList: [],
+ earliestMsgTime: 0,
+ firstLoadComplete: false,
+ messageIds: new Set(),
+ sendingMessages: new Set()
+ });
+
+ // 获取聊天记录(从第一页开始)
+ this.getChatHistory(id, false);
+
+ // 连接WebSocket
+ const userId = this.getCurrentUserId();
+ if (userId) {
+ console.log('用户ID已存在,立即连接WebSocket');
+ this.connectWebSocket();
+ } else {
+ console.log('用户ID尚未加载,等待用户信息加载完成');
+ }
+ } else {
+ wx.hideLoading();
+ wx.showToast({
+ title: '创建对话失败',
+ icon: 'none'
+ });
+ }
+ },
+ fail: err => {
+ console.error('创建对话失败:', err);
+ wx.hideLoading();
+ wx.showToast({
+ title: '网络错误',
+ icon: 'none'
+ });
+ }
});
},
+ // 获取聊天记录
+ 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({
+ data: params,
+ success: res => {
+ console.log('获取聊天记录响应:', res);
+
+ if (!isLoadMore) {
+ wx.hideLoading();
+ }
+
+ if (res && res.code === 200) {
+ // 处理返回的消息数据
+ this.processChatHistory(res, isLoadMore);
+ } else {
+ // 如果没有聊天记录,显示空状态
+ this.setData({
+ messageList: [],
+ loading: false,
+ loadingMore: false,
+ hasMore: false,
+ firstLoadComplete: true
+ });
+
+ if (!isLoadMore) {
+ wx.showToast({
+ title: res?.msg || '获取聊天记录失败',
+ icon: 'none'
+ });
+ }
+ }
+ },
+ fail: err => {
+ console.error('获取聊天记录失败:', err);
+
+ if (!isLoadMore) {
+ wx.hideLoading();
+ }
+
+ wx.showToast({
+ title: '网络错误',
+ icon: 'none'
+ });
+
+ this.setData({
+ messageList: [],
+ loading: false,
+ loadingMore: false,
+ firstLoadComplete: true
+ });
+ }
+ });
+ },
- // 加载模拟消息
- loadMockMessages: function() {
- // 处理消息时间显示
- const messages = this.processMessageTimes(this.data.mockMessages);
+ // 处理聊天历史数据
+ processChatHistory(response, isLoadMore = false) {
+ let messages = [];
+ let total = 0;
+
+ // 根据实际返回的数据结构调整这里
+ if (response.rows && Array.isArray(response.rows)) {
+ messages = response.rows;
+ total = response.total || messages.length;
+ } else if (response.data && Array.isArray(response.data)) {
+ messages = response.data;
+ total = response.total || messages.length;
+ } else if (Array.isArray(response)) {
+ messages = response;
+ total = messages.length;
+ }
+
+ console.log('处理消息数据:', {
+ messageCount: messages.length,
+ total,
+ isLoadMore,
+ currentPage: this.data.page
+ });
+
+ // 格式化消息数据
+ const formattedMessages = this.formatMessages(messages);
+
+ // 判断是否还有更多数据
+ 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({
- messageList: messages,
- hasMore: false
+ messageList: processedMessages,
+ messageIds: messageIds,
+ loading: false,
+ loadingMore: false,
+ hasMore: hasMore,
+ total: total,
+ earliestMsgTime: earliestTime,
+ firstLoadComplete: true
+ }, () => {
+ if (isLoadMore) {
+ // 加载更多后,调整滚动位置以保持在当前查看的位置
+ setTimeout(() => {
+ this.setData({
+ scrollAnimate: false,
+ scrollTop: targetScrollTop
+ }, () => {
+ setTimeout(() => {
+ this.setData({ scrollAnimate: true });
+ }, 100);
+ });
+ }, 50);
+ } else {
+ // 首次加载,滚动到底部(显示最新消息)
+ setTimeout(() => {
+ this.scrollToBottom(false);
+ }, 100);
+ }
});
},
- // 加载更多消息(模拟)
- loadMoreMessages: function() {
- if (this.data.loadingMore || !this.data.hasMore) return;
+ // 格式化消息数据
+ formatMessages(messages) {
+ if (!messages || messages.length === 0) return [];
- this.setData({ loadingMore: true });
+ const userId = this.getCurrentUserId();
+ console.log('格式化消息使用的用户ID:', userId);
- // 模拟网络延迟
- setTimeout(() => {
- // 模拟更早的消息
- const olderMessages = [
- {
- id: 'old_' + Date.now(),
- isMe: false,
- type: 'text',
- content: '之前也出现过类似情况',
- timestamp: Date.now() - 86400000,
- status: 'success',
- avatar: '/pages/images/tx.png'
- },
- {
- id: 'old_' + (Date.now() + 1),
- isMe: true,
- type: 'text',
- content: '那当时是怎么处理的呢?',
- timestamp: Date.now() - 86000000,
- status: 'success',
- avatar: '/pages/images/tx.png'
- }
- ];
+ return messages.map(msg => {
+ // 统一使用contentType字段
+ let contentType = msg.contentType || msg.type || 'text';
+ let content = msg.content || '';
+
+ // 判断发送者
+ const isMe = msg.senderId === userId ||
+ msg.fromUserId === userId ||
+ msg.userId === userId ||
+ msg.sendId === userId;
- const processedOld = this.processMessageTimes(olderMessages);
- const newList = [...processedOld, ...this.data.messageList];
+ return {
+ id: msg.id || 'msg_' + Date.now() + Math.random(),
+ isMe: isMe,
+ sender: isMe ? 'user' : 'expert',
+ senderId: msg.senderId || msg.fromUserId || msg.userId,
+ contentType: contentType,
+ content: content,
+ timestamp: msg.createTime || msg.timestamp || Date.now(),
+ status: msg.status || 'success',
+ fileName: msg.fileName || '',
+ fileSize: msg.fileSize || 0,
+ thumb: msg.thumb || '',
+ progress: msg.progress || 100
+ };
+ });
+ },
+
+ // 加载更多消息
+ 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({
- messageList: newList,
- loadingMore: false,
- hasMore: false // 模拟没有更多了
+ page: nextPage
+ }, () => {
+ // 调用获取聊天记录方法,传入true表示是加载更多操作
+ this.getChatHistory(this.data.otherUserId, true);
});
+ });
+ },
+
+ // 加载用户信息
+ loadUserInfo: function() {
+ try {
+ const userInfo = wx.getStorageSync('userInfo');
+ console.log('从缓存加载的用户信息:', userInfo);
- // 调整滚动位置
- setTimeout(() => {
- this.setData({
- scrollAnimate: false,
- scrollTop: 300
- }, () => {
- setTimeout(() => {
- this.setData({ scrollAnimate: true });
- }, 100);
- });
- }, 50);
- }, 800);
+ if (userInfo) {
+ // 处理用户信息,确保正确的结构
+ let processedUserInfo = { ...this.data.userInfo };
+
+ if (userInfo.user) {
+ // 如果已经有user结构
+ processedUserInfo = userInfo;
+ } else if (userInfo.userId) {
+ // 如果是直接返回的用户数据
+ processedUserInfo = {
+ user: userInfo
+ };
+ }
+
+ // 确保有user对象
+ if (!processedUserInfo.user) {
+ processedUserInfo.user = {};
+ }
+
+ // 确保userId存在
+ if (userInfo.userId && !processedUserInfo.user.userId) {
+ processedUserInfo.user.userId = userInfo.userId;
+ }
+
+ // 设置用户ID
+ const userId = processedUserInfo.user?.userId || userInfo.userId;
+ if (userId) {
+ processedUserInfo.id = userId;
+ }
+
+ console.log('处理后的用户信息:', processedUserInfo);
+ this.setData({ userInfo: processedUserInfo });
+
+ // 用户信息加载完成后,如果对话ID已存在,连接WebSocket
+ if (this.data.conversationId && !this.data.socketConnected) {
+ console.log('用户信息加载完成,连接WebSocket');
+ this.connectWebSocket();
+ }
+ }
+ } catch (e) {
+ console.error('加载用户信息失败:', e);
+ }
},
- // 模拟用户在线状态变化
- simulateOnlineStatus: function() {
- setInterval(() => {
- // 随机改变在线状态(模拟)
- const random = Math.random();
+ // ========== WebSocket相关方法 ==========
+
+ // 连接WebSocket
+ connectWebSocket() {
+ // 如果已有连接,先检查是否可用
+ if (socketOpen) {
+ console.log('WebSocket已连接,无需重复连接');
+ return;
+ }
+
+ const userId = this.getCurrentUserId();
+ console.log('准备连接WebSocket,用户ID:', userId);
+
+ if (!userId) {
+ console.error('用户ID不存在,无法连接WebSocket');
+ wx.showToast({
+ title: '用户信息加载中,请稍后',
+ icon: 'none'
+ });
+ return;
+ }
+
+ // WebSocket连接URL
+ const basurl = '192.168.101.109:8080'; // 从配置文件获取
+ const wsUrl = `ws://${basurl}/ws/mini/chat?userId=${userId}`;
+
+ console.log('开始连接WebSocket:', wsUrl);
+
+ wx.connectSocket({
+ url: wsUrl,
+ success: () => {
+ console.log('WebSocket连接初始化成功');
+ },
+ fail: (err) => {
+ console.error('WebSocket连接初始化失败:', err);
+ this.reconnectWebSocket();
+ }
+ });
+
+ // 监听连接打开
+ wx.onSocketOpen(() => {
+ console.log('WebSocket连接已打开');
+ socketOpen = true;
+ this.setData({ socketConnected: true, reconnectCount: 0 });
+
+ // 发送队列中的消息
+ while (socketMsgQueue.length > 0) {
+ const msg = socketMsgQueue.shift();
+ this.sendWebSocketMessage(msg);
+ }
+
+ // 开始心跳
+ this.startHeartbeat();
+ });
+
+ // 监听收到消息
+ wx.onSocketMessage((res) => {
+ try {
+ console.log('收到原始WebSocket消息:', res.data);
+ const data = JSON.parse(res.data);
+ console.log('解析后的消息:', data);
+
+ // 处理不同类型的消息
+ this.handleWebSocketMessage(data);
+ } catch (e) {
+ console.error('解析消息失败:', e, '原始数据:', res.data);
+ }
+ });
+
+ // 监听连接关闭
+ wx.onSocketClose(() => {
+ console.log('WebSocket连接已关闭');
+ socketOpen = false;
+ this.setData({ socketConnected: false });
+
+ // 停止心跳
+ if (heartBeatTimer) {
+ clearInterval(heartBeatTimer);
+ heartBeatTimer = null;
+ }
+
+ // 如果不是手动关闭,尝试重连
+ if (!isManualClose) {
+ this.reconnectWebSocket();
+ }
+ });
+
+ // 监听错误
+ wx.onSocketError((err) => {
+ console.error('WebSocket错误:', err);
+ socketOpen = false;
+ this.setData({ socketConnected: false });
+ });
+ },
+
+ // 关闭WebSocket
+ closeWebSocket() {
+ if (socketOpen) {
+ wx.closeSocket({
+ success: () => {
+ console.log('WebSocket连接已主动关闭');
+ }
+ });
+ socketOpen = false;
+ this.setData({ socketConnected: false });
+ }
+
+ if (heartBeatTimer) {
+ clearInterval(heartBeatTimer);
+ heartBeatTimer = null;
+ }
+ },
+
+ // 重连WebSocket
+ reconnectWebSocket() {
+ if (this.data.reconnectCount >= this.data.maxReconnectCount) {
+ console.log('达到最大重连次数,停止重连');
+ return;
+ }
+
+ if (reconnectTimer) {
+ clearTimeout(reconnectTimer);
+ }
+
+ reconnectTimer = setTimeout(() => {
+ console.log(`尝试第${this.data.reconnectCount + 1}次重连`);
this.setData({
- onlineStatus: random > 0.3 // 70%时间在线
+ reconnectCount: this.data.reconnectCount + 1
});
+ this.connectWebSocket();
+ }, 3000);
+ },
+
+ // 开始心跳
+ startHeartbeat() {
+ if (heartBeatTimer) {
+ clearInterval(heartBeatTimer);
+ }
+
+ heartBeatTimer = setInterval(() => {
+ if (socketOpen) {
+ console.log('发送心跳');
+ this.sendWebSocketMessage(JSON.stringify({
+ type: 'heartbeat',
+ userId: this.getCurrentUserId(),
+ timestamp: Date.now()
+ }));
+ }
}, 30000);
},
- // 模拟用户正在输入并发送消息
- startMockUserTyping: function() {
- // 每45-90秒模拟用户发送一条消息
- const timer = setInterval(() => {
- // 只有在线时才发送
- if (this.data.onlineStatus) {
- this.simulateUserMessage();
+ // 发送WebSocket消息
+ sendWebSocketMessage(data) {
+ if (socketOpen) {
+ wx.sendSocketMessage({
+ data: typeof data === 'string' ? data : JSON.stringify(data),
+ success: () => {
+ console.log('WebSocket消息发送成功:', data);
+ },
+ fail: (err) => {
+ console.error('WebSocket消息发送失败:', err);
+ socketMsgQueue.push(data);
+ }
+ });
+ } else {
+ console.log('WebSocket未连接,消息加入队列');
+ socketMsgQueue.push(data);
+
+ if (!isManualClose) {
+ this.connectWebSocket();
}
- }, Math.random() * 45000 + 45000); // 45-90秒
-
- this.setData({ mockUserTimer: timer });
- },
-
- // 模拟用户发送消息
- simulateUserMessage: function() {
- const messages = [
- '好的,我明白了',
- '谢谢医生!',
- '需要用什么药吗?',
- '大概多久能好?',
- '有没有什么需要注意的?',
- '好的,我试试看',
- '明白了,非常感谢!'
- ];
-
- const randomMsg = messages[Math.floor(Math.random() * messages.length)];
-
- const newMsg = {
- id: 'user_' + Date.now() + Math.random(),
- isMe: false,
- type: 'text',
- content: randomMsg,
+ }
+ },
+
+ // 处理WebSocket消息
+ handleWebSocketMessage(data) {
+ console.log('处理消息类型:', data.type);
+
+ switch (data.type) {
+ case 'chat':
+ case 'message':
+ this.handleNewMessage(data);
+ break;
+
+ case 'message_status':
+ this.handleMessageStatus(data);
+ break;
+
+ case 'typing':
+ this.handleTypingStatus(data);
+ break;
+
+ case 'online_status':
+ this.handleOnlineStatus(data);
+ break;
+
+ case 'heartbeat_ack':
+ console.log('收到心跳响应');
+ break;
+
+ default:
+ console.log('未知消息类型:', data);
+ }
+ },
+
+ // 处理新消息
+ handleNewMessage(data) {
+ console.log('处理新消息 - 完整数据:', data);
+
+ // 提取消息内容
+ let messageData = data.data || data;
+
+ // 获取消息ID
+ const messageId = messageData.id || messageData.messageId || 'msg_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
+
+ console.log('消息ID:', messageId);
+ console.log('当前发送中消息集合:', this.data.sendingMessages);
+
+ // 检查是否是自己发送的消息(通过sendingMessages集合判断)
+ if (this.data.sendingMessages.has(messageId)) {
+ console.log('收到自己发送的消息回执,跳过处理:', messageId);
+
+ // 从发送中集合移除
+ const sendingMessages = new Set(this.data.sendingMessages);
+ sendingMessages.delete(messageId);
+ this.setData({
+ sendingMessages: sendingMessages,
+ sendingCount: sendingMessages.size,
+ showSendingTip: sendingMessages.size > 0
+ });
+
+ // 更新本地消息状态为成功
+ this.updateMessageStatus(messageId, 'success');
+ return;
+ }
+
+ // 检查消息是否已存在(去重)
+ if (this.data.messageIds.has(messageId)) {
+ console.log('消息已存在,跳过处理:', messageId);
+ return;
+ }
+
+ const userId = this.getCurrentUserId();
+ const senderId = messageData.senderId || messageData.fromUserId || messageData.userId;
+
+ // 判断是否是自己发送的消息
+ const isMe = senderId === userId;
+
+ console.log('消息归属判断:', {
+ senderId: senderId,
+ userId: userId,
+ isMe: isMe,
+ messageId: messageId
+ });
+
+ // 如果是自己发送的消息但不在sendingMessages中,说明是通过其他方式发送的
+ // 也跳过处理,避免重复
+ if (isMe) {
+ console.log('是自己发送的消息,但不在发送中集合,可能已存在,跳过处理');
+ return;
+ }
+
+ // 创建新消息对象 - 统一使用contentType
+ const newMessage = {
+ id: messageId,
+ isMe: isMe,
+ sender: isMe ? 'user' : 'expert',
+ senderId: senderId,
+ contentType: messageData.contentType || messageData.type || 'text',
+ content: messageData.content || '',
+ timestamp: messageData.timestamp || Date.now(),
+ status: 'success', // 对方消息直接显示成功
+ progress: 100,
+ fileName: messageData.fileName || '',
+ fileSize: messageData.fileSize || 0,
+ thumb: messageData.thumb || ''
+ };
+
+ console.log('创建的新消息对象:', newMessage);
+
+ // 获取当前消息列表
+ const currentList = [...this.data.messageList];
+ console.log('当前消息列表长度:', currentList.length);
+
+ // 添加新消息到列表末尾
+ currentList.push(newMessage);
+
+ // 更新消息ID集合
+ const messageIds = new Set(this.data.messageIds);
+ messageIds.add(messageId);
+
+ // 重新处理时间显示
+ const processedMessages = this.processMessageTimes(currentList);
+
+ console.log('处理后的消息列表长度:', processedMessages.length);
+ console.log('最后一条消息:', processedMessages[processedMessages.length - 1]);
+
+ // 更新数据
+ 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) {
+ this.sendReadReceipt(messageId);
+ }
+ });
+ },
+
+ // 处理消息状态
+ handleMessageStatus(data) {
+ const { messageList } = this.data;
+ const messageId = data.messageId || data.id;
+ const index = messageList.findIndex(msg => msg.id === messageId);
+
+ if (index !== -1) {
+ messageList[index].status = data.status || 'success';
+ this.setData({ messageList });
+ }
+
+ // 如果收到成功状态,从发送中集合移除
+ if (data.status === 'success' || data.status === 'read') {
+ const sendingMessages = new Set(this.data.sendingMessages);
+ sendingMessages.delete(messageId);
+ this.setData({
+ sendingMessages: sendingMessages,
+ sendingCount: sendingMessages.size,
+ showSendingTip: sendingMessages.size > 0
+ });
+ }
+ },
+
+ // 处理正在输入状态
+ handleTypingStatus(data) {
+ const userId = data.userId || data.senderId;
+ if (userId === this.data.otherUserId) {
+ this.setData({ 'expertInfo.typing': true });
+
+ clearTimeout(this.typingTimer);
+ this.typingTimer = setTimeout(() => {
+ this.setData({ 'expertInfo.typing': false });
+ }, 3000);
+ }
+ },
+
+ // 处理在线状态
+ handleOnlineStatus(data) {
+ const userId = data.userId || data.senderId;
+ if (userId === this.data.otherUserId) {
+ this.setData({
+ 'expertInfo.online': data.online || data.status === 'online'
+ });
+ }
+ },
+
+ // 发送消息到服务器
+ sendMessageToServer: function(content, contentType, messageId) {
+ const receiverId = this.data.otherUserId;
+ const senderId = this.getCurrentUserId();
+
+ console.log('发送消息参数:', {
+ senderId: senderId,
+ receiverId: receiverId,
+ content: content,
+ contentType: contentType,
+ messageId: messageId
+ });
+
+ if (!receiverId || !senderId) {
+ console.error('发送者或接收者ID不存在', { senderId, receiverId });
+ wx.showToast({
+ title: '发送失败,用户信息错误',
+ icon: 'none'
+ });
+
+ // 发送失败,从发送中集合移除
+ const sendingMessages = new Set(this.data.sendingMessages);
+ sendingMessages.delete(messageId);
+ this.setData({
+ sendingMessages: sendingMessages,
+ sendingCount: sendingMessages.size,
+ showSendingTip: sendingMessages.size > 0
+ });
+
+ // 更新消息状态为失败
+ this.updateMessageStatus(messageId, 'failed');
+ return;
+ }
+
+ const message = {
+ type: 'chat',
+ receiverId: receiverId,
+ senderId: senderId,
+ content: content,
+ contentType: contentType,
timestamp: Date.now(),
- status: 'success',
- avatar: this.data.conversation.userAvatar || '/pages/images/tx.png'
+ messageId: messageId
};
+
+ const messageStr = JSON.stringify(message);
+ console.log('发送消息到服务器:', JSON.parse(messageStr));
+
+ if (!socketOpen) {
+ console.log('WebSocket未连接,尝试连接');
+ wx.showToast({
+ title: '连接中,请稍后',
+ icon: 'none'
+ });
+
+ this.connectWebSocket();
+ socketMsgQueue.push(messageStr);
+ return;
+ }
+
+ this.sendWebSocketMessage(messageStr);
+ },
+
+ // 发送已读回执
+ sendReadReceipt(messageId) {
+ if (!socketOpen) return;
- this.addMessageToList(newMsg);
+ this.sendWebSocketMessage(JSON.stringify({
+ type: 'message_status',
+ messageId: messageId,
+ status: 'read',
+ userId: this.getCurrentUserId(),
+ receiverId: this.data.otherUserId,
+ conversationId: this.data.conversationId,
+ timestamp: Date.now()
+ }));
+ },
+
+ // 发送正在输入状态
+ sendTypingStatus() {
+ if (!socketOpen || !this.data.inputValue) return;
- // 震动提示(可选)
- wx.vibrateShort({ type: 'light' });
+ this.sendWebSocketMessage(JSON.stringify({
+ type: 'typing',
+ userId: this.getCurrentUserId(),
+ receiverId: this.data.otherUserId,
+ conversationId: this.data.conversationId,
+ timestamp: Date.now()
+ }));
},
- // 发送文本消息
+ // ========== 消息发送相关方法 ==========
+
+ // 发送文本消息
sendTextMessage: function() {
const content = this.data.inputValue.trim();
if (!content) return;
const messageId = 'msg_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
- // 创建本地消息
+ console.log('发送文本消息,ID:', messageId);
+
+ // 将消息ID添加到发送中集合
+ const sendingMessages = new Set(this.data.sendingMessages);
+ sendingMessages.add(messageId);
+
+ // 创建本地消息 - 使用contentType
const newMessage = {
id: messageId,
isMe: true,
- type: 'text',
+ sender: 'user',
+ senderId: this.getCurrentUserId(),
+ contentType: 'text',
content: content,
timestamp: Date.now(),
- status: 'sending',
- avatar: this.data.expertAvatar
+ status: 'sending', // 设置为发送中状态
+ progress: 100
};
// 添加到列表
this.addMessageToList(newMessage);
+ // 更新发送中集合和发送计数
+ this.setData({
+ sendingMessages: sendingMessages,
+ sendingCount: sendingMessages.size,
+ showSendingTip: sendingMessages.size > 0
+ });
+
// 清空输入框
this.setData({ inputValue: '' });
- // 模拟发送延迟
+ // 通过WebSocket发送到服务器
+ this.sendMessageToServer(content, 'text', messageId);
+
+ // 设置超时,如果5秒后还没有收到回执,认为发送失败
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,
+ sendingCount: sendingMessages.size,
+ showSendingTip: sendingMessages.size > 0
+ });
+
+ wx.showToast({
+ title: '发送超时',
+ icon: 'none'
+ });
+ }
+ }, 5000);
},
- // 添加消息到列表
- addMessageToList: function(message) {
- const { messageList } = this.data;
+ // 上传图片后发送
+ uploadImages: function(tempFilePaths) {
+ // 改为依次上传,避免并发问题
+ this.uploadNextImage(tempFilePaths, 0);
+ },
+
+ // 递归上传下一张图片
+ uploadNextImage: function(tempFilePaths, index) {
+ if (index >= tempFilePaths.length) return;
- // 新消息添加到末尾
- messageList.push(message);
+ const tempFilePath = tempFilePaths[index];
+ const fileName = `image_${Date.now()}_${index}.jpg`;
- // 重新处理时间显示
- const processedMessages = this.processMessageTimes(messageList);
+ this.uploadImage(tempFilePath, fileName, () => {
+ // 上传完成后,继续上传下一张
+ this.uploadNextImage(tempFilePaths, index + 1);
+ });
+ },
+
+ // 上传单张图片 - 修复版
+ uploadImage: function(tempPath, fileName, callback) {
+ if (this.data.isUploading) {
+ wx.showToast({
+ title: '已有上传任务,请稍后',
+ icon: 'none'
+ });
+ return;
+ }
+
+ this.setData({ isUploading: true });
+
+ // 显示上传中提示
+ wx.showLoading({
+ title: '上传中...',
+ mask: true
+ });
+
+ const messageId = 'img_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
+
+ // 将消息ID添加到发送中集合
+ const sendingMessages = new Set(this.data.sendingMessages);
+ sendingMessages.add(messageId);
+
+ // 先显示本地图片(临时路径)
+ const tempMessage = {
+ id: messageId,
+ isMe: true,
+ sender: 'user',
+ senderId: this.getCurrentUserId(),
+ contentType: 'image',
+ content: tempPath, // 先用临时路径显示
+ timestamp: Date.now(),
+ status: 'uploading',
+ progress: 0,
+ fileName: fileName
+ };
+
+ this.addMessageToList(tempMessage);
this.setData({
- messageList: processedMessages
- }, () => {
- this.scrollToBottom(true);
+ sendingMessages: sendingMessages,
+ sendingCount: sendingMessages.size,
+ showSendingTip: sendingMessages.size > 0
});
+
+ // 开始上传
+ wx.uploadFile({
+ url: baseUrl + '/common/upload',
+ header: {
+ 'Authorization': 'Bearer ' + wx.getStorageSync('token')
+ },
+ filePath: tempPath,
+ name: 'file',
+ success: (uploadRes) => {
+ try {
+ console.log('上传响应:', uploadRes);
+ const result = JSON.parse(uploadRes.data);
+
+ if (result.code === 200 || result.fileName) {
+ // 获取服务器返回的文件路径
+ const serverPath = result.fileName || result.url;
+ console.log('服务器返回路径:', serverPath);
+
+ // 更新消息内容为服务器路径
+ this.updateMessageContent(messageId, serverPath);
+
+ // 通过WebSocket发送消息
+ this.sendMessageToServer(serverPath, 'image', messageId);
+
+ wx.hideLoading();
+ wx.showToast({
+ title: '上传成功',
+ icon: 'success'
+ });
+ } else {
+ throw new Error(result.msg || '上传失败');
+ }
+ } catch (error) {
+ console.error('上传失败:', error);
+ wx.hideLoading();
+
+ // 更新消息状态为失败
+ this.updateMessageStatus(messageId, 'failed');
+
+ // 从发送中集合移除
+ const sendingMessages = new Set(this.data.sendingMessages);
+ sendingMessages.delete(messageId);
+ this.setData({
+ sendingMessages: sendingMessages,
+ sendingCount: sendingMessages.size,
+ showSendingTip: sendingMessages.size > 0
+ });
+
+ wx.showToast({
+ title: error.message || '上传失败',
+ icon: 'none'
+ });
+ }
+ },
+ fail: (error) => {
+ console.error('网络请求失败:', error);
+ wx.hideLoading();
+
+ // 更新消息状态为失败
+ this.updateMessageStatus(messageId, 'failed');
+
+ // 从发送中集合移除
+ const sendingMessages = new Set(this.data.sendingMessages);
+ sendingMessages.delete(messageId);
+ this.setData({
+ sendingMessages: sendingMessages,
+ sendingCount: sendingMessages.size,
+ showSendingTip: sendingMessages.size > 0
+ });
+
+ wx.showToast({
+ title: '网络请求失败',
+ icon: 'none'
+ });
+ },
+ complete: () => {
+ this.setData({ isUploading: false });
+ if (callback) callback();
+ }
+ });
+
+ // 模拟上传进度(因为wx.uploadFile不支持进度回调)
+ let progress = 0;
+ const progressInterval = setInterval(() => {
+ progress += Math.random() * 20 + 5;
+ if (progress >= 95) {
+ progress = 95;
+ clearInterval(progressInterval);
+ }
+ this.updateMessageProgress(messageId, Math.min(Math.floor(progress), 95));
+ }, 200);
},
- // 更新消息状态
- updateMessageStatus: function(messageId, status) {
+ // 更新消息内容
+ updateMessageContent: function(messageId, serverPath) {
const { messageList } = this.data;
const index = messageList.findIndex(msg => msg.id === messageId);
if (index !== -1) {
- messageList[index].status = status;
+ messageList[index].content = serverPath;
+ messageList[index].status = 'success';
+ messageList[index].progress = 100;
this.setData({ messageList });
}
},
- // 重试发送
- retrySend: function(e) {
- const messageId = e.currentTarget.dataset.id;
+ // 更新消息进度
+ updateMessageProgress: function(messageId, progress) {
const { messageList } = this.data;
- const msg = messageList.find(m => m.id === messageId);
+ const index = messageList.findIndex(msg => msg.id === messageId);
- if (msg) {
- msg.status = 'sending';
+ if (index !== -1) {
+ messageList[index].progress = progress;
this.setData({ messageList });
-
- setTimeout(() => {
- msg.status = 'success';
- this.setData({ messageList });
- }, 500);
}
},
- // 处理消息时间显示
- processMessageTimes: function(messages) {
- if (!messages || messages.length === 0) return [];
+ // 添加消息到列表
+ addMessageToList: function(message) {
+ const { messageList, messageIds } = this.data;
+
+ // 检查是否已存在
+ if (messageIds.has(message.id)) {
+ console.log('消息已存在,不重复添加:', message.id);
+ return;
+ }
- const timeInterval = 5; // 5分钟间隔显示时间
+ // 新消息添加到末尾
+ messageList.push(message);
- return messages.map((msg, index) => {
- const showTime = index === 0 ||
- (msg.timestamp - messages[index - 1].timestamp) > timeInterval * 60 * 1000;
-
- return {
- ...msg,
- showTime
- };
- });
- },
-
- // 格式化时间
- formatTime: function(timestamp) {
- if (!timestamp) return '';
+ // 更新消息ID集合
+ messageIds.add(message.id);
- const date = new Date(Number(timestamp));
- const hours = date.getHours().toString().padStart(2, '0');
- const minutes = date.getMinutes().toString().padStart(2, '0');
- return `${hours}:${minutes}`;
+ // 重新处理时间显示
+ const processedMessages = this.processMessageTimes(messageList);
+
+ this.setData({
+ messageList: processedMessages,
+ messageIds: messageIds
+ }, () => {
+ this.scrollToBottom();
+ });
},
- // 格式化文件大小
- formatFileSize: function(bytes) {
- if (!bytes || bytes === 0) return '0B';
-
- const units = ['B', 'KB', 'MB', 'GB'];
- let size = bytes;
- let unitIndex = 0;
+ // 更新消息状态
+ updateMessageStatus: function(messageId, status) {
+ const { messageList } = this.data;
+ const index = messageList.findIndex(msg => msg.id === messageId);
- while (size >= 1024 && unitIndex < units.length - 1) {
- size /= 1024;
- unitIndex++;
+ if (index !== -1) {
+ messageList[index].status = status;
+ this.setData({ messageList });
+
+ // 如果是失败状态,从发送中集合移除
+ if (status === 'failed' || status === 'timeout') {
+ const sendingMessages = new Set(this.data.sendingMessages);
+ sendingMessages.delete(messageId);
+ this.setData({
+ sendingMessages: sendingMessages,
+ sendingCount: sendingMessages.size,
+ showSendingTip: sendingMessages.size > 0
+ });
+ }
}
-
- return size.toFixed(1) + units[unitIndex];
},
// 输入处理
onInput: function(e) {
this.setData({ inputValue: e.detail.value });
+
+ clearTimeout(this.typingDebounce);
+ this.typingDebounce = setTimeout(() => {
+ this.sendTypingStatus();
+ }, 500);
},
// 输入框获得焦点
onInputFocus: function() {
this.setData({ inputFocus: true }, () => {
setTimeout(() => {
- this.scrollToBottom(true);
+ this.scrollToBottom();
}, 200);
});
},
@@ -404,18 +1331,44 @@ Page({
// 滚动事件
onScroll: function(e) {
const scrollTop = e.detail.scrollTop;
- this.setData({ lastScrollTop: scrollTop });
+ this.setData({
+ lastScrollTop: scrollTop,
+ isScrolling: true
+ });
- // 滚动到顶部加载更多
- if (scrollTop <= 30 && !this.data.loadingMore && this.data.hasMore) {
+ clearTimeout(this.scrollTimer);
+ this.scrollTimer = setTimeout(() => {
+ this.setData({ isScrolling: false });
+ }, 200);
+
+ // 当滚动到顶部附近时加载更多
+ if (scrollTop <= 50 && !this.data.loadingMore && this.data.hasMore && this.data.firstLoadComplete) {
+ console.log('触发加载更多,当前滚动位置:', scrollTop);
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 });
+ }
+ }
+ });
},
// 滚动到底部
scrollToBottom: function(animate = true) {
+ if (this.data.isScrolling) return;
+
this.setData({
- scrollAnimate: animate
+ scrollAnimate: animate,
+ hasNewMessage: false
}, () => {
setTimeout(() => {
this.setData({ scrollTop: 999999 });
@@ -423,6 +1376,18 @@ Page({
});
},
+ // 键盘高度变化
+ onKeyboardHeightChange: function(res) {
+ this.setData({ keyboardHeight: res.height });
+
+ if (res.height > 0) {
+ this.setData({ showMediaSheet: false });
+ setTimeout(() => {
+ this.scrollToBottom();
+ }, 100);
+ }
+ },
+
// 显示多媒体选择面板
showMediaActionSheet: function() {
this.setData({
@@ -436,97 +1401,101 @@ Page({
this.setData({ showMediaSheet: false });
},
- // 选择图片(模拟)
+ // 选择图片
chooseImage: function() {
this.hideMediaActionSheet();
- // 模拟选择图片
- const mockImages = [
- 'https://picsum.photos/200/200?random=2',
- 'https://picsum.photos/200/200?random=3',
- 'https://picsum.photos/200/200?random=4'
- ];
-
- const randomImage = mockImages[Math.floor(Math.random() * mockImages.length)];
-
- const messageId = 'img_' + Date.now();
-
- const newMessage = {
- id: messageId,
- isMe: true,
- type: 'image',
- content: randomImage,
- timestamp: Date.now(),
- status: 'uploading',
- progress: 0,
- avatar: this.data.expertAvatar
- };
+ wx.chooseImage({
+ count: 9,
+ sizeType: ['compressed'],
+ sourceType: ['album', 'camera'],
+ success: (res) => {
+ console.log('选择的图片:', res.tempFilePaths);
+ this.uploadImages(res.tempFilePaths);
+ },
+ fail: (err) => {
+ console.error('选择图片失败:', err);
+ wx.showToast({
+ title: '选择图片失败',
+ icon: 'none'
+ });
+ }
+ });
+ },
+
+ // 预览图片
+ previewImage: function(e) {
+ const url = e.currentTarget.dataset.url;
+ wx.previewImage({
+ current: url,
+ urls: [url]
+ });
+ },
+
+ // 设置今天日期
+ setTodayDate: function() {
+ const now = new Date();
+ const month = now.getMonth() + 1;
+ const date = now.getDate();
+ const week = ['日', '一', '二', '三', '四', '五', '六'][now.getDay()];
+ this.setData({
+ todayDate: `${month}月${date}日 星期${week}`
+ });
+ },
+
+ // 处理消息时间显示
+ processMessageTimes: function(messages) {
+ if (!messages || messages.length === 0) return [];
- this.addMessageToList(newMessage);
+ const processedMessages = [];
+ let lastShowTime = null;
- // 模拟上传进度
- let progress = 0;
- const interval = setInterval(() => {
- progress += 20;
+ for (let i = 0; i < messages.length; i++) {
+ const msg = { ...messages[i] };
- if (progress >= 100) {
- clearInterval(interval);
- this.updateMessageStatus(messageId, 'success');
+ if (i === 0) {
+ msg.showTime = true;
+ lastShowTime = msg.timestamp;
} else {
- const msgIndex = this.data.messageList.findIndex(m => m.id === messageId);
- if (msgIndex !== -1) {
- this.data.messageList[msgIndex].progress = progress;
- this.setData({ messageList: this.data.messageList });
- }
+ const timeDiffMinutes = (msg.timestamp - lastShowTime) / (1000 * 60);
+ msg.showTime = timeDiffMinutes >= this.data.timeInterval;
+ if (msg.showTime) lastShowTime = msg.timestamp;
}
- }, 200);
+
+ processedMessages.push(msg);
+ }
+
+ if (lastShowTime) {
+ this.setData({ lastShowTimeStamp: lastShowTime });
+ }
+
+ return processedMessages;
},
- // 选择视频(模拟)
- chooseVideo: function() {
- this.hideMediaActionSheet();
+ // 格式化时间
+ formatTime: function(timestamp) {
+ if (!timestamp) return '';
- const messageId = 'video_' + Date.now();
+ const date = new Date(Number(timestamp));
+ const hours = date.getHours().toString().padStart(2, '0');
+ const minutes = date.getMinutes().toString().padStart(2, '0');
+ return `${hours}:${minutes}`;
+ },
+
+ // 格式化文件大小
+ formatFileSize: function(bytes) {
+ if (!bytes || bytes === 0) return '0B';
- const newMessage = {
- id: messageId,
- isMe: true,
- type: 'video',
- content: 'https://example.com/video.mp4',
- thumb: 'https://picsum.photos/200/200?random=5',
- timestamp: Date.now(),
- status: 'uploading',
- progress: 0,
- avatar: this.data.expertAvatar
- };
+ const units = ['B', 'KB', 'MB', 'GB'];
+ let size = bytes;
+ let unitIndex = 0;
- this.addMessageToList(newMessage);
+ while (size >= 1024 && unitIndex < units.length - 1) {
+ size /= 1024;
+ unitIndex++;
+ }
- // 模拟上传进度
- let progress = 0;
- const interval = setInterval(() => {
- progress += 25;
-
- if (progress >= 100) {
- clearInterval(interval);
- this.updateMessageStatus(messageId, 'success');
- } else {
- const msgIndex = this.data.messageList.findIndex(m => m.id === messageId);
- if (msgIndex !== -1) {
- this.data.messageList[msgIndex].progress = progress;
- this.setData({ messageList: this.data.messageList });
- }
- }
- }, 300);
- },
-
- // 预览图片
- previewImage: function(e) {
- const url = e.currentTarget.dataset.url;
- wx.previewImage({
- current: url,
- urls: [url]
- });
+ return size.toFixed(1) + units[unitIndex];
},
// 阻止事件冒泡
diff --git a/pagesA/pages/expertChat/expertChat.wxml b/pagesA/pages/expertChat/expertChat.wxml
index 11c7c54..79a5d57 100644
--- a/pagesA/pages/expertChat/expertChat.wxml
+++ b/pagesA/pages/expertChat/expertChat.wxml
@@ -1,17 +1,14 @@
-
+
-
-
+
@@ -209,7 +138,7 @@
disable-default-padding="{{true}}"
>
-
+
-
+
-
+
@@ -248,13 +177,6 @@
照片
-
-
-
-
-
- 视频
-
@@ -262,19 +184,4 @@
-
-
-
\ No newline at end of file
diff --git a/pagesA/pages/expertChat/expertChat.wxss b/pagesA/pages/expertChat/expertChat.wxss
index 392581c..389e3c6 100644
--- a/pagesA/pages/expertChat/expertChat.wxss
+++ b/pagesA/pages/expertChat/expertChat.wxss
@@ -1,685 +1,652 @@
-/* 专家端聊天样式 */
+/* ========== 页面整体样式 ========== */
.consult-page {
- width: 100vw;
- height: 100vh;
- background: #f5f5f5;
- display: flex;
- flex-direction: column;
- overflow: hidden;
- }
-
- /* 头部样式 */
- .consult-header {
- background: #ffffff;
- border-bottom: 1rpx solid #e5e5e5;
- position: relative;
- z-index: 1000;
- box-shadow: 0 1rpx 6rpx rgba(0, 0, 0, 0.05);
- flex-shrink: 0;
- }
-
- .header-content {
- display: flex;
- align-items: center;
- padding: 12rpx 24rpx;
- height: 96rpx;
- }
-
- .back-btn, .header-right {
- width: 48rpx;
- height: 48rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- .back-icon, .more-icon {
- width: 40rpx;
- height: 40rpx;
- }
-
- .header-center {
- flex: 1;
- display: flex;
- justify-content: center;
- }
-
- .expert-info {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- }
-
- .expert-name {
- font-size: 32rpx;
- font-weight: 600;
- color: #000000;
- line-height: 44rpx;
- margin-bottom: 4rpx;
- }
-
- .expert-status {
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- .status-dot {
- width: 16rpx;
- height: 16rpx;
- border-radius: 50%;
- margin-right: 8rpx;
- }
-
- .status-dot.online {
- background: #07c160;
- animation: pulse 2s infinite;
- }
-
- .status-dot.offline {
- background: #cccccc;
- }
-
- @keyframes pulse {
- 0% { box-shadow: 0 0 0 0 rgba(7, 193, 96, 0.4); }
- 70% { box-shadow: 0 0 0 8rpx rgba(7, 193, 96, 0); }
- 100% { box-shadow: 0 0 0 0 rgba(7, 193, 96, 0); }
- }
-
- .status-text {
- font-size: 24rpx;
- color: #666666;
- }
-
- /* 聊天容器 */
- .chat-container {
- flex: 1;
- padding: 20rpx 0;
- background: #f5f5f5;
- overflow-y: auto;
- position: relative;
- height: 0;
- }
-
- /* 消息项 */
- .message-item {
- display: flex;
- margin-bottom: 24rpx;
- padding: 0 30rpx;
- opacity: 0;
- animation: fadeIn 0.3s ease forwards;
- align-items: flex-start;
- position: relative;
- }
-
- @keyframes fadeIn {
- from { opacity: 0; transform: translateY(10rpx); }
- to { opacity: 1; transform: translateY(0); }
- }
-
- .message-left { justify-content: flex-start; }
- .message-right { justify-content: flex-end; }
-
- /* 头像 */
- .message-avatar {
- width: 80rpx;
- height: 80rpx;
- border-radius: 8rpx;
- overflow: hidden;
- flex-shrink: 0;
- background: #ffffff;
- border: 1rpx solid #f0f0f0;
- position: relative;
- z-index: 1;
- }
-
- .message-left .message-avatar { margin-right: 16rpx; }
- .message-right .message-avatar { margin-left: 16rpx; }
-
- .avatar-img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
-
- /* 消息内容包装器 */
- .message-content-wrapper {
- max-width: 480rpx;
- position: relative;
- display: flex;
- flex-direction: column;
- z-index: 2;
- }
-
- .message-left .message-content-wrapper { align-items: flex-start; }
- .message-right .message-content-wrapper { align-items: flex-end; }
-
- /* 气泡箭头 */
- .message-arrow {
- position: absolute;
- width: 0;
- height: 0;
- border-style: solid;
- border-width: 12rpx;
- top: 30rpx;
- z-index: 1;
- }
-
- .arrow-left {
- left: -24rpx;
- border-color: transparent #ffffff transparent transparent;
- }
-
- .arrow-right {
- right: -24rpx;
- border-color: transparent transparent transparent #95ec69;
- }
-
- /* 消息气泡 */
- .message-bubble {
- position: relative;
- padding: 16rpx 20rpx;
- word-break: break-word;
- box-sizing: border-box;
- min-height: 60rpx;
- display: flex;
- align-items: center;
- }
-
- .bubble-left {
- background: #ffffff;
- border-radius: 10rpx;
- border-top-left-radius: 4rpx;
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
- }
-
- .bubble-right {
- background: #95ec69;
- border-radius: 10rpx;
- border-top-right-radius: 4rpx;
- box-shadow: 0 2rpx 8rpx rgba(149, 236, 105, 0.2);
- }
-
- /* 文本消息 */
- .message-text {
- font-size: 32rpx;
- color: #000000;
- line-height: 1.4;
- }
-
- .bubble-right .message-text { color: #000000; }
-
- /* 媒体消息 */
- .media-bubble {
- position: relative;
- border-radius: 10rpx;
- overflow: hidden;
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
- background: #ffffff;
- min-height: 60rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- .message-left .media-bubble { border-top-left-radius: 4rpx; }
- .message-right .media-bubble { border-top-right-radius: 4rpx; }
-
- .message-image {
- width: 280rpx;
- height: 280rpx;
- display: block;
- }
-
- .message-video {
- width: 280rpx;
- height: 280rpx;
- background: #000000;
- }
-
- .video-play-overlay {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 80rpx;
- height: 80rpx;
- border-radius: 50%;
- background: rgba(0, 0, 0, 0.6);
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- .play-icon {
- width: 40rpx;
- height: 40rpx;
- margin-left: 4rpx;
- }
-
- /* 文件消息 */
- .message-file {
- min-width: 280rpx;
- padding: 20rpx;
- display: flex;
- align-items: center;
- min-height: 60rpx;
- }
-
- .file-icon-box {
- width: 56rpx;
- height: 72rpx;
- margin-right: 16rpx;
- position: relative;
- flex-shrink: 0;
- }
-
- .file-icon {
- width: 100%;
- height: 100%;
- }
-
- .file-info {
- flex: 1;
- display: flex;
- flex-direction: column;
- justify-content: center;
- overflow: hidden;
- }
-
- .file-name {
- font-size: 28rpx;
- font-weight: 500;
- color: #000000;
- margin-bottom: 6rpx;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-
- .file-size {
- font-size: 24rpx;
- color: #666666;
- }
-
- /* 消息时间 */
- .message-time {
- font-size: 22rpx;
- color: #999999;
- margin-top: 8rpx;
- align-self: flex-start;
- }
-
- .message-right .message-time {
- align-self: flex-end;
- }
-
- /* 消息状态 */
- .message-status {
- font-size: 22rpx;
- color: #999999;
- margin-top: 4rpx;
- display: flex;
- align-items: center;
- gap: 8rpx;
- }
-
- .message-status .fail {
- color: #ff4d4f;
- }
-
- .retry-text {
- color: #1890ff;
- text-decoration: underline;
- margin-left: 8rpx;
- }
-
- /* 上传进度 */
- .upload-progress {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(0, 0, 0, 0.5);
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: inherit;
- }
-
- .progress-circle {
- width: 80rpx;
- height: 80rpx;
- border-radius: 50%;
- border: 6rpx solid rgba(255, 255, 255, 0.3);
- border-top-color: #ffffff;
- animation: spin 1s linear infinite;
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- @keyframes spin {
- 0% { transform: rotate(0deg); }
- 100% { transform: rotate(360deg); }
- }
-
- .progress-text {
- font-size: 20rpx;
- color: #ffffff;
- font-weight: 600;
- }
-
- /* 底部留白 */
- .chat-bottom-space { height: 40rpx; }
-
- /* 加载更多 */
- .load-more-tip {
- display: flex;
- justify-content: center;
- align-items: center;
- padding: 30rpx 0;
- color: #999999;
- font-size: 24rpx;
- }
-
- /* 空状态 */
- .empty-tip {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 100rpx 0;
- color: #999999;
- }
-
- .empty-text {
- font-size: 28rpx;
- color: #999999;
- }
-
- /* 输入区域 */
- .input-section {
- background: #ffffff;
- border-top: 1rpx solid #e5e5e5;
- padding: 16rpx 30rpx;
- position: relative;
- z-index: 100;
- box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
- flex-shrink: 0;
- width: 100%;
- box-sizing: border-box;
- }
-
- .text-input-panel {
- display: flex;
- align-items: center;
- gap: 16rpx;
- min-height: 72rpx;
- }
-
- .add-btn {
- width: 72rpx;
- height: 72rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- }
-
- .add-icon {
- width: 60rpx;
- height: 60rpx;
- }
-
- .input-wrapper {
- flex: 1;
- position: relative;
- background: #f5f5f5;
- border-radius: 10rpx;
- min-height: 72rpx;
- display: flex;
- align-items: center;
- padding: 0 24rpx;
- transition: all 0.2s;
- box-sizing: border-box;
- }
-
- .input-wrapper:active {
- background: #e8e8e8;
- }
-
- .chat-textarea {
- flex: 1;
- width: 100%;
- font-size: 30rpx;
- color: #333333;
- line-height: 1.4;
- min-height: 48rpx;
- max-height: 160rpx;
- padding: 12rpx 0;
- margin: 0;
- background: transparent;
- border: none;
- box-sizing: border-box;
- overflow-y: auto;
- }
-
- .input-placeholder {
- color: #999999;
- font-size: 28rpx;
- line-height: 1.4;
- }
-
- .input-actions {
- position: absolute;
- right: 16rpx;
- top: 50%;
- transform: translateY(-50%);
- z-index: 2;
- flex-shrink: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- .clear-btn {
- width: 36rpx;
- height: 36rpx;
- border: none;
- background: transparent;
- padding: 0;
- margin: 0;
- line-height: 1;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- .clear-btn::after {
- border: none;
- }
-
- .clear-btn:active {
- background: rgba(0, 0, 0, 0.1);
- }
-
- .clear-icon {
- width: 28rpx;
- height: 28rpx;
- }
-
- .send-btn {
- background: linear-gradient(135deg, #07c160 0%, #06ae56 100%);
- width: 112rpx;
- height: 66rpx;
- border-radius: 10rpx;
- border: none;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.3s ease;
- padding: 0;
- margin: 0;
- line-height: 1;
- flex-shrink: 0;
- box-shadow: 0 4rpx 8rpx rgba(7, 193, 96, 0.2);
- position: relative;
- overflow: hidden;
- }
-
- .send-btn:active {
- background: linear-gradient(135deg, #06ae56 0%, #059c4c 100%);
- transform: scale(0.96);
- box-shadow: 0 2rpx 4rpx rgba(7, 193, 96, 0.3);
- }
-
- .send-text {
- font-size: 28rpx;
- color: #ffffff;
- font-weight: 600;
- letter-spacing: 2rpx;
- }
-
- .send-placeholder {
- width: 112rpx;
- height: 72rpx;
- flex-shrink: 0;
- }
-
- /* 多媒体选择面板 */
- .media-action-sheet {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(0, 0, 0, 0.5);
- display: flex;
- align-items: flex-end;
- justify-content: center;
- z-index: 2000;
- animation: fadeIn 0.3s ease;
- }
-
- .media-sheet-content {
- width: 100%;
- background: #F7F7F7;
- border-radius: 40rpx 40rpx 0 0;
- padding: 40rpx 30rpx calc(10rpx + env(safe-area-inset-bottom));
- animation: slideUp 0.3s ease;
- box-sizing: border-box;
- }
-
- @keyframes slideUp {
- from { transform: translateY(100%); }
- to { transform: translateY(0); }
- }
-
- .media-sheet-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 40rpx;
- padding: 0 10rpx 20rpx;
- border-bottom: 1px solid #E4E4E4;
- }
-
- .sheet-title {
- font-size: 32rpx;
- font-weight: 600;
- color: #000000;
- }
-
- .close-sheet-btn image {
- width: 60rpx;
- height: 60rpx;
- }
-
- .media-options-grid {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: 40rpx 30rpx;
- margin-bottom: 40rpx;
- }
-
- .media-option {
- display: flex;
- flex-direction: column;
- align-items: center;
- }
-
- .option-icon-box {
- width: 120rpx;
- height: 120rpx;
- border-radius: 30rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-bottom: 16rpx;
- transition: transform 0.2s;
- background-color: #fff;
- }
-
- .media-option:active .option-icon-box {
- transform: scale(0.95);
- }
-
- .option-icon-box image {
- width: 60rpx;
- height: 60rpx;
- }
-
- .option-text {
- font-size: 26rpx;
- color: #666666;
- }
-
- .sheet-bottom {
- text-align: center;
- }
-
- .bottom-tip {
- font-size: 24rpx;
- color: #999999;
- }
-
- /* 更多菜单 */
- .more-menu {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(0, 0, 0, 0.3);
- z-index: 2000;
- }
-
- .menu-content {
- position: absolute;
- top: 96rpx;
- right: 24rpx;
- background: #ffffff;
- border-radius: 12rpx;
- padding: 16rpx 0;
- min-width: 200rpx;
- box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.15);
- }
-
- .menu-item {
- padding: 24rpx 32rpx;
- font-size: 28rpx;
- color: #333333;
- border-bottom: 1rpx solid #f0f0f0;
- }
-
- .menu-item:last-child {
- border-bottom: none;
- }
-
- .menu-item:active {
- background: #f5f5f5;
- }
\ No newline at end of file
+ width: 100vw;
+ height: 100vh;
+ background: #f5f5f5;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+/* ========== 头部样式 ========== */
+.consult-header {
+ background: #ffffff;
+ border-bottom: 1rpx solid #e5e5e5;
+ position: relative;
+ z-index: 1000;
+ box-shadow: 0 1rpx 6rpx rgba(0, 0, 0, 0.05);
+ flex-shrink: 0;
+}
+
+.header-content {
+ display: flex;
+ align-items: center;
+ padding: 12rpx 24rpx;
+ height: 96rpx;
+}
+
+.header-center {
+ flex: 1;
+ display: flex;
+ justify-content: center;
+}
+
+.expert-info {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.expert-name {
+ font-size: 32rpx;
+ font-weight: 600;
+ color: #000000;
+ line-height: 44rpx;
+ margin-bottom: 4rpx;
+}
+
+.expert-status {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.status-dot {
+ width: 16rpx;
+ height: 16rpx;
+ border-radius: 50%;
+ margin-right: 8rpx;
+}
+
+.status-dot.online {
+ background: #07c160;
+ animation: pulse 2s infinite;
+}
+
+.status-dot.offline {
+ background: #cccccc;
+}
+
+@keyframes pulse {
+ 0% { box-shadow: 0 0 0 0 rgba(7, 193, 96, 0.4); }
+ 70% { box-shadow: 0 0 0 8rpx rgba(7, 193, 96, 0); }
+ 100% { box-shadow: 0 0 0 0 rgba(7, 193, 96, 0); }
+}
+
+.status-text {
+ font-size: 24rpx;
+ color: #666666;
+}
+
+/* ========== 聊天容器 ========== */
+.chat-container {
+ flex: 1;
+ padding: 20rpx 0;
+ background: #f5f5f5;
+ overflow-y: auto;
+ position: relative;
+ height: 0;
+}
+
+/* 日期分隔线 */
+.date-divider {
+ display: flex;
+ justify-content: center;
+ margin: 40rpx 0 30rpx;
+}
+
+.date-text {
+ background: rgba(0, 0, 0, 0.1);
+ padding: 8rpx 32rpx;
+ border-radius: 100rpx;
+ font-size: 24rpx;
+ color: #ffffff;
+ background-color: #d8d8d8;
+}
+
+/* ========== 消息项 ========== */
+.message-item {
+ display: flex;
+ margin-bottom: 24rpx;
+ padding: 0 30rpx;
+ opacity: 0;
+ animation: fadeIn 0.3s ease forwards;
+ align-items: flex-start;
+ position: relative;
+}
+
+@keyframes fadeIn {
+ from { opacity: 0; transform: translateY(10rpx); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+.message-left { justify-content: flex-start; }
+.message-right { justify-content: flex-end; }
+
+/* 头像 */
+.message-avatar {
+ width: 80rpx;
+ height: 80rpx;
+ border-radius: 8rpx;
+ overflow: hidden;
+ flex-shrink: 0;
+ background: #ffffff;
+ border: 1rpx solid #f0f0f0;
+ position: relative;
+ z-index: 1;
+}
+
+.message-left .message-avatar { margin-right: 16rpx; }
+.message-right .message-avatar { margin-left: 16rpx; }
+
+.avatar-img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+/* 消息内容包装器 - 统一类名 */
+.message-content-wrapper {
+ max-width: 480rpx;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ z-index: 2;
+}
+
+.message-left .message-content-wrapper { align-items: flex-start; }
+.message-right .message-content-wrapper { align-items: flex-end; }
+
+/* 气泡箭头 */
+.message-arrow {
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 12rpx;
+ top: 30rpx;
+ z-index: 1;
+}
+
+.arrow-left {
+ left: -24rpx;
+ border-color: transparent #ffffff transparent transparent;
+}
+
+.arrow-right {
+ right: -24rpx;
+ border-color: transparent transparent transparent #95ec69;
+}
+
+/* 消息气泡 */
+.message-bubble {
+ position: relative;
+ padding: 16rpx 20rpx;
+ word-break: break-word;
+ box-sizing: border-box;
+ min-height: 60rpx;
+ display: flex;
+ align-items: center;
+}
+
+.bubble-left {
+ background: #ffffff;
+ border-radius: 10rpx;
+ border-top-left-radius: 4rpx;
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+}
+
+.bubble-right {
+ background: #95ec69;
+ border-radius: 10rpx;
+ border-top-right-radius: 4rpx;
+ box-shadow: 0 2rpx 8rpx rgba(149, 236, 105, 0.2);
+}
+
+/* 文本消息 */
+.message-text {
+ font-size: 32rpx;
+ color: #000000;
+ line-height: 1.4;
+}
+
+.bubble-right .message-text { color: #000000; }
+
+/* 媒体消息 */
+.media-bubble {
+ position: relative;
+ border-radius: 10rpx;
+ overflow: hidden;
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+ background: #ffffff;
+ min-height: 60rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.message-left .media-bubble { border-top-left-radius: 4rpx; }
+.message-right .media-bubble { border-top-right-radius: 4rpx; }
+
+.message-image {
+ width: 280rpx;
+ height: 280rpx;
+ display: block;
+}
+
+.message-video {
+ width: 280rpx;
+ height: 280rpx;
+ background: #000000;
+}
+
+/* 文件消息 */
+.message-file {
+ min-width: 280rpx;
+ padding: 20rpx;
+ display: flex;
+ align-items: center;
+ min-height: 60rpx;
+}
+
+.file-icon-box {
+ width: 56rpx;
+ height: 72rpx;
+ margin-right: 16rpx;
+ position: relative;
+ flex-shrink: 0;
+}
+
+.file-icon {
+ width: 100%;
+ height: 100%;
+}
+
+.file-info {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ overflow: hidden;
+}
+
+.file-name {
+ font-size: 28rpx;
+ font-weight: 500;
+ color: #000000;
+ margin-bottom: 6rpx;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.file-size {
+ font-size: 24rpx;
+ color: #666666;
+}
+
+
+
+/* 上传进度 */
+.upload-progress {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: inherit;
+}
+
+.progress-circle {
+ width: 80rpx;
+ height: 80rpx;
+ border-radius: 50%;
+ border: 6rpx solid rgba(255, 255, 255, 0.3);
+ border-top-color: #ffffff;
+ animation: spin 1s linear infinite;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+.progress-text {
+ font-size: 20rpx;
+ color: #ffffff;
+ font-weight: 600;
+}
+
+/* 底部留白 */
+.chat-bottom-space { height: 40rpx; }
+
+/* 加载更多 */
+.load-more-tip {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 30rpx 0;
+ color: #999999;
+ font-size: 24rpx;
+}
+
+/* 空状态 */
+.empty-tip {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 100rpx 0;
+ color: #999999;
+}
+
+.empty-text {
+ font-size: 28rpx;
+ color: #999999;
+}
+
+/* ========== 输入区域 - 优化垂直对齐和按钮美化 ========== */
+.input-section {
+ background: #ffffff;
+ border-top: 1rpx solid #e5e5e5;
+ padding: 16rpx 30rpx;
+ position: relative;
+ z-index: 100;
+ box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
+ flex-shrink: 0;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+/* 文字输入面板 - 垂直居中优化 */
+.text-input-panel {
+ display: flex;
+ align-items: center;
+ gap: 16rpx;
+ min-height: 72rpx;
+}
+
+/* 添加按钮 - 完美垂直居中 */
+.add-btn {
+ width: 72rpx;
+ height: 72rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+}
+
+.add-icon {
+ width: 60rpx;
+ height: 60rpx;
+}
+
+/* 输入框包装器 - 优化高度和内边距 */
+.input-wrapper {
+ flex: 1;
+ position: relative;
+ background: #f5f5f5;
+ border-radius: 10rpx;
+ min-height: 72rpx;
+ display: flex;
+ align-items: center;
+ padding: 0 24rpx;
+ transition: all 0.2s;
+ box-sizing: border-box;
+}
+
+.input-wrapper:active {
+ background: #e8e8e8;
+}
+
+/* 多行文本输入框 - 优化垂直居中 */
+.chat-textarea {
+ flex: 1;
+ width: 100%;
+ font-size: 30rpx;
+ color: #333333;
+ line-height: 1.4;
+ min-height: 48rpx;
+ max-height: 160rpx;
+ padding: 12rpx 0;
+ margin: 0;
+ background: transparent;
+ border: none;
+ box-sizing: border-box;
+ overflow-y: auto;
+}
+
+/* 占位符样式 */
+.input-placeholder {
+ color: #999999;
+ font-size: 28rpx;
+ line-height: 1.4;
+}
+
+/* 清空按钮 - 完美垂直居中 */
+.input-actions {
+ position: absolute;
+ right: 16rpx;
+ top: 50%;
+ transform: translateY(-50%);
+ z-index: 2;
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.clear-btn {
+ width: 36rpx;
+ height: 36rpx;
+ border: none;
+ background: transparent;
+ padding: 0;
+ margin: 0;
+ line-height: 1;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.clear-btn::after {
+ border: none;
+}
+
+.clear-btn:active {
+ background: rgba(0, 0, 0, 0.1);
+}
+
+.clear-icon {
+ width: 28rpx;
+ height: 28rpx;
+}
+
+/* 发送按钮 */
+.send-btn {
+ background: linear-gradient(135deg, #07c160 0%, #06ae56 100%);
+ width: 112rpx;
+ height: 66rpx;
+ border-radius: 10rpx;
+ border: none;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.3s ease;
+ padding: 0;
+ margin: 0;
+ line-height: 1;
+ flex-shrink: 0;
+ box-shadow: 0 4rpx 8rpx rgba(7, 193, 96, 0.2);
+ position: relative;
+ overflow: hidden;
+}
+
+/* 发送按钮点击效果 */
+.send-btn:active {
+ background: linear-gradient(135deg, #06ae56 0%, #059c4c 100%);
+ transform: scale(0.96);
+ box-shadow: 0 2rpx 4rpx rgba(7, 193, 96, 0.3);
+}
+
+/* 发送按钮水波纹效果 */
+.send-btn::after {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 0;
+ height: 0;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, 0.3);
+ transform: translate(-50%, -50%);
+ transition: width 0.3s, height 0.3s;
+}
+
+.send-btn:active::after {
+ width: 200rpx;
+ height: 200rpx;
+ opacity: 0;
+}
+
+.send-text {
+ font-size: 28rpx;
+ color: #ffffff;
+ font-weight: 600;
+ letter-spacing: 2rpx;
+}
+
+/* 发送按钮占位 - 保持布局稳定 */
+.send-placeholder {
+ width: 112rpx;
+ height: 72rpx;
+ flex-shrink: 0;
+}
+
+/* 适配小屏幕 */
+@media screen and (max-width: 320px) {
+ .send-btn { width: 100rpx; }
+ .send-placeholder { width: 100rpx; }
+}
+
+/* ========== 多媒体选择面板 ========== */
+.media-action-sheet {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: flex-end;
+ justify-content: center;
+ z-index: 2000;
+ animation: fadeIn 0.3s ease;
+}
+
+.media-sheet-content {
+ width: 100%;
+ background: #F7F7F7;
+ border-radius: 40rpx 40rpx 0 0;
+ padding: 40rpx 30rpx calc(10rpx + env(safe-area-inset-bottom));
+ animation: slideUp 0.3s ease;
+ box-sizing: border-box;
+}
+
+@keyframes slideUp {
+ from { transform: translateY(100%); }
+ to { transform: translateY(0); }
+}
+
+.media-sheet-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 40rpx;
+ padding: 0 10rpx 20rpx;
+ border-bottom: 1px solid #E4E4E4;
+}
+
+.sheet-title {
+ font-size: 32rpx;
+ font-weight: 600;
+ color: #000000;
+}
+
+.close-sheet-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.close-sheet-btn image {
+ width: 60rpx;
+ height: 60rpx;
+}
+
+.media-options-grid {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 40rpx 30rpx;
+ margin-bottom: 40rpx;
+}
+
+.media-option {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ border: none;
+ background: transparent;
+ padding: 0;
+ margin: 0;
+ line-height: 1;
+}
+
+.option-icon-box {
+ width: 120rpx;
+ height: 120rpx;
+ border-radius: 30rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 16rpx;
+ transition: transform 0.2s;
+ background-color: #fff;
+}
+
+.media-option:active .option-icon-box {
+ transform: scale(0.95);
+}
+
+.option-icon-box image {
+ width: 60rpx;
+ height: 60rpx;
+}
+
+.option-text {
+ font-size: 26rpx;
+ color: #666666;
+}
+
+.sheet-bottom {
+ text-align: center;
+}
+
+.bottom-tip {
+ font-size: 24rpx;
+ color: #999999;
+}
+
+/* 适配全面屏 */
+.safe-area-bottom {
+ padding-bottom: env(safe-area-inset-bottom);
+}
\ No newline at end of file
diff --git a/utils/api.js b/utils/api.js
index 7145fa1..6a9704e 100644
--- a/utils/api.js
+++ b/utils/api.js
@@ -149,8 +149,21 @@ function feedback(params) {
http('/muhu/feedback/list', 'get', params)
}
+//咨询列表
+function sessions(params) {
+ http('/system/chat/sessions', 'get', params)
+}
+
+// 建立兽医一对一聊天
+function create(params) {
+ http('/system/chat/session/create', 'post', params)
+}
+// 查找一对一聊天的记录
+function direct(params) {
+ http('/system/chat/messages/direct', 'get', params)
+}
@@ -174,5 +187,5 @@ export default { // 暴露接口
login,carousel,getPhoneNumber,article,articleDetails,articleZd,wzd,wzdAdd,shareAdd,
areaChildren,userCode,UserInfo,videoList,videoZd,videoDetails,forumList,forumAdd,forumDetails,
forumReply,commentReply,experience,experiencezd,experienceDetails,realName,revise,feedback,
- today,carouselDetail,videoAdd,articleAdd,wzdxq,fazdAdd
+ today,carouselDetail,videoAdd,articleAdd,wzdxq,fazdAdd,sessions,create,direct
}