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

1365 lines
36 KiB

  1. // import http from '../../../utils/api'
  2. Page({
  3. data: {
  4. // 专家信息
  5. expertInfo: {
  6. id: 0,
  7. name: '',
  8. title: '',
  9. expertise: '',
  10. avatar: '/images/avatars/expert1.png',
  11. online: false,
  12. phone: ''
  13. },
  14. // 用户信息
  15. userInfo: {
  16. id: 0,
  17. name: '用户',
  18. avatar: '/images/avatars/user.png'
  19. },
  20. // 消息列表
  21. messageList: [],
  22. scrollTop: 0,
  23. scrollAnimate: false,
  24. // 输入相关
  25. inputValue: '',
  26. inputFocus: false,
  27. inputMode: 'keyboard',
  28. inputPlaceholder: '请输入消息...',
  29. // 多媒体
  30. showMediaSheet: false,
  31. // 录音相关
  32. isRecording: false,
  33. isCanceling: false,
  34. recordingTime: 0,
  35. recordingTip: '松开 发送',
  36. voiceTip: '按住 说话',
  37. recordingTimer: null,
  38. recordManager: null,
  39. // 页面状态
  40. showDateDivider: true,
  41. todayDate: '',
  42. loading: false,
  43. loadingMore: false,
  44. // 滚动相关
  45. isScrolling: false,
  46. lastScrollTop: 0,
  47. // 录音相关
  48. recordStartY: 0,
  49. // 当前专家ID
  50. currentExpertId: 0,
  51. // 分页相关
  52. page: 1,
  53. pageSize: 20,
  54. hasMore: true,
  55. // 时间显示间隔(分钟) - 微信默认为5分钟
  56. timeInterval: 5,
  57. // 用于存储最后一条显示时间的消息的时间戳
  58. lastShowTimeStamp: 0
  59. },
  60. onLoad: function(options) {
  61. console.log('页面加载,参数:', options);
  62. // 初始化录音管理器
  63. this.initRecordManager();
  64. // 获取今天日期
  65. this.setTodayDate();
  66. // 加载用户信息
  67. this.loadUserInfo();
  68. // 加载专家信息
  69. if (options.expertId) {
  70. this.setData({ currentExpertId: options.expertId });
  71. this.loadExpertInfo(options.expertId);
  72. } else {
  73. // 如果没有传expertId,使用默认值
  74. this.setData({
  75. currentExpertId: 1,
  76. expertInfo: {
  77. id: 1,
  78. name: '张明专家',
  79. title: '资深畜牧兽医',
  80. expertise: '牛羊疾病防治',
  81. avatar: '/images/avatars/expert1.png',
  82. online: true,
  83. phone: '13800138000'
  84. }
  85. }, () => {
  86. // 加载聊天记录
  87. this.loadChatHistory();
  88. });
  89. }
  90. // 设置键盘监听
  91. wx.onKeyboardHeightChange(this.onKeyboardHeightChange.bind(this));
  92. },
  93. onUnload: function() {
  94. console.log('页面卸载');
  95. // 清理定时器
  96. if (this.data.recordingTimer) {
  97. clearInterval(this.data.recordingTimer);
  98. }
  99. // 移除监听器
  100. wx.offKeyboardHeightChange();
  101. },
  102. onShow: function() {
  103. console.log('页面显示');
  104. },
  105. onReady: function() {
  106. console.log('页面初次渲染完成');
  107. },
  108. // 初始化录音管理器
  109. initRecordManager: function() {
  110. this.recordManager = wx.getRecorderManager();
  111. this.recordManager.onStart(() => {
  112. console.log('录音开始');
  113. this.setData({
  114. isRecording: true,
  115. recordingTime: 0,
  116. recordingTip: '松开 发送'
  117. });
  118. // 开始计时
  119. const timer = setInterval(() => {
  120. const time = this.data.recordingTime + 1;
  121. this.setData({ recordingTime: time });
  122. }, 1000);
  123. this.setData({ recordingTimer: timer });
  124. });
  125. this.recordManager.onStop((res) => {
  126. console.log('录音停止', res);
  127. if (this.data.recordingTimer) {
  128. clearInterval(this.data.recordingTimer);
  129. }
  130. const { tempFilePath, duration } = res;
  131. if (tempFilePath && !this.data.isCanceling) {
  132. this.sendAudioMessage(tempFilePath, Math.floor(duration / 1000));
  133. }
  134. this.setData({
  135. isRecording: false,
  136. isCanceling: false,
  137. recordingTime: 0
  138. });
  139. });
  140. this.recordManager.onError((err) => {
  141. console.error('录音失败:', err);
  142. wx.showToast({
  143. title: '录音失败',
  144. icon: 'none'
  145. });
  146. this.setData({
  147. isRecording: false,
  148. isCanceling: false
  149. });
  150. });
  151. },
  152. // 设置今天日期
  153. setTodayDate: function() {
  154. const now = new Date();
  155. const month = now.getMonth() + 1;
  156. const date = now.getDate();
  157. const week = ['日', '一', '二', '三', '四', '五', '六'][now.getDay()];
  158. this.setData({
  159. todayDate: `${month}${date}日 星期${week}`
  160. });
  161. },
  162. // 加载用户信息
  163. loadUserInfo: function() {
  164. try {
  165. // 从本地存储获取用户信息,如果没有则使用默认值
  166. const userInfo = wx.getStorageSync('userInfo');
  167. if (userInfo) {
  168. this.setData({ userInfo });
  169. } else {
  170. // 默认用户信息
  171. const defaultUserInfo = {
  172. id: 1001,
  173. name: '养殖户',
  174. avatar: '/images/avatars/user.png'
  175. };
  176. this.setData({ userInfo: defaultUserInfo });
  177. wx.setStorageSync('userInfo', defaultUserInfo);
  178. }
  179. } catch (e) {
  180. console.error('加载用户信息失败:', e);
  181. // 使用默认用户信息
  182. this.setData({
  183. userInfo: {
  184. id: 1001,
  185. name: '养殖户',
  186. avatar: '/images/avatars/user.png'
  187. }
  188. });
  189. }
  190. },
  191. // 加载专家信息 - 使用您的接口调用方式
  192. loadExpertInfo: function(expertId) {
  193. console.log('加载专家信息:', expertId);
  194. wx.showLoading({ title: '加载中...' });
  195. // 使用您的接口调用方式
  196. // http.getExpertInfo({
  197. // data: { expertId: expertId },
  198. // success: (res) => {
  199. // console.log('专家信息:', res);
  200. // if (res.code === 0) {
  201. // this.setData({
  202. // expertInfo: res.data,
  203. // loading: false
  204. // });
  205. // // 加载聊天记录
  206. // this.loadChatHistory();
  207. // } else {
  208. // // 如果接口返回失败,使用默认数据
  209. // this.loadDefaultExpertInfo(expertId);
  210. // }
  211. // wx.hideLoading();
  212. // },
  213. // fail: (err) => {
  214. // console.error('加载专家信息失败:', err);
  215. // wx.hideLoading();
  216. // wx.showToast({
  217. // title: '加载失败',
  218. // icon: 'none'
  219. // });
  220. // // 如果接口调用失败,使用默认数据
  221. // this.loadDefaultExpertInfo(expertId);
  222. // }
  223. // });
  224. // 模拟数据
  225. setTimeout(() => {
  226. const experts = [
  227. {
  228. id: 1,
  229. name: '张明专家',
  230. title: '资深畜牧兽医',
  231. expertise: '牛羊疾病防治',
  232. avatar: '/images/avatars/expert1.png',
  233. online: true,
  234. phone: '13800138000'
  235. },
  236. {
  237. id: 2,
  238. name: '李华专家',
  239. title: '高级畜牧师',
  240. expertise: '饲料营养',
  241. avatar: '/images/avatars/expert2.png',
  242. online: false,
  243. phone: '13800138001'
  244. },
  245. {
  246. id: 3,
  247. name: '王强专家',
  248. title: '兽医专家',
  249. expertise: '疾病防治',
  250. avatar: '/images/avatars/expert3.png',
  251. online: true,
  252. phone: '13800138002'
  253. }
  254. ];
  255. const expertInfo = experts.find(e => e.id == expertId) || experts[0];
  256. this.setData({
  257. expertInfo,
  258. loading: false
  259. });
  260. wx.hideLoading();
  261. // 加载聊天记录
  262. this.loadChatHistory();
  263. }, 500);
  264. },
  265. // 加载默认专家信息(当接口失败时使用)
  266. loadDefaultExpertInfo: function(expertId) {
  267. const experts = [
  268. {
  269. id: 1,
  270. name: '张明专家',
  271. title: '资深畜牧兽医',
  272. expertise: '牛羊疾病防治',
  273. avatar: '/images/avatars/expert1.png',
  274. online: true,
  275. phone: '13800138000'
  276. },
  277. {
  278. id: 2,
  279. name: '李华专家',
  280. title: '高级畜牧师',
  281. expertise: '饲料营养',
  282. avatar: '/images/avatars/expert2.png',
  283. online: false,
  284. phone: '13800138001'
  285. },
  286. {
  287. id: 3,
  288. name: '王强专家',
  289. title: '兽医专家',
  290. expertise: '疾病防治',
  291. avatar: '/images/avatars/expert3.png',
  292. online: true,
  293. phone: '13800138002'
  294. }
  295. ];
  296. const expertInfo = experts.find(e => e.id == expertId) || experts[0];
  297. this.setData({
  298. expertInfo,
  299. loading: false
  300. });
  301. // 加载聊天记录
  302. this.loadChatHistory();
  303. },
  304. // 加载聊天记录 - 使用您的接口调用方式
  305. loadChatHistory: function() {
  306. const { currentExpertId, userInfo, page, pageSize } = this.data;
  307. this.setData({ loading: true });
  308. console.log('加载聊天记录:', {
  309. expertId: currentExpertId,
  310. userId: userInfo.id,
  311. page: page,
  312. pageSize: pageSize
  313. });
  314. // 使用您的接口调用方式
  315. // http.getChatHistory({
  316. // data: {
  317. // expertId: currentExpertId,
  318. // userId: userInfo.id,
  319. // page: page,
  320. // pageSize: pageSize
  321. // },
  322. // success: (res) => {
  323. // console.log('聊天记录:', res);
  324. // if (res.code === 0) {
  325. // const messages = res.data.list || [];
  326. // if (messages.length > 0) {
  327. // // 处理消息时间显示 - 使用完全修复的时间处理逻辑
  328. // const processedMessages = this.processMessageTimes(messages);
  329. // // 调试:查看处理后的消息
  330. // console.log('处理后的消息数据:', processedMessages.map(msg => ({
  331. // id: msg.id,
  332. // showTime: msg.showTime,
  333. // time: this.formatTime(msg.timestamp),
  334. // timestamp: msg.timestamp
  335. // })));
  336. // this.setData({
  337. // messageList: processedMessages,
  338. // loading: false,
  339. // hasMore: messages.length >= pageSize
  340. // }, () => {
  341. // // 滚动到底部
  342. // this.scrollToBottom(true);
  343. // });
  344. // } else {
  345. // // 如果没有历史记录,添加一条欢迎消息
  346. // this.addWelcomeMessage();
  347. // }
  348. // } else {
  349. // // 如果接口返回失败,使用测试数据
  350. // this.loadMockChatHistory();
  351. // }
  352. // },
  353. // fail: (err) => {
  354. // console.error('加载聊天记录失败:', err);
  355. // this.setData({ loading: false });
  356. // // 如果接口调用失败,使用测试数据
  357. // this.loadMockChatHistory();
  358. // }
  359. // });
  360. // 模拟数据 - 修复时间戳问题
  361. setTimeout(() => {
  362. const now = Date.now();
  363. let mockMessages = [];
  364. if (page === 1) {
  365. // 第一页数据 - 测试不同时间间隔的消息
  366. mockMessages = [
  367. {
  368. id: 'msg-1',
  369. sender: 'expert',
  370. type: 'text',
  371. content: '您好,我是张明专家,有什么可以帮您?',
  372. timestamp: now - 10 * 60 * 1000, // 10分钟前 - 应该显示时间
  373. status: 'success'
  374. },
  375. {
  376. id: 'msg-2',
  377. sender: 'user',
  378. type: 'text',
  379. content: '您好,我养的牛最近食欲不振,请问是什么原因?',
  380. timestamp: now - 9 * 60 * 1000, // 9分钟前 - 不显示时间(与上条间隔1分钟)
  381. status: 'success'
  382. },
  383. {
  384. id: 'msg-3',
  385. sender: 'expert',
  386. type: 'text',
  387. content: '可能是饲料问题或环境变化引起的,请描述一下具体情况。',
  388. timestamp: now - 7 * 60 * 1000, // 7分钟前 - 显示时间(与上条间隔2分钟,但与第一条间隔3分钟)
  389. status: 'success'
  390. },
  391. {
  392. id: 'msg-4',
  393. sender: 'user',
  394. type: 'text',
  395. content: '具体症状是拉稀,体温偏高,精神状态不好。',
  396. timestamp: now - 2 * 60 * 1000, // 2分钟前 - 显示时间(与上条间隔5分钟)
  397. status: 'success'
  398. },
  399. {
  400. id: 'msg-5',
  401. sender: 'expert',
  402. type: 'text',
  403. content: '明白了,建议您调整饲料配方,添加一些益生菌。',
  404. timestamp: now - 1 * 60 * 1000, // 1分钟前 - 不显示时间(与上条间隔1分钟)
  405. status: 'success'
  406. }
  407. ];
  408. } else {
  409. // 更多数据
  410. mockMessages = [
  411. {
  412. id: 'msg-6',
  413. sender: 'user',
  414. type: 'text',
  415. content: '之前喂的是玉米秸秆,需要换饲料吗?',
  416. timestamp: now - 30 * 60 * 1000, // 30分钟前
  417. status: 'success'
  418. },
  419. {
  420. id: 'msg-7',
  421. sender: 'expert',
  422. type: 'text',
  423. content: '可以尝试添加一些豆粕和麦麸,改善营养结构。',
  424. timestamp: now - 25 * 60 * 1000, // 25分钟前
  425. status: 'success'
  426. }
  427. ];
  428. }
  429. if (mockMessages.length > 0) {
  430. // 处理消息时间显示 - 使用完全修复的时间处理逻辑
  431. const processedMessages = this.processMessageTimes(mockMessages);
  432. // 调试:查看处理后的消息
  433. console.log('处理后的消息数据:', processedMessages.map(msg => ({
  434. id: msg.id,
  435. showTime: msg.showTime,
  436. time: this.formatTime(msg.timestamp),
  437. timestamp: msg.timestamp,
  438. sender: msg.sender
  439. })));
  440. let newMessageList = [];
  441. if (page === 1) {
  442. newMessageList = processedMessages;
  443. } else {
  444. newMessageList = [...processedMessages, ...this.data.messageList];
  445. }
  446. this.setData({
  447. messageList: newMessageList,
  448. loading: false,
  449. loadingMore: false,
  450. hasMore: mockMessages.length >= pageSize
  451. }, () => {
  452. if (page === 1) {
  453. // 滚动到底部
  454. this.scrollToBottom(true);
  455. }
  456. });
  457. } else {
  458. // 如果没有历史记录,添加一条欢迎消息
  459. this.addWelcomeMessage();
  460. }
  461. }, 800);
  462. },
  463. // 加载模拟聊天记录(当接口失败时使用)
  464. loadMockChatHistory: function() {
  465. const now = Date.now();
  466. // 测试数据 - 确保有时间戳
  467. const mockMessages = [
  468. {
  469. id: 'msg-1',
  470. sender: 'expert',
  471. type: 'text',
  472. content: '您好,我是张明专家,有什么可以帮您?',
  473. timestamp: now - 10 * 60 * 1000, // 10分钟前
  474. status: 'success'
  475. },
  476. {
  477. id: 'msg-2',
  478. sender: 'user',
  479. type: 'text',
  480. content: '您好,我养的牛最近食欲不振,请问是什么原因?',
  481. timestamp: now - 8 * 60 * 1000, // 8分钟前
  482. status: 'success'
  483. },
  484. {
  485. id: 'msg-3',
  486. sender: 'expert',
  487. type: 'text',
  488. content: '可能是饲料问题或环境变化引起的,请描述一下具体情况。',
  489. timestamp: now - 6 * 60 * 1000, // 6分钟前
  490. status: 'success'
  491. },
  492. {
  493. id: 'msg-4',
  494. sender: 'user',
  495. type: 'text',
  496. content: '具体症状是...',
  497. timestamp: now - 4 * 60 * 1000, // 4分钟前
  498. status: 'success'
  499. },
  500. {
  501. id: 'msg-5',
  502. sender: 'expert',
  503. type: 'text',
  504. content: '明白了,建议您调整饲料配方。',
  505. timestamp: now - 2 * 60 * 1000, // 2分钟前
  506. status: 'success'
  507. }
  508. ];
  509. // 处理消息时间显示
  510. const processedMessages = this.processMessageTimes(mockMessages);
  511. this.setData({
  512. messageList: processedMessages,
  513. loading: false,
  514. hasMore: false
  515. }, () => {
  516. // 滚动到底部
  517. this.scrollToBottom(true);
  518. });
  519. },
  520. // 添加欢迎消息
  521. addWelcomeMessage: function() {
  522. const welcomeMessage = {
  523. id: 'welcome-' + Date.now(),
  524. sender: 'expert',
  525. type: 'text',
  526. content: `您好,我是${this.data.expertInfo.name},有什么可以帮您?`,
  527. timestamp: Date.now(),
  528. status: 'success'
  529. };
  530. const processedMessage = this.processSingleMessageTime(welcomeMessage, []);
  531. this.setData({
  532. messageList: [processedMessage],
  533. loading: false
  534. }, () => {
  535. // 滚动到底部
  536. this.scrollToBottom(true);
  537. });
  538. },
  539. // 完全修复:处理消息时间显示逻辑(类似微信)
  540. processMessageTimes: function(messages) {
  541. if (!messages || messages.length === 0) return [];
  542. // 按时间排序(从早到晚)
  543. const sortedMessages = [...messages].sort((a, b) => a.timestamp - b.timestamp);
  544. const processedMessages = [];
  545. let lastShowTime = null; // 最后一条显示时间消息的时间戳
  546. for (let i = 0; i < sortedMessages.length; i++) {
  547. const msg = { ...sortedMessages[i] };
  548. // 确保时间戳是有效数字
  549. if (!msg.timestamp || isNaN(msg.timestamp) || msg.timestamp <= 0) {
  550. msg.timestamp = Date.now() - (sortedMessages.length - i) * 60000;
  551. }
  552. // 第一条消息始终显示时间
  553. if (i === 0) {
  554. msg.showTime = true;
  555. lastShowTime = msg.timestamp;
  556. } else {
  557. // 计算与最后一条显示时间的消息的时间差(分钟)
  558. const timeDiffMinutes = (msg.timestamp - lastShowTime) / (1000 * 60);
  559. // 超过5分钟显示时间
  560. if (timeDiffMinutes >= this.data.timeInterval) {
  561. msg.showTime = true;
  562. lastShowTime = msg.timestamp;
  563. } else {
  564. msg.showTime = false;
  565. }
  566. }
  567. processedMessages.push(msg);
  568. }
  569. // 存储最后一条显示时间的消息的时间戳
  570. if (lastShowTime) {
  571. this.setData({ lastShowTimeStamp: lastShowTime });
  572. }
  573. return processedMessages;
  574. },
  575. // 处理单条消息的时间显示(添加新消息时调用)
  576. processSingleMessageTime: function(message, messageList) {
  577. const msg = { ...message };
  578. // 确保时间戳是有效数字
  579. if (!msg.timestamp || isNaN(msg.timestamp) || msg.timestamp <= 0) {
  580. msg.timestamp = Date.now();
  581. }
  582. if (messageList.length === 0) {
  583. // 第一条消息
  584. msg.showTime = true;
  585. this.setData({ lastShowTimeStamp: msg.timestamp });
  586. return msg;
  587. }
  588. // 获取最后一条显示时间的消息
  589. const lastShowTime = this.data.lastShowTimeStamp;
  590. // 计算时间差(分钟)
  591. const timeDiffMinutes = (msg.timestamp - lastShowTime) / (1000 * 60);
  592. // 超过5分钟显示时间
  593. if (timeDiffMinutes >= this.data.timeInterval) {
  594. msg.showTime = true;
  595. this.setData({ lastShowTimeStamp: msg.timestamp });
  596. } else {
  597. msg.showTime = false;
  598. }
  599. return msg;
  600. },
  601. // 返回上一页
  602. goBack: function() {
  603. wx.navigateBack();
  604. },
  605. // 打电话
  606. makePhoneCall: function() {
  607. const phone = this.data.expertInfo.phone;
  608. if (!phone) {
  609. wx.showToast({
  610. title: '暂无联系电话',
  611. icon: 'none'
  612. });
  613. return;
  614. }
  615. wx.makePhoneCall({
  616. phoneNumber: phone,
  617. success: () => {
  618. console.log('拨打电话成功');
  619. },
  620. fail: (err) => {
  621. console.error('拨打电话失败:', err);
  622. wx.showToast({
  623. title: '拨打失败',
  624. icon: 'none'
  625. });
  626. }
  627. });
  628. },
  629. // 输入处理
  630. onInput: function(e) {
  631. this.setData({
  632. inputValue: e.detail.value
  633. });
  634. },
  635. // 输入框获得焦点
  636. onInputFocus: function() {
  637. this.setData({
  638. inputFocus: true
  639. });
  640. },
  641. // 输入框失去焦点
  642. onInputBlur: function() {
  643. this.setData({
  644. inputFocus: false
  645. });
  646. },
  647. // 清除输入
  648. clearInput: function() {
  649. this.setData({
  650. inputValue: '',
  651. inputFocus: true
  652. });
  653. },
  654. // 发送文本消息 - 使用您的接口调用方式
  655. sendTextMessage: function() {
  656. const content = this.data.inputValue.trim();
  657. if (!content) return;
  658. console.log('发送文本消息:', content);
  659. const newMessage = {
  660. id: 'msg-' + Date.now(),
  661. sender: 'user',
  662. type: 'text',
  663. content: content,
  664. timestamp: Date.now(),
  665. status: 'sending'
  666. };
  667. // 处理时间显示并添加到消息列表
  668. this.addMessageToList(newMessage);
  669. // 清空输入框
  670. this.setData({
  671. inputValue: '',
  672. inputFocus: false
  673. });
  674. // 使用您的接口调用方式发送消息
  675. // http.sendTextMessage({
  676. // data: {
  677. // expertId: this.data.currentExpertId,
  678. // userId: this.data.userInfo.id,
  679. // content: content,
  680. // timestamp: Date.now()
  681. // },
  682. // success: (res) => {
  683. // console.log('发送消息成功:', res);
  684. // if (res.code === 0) {
  685. // // 更新消息状态
  686. // this.updateMessageStatus(newMessage.id, 'success');
  687. // // 模拟专家回复
  688. // setTimeout(() => {
  689. // this.receiveExpertReply();
  690. // }, 1000 + Math.random() * 1000);
  691. // } else {
  692. // // 发送失败
  693. // this.updateMessageStatus(newMessage.id, 'error');
  694. // wx.showToast({
  695. // title: '发送失败',
  696. // icon: 'none'
  697. // });
  698. // }
  699. // },
  700. // fail: (err) => {
  701. // console.error('发送消息失败:', err);
  702. // this.updateMessageStatus(newMessage.id, 'error');
  703. // wx.showToast({
  704. // title: '发送失败',
  705. // icon: 'none'
  706. // });
  707. // }
  708. // });
  709. // 模拟发送成功
  710. setTimeout(() => {
  711. this.updateMessageStatus(newMessage.id, 'success');
  712. // 模拟专家回复
  713. setTimeout(() => {
  714. this.receiveExpertReply();
  715. }, 1000 + Math.random() * 1000);
  716. }, 500);
  717. },
  718. // 添加消息到列表
  719. addMessageToList: function(message) {
  720. const { messageList } = this.data;
  721. // 处理消息时间显示
  722. const processedMessage = this.processSingleMessageTime(message, messageList);
  723. // 添加到列表
  724. messageList.push(processedMessage);
  725. this.setData({
  726. messageList
  727. }, () => {
  728. // 消息添加后滚动到底部
  729. this.scrollToBottom();
  730. });
  731. },
  732. // 更新消息状态
  733. updateMessageStatus: function(messageId, status) {
  734. const { messageList } = this.data;
  735. const index = messageList.findIndex(msg => msg.id === messageId);
  736. if (index !== -1) {
  737. messageList[index].status = status;
  738. this.setData({ messageList });
  739. }
  740. },
  741. // 接收专家回复 - 使用您的接口调用方式
  742. receiveExpertReply: function() {
  743. // 这里可以使用WebSocket或轮询获取新消息
  744. // 模拟专家回复
  745. const replies = [
  746. '收到您的消息,让我分析一下您说的情况。',
  747. '建议您提供更多细节,比如发病时间、具体症状等。',
  748. '根据描述,可能是饲料问题引起的,建议调整饲料配方。',
  749. '可以考虑添加一些维生素补充剂,改善食欲问题。',
  750. '最好能提供照片,这样我可以更准确地判断情况。'
  751. ];
  752. const randomReply = replies[Math.floor(Math.random() * replies.length)];
  753. const newMessage = {
  754. id: 'exp-' + Date.now(),
  755. sender: 'expert',
  756. type: 'text',
  757. content: randomReply,
  758. timestamp: Date.now(),
  759. status: 'success'
  760. };
  761. this.addMessageToList(newMessage);
  762. },
  763. // 切换输入模式(语音/键盘)
  764. switchInputMode: function() {
  765. const currentMode = this.data.inputMode;
  766. const newMode = currentMode === 'keyboard' ? 'voice' : 'keyboard';
  767. console.log('切换输入模式:', currentMode, '->', newMode);
  768. this.setData({
  769. inputMode: newMode,
  770. showMediaSheet: false
  771. });
  772. if (newMode === 'keyboard') {
  773. // 切换到键盘模式
  774. setTimeout(() => {
  775. this.setData({
  776. inputFocus: true,
  777. inputPlaceholder: '请输入消息...'
  778. });
  779. }, 100);
  780. } else {
  781. // 切换到语音模式
  782. this.setData({
  783. inputFocus: false,
  784. inputPlaceholder: '按住说话'
  785. });
  786. }
  787. },
  788. // 滚动事件处理
  789. onScroll: function(e) {
  790. const scrollTop = e.detail.scrollTop;
  791. this.setData({
  792. lastScrollTop: scrollTop,
  793. isScrolling: true
  794. });
  795. // 延迟重置滚动状态
  796. clearTimeout(this.scrollTimer);
  797. this.scrollTimer = setTimeout(() => {
  798. this.setData({ isScrolling: false });
  799. }, 200);
  800. // 检查是否需要加载更多
  801. if (scrollTop <= 100 && !this.data.loadingMore && this.data.hasMore && this.data.page > 1) {
  802. this.loadMoreMessages();
  803. }
  804. },
  805. // 加载更多消息
  806. loadMoreMessages: function() {
  807. if (this.data.loadingMore || !this.data.hasMore) return;
  808. this.setData({
  809. loadingMore: true
  810. });
  811. // 加载更多聊天记录
  812. setTimeout(() => {
  813. this.setData({
  814. page: this.data.page + 1
  815. }, () => {
  816. this.loadChatHistory();
  817. });
  818. }, 500);
  819. },
  820. // 滚动到底部
  821. scrollToBottom: function(animate = true) {
  822. if (this.data.isScrolling) return;
  823. this.setData({
  824. scrollAnimate: animate
  825. }, () => {
  826. // 设置一个足够大的值确保滚动到底部
  827. setTimeout(() => {
  828. this.setData({
  829. scrollTop: 999999
  830. });
  831. }, 100);
  832. });
  833. },
  834. // 显示多媒体选择面板
  835. showMediaActionSheet: function() {
  836. this.setData({
  837. showMediaSheet: true,
  838. inputFocus: false
  839. });
  840. },
  841. // 隐藏多媒体选择面板
  842. hideMediaActionSheet: function() {
  843. this.setData({
  844. showMediaSheet: false
  845. });
  846. },
  847. // 键盘高度变化
  848. onKeyboardHeightChange: function(res) {
  849. console.log('键盘高度变化:', res.height);
  850. if (res.height > 0) {
  851. // 键盘弹出时隐藏多媒体面板
  852. this.setData({ showMediaSheet: false });
  853. // 键盘弹出后滚动到底部
  854. setTimeout(() => {
  855. this.scrollToBottom();
  856. }, 300);
  857. }
  858. },
  859. // 选择图片
  860. chooseImage: function() {
  861. this.hideMediaActionSheet();
  862. wx.chooseImage({
  863. count: 9,
  864. sizeType: ['compressed'],
  865. sourceType: ['album'],
  866. success: (res) => {
  867. console.log('选择图片成功:', res.tempFilePaths);
  868. this.uploadImages(res.tempFilePaths);
  869. },
  870. fail: (err) => {
  871. console.error('选择图片失败:', err);
  872. wx.showToast({
  873. title: '选择图片失败',
  874. icon: 'none'
  875. });
  876. }
  877. });
  878. },
  879. // 选择视频
  880. chooseVideo: function() {
  881. this.hideMediaActionSheet();
  882. wx.chooseVideo({
  883. sourceType: ['album'],
  884. compressed: true,
  885. maxDuration: 60,
  886. success: (res) => {
  887. console.log('选择视频成功:', res);
  888. this.uploadVideo(res.tempFilePath, res.thumbTempFilePath);
  889. },
  890. fail: (err) => {
  891. console.error('选择视频失败:', err);
  892. wx.showToast({
  893. title: '选择视频失败',
  894. icon: 'none'
  895. });
  896. }
  897. });
  898. },
  899. // 选择文件
  900. chooseFile: function() {
  901. this.hideMediaActionSheet();
  902. wx.chooseMessageFile({
  903. count: 1,
  904. type: 'all',
  905. success: (res) => {
  906. console.log('选择文件成功:', res);
  907. const file = res.tempFiles[0];
  908. this.uploadFile(file.path, file.name, file.size);
  909. },
  910. fail: (err) => {
  911. console.error('选择文件失败:', err);
  912. wx.showToast({
  913. title: '选择文件失败',
  914. icon: 'none'
  915. });
  916. }
  917. });
  918. },
  919. // 上传图片
  920. uploadImages: function(tempFilePaths) {
  921. tempFilePaths.forEach((tempFilePath, index) => {
  922. const fileName = `image_${Date.now()}_${index}.jpg`;
  923. this.uploadFile(tempFilePath, fileName, 0, 'image');
  924. });
  925. },
  926. // 上传视频
  927. uploadVideo: function(tempFilePath, thumbPath) {
  928. const fileName = `video_${Date.now()}.mp4`;
  929. this.uploadFile(tempFilePath, fileName, 0, 'video', thumbPath);
  930. },
  931. // 通用文件上传
  932. uploadFile: function(tempFilePath, fileName, fileSize = 0, type = 'file', thumbPath = '') {
  933. console.log('开始上传文件:', { fileName, type, fileSize });
  934. // 获取文件扩展名
  935. const extension = fileName.split('.').pop().toLowerCase();
  936. // 创建消息
  937. const messageId = 'file-' + Date.now();
  938. const message = {
  939. id: messageId,
  940. sender: 'user',
  941. type: type,
  942. content: tempFilePath,
  943. thumb: thumbPath,
  944. fileName: fileName,
  945. fileSize: fileSize,
  946. extension: extension,
  947. timestamp: Date.now(),
  948. status: 'uploading',
  949. progress: 0
  950. };
  951. this.addMessageToList(message);
  952. // 模拟上传过程
  953. this.simulateUpload(messageId, type);
  954. },
  955. // 模拟上传过程
  956. simulateUpload: function(messageId, type) {
  957. let progress = 0;
  958. const uploadInterval = setInterval(() => {
  959. progress += Math.random() * 20 + 10;
  960. if (progress >= 100) {
  961. progress = 100;
  962. clearInterval(uploadInterval);
  963. setTimeout(() => {
  964. this.updateMessageStatus(messageId, 'success');
  965. // 清除进度信息
  966. const { messageList } = this.data;
  967. const index = messageList.findIndex(msg => msg.id === messageId);
  968. if (index !== -1) {
  969. delete messageList[index].progress;
  970. this.setData({ messageList });
  971. // 模拟专家回复
  972. if (type === 'image' || type === 'video') {
  973. setTimeout(() => {
  974. this.receiveMediaReply(type);
  975. }, 800);
  976. }
  977. }
  978. }, 200);
  979. }
  980. // 更新进度
  981. const { messageList } = this.data;
  982. const index = messageList.findIndex(msg => msg.id === messageId);
  983. if (index !== -1) {
  984. messageList[index].progress = Math.min(progress, 100);
  985. this.setData({ messageList });
  986. }
  987. }, 100);
  988. },
  989. // 接收媒体回复
  990. receiveMediaReply: function(type) {
  991. const imageReplies = [
  992. '照片收到了,牛的状况看起来确实不太理想。',
  993. '从照片看,饲养环境需要改善一下。',
  994. '图片清晰,我可以更准确地判断问题了。'
  995. ];
  996. const videoReplies = [
  997. '视频看到了,动物的精神状态需要关注。',
  998. '从视频可以观察到更多细节,这很有帮助。',
  999. '视频内容很有价值,让我了解了具体情况。'
  1000. ];
  1001. const replies = type === 'image' ? imageReplies : videoReplies;
  1002. const randomReply = replies[Math.floor(Math.random() * replies.length)];
  1003. const newMessage = {
  1004. id: 'exp-media-' + Date.now(),
  1005. sender: 'expert',
  1006. type: 'text',
  1007. content: randomReply,
  1008. timestamp: Date.now(),
  1009. status: 'success'
  1010. };
  1011. this.addMessageToList(newMessage);
  1012. },
  1013. // 开始语音录制
  1014. startVoiceRecord: function(e) {
  1015. if (e && e.touches && e.touches[0]) {
  1016. this.setData({
  1017. recordStartY: e.touches[0].clientY
  1018. });
  1019. }
  1020. this.recordManager.start({
  1021. duration: 60000,
  1022. sampleRate: 44100,
  1023. numberOfChannels: 1,
  1024. encodeBitRate: 192000,
  1025. format: 'aac'
  1026. });
  1027. },
  1028. // 语音录制触摸移动
  1029. onVoiceTouchMove: function(e) {
  1030. if (!this.data.isRecording) return;
  1031. if (e.touches && e.touches[0]) {
  1032. const currentY = e.touches[0].clientY;
  1033. const startY = this.data.recordStartY;
  1034. const deltaY = startY - currentY;
  1035. const isCanceling = deltaY > 50;
  1036. if (isCanceling !== this.data.isCanceling) {
  1037. this.setData({
  1038. isCanceling: isCanceling,
  1039. recordingTip: isCanceling ? '松开取消' : '松开 发送',
  1040. voiceTip: isCanceling ? '松开取消' : '按住 说话'
  1041. });
  1042. }
  1043. }
  1044. },
  1045. // 结束语音录制
  1046. endVoiceRecord: function() {
  1047. if (this.data.isRecording) {
  1048. this.recordManager.stop();
  1049. }
  1050. },
  1051. // 取消语音录制
  1052. cancelVoiceRecord: function() {
  1053. if (this.data.isRecording) {
  1054. this.setData({
  1055. isCanceling: true
  1056. });
  1057. this.recordManager.stop();
  1058. }
  1059. },
  1060. // 发送语音消息
  1061. sendAudioMessage: function(tempFilePath, duration) {
  1062. console.log('发送语音消息:', { duration });
  1063. const message = {
  1064. id: 'audio-' + Date.now(),
  1065. sender: 'user',
  1066. type: 'audio',
  1067. content: tempFilePath,
  1068. duration: duration,
  1069. timestamp: Date.now(),
  1070. status: 'sending'
  1071. };
  1072. this.addMessageToList(message);
  1073. // 模拟发送成功
  1074. setTimeout(() => {
  1075. this.updateMessageStatus(message.id, 'success');
  1076. // 模拟专家回复
  1077. setTimeout(() => {
  1078. const reply = {
  1079. id: 'exp-audio-' + Date.now(),
  1080. sender: 'expert',
  1081. type: 'text',
  1082. content: '语音收到了,我会仔细听取分析。',
  1083. timestamp: Date.now(),
  1084. status: 'success'
  1085. };
  1086. this.addMessageToList(reply);
  1087. }, 1500);
  1088. }, 800);
  1089. },
  1090. // 预览图片
  1091. previewImage: function(e) {
  1092. const url = e.currentTarget.dataset.url;
  1093. wx.previewImage({
  1094. current: url,
  1095. urls: [url],
  1096. fail: (err) => {
  1097. console.error('预览图片失败:', err);
  1098. wx.showToast({
  1099. title: '预览失败',
  1100. icon: 'none'
  1101. });
  1102. }
  1103. });
  1104. },
  1105. // 下载文件
  1106. downloadFile: function(e) {
  1107. const url = e.currentTarget.dataset.url;
  1108. wx.showLoading({ title: '下载中...' });
  1109. wx.downloadFile({
  1110. url: url,
  1111. success: (res) => {
  1112. wx.hideLoading();
  1113. wx.showToast({
  1114. title: '下载成功',
  1115. icon: 'success'
  1116. });
  1117. // 保存到相册(如果是图片)
  1118. if (url.match(/\.(jpg|jpeg|png|gif)$/i)) {
  1119. wx.saveImageToPhotosAlbum({
  1120. filePath: res.tempFilePath,
  1121. success: () => {
  1122. wx.showToast({
  1123. title: '已保存到相册',
  1124. icon: 'success'
  1125. });
  1126. },
  1127. fail: (err) => {
  1128. console.error('保存到相册失败:', err);
  1129. }
  1130. });
  1131. }
  1132. },
  1133. fail: (err) => {
  1134. wx.hideLoading();
  1135. console.error('下载失败:', err);
  1136. wx.showToast({
  1137. title: '下载失败',
  1138. icon: 'none'
  1139. });
  1140. }
  1141. });
  1142. },
  1143. // 修复:时间格式化函数 - 确保正确显示
  1144. formatTime: function(timestamp) {
  1145. // 调试日志
  1146. console.log('formatTime 接收到的timestamp:', timestamp, '类型:', typeof timestamp);
  1147. if (!timestamp || timestamp <= 0) {
  1148. console.warn('无效的时间戳:', timestamp);
  1149. return '未知时间';
  1150. }
  1151. const timeNum = Number(timestamp);
  1152. if (isNaN(timeNum)) {
  1153. console.warn('时间戳不是有效数字:', timestamp);
  1154. return '未知时间';
  1155. }
  1156. const date = new Date(timeNum);
  1157. if (isNaN(date.getTime())) {
  1158. console.warn('无法创建有效日期对象:', timeNum);
  1159. return '未知时间';
  1160. }
  1161. // 只显示小时和分钟
  1162. const hours = date.getHours().toString().padStart(2, '0');
  1163. const minutes = date.getMinutes().toString().padStart(2, '0');
  1164. const result = `${hours}:${minutes}`;
  1165. console.log('formatTime 结果:', result);
  1166. return result;
  1167. },
  1168. // 格式化文件大小
  1169. formatFileSize: function(bytes) {
  1170. if (bytes === 0 || !bytes) return '未知大小';
  1171. const units = ['B', 'KB', 'MB', 'GB'];
  1172. let size = bytes;
  1173. let unitIndex = 0;
  1174. while (size >= 1024 && unitIndex < units.length - 1) {
  1175. size /= 1024;
  1176. unitIndex++;
  1177. }
  1178. return size.toFixed(1) + units[unitIndex];
  1179. },
  1180. // 阻止事件冒泡
  1181. stopPropagation: function() {
  1182. // 空函数,仅用于阻止事件冒泡
  1183. }
  1184. });