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

1240 lines
32 KiB

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