与牧同行-小程序用户端
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1373 lines
37 KiB

  1. import http from '../../../utils/api'
  2. const baseUrl = require('../../../utils/baseUrl')
  3. // WebSocket实例
  4. let socketOpen = false
  5. let socketMsgQueue = []
  6. let heartbeatInterval = null
  7. let reconnectTimer = null
  8. let isManualClose = false // 是否手动关闭
  9. let heartBeatTimer = null // 心跳定时器
  10. Page({
  11. data: {
  12. baseUrl: baseUrl,
  13. // 专家信息
  14. expertInfo: {},
  15. // 用户信息
  16. userInfo: {
  17. user: {} // 完整用户信息
  18. },
  19. // 对话信息
  20. conversation: {},
  21. // 消息列表
  22. messageList: [],
  23. scrollTop: 0,
  24. scrollAnimate: false,
  25. // 输入相关
  26. inputValue: '',
  27. inputFocus: false,
  28. // 多媒体
  29. showMediaSheet: false,
  30. // 页面状态
  31. showDateDivider: true,
  32. todayDate: '',
  33. loading: false,
  34. loadingMore: false,
  35. // 滚动相关
  36. isScrolling: false,
  37. lastScrollTop: 0,
  38. // 当前对话ID和对方用户ID
  39. currentExpertId: '',
  40. conversationId: '',
  41. otherUserId: '',
  42. // 分页相关
  43. page: 1,
  44. pageSize: 20,
  45. hasMore: true,
  46. total: 0,
  47. // 记录最早消息的时间戳,用于加载更早的消息
  48. earliestMsgTime: 0,
  49. // 时间显示间隔
  50. timeInterval: 5,
  51. lastShowTimeStamp: 0,
  52. // 键盘高度
  53. keyboardHeight: 0,
  54. // WebSocket状态
  55. socketConnected: false,
  56. reconnectCount: 0,
  57. maxReconnectCount: 5,
  58. // 第一次加载完成标记
  59. firstLoadComplete: false,
  60. // 是否有新消息
  61. hasNewMessage: false,
  62. // 消息ID集合,用于去重
  63. messageIds: new Set(),
  64. // 正在发送中的消息ID集合
  65. sendingMessages: new Set()
  66. },
  67. onLoad: function(options) {
  68. console.log('接收到的参数:', options);
  69. // 设置今天日期
  70. this.setTodayDate();
  71. // 加载用户信息
  72. this.loadUserInfo();
  73. // 获取对方用户ID
  74. if (options.id) {
  75. this.setData({
  76. otherUserId: options.id,
  77. currentExpertId: options.id,
  78. // 初始页码设置为1
  79. page: 1,
  80. messageList: [],
  81. hasMore: true,
  82. earliestMsgTime: 0,
  83. firstLoadComplete: false,
  84. messageIds: new Set(),
  85. sendingMessages: new Set()
  86. });
  87. // 先创建对话,然后获取聊天记录
  88. this.createConversation(options.id);
  89. }
  90. // 监听键盘高度变化
  91. wx.onKeyboardHeightChange(this.onKeyboardHeightChange.bind(this));
  92. },
  93. onShow: function() {
  94. // 页面显示时连接WebSocket
  95. isManualClose = false;
  96. // 确保用户ID和对话ID都存在再连接
  97. const userId = this.getCurrentUserId();
  98. if (userId && this.data.conversationId) {
  99. console.log('页面显示,准备连接WebSocket,用户ID:', userId);
  100. this.connectWebSocket();
  101. } else {
  102. console.log('用户ID或对话ID不存在,稍后再连接', {
  103. userId: userId,
  104. conversationId: this.data.conversationId
  105. });
  106. }
  107. },
  108. onHide: function() {
  109. // 页面隐藏时暂停心跳检查但不关闭连接
  110. console.log('页面隐藏,暂停心跳检查');
  111. if (heartBeatTimer) {
  112. clearInterval(heartBeatTimer);
  113. heartBeatTimer = null;
  114. }
  115. },
  116. onUnload: function() {
  117. // 页面卸载时关闭WebSocket并清理资源
  118. isManualClose = true;
  119. this.closeWebSocket();
  120. wx.offKeyboardHeightChange();
  121. // 清理定时器
  122. if (heartbeatInterval) {
  123. clearInterval(heartbeatInterval);
  124. heartbeatInterval = null;
  125. }
  126. if (reconnectTimer) {
  127. clearTimeout(reconnectTimer);
  128. reconnectTimer = null;
  129. }
  130. if (heartBeatTimer) {
  131. clearInterval(heartBeatTimer);
  132. heartBeatTimer = null;
  133. }
  134. },
  135. // 获取当前用户ID的辅助方法
  136. getCurrentUserId: function() {
  137. const userId = this.data.userInfo?.user?.userId;
  138. console.log('获取到的用户ID:', userId);
  139. return userId;
  140. },
  141. // 创建一对一聊天
  142. createConversation(id) {
  143. wx.showLoading({ title: '加载中...' });
  144. http.create({
  145. data: {
  146. otherUserId: id
  147. },
  148. success: res => {
  149. console.log('创建对话响应:', res);
  150. if (res && res.data) {
  151. // 保存对话ID
  152. const conversationData = res.data;
  153. this.setData({
  154. conversationId: conversationData.id || conversationData.conversationId || id,
  155. conversation: conversationData,
  156. otherUserId: id,
  157. // 重置分页参数
  158. page: 1,
  159. hasMore: true,
  160. messageList: [],
  161. earliestMsgTime: 0,
  162. firstLoadComplete: false,
  163. messageIds: new Set(),
  164. sendingMessages: new Set()
  165. });
  166. // 获取聊天记录(从第一页开始)
  167. this.getChatHistory(id, false);
  168. // 连接WebSocket
  169. const userId = this.getCurrentUserId();
  170. if (userId) {
  171. console.log('用户ID已存在,立即连接WebSocket');
  172. this.connectWebSocket();
  173. } else {
  174. console.log('用户ID尚未加载,等待用户信息加载完成');
  175. }
  176. } else {
  177. wx.hideLoading();
  178. wx.showToast({
  179. title: '创建对话失败',
  180. icon: 'none'
  181. });
  182. }
  183. },
  184. fail: err => {
  185. console.error('创建对话失败:', err);
  186. wx.hideLoading();
  187. wx.showToast({
  188. title: '网络错误',
  189. icon: 'none'
  190. });
  191. }
  192. });
  193. },
  194. // 获取聊天记录
  195. getChatHistory(id, isLoadMore = false) {
  196. // 如果是加载更多且没有更多数据,直接返回
  197. if (isLoadMore && !this.data.hasMore) {
  198. console.log('没有更多消息了');
  199. this.setData({ loadingMore: false });
  200. return;
  201. }
  202. // 如果不是加载更多,显示加载提示
  203. if (!isLoadMore) {
  204. wx.showLoading({ title: '加载中...' });
  205. }
  206. const params = {
  207. otherUserId: id,
  208. page: this.data.page,
  209. pageSize: this.data.pageSize
  210. };
  211. console.log('请求聊天记录参数:', params);
  212. http.direct({
  213. data: params,
  214. success: res => {
  215. console.log('获取聊天记录响应:', res);
  216. if (!isLoadMore) {
  217. wx.hideLoading();
  218. }
  219. if (res && res.code === 200) {
  220. // 处理返回的消息数据
  221. this.processChatHistory(res, isLoadMore);
  222. } else {
  223. // 如果没有聊天记录,显示空状态
  224. this.setData({
  225. messageList: [],
  226. loading: false,
  227. loadingMore: false,
  228. hasMore: false,
  229. firstLoadComplete: true
  230. });
  231. if (!isLoadMore) {
  232. wx.showToast({
  233. title: res?.msg || '获取聊天记录失败',
  234. icon: 'none'
  235. });
  236. }
  237. }
  238. },
  239. fail: err => {
  240. console.error('获取聊天记录失败:', err);
  241. if (!isLoadMore) {
  242. wx.hideLoading();
  243. }
  244. wx.showToast({
  245. title: '网络错误',
  246. icon: 'none'
  247. });
  248. this.setData({
  249. messageList: [],
  250. loading: false,
  251. loadingMore: false,
  252. firstLoadComplete: true
  253. });
  254. }
  255. });
  256. },
  257. // 处理聊天历史数据
  258. processChatHistory(response, isLoadMore = false) {
  259. let messages = [];
  260. let total = 0;
  261. // 根据实际返回的数据结构调整这里
  262. if (response.rows && Array.isArray(response.rows)) {
  263. messages = response.rows;
  264. total = response.total || messages.length;
  265. } else if (response.data && Array.isArray(response.data)) {
  266. messages = response.data;
  267. total = response.total || messages.length;
  268. } else if (Array.isArray(response)) {
  269. messages = response;
  270. total = messages.length;
  271. }
  272. console.log('处理消息数据:', {
  273. messageCount: messages.length,
  274. total,
  275. isLoadMore,
  276. currentPage: this.data.page
  277. });
  278. // 格式化消息数据
  279. const formattedMessages = this.formatMessages(messages);
  280. // 判断是否还有更多数据
  281. const hasMore = this.data.page * this.data.pageSize < total;
  282. // 记录最早消息的时间戳(用于加载更多)
  283. let earliestTime = this.data.earliestMsgTime;
  284. if (formattedMessages.length > 0) {
  285. // 因为接口返回的是正序,第一条就是最早的
  286. earliestTime = formattedMessages[0].timestamp;
  287. }
  288. let newMessageList;
  289. let targetScrollTop = 0;
  290. // 构建消息ID集合
  291. const messageIds = new Set(this.data.messageIds);
  292. formattedMessages.forEach(msg => {
  293. if (msg.id) {
  294. messageIds.add(msg.id);
  295. }
  296. });
  297. if (isLoadMore) {
  298. // 加载更多:新加载的消息(更早的)应该放在现有消息列表的前面
  299. newMessageList = [...formattedMessages, ...this.data.messageList];
  300. // 记录新消息的总高度,用于滚动定位
  301. targetScrollTop = formattedMessages.length * 120; // 估算每条消息高度
  302. } else {
  303. // 首次加载
  304. newMessageList = formattedMessages;
  305. }
  306. // 重新处理消息时间显示
  307. const processedMessages = this.processMessageTimes(newMessageList);
  308. this.setData({
  309. messageList: processedMessages,
  310. messageIds: messageIds,
  311. loading: false,
  312. loadingMore: false,
  313. hasMore: hasMore,
  314. total: total,
  315. earliestMsgTime: earliestTime,
  316. firstLoadComplete: true
  317. }, () => {
  318. if (isLoadMore) {
  319. // 加载更多后,调整滚动位置以保持在当前查看的位置
  320. setTimeout(() => {
  321. this.setData({
  322. scrollAnimate: false,
  323. scrollTop: targetScrollTop
  324. }, () => {
  325. setTimeout(() => {
  326. this.setData({ scrollAnimate: true });
  327. }, 100);
  328. });
  329. }, 50);
  330. } else {
  331. // 首次加载,滚动到底部(显示最新消息)
  332. setTimeout(() => {
  333. this.scrollToBottom(false);
  334. }, 100);
  335. }
  336. });
  337. },
  338. // 格式化消息数据
  339. formatMessages(messages) {
  340. if (!messages || messages.length === 0) return [];
  341. const userId = this.getCurrentUserId();
  342. console.log('格式化消息使用的用户ID:', userId);
  343. return messages.map(msg => {
  344. // 判断消息类型
  345. let contentType = msg.contentType || 'text';
  346. let type = msg.type || 'text';
  347. let content = msg.content || '';
  348. // 判断发送者
  349. const isMe = msg.senderId === userId ||
  350. msg.fromUserId === userId ||
  351. msg.userId === userId ||
  352. msg.sendId === userId;
  353. return {
  354. id: msg.id || 'msg_' + Date.now() + Math.random(),
  355. isMe: isMe,
  356. sender: isMe ? 'user' : 'expert',
  357. senderId: msg.senderId || msg.fromUserId || msg.userId,
  358. contentType: contentType,
  359. type: type,
  360. content: content,
  361. timestamp: msg.createTime || msg.timestamp || Date.now(),
  362. status: msg.status || 'success',
  363. ...msg
  364. };
  365. });
  366. },
  367. // 加载更多消息
  368. loadMoreMessages: function() {
  369. // 防止重复加载
  370. if (this.data.loadingMore) {
  371. console.log('正在加载中,请稍后');
  372. return;
  373. }
  374. // 检查是否有更多数据
  375. if (!this.data.hasMore) {
  376. console.log('没有更多消息了');
  377. return;
  378. }
  379. // 检查对方用户ID是否存在
  380. if (!this.data.otherUserId) {
  381. console.error('对方用户ID不存在');
  382. return;
  383. }
  384. console.log('开始加载更多消息,当前页码:', this.data.page);
  385. // 设置加载状态
  386. this.setData({
  387. loadingMore: true
  388. }, () => {
  389. // 页码+1,加载更早的消息
  390. const nextPage = this.data.page + 1;
  391. this.setData({
  392. page: nextPage
  393. }, () => {
  394. // 调用获取聊天记录方法,传入true表示是加载更多操作
  395. this.getChatHistory(this.data.otherUserId, true);
  396. });
  397. });
  398. },
  399. // 加载用户信息
  400. loadUserInfo: function() {
  401. try {
  402. const userInfo = wx.getStorageSync('userInfo');
  403. console.log('从缓存加载的用户信息:', userInfo);
  404. if (userInfo) {
  405. // 处理用户信息,确保正确的结构
  406. let processedUserInfo = { ...this.data.userInfo };
  407. if (userInfo.user) {
  408. // 如果已经有user结构
  409. processedUserInfo = userInfo;
  410. } else if (userInfo.userId) {
  411. // 如果是直接返回的用户数据
  412. processedUserInfo = {
  413. user: userInfo
  414. };
  415. }
  416. // 确保有user对象
  417. if (!processedUserInfo.user) {
  418. processedUserInfo.user = {};
  419. }
  420. // 确保userId存在
  421. if (userInfo.userId && !processedUserInfo.user.userId) {
  422. processedUserInfo.user.userId = userInfo.userId;
  423. }
  424. // 设置用户ID
  425. const userId = processedUserInfo.user?.userId || userInfo.userId;
  426. if (userId) {
  427. processedUserInfo.id = userId;
  428. }
  429. console.log('处理后的用户信息:', processedUserInfo);
  430. this.setData({ userInfo: processedUserInfo });
  431. // 用户信息加载完成后,如果对话ID已存在,连接WebSocket
  432. if (this.data.conversationId && !this.data.socketConnected) {
  433. console.log('用户信息加载完成,连接WebSocket');
  434. this.connectWebSocket();
  435. }
  436. }
  437. } catch (e) {
  438. console.error('加载用户信息失败:', e);
  439. }
  440. },
  441. // ========== WebSocket相关方法 ==========
  442. // 连接WebSocket
  443. connectWebSocket() {
  444. // 如果已有连接,先检查是否可用
  445. if (socketOpen) {
  446. console.log('WebSocket已连接,无需重复连接');
  447. return;
  448. }
  449. const userId = this.getCurrentUserId();
  450. console.log('准备连接WebSocket,用户ID:', userId);
  451. if (!userId) {
  452. console.error('用户ID不存在,无法连接WebSocket');
  453. wx.showToast({
  454. title: '用户信息加载中,请稍后',
  455. icon: 'none'
  456. });
  457. return;
  458. }
  459. // WebSocket连接URL
  460. const basurl = '192.168.101.109:8080'; // 从配置文件获取
  461. const wsUrl = `ws://${basurl}/ws/mini/chat?userId=${userId}`;
  462. console.log('开始连接WebSocket:', wsUrl);
  463. wx.connectSocket({
  464. url: wsUrl,
  465. success: () => {
  466. console.log('WebSocket连接初始化成功');
  467. },
  468. fail: (err) => {
  469. console.error('WebSocket连接初始化失败:', err);
  470. this.reconnectWebSocket();
  471. }
  472. });
  473. // 监听连接打开
  474. wx.onSocketOpen(() => {
  475. console.log('WebSocket连接已打开');
  476. socketOpen = true;
  477. this.setData({ socketConnected: true, reconnectCount: 0 });
  478. // 发送队列中的消息
  479. while (socketMsgQueue.length > 0) {
  480. const msg = socketMsgQueue.shift();
  481. this.sendWebSocketMessage(msg);
  482. }
  483. // 开始心跳
  484. this.startHeartbeat();
  485. });
  486. // 监听收到消息
  487. wx.onSocketMessage((res) => {
  488. try {
  489. console.log('收到原始WebSocket消息:', res.data);
  490. const data = JSON.parse(res.data);
  491. console.log('解析后的消息:', data);
  492. // 处理不同类型的消息
  493. this.handleWebSocketMessage(data);
  494. } catch (e) {
  495. console.error('解析消息失败:', e, '原始数据:', res.data);
  496. }
  497. });
  498. // 监听连接关闭
  499. wx.onSocketClose(() => {
  500. console.log('WebSocket连接已关闭');
  501. socketOpen = false;
  502. this.setData({ socketConnected: false });
  503. // 停止心跳
  504. if (heartBeatTimer) {
  505. clearInterval(heartBeatTimer);
  506. heartBeatTimer = null;
  507. }
  508. // 如果不是手动关闭,尝试重连
  509. if (!isManualClose) {
  510. this.reconnectWebSocket();
  511. }
  512. });
  513. // 监听错误
  514. wx.onSocketError((err) => {
  515. console.error('WebSocket错误:', err);
  516. socketOpen = false;
  517. this.setData({ socketConnected: false });
  518. });
  519. },
  520. // 关闭WebSocket
  521. closeWebSocket() {
  522. if (socketOpen) {
  523. wx.closeSocket({
  524. success: () => {
  525. console.log('WebSocket连接已主动关闭');
  526. }
  527. });
  528. socketOpen = false;
  529. this.setData({ socketConnected: false });
  530. }
  531. if (heartBeatTimer) {
  532. clearInterval(heartBeatTimer);
  533. heartBeatTimer = null;
  534. }
  535. },
  536. // 重连WebSocket
  537. reconnectWebSocket() {
  538. if (this.data.reconnectCount >= this.data.maxReconnectCount) {
  539. console.log('达到最大重连次数,停止重连');
  540. return;
  541. }
  542. if (reconnectTimer) {
  543. clearTimeout(reconnectTimer);
  544. }
  545. reconnectTimer = setTimeout(() => {
  546. console.log(`尝试第${this.data.reconnectCount + 1}次重连`);
  547. this.setData({
  548. reconnectCount: this.data.reconnectCount + 1
  549. });
  550. this.connectWebSocket();
  551. }, 3000);
  552. },
  553. // 开始心跳
  554. startHeartbeat() {
  555. if (heartBeatTimer) {
  556. clearInterval(heartBeatTimer);
  557. }
  558. heartBeatTimer = setInterval(() => {
  559. if (socketOpen) {
  560. console.log('发送心跳');
  561. this.sendWebSocketMessage(JSON.stringify({
  562. type: 'heartbeat',
  563. userId: this.getCurrentUserId(),
  564. timestamp: Date.now()
  565. }));
  566. }
  567. }, 30000);
  568. },
  569. // 发送WebSocket消息
  570. sendWebSocketMessage(data) {
  571. if (socketOpen) {
  572. wx.sendSocketMessage({
  573. data: typeof data === 'string' ? data : JSON.stringify(data),
  574. success: () => {
  575. console.log('WebSocket消息发送成功:', data);
  576. },
  577. fail: (err) => {
  578. console.error('WebSocket消息发送失败:', err);
  579. socketMsgQueue.push(data);
  580. }
  581. });
  582. } else {
  583. console.log('WebSocket未连接,消息加入队列');
  584. socketMsgQueue.push(data);
  585. if (!isManualClose) {
  586. this.connectWebSocket();
  587. }
  588. }
  589. },
  590. // 处理WebSocket消息
  591. handleWebSocketMessage(data) {
  592. console.log('处理消息类型:', data.type);
  593. switch (data.type) {
  594. case 'chat':
  595. case 'message':
  596. this.handleNewMessage(data);
  597. break;
  598. case 'message_status':
  599. this.handleMessageStatus(data);
  600. break;
  601. case 'typing':
  602. this.handleTypingStatus(data);
  603. break;
  604. case 'online_status':
  605. this.handleOnlineStatus(data);
  606. break;
  607. case 'heartbeat_ack':
  608. console.log('收到心跳响应');
  609. break;
  610. default:
  611. console.log('未知消息类型:', data);
  612. }
  613. },
  614. // 处理新消息
  615. handleNewMessage(data) {
  616. console.log('处理新消息 - 完整数据:', data);
  617. // 提取消息内容
  618. let messageData = data.data || data;
  619. // 获取消息ID
  620. const messageId = messageData.id || messageData.messageId || 'msg_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
  621. console.log('消息ID:', messageId);
  622. console.log('当前发送中消息集合:', this.data.sendingMessages);
  623. // 检查是否是自己发送的消息(通过sendingMessages集合判断)
  624. if (this.data.sendingMessages.has(messageId)) {
  625. console.log('收到自己发送的消息回执,跳过处理:', messageId);
  626. // 从发送中集合移除
  627. const sendingMessages = new Set(this.data.sendingMessages);
  628. sendingMessages.delete(messageId);
  629. this.setData({ sendingMessages: sendingMessages });
  630. // 更新本地消息状态为成功
  631. this.updateMessageStatus(messageId, 'success');
  632. return;
  633. }
  634. // 检查消息是否已存在(去重)
  635. if (this.data.messageIds.has(messageId)) {
  636. console.log('消息已存在,跳过处理:', messageId);
  637. return;
  638. }
  639. const userId = this.getCurrentUserId();
  640. const senderId = messageData.senderId || messageData.fromUserId || messageData.userId;
  641. // 判断是否是自己发送的消息
  642. const isMe = senderId === userId;
  643. console.log('消息归属判断:', {
  644. senderId: senderId,
  645. userId: userId,
  646. isMe: isMe,
  647. messageId: messageId
  648. });
  649. // 如果是自己发送的消息但不在sendingMessages中,说明是通过其他方式发送的
  650. // 也跳过处理,避免重复
  651. if (isMe) {
  652. console.log('是自己发送的消息,但不在发送中集合,可能已存在,跳过处理');
  653. return;
  654. }
  655. // 创建新消息对象
  656. const newMessage = {
  657. id: messageId,
  658. isMe: isMe,
  659. sender: isMe ? 'user' : 'expert',
  660. senderId: senderId,
  661. contentType: messageData.contentType || messageData.type || 'text',
  662. type: messageData.type || messageData.contentType || 'text',
  663. content: messageData.content || '',
  664. timestamp: messageData.timestamp || Date.now(),
  665. status: 'success',
  666. progress: 100
  667. };
  668. console.log('创建的新消息对象:', newMessage);
  669. // 获取当前消息列表
  670. const currentList = [...this.data.messageList];
  671. console.log('当前消息列表长度:', currentList.length);
  672. // 添加新消息到列表末尾
  673. currentList.push(newMessage);
  674. // 更新消息ID集合
  675. const messageIds = new Set(this.data.messageIds);
  676. messageIds.add(messageId);
  677. // 重新处理时间显示
  678. const processedMessages = this.processMessageTimes(currentList);
  679. console.log('处理后的消息列表长度:', processedMessages.length);
  680. console.log('最后一条消息:', processedMessages[processedMessages.length - 1]);
  681. // 更新数据
  682. this.setData({
  683. messageList: processedMessages,
  684. messageIds: messageIds,
  685. hasNewMessage: !isMe // 如果不是自己发的,标记有新消息
  686. }, () => {
  687. console.log('消息列表已更新,当前列表:', this.data.messageList);
  688. // 判断是否需要滚动到底部
  689. const query = wx.createSelectorQuery();
  690. query.select('#chatScroll').boundingClientRect();
  691. query.select('.chat-bottom-space').boundingClientRect();
  692. query.exec((res) => {
  693. if (res[0] && res[1]) {
  694. const scrollHeight = res[0].height;
  695. const bottomOffset = res[1].top - res[0].top;
  696. const scrollTop = this.data.lastScrollTop;
  697. // 如果在底部附近(距离底部小于200px)或是自己发的消息,则滚动到底部
  698. const shouldScroll = isMe || (scrollTop + scrollHeight >= bottomOffset - 200);
  699. if (shouldScroll) {
  700. this.scrollToBottom(true);
  701. }
  702. } else {
  703. // 如果获取不到位置信息,默认滚动到底部
  704. if (isMe) {
  705. this.scrollToBottom(true);
  706. }
  707. }
  708. });
  709. // 如果不是自己发的消息,震动提示
  710. if (!isMe) {
  711. // wx.vibrateShort({ type: 'light' });
  712. // 发送已读回执
  713. this.sendReadReceipt(messageId);
  714. }
  715. });
  716. },
  717. // 处理消息状态
  718. handleMessageStatus(data) {
  719. const { messageList } = this.data;
  720. const messageId = data.messageId || data.id;
  721. const index = messageList.findIndex(msg => msg.id === messageId);
  722. if (index !== -1) {
  723. messageList[index].status = data.status || 'success';
  724. this.setData({ messageList });
  725. }
  726. // 如果收到成功状态,从发送中集合移除
  727. if (data.status === 'success') {
  728. const sendingMessages = new Set(this.data.sendingMessages);
  729. sendingMessages.delete(messageId);
  730. this.setData({ sendingMessages: sendingMessages });
  731. }
  732. },
  733. // 处理正在输入状态
  734. handleTypingStatus(data) {
  735. const userId = data.userId || data.senderId;
  736. if (userId === this.data.otherUserId) {
  737. this.setData({ 'expertInfo.typing': true });
  738. clearTimeout(this.typingTimer);
  739. this.typingTimer = setTimeout(() => {
  740. this.setData({ 'expertInfo.typing': false });
  741. }, 3000);
  742. }
  743. },
  744. // 处理在线状态
  745. handleOnlineStatus(data) {
  746. const userId = data.userId || data.senderId;
  747. if (userId === this.data.otherUserId) {
  748. this.setData({
  749. 'expertInfo.online': data.online || data.status === 'online'
  750. });
  751. }
  752. },
  753. // 发送消息到服务器
  754. sendMessageToServer: function(content, type, messageId) {
  755. const receiverId = this.data.otherUserId;
  756. const senderId = this.getCurrentUserId();
  757. console.log('发送消息参数:', {
  758. senderId: senderId,
  759. receiverId: receiverId,
  760. content: content,
  761. type: type,
  762. messageId: messageId
  763. });
  764. if (!receiverId || !senderId) {
  765. console.error('发送者或接收者ID不存在', { senderId, receiverId });
  766. wx.showToast({
  767. title: '发送失败,用户信息错误',
  768. icon: 'none'
  769. });
  770. // 发送失败,从发送中集合移除
  771. const sendingMessages = new Set(this.data.sendingMessages);
  772. sendingMessages.delete(messageId);
  773. this.setData({ sendingMessages: sendingMessages });
  774. // 更新消息状态为失败
  775. this.updateMessageStatus(messageId, 'failed');
  776. return;
  777. }
  778. const message = {
  779. type: 'chat',
  780. receiverId: receiverId,
  781. senderId: senderId,
  782. content: content,
  783. contentType: type || 'text',
  784. timestamp: Date.now(),
  785. messageId: messageId
  786. };
  787. const messageStr = JSON.stringify(message);
  788. console.log('发送消息到服务器:', JSON.parse(messageStr));
  789. if (!socketOpen) {
  790. console.log('WebSocket未连接,尝试连接');
  791. wx.showToast({
  792. title: '连接中,请稍后',
  793. icon: 'none'
  794. });
  795. this.connectWebSocket();
  796. socketMsgQueue.push(messageStr);
  797. return;
  798. }
  799. this.sendWebSocketMessage(messageStr);
  800. },
  801. // 发送已读回执
  802. sendReadReceipt(messageId) {
  803. if (!socketOpen) return;
  804. this.sendWebSocketMessage(JSON.stringify({
  805. type: 'message_status',
  806. messageId: messageId,
  807. status: 'read',
  808. userId: this.getCurrentUserId(),
  809. receiverId: this.data.otherUserId,
  810. conversationId: this.data.conversationId,
  811. timestamp: Date.now()
  812. }));
  813. },
  814. // 发送正在输入状态
  815. sendTypingStatus() {
  816. if (!socketOpen || !this.data.inputValue) return;
  817. this.sendWebSocketMessage(JSON.stringify({
  818. type: 'typing',
  819. userId: this.getCurrentUserId(),
  820. receiverId: this.data.otherUserId,
  821. conversationId: this.data.conversationId,
  822. timestamp: Date.now()
  823. }));
  824. },
  825. // ========== 消息发送相关方法 ==========
  826. // 发送文本消息
  827. sendTextMessage: function() {
  828. const content = this.data.inputValue.trim();
  829. if (!content) return;
  830. const messageId = 'msg_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
  831. console.log('发送文本消息,ID:', messageId);
  832. // 将消息ID添加到发送中集合
  833. const sendingMessages = new Set(this.data.sendingMessages);
  834. sendingMessages.add(messageId);
  835. // 创建本地消息
  836. const newMessage = {
  837. id: messageId,
  838. isMe: true,
  839. sender: 'user',
  840. senderId: this.getCurrentUserId(),
  841. contentType: 'text',
  842. type: 'text',
  843. content: content,
  844. timestamp: Date.now(),
  845. status: 'sending'
  846. };
  847. // 添加到列表
  848. this.addMessageToList(newMessage);
  849. // 更新发送中集合
  850. this.setData({ sendingMessages: sendingMessages });
  851. // 清空输入框
  852. this.setData({ inputValue: '' });
  853. // 通过WebSocket发送到服务器
  854. this.sendMessageToServer(content, 'text', messageId);
  855. // 设置超时,如果5秒后还没有收到回执,认为发送失败
  856. setTimeout(() => {
  857. if (this.data.sendingMessages.has(messageId)) {
  858. console.log('消息发送超时:', messageId);
  859. this.updateMessageStatus(messageId, 'timeout');
  860. // 从发送中集合移除
  861. const sendingMessages = new Set(this.data.sendingMessages);
  862. sendingMessages.delete(messageId);
  863. this.setData({ sendingMessages: sendingMessages });
  864. wx.showToast({
  865. title: '发送超时',
  866. icon: 'none'
  867. });
  868. }
  869. }, 5000);
  870. },
  871. // 上传图片后发送
  872. uploadImages: function(tempFilePaths) {
  873. tempFilePaths.forEach((tempFilePath, index) => {
  874. const fileName = `image_${Date.now()}_${index}.jpg`;
  875. this.uploadFile(tempFilePath, fileName, 0, 'image');
  876. });
  877. },
  878. // 上传视频后发送
  879. uploadVideo: function(tempFilePath, thumbPath) {
  880. const fileName = `video_${Date.now()}.mp4`;
  881. this.uploadFile(tempFilePath, fileName, 0, 'video', thumbPath);
  882. },
  883. // 通用文件上传
  884. uploadFile: function(tempFilePath, fileName, fileSize = 0, type = 'file', thumbPath = '') {
  885. const messageId = 'file_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
  886. // 将消息ID添加到发送中集合
  887. const sendingMessages = new Set(this.data.sendingMessages);
  888. sendingMessages.add(messageId);
  889. const message = {
  890. id: messageId,
  891. isMe: true,
  892. sender: 'user',
  893. type: type,
  894. contentType: type,
  895. content: tempFilePath,
  896. thumb: thumbPath,
  897. fileName: fileName,
  898. fileSize: fileSize,
  899. extension: fileName.split('.').pop().toLowerCase(),
  900. timestamp: Date.now(),
  901. status: 'uploading',
  902. progress: 0
  903. };
  904. this.addMessageToList(message);
  905. this.setData({ sendingMessages: sendingMessages });
  906. this.simulateUpload(messageId, type, tempFilePath);
  907. },
  908. // 模拟上传
  909. simulateUpload: function(messageId, type, tempFilePath) {
  910. let progress = 0;
  911. const uploadInterval = setInterval(() => {
  912. progress += Math.random() * 20 + 10;
  913. if (progress >= 100) {
  914. progress = 100;
  915. clearInterval(uploadInterval);
  916. setTimeout(() => {
  917. this.updateMessageStatus(messageId, 'success');
  918. const { messageList } = this.data;
  919. const index = messageList.findIndex(msg => msg.id === messageId);
  920. if (index !== -1) {
  921. delete messageList[index].progress;
  922. this.setData({ messageList });
  923. const fileUrl = tempFilePath;
  924. this.sendMessageToServer(fileUrl, type, messageId);
  925. }
  926. }, 200);
  927. }
  928. const { messageList } = this.data;
  929. const index = messageList.findIndex(msg => msg.id === messageId);
  930. if (index !== -1) {
  931. messageList[index].progress = Math.min(Math.floor(progress), 100);
  932. this.setData({ messageList });
  933. }
  934. }, 100);
  935. },
  936. // 添加消息到列表
  937. addMessageToList: function(message) {
  938. const { messageList, messageIds } = this.data;
  939. // 检查是否已存在
  940. if (messageIds.has(message.id)) {
  941. console.log('消息已存在,不重复添加:', message.id);
  942. return;
  943. }
  944. // 新消息添加到末尾
  945. messageList.push(message);
  946. // 更新消息ID集合
  947. messageIds.add(message.id);
  948. // 重新处理时间显示
  949. const processedMessages = this.processMessageTimes(messageList);
  950. this.setData({
  951. messageList: processedMessages,
  952. messageIds: messageIds
  953. }, () => {
  954. this.scrollToBottom();
  955. });
  956. },
  957. // 更新消息状态
  958. updateMessageStatus: function(messageId, status) {
  959. const { messageList } = this.data;
  960. const index = messageList.findIndex(msg => msg.id === messageId);
  961. if (index !== -1) {
  962. messageList[index].status = status;
  963. this.setData({ messageList });
  964. }
  965. },
  966. // 输入处理
  967. onInput: function(e) {
  968. this.setData({ inputValue: e.detail.value });
  969. clearTimeout(this.typingDebounce);
  970. this.typingDebounce = setTimeout(() => {
  971. this.sendTypingStatus();
  972. }, 500);
  973. },
  974. // 输入框获得焦点
  975. onInputFocus: function() {
  976. this.setData({ inputFocus: true }, () => {
  977. setTimeout(() => {
  978. this.scrollToBottom();
  979. }, 200);
  980. });
  981. },
  982. // 输入框失去焦点
  983. onInputBlur: function() {
  984. this.setData({ inputFocus: false });
  985. },
  986. // 清除输入
  987. clearInput: function() {
  988. this.setData({
  989. inputValue: '',
  990. inputFocus: true
  991. });
  992. },
  993. // 滚动事件
  994. onScroll: function(e) {
  995. const scrollTop = e.detail.scrollTop;
  996. this.setData({
  997. lastScrollTop: scrollTop,
  998. isScrolling: true
  999. });
  1000. clearTimeout(this.scrollTimer);
  1001. this.scrollTimer = setTimeout(() => {
  1002. this.setData({ isScrolling: false });
  1003. }, 200);
  1004. // 当滚动到顶部附近时加载更多
  1005. if (scrollTop <= 50 && !this.data.loadingMore && this.data.hasMore && this.data.firstLoadComplete) {
  1006. console.log('触发加载更多,当前滚动位置:', scrollTop);
  1007. this.loadMoreMessages();
  1008. }
  1009. // 当滚动到底部时,清除新消息标记
  1010. const query = wx.createSelectorQuery();
  1011. query.select('#chatScroll').boundingClientRect();
  1012. query.select('.chat-bottom-space').boundingClientRect();
  1013. query.exec((res) => {
  1014. if (res[0] && res[1]) {
  1015. const scrollHeight = res[0].height;
  1016. const bottomOffset = res[1].top - res[0].top;
  1017. if (scrollTop + scrollHeight >= bottomOffset - 50) {
  1018. this.setData({ hasNewMessage: false });
  1019. }
  1020. }
  1021. });
  1022. },
  1023. // 滚动到底部
  1024. scrollToBottom: function(animate = true) {
  1025. if (this.data.isScrolling) return;
  1026. this.setData({
  1027. scrollAnimate: animate,
  1028. hasNewMessage: false
  1029. }, () => {
  1030. setTimeout(() => {
  1031. this.setData({ scrollTop: 999999 });
  1032. }, 50);
  1033. });
  1034. },
  1035. // 键盘高度变化
  1036. onKeyboardHeightChange: function(res) {
  1037. this.setData({ keyboardHeight: res.height });
  1038. if (res.height > 0) {
  1039. this.setData({ showMediaSheet: false });
  1040. setTimeout(() => {
  1041. this.scrollToBottom();
  1042. }, 100);
  1043. }
  1044. },
  1045. // 显示多媒体选择面板
  1046. showMediaActionSheet: function() {
  1047. this.setData({
  1048. showMediaSheet: true,
  1049. inputFocus: false
  1050. });
  1051. },
  1052. // 隐藏多媒体选择面板
  1053. hideMediaActionSheet: function() {
  1054. this.setData({ showMediaSheet: false });
  1055. },
  1056. // 选择图片
  1057. chooseImage: function() {
  1058. this.hideMediaActionSheet();
  1059. wx.chooseImage({
  1060. count: 9,
  1061. sizeType: ['compressed'],
  1062. sourceType: ['album'],
  1063. success: (res) => {
  1064. this.uploadImages(res.tempFilePaths);
  1065. },
  1066. fail: () => {
  1067. wx.showToast({
  1068. title: '选择图片失败',
  1069. icon: 'none'
  1070. });
  1071. }
  1072. });
  1073. },
  1074. // 选择视频
  1075. chooseVideo: function() {
  1076. this.hideMediaActionSheet();
  1077. wx.chooseVideo({
  1078. sourceType: ['album'],
  1079. compressed: true,
  1080. maxDuration: 60,
  1081. success: (res) => {
  1082. this.uploadVideo(res.tempFilePath, res.thumbTempFilePath);
  1083. },
  1084. fail: () => {
  1085. wx.showToast({
  1086. title: '选择视频失败',
  1087. icon: 'none'
  1088. });
  1089. }
  1090. });
  1091. },
  1092. // 预览图片
  1093. previewImage: function(e) {
  1094. const url = e.currentTarget.dataset.url;
  1095. wx.previewImage({
  1096. current: url,
  1097. urls: [url]
  1098. });
  1099. },
  1100. // 设置今天日期
  1101. setTodayDate: function() {
  1102. const now = new Date();
  1103. const month = now.getMonth() + 1;
  1104. const date = now.getDate();
  1105. const week = ['日', '一', '二', '三', '四', '五', '六'][now.getDay()];
  1106. this.setData({
  1107. todayDate: `${month}${date}日 星期${week}`
  1108. });
  1109. },
  1110. // 处理消息时间显示
  1111. processMessageTimes: function(messages) {
  1112. if (!messages || messages.length === 0) return [];
  1113. const processedMessages = [];
  1114. let lastShowTime = null;
  1115. for (let i = 0; i < messages.length; i++) {
  1116. const msg = { ...messages[i] };
  1117. if (i === 0) {
  1118. msg.showTime = true;
  1119. lastShowTime = msg.timestamp;
  1120. } else {
  1121. const timeDiffMinutes = (msg.timestamp - lastShowTime) / (1000 * 60);
  1122. msg.showTime = timeDiffMinutes >= this.data.timeInterval;
  1123. if (msg.showTime) lastShowTime = msg.timestamp;
  1124. }
  1125. processedMessages.push(msg);
  1126. }
  1127. if (lastShowTime) {
  1128. this.setData({ lastShowTimeStamp: lastShowTime });
  1129. }
  1130. return processedMessages;
  1131. },
  1132. // 格式化时间
  1133. formatTime: function(timestamp) {
  1134. if (!timestamp) return '';
  1135. const date = new Date(Number(timestamp));
  1136. const hours = date.getHours().toString().padStart(2, '0');
  1137. const minutes = date.getMinutes().toString().padStart(2, '0');
  1138. return `${hours}:${minutes}`;
  1139. },
  1140. // 格式化文件大小
  1141. formatFileSize: function(bytes) {
  1142. if (!bytes || bytes === 0) return '0B';
  1143. const units = ['B', 'KB', 'MB', 'GB'];
  1144. let size = bytes;
  1145. let unitIndex = 0;
  1146. while (size >= 1024 && unitIndex < units.length - 1) {
  1147. size /= 1024;
  1148. unitIndex++;
  1149. }
  1150. return size.toFixed(1) + units[unitIndex];
  1151. },
  1152. // 阻止事件冒泡
  1153. stopPropagation: function() {}
  1154. });