与牧同行-小程序用户端
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.

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