// 获取应用实例 const app = getApp() Page({ data: { // 专家信息 expertInfo: { id: 1, name: '张明专家', title: '资深畜牧兽医', expertise: '牛羊疾病防治', avatar: '/images/avatars/expert1.png', online: true, phone: '13800138000' }, // 用户信息 userInfo: { id: 1001, name: '养殖户', avatar: '/images/avatars/user.png' }, // 消息列表 messageList: [], scrollToView: '', // 输入相关 inputValue: '', inputFocus: false, inputMode: 'keyboard', // keyboard or voice inputPlaceholder: '输入消息...', // 多媒体 showMediaSheet: false, // 录音相关 isRecording: false, recordingTime: 0, recordingTip: '上滑取消录音', recordingTimer: null, recordManager: null, // 页面状态 isFirstLoad: true, showDateDivider: true, todayDate: '', // 消息ID计数器 messageId: 1000, // 存储键名 storageKey: 'consult_messages_' }, onLoad: function(options) { // 初始化录音管理器 this.initRecordManager() // 获取今天日期 this.setTodayDate() // 加载用户信息 this.loadUserInfo() // 加载专家信息 if (options.expertId) { this.loadExpertInfo(options.expertId) } // 加载聊天记录 this.loadChatHistory() // 设置键盘监听 wx.onKeyboardHeightChange(this.onKeyboardHeightChange) // 模拟首次进入时的欢迎消息 setTimeout(() => { this.setData({ isFirstLoad: false }) }, 2000) }, onUnload: function() { // 清理定时器 if (this.data.recordingTimer) { clearInterval(this.data.recordingTimer) } // 移除监听器 wx.offKeyboardHeightChange() // 保存聊天记录 this.saveChatHistory() }, onShow: function() { // 页面显示时自动滚动到底部 setTimeout(() => { this.scrollToBottom() }, 300) }, // 初始化录音管理器 initRecordManager: function() { this.recordManager = wx.getRecorderManager() this.recordManager.onStart(() => { console.log('录音开始') }) this.recordManager.onStop((res) => { const { tempFilePath, duration } = res if (tempFilePath) { this.sendAudioMessage(tempFilePath, Math.floor(duration / 1000)) } }) this.recordManager.onError((err) => { console.error('录音失败:', err) wx.showToast({ title: '录音失败', icon: 'none' }) this.setData({ isRecording: false }) }) }, // 设置今天日期 setTodayDate: function() { const now = new Date() const month = now.getMonth() + 1 const date = now.getDate() const week = ['日', '一', '二', '三', '四', '五', '六'][now.getDay()] this.setData({ todayDate: `${month}月${date}日 星期${week}` }) }, // 加载用户信息 loadUserInfo: function() { const userInfo = wx.getStorageSync('userInfo') || this.data.userInfo this.setData({ userInfo }) }, // 加载专家信息 loadExpertInfo: function(expertId) { // 这里应该是API请求,暂时使用模拟数据 wx.showLoading({ title: '加载中...' }) setTimeout(() => { const expertInfo = { id: expertId, name: ['张明', '李华', '王强', '赵刚'][expertId - 1] + '专家', title: '资深畜牧兽医', expertise: '牛羊疾病防治', avatar: `/images/avatars/expert${expertId}.png`, online: Math.random() > 0.3, phone: '138' + Math.floor(Math.random() * 100000000).toString().padStart(8, '0') } this.setData({ expertInfo, storageKey: `consult_messages_${expertId}_${this.data.userInfo.id}` }) wx.hideLoading() }, 500) }, // 加载聊天记录 loadChatHistory: function() { const storageKey = this.data.storageKey const savedMessages = wx.getStorageSync(storageKey) || [] if (savedMessages.length > 0) { // 处理消息时间显示 const processedMessages = this.processMessageTimes(savedMessages) this.setData({ messageList: processedMessages, isFirstLoad: false }) // 滚动到底部 setTimeout(() => { this.scrollToBottom() }, 200) } else { // 初始化第一条欢迎消息 this.initWelcomeMessage() } }, // 处理消息时间显示 processMessageTimes: function(messages) { if (!messages || messages.length === 0) return messages let lastTimestamp = 0 return messages.map((msg, index) => { const currentTimestamp = msg.timestamp // 如果两条消息间隔超过5分钟,显示时间 const timeDiff = currentTimestamp - lastTimestamp msg.showTime = timeDiff > 5 * 60 * 1000 || index === 0 if (msg.showTime) { lastTimestamp = currentTimestamp } return msg }) }, // 初始化欢迎消息 initWelcomeMessage: function() { const welcomeMessage = { id: 'welcome-' + Date.now(), sender: 'expert', type: 'text', content: '您好,我是' + this.data.expertInfo.name + ',有什么可以帮您?', timestamp: Date.now() - 60000, showTime: true } this.setData({ messageList: [welcomeMessage] }) }, // 保存聊天记录 saveChatHistory: function() { const { storageKey, messageList } = this.data if (messageList.length > 0) { try { wx.setStorageSync(storageKey, messageList) } catch (e) { console.error('保存聊天记录失败:', e) } } }, // 返回上一页 goBack: function() { wx.navigateBack() }, // 打电话 makePhoneCall: function() { const phone = this.data.expertInfo.phone wx.makePhoneCall({ phoneNumber: phone, fail: () => { wx.showToast({ title: '拨打失败', icon: 'none' }) } }) }, // 输入处理 onInput: function(e) { this.setData({ inputValue: e.detail.value }) }, // 发送文本消息 sendTextMessage: function() { const content = this.data.inputValue.trim() if (!content) return const newMessage = { id: 'msg-' + (++this.data.messageId), sender: 'user', type: 'text', content: content, timestamp: Date.now(), status: 'sending' } // 添加到消息列表 this.addMessageToList(newMessage) // 清空输入框 this.setData({ inputValue: '', inputFocus: false }) // 模拟发送成功 setTimeout(() => { this.updateMessageStatus(newMessage.id, 'success') // 模拟专家回复 setTimeout(() => { this.receiveExpertReply() }, 1000) }, 500) }, // 添加消息到列表 addMessageToList: function(message) { const { messageList } = this.data // 确定是否需要显示时间 const lastMessage = messageList[messageList.length - 1] const timeDiff = lastMessage ? message.timestamp - lastMessage.timestamp : 0 message.showTime = timeDiff > 5 * 60 * 1000 || !lastMessage messageList.push(message) this.setData({ messageList, scrollToView: 'msg-' + message.id }) }, // 更新消息状态 updateMessageStatus: function(messageId, status) { const { messageList } = this.data const index = messageList.findIndex(msg => msg.id === messageId) if (index !== -1) { messageList[index].status = status this.setData({ messageList }) } }, // 接收专家回复 receiveExpertReply: function() { const replies = [ '收到您的消息,让我分析一下您说的情况。', '建议您提供更多细节,比如发病时间、具体症状等。', '根据描述,可能是饲料问题引起的,建议调整饲料配方。', '可以考虑添加一些维生素补充剂,改善食欲问题。', '最好能提供照片,这样我可以更准确地判断情况。' ] const randomReply = replies[Math.floor(Math.random() * replies.length)] const newMessage = { id: 'exp-' + Date.now(), sender: 'expert', type: 'text', content: randomReply, timestamp: Date.now(), showTime: false } this.addMessageToList(newMessage) }, // 切换输入模式 switchInputMode: function() { const newMode = this.data.inputMode === 'keyboard' ? 'voice' : 'keyboard' const placeholder = newMode === 'voice' ? '按住说话' : '输入消息...' this.setData({ inputMode: newMode, inputPlaceholder: placeholder, inputFocus: newMode === 'keyboard' }) }, // 显示多媒体选择面板 showMediaActionSheet: function() { this.setData({ showMediaSheet: true, inputFocus: false }) }, // 隐藏多媒体选择面板 hideMediaActionSheet: function() { this.setData({ showMediaSheet: false }) }, // 选择图片 chooseImage: function() { this.hideMediaActionSheet() wx.chooseImage({ count: 9, sizeType: ['compressed'], sourceType: ['album'], success: (res) => { this.uploadImages(res.tempFilePaths) } }) }, // 拍照 takePhoto: function() { this.hideMediaActionSheet() wx.chooseImage({ count: 1, sizeType: ['compressed'], sourceType: ['camera'], success: (res) => { this.uploadImages(res.tempFilePaths) } }) }, // 选择视频 chooseVideo: function() { this.hideMediaActionSheet() wx.chooseVideo({ sourceType: ['album'], compressed: true, maxDuration: 60, success: (res) => { this.uploadVideo(res.tempFilePath, res.thumbTempFilePath) } }) }, // 录制语音 recordAudio: function() { this.hideMediaActionSheet() this.startVoiceRecord() }, // 选择文件 chooseFile: function() { this.hideMediaActionSheet() wx.chooseMessageFile({ count: 1, type: 'all', success: (res) => { const file = res.tempFiles[0] this.uploadFile(file.path, file.name, file.size) } }) }, // 上传图片 uploadImages: function(tempFilePaths) { tempFilePaths.forEach((tempFilePath, index) => { const fileName = 'image_' + Date.now() + '_' + index + '.jpg' this.uploadFile(tempFilePath, fileName, 0, 'image') }) }, // 上传视频 uploadVideo: function(tempFilePath, thumbPath) { const fileName = 'video_' + Date.now() + '.mp4' this.uploadFile(tempFilePath, fileName, 0, 'video', thumbPath) }, // 通用文件上传 uploadFile: function(tempFilePath, fileName, fileSize = 0, type = 'file', thumbPath = '') { // 获取文件扩展名 const extension = fileName.split('.').pop().toLowerCase() // 创建消息 const messageId = 'file-' + Date.now() const message = { id: messageId, sender: 'user', type: type, content: tempFilePath, thumb: thumbPath, fileName: fileName, fileSize: fileSize, extension: extension, timestamp: Date.now(), status: 'uploading', progress: 0 } this.addMessageToList(message) // 模拟上传过程 let progress = 0 const uploadInterval = setInterval(() => { progress += Math.random() * 20 + 10 if (progress >= 100) { progress = 100 clearInterval(uploadInterval) setTimeout(() => { this.updateMessageStatus(messageId, 'success') // 清除进度信息 const { messageList } = this.data const index = messageList.findIndex(msg => msg.id === messageId) if (index !== -1) { delete messageList[index].progress this.setData({ messageList }) // 模拟专家回复 if (type === 'image' || type === 'video') { setTimeout(() => { this.receiveMediaReply(type) }, 800) } } }, 200) } // 更新进度 const { messageList } = this.data const index = messageList.findIndex(msg => msg.id === messageId) if (index !== -1) { messageList[index].progress = Math.min(progress, 100) this.setData({ messageList }) } }, 100) }, // 接收媒体回复 receiveMediaReply: function(type) { const imageReplies = [ '照片收到了,牛的状况看起来确实不太理想。', '从照片看,饲养环境需要改善一下。', '图片清晰,我可以更准确地判断问题了。' ] const videoReplies = [ '视频看到了,动物的精神状态需要关注。', '从视频可以观察到更多细节,这很有帮助。', '视频内容很有价值,让我了解了具体情况。' ] const replies = type === 'image' ? imageReplies : videoReplies const randomReply = replies[Math.floor(Math.random() * replies.length)] const newMessage = { id: 'exp-media-' + Date.now(), sender: 'expert', type: 'text', content: randomReply, timestamp: Date.now(), showTime: false } this.addMessageToList(newMessage) }, // 开始语音录制 startVoiceRecord: function(e) { this.setData({ isRecording: true, recordingTime: 0, recordingTip: '上滑取消录音' }) // 开始录音 this.recordManager.start({ duration: 60000, // 最长60秒 sampleRate: 44100, numberOfChannels: 1, encodeBitRate: 192000, format: 'mp3' }) // 开始计时 const timer = setInterval(() => { const time = this.data.recordingTime + 1 this.setData({ recordingTime: time }) }, 1000) this.setData({ recordingTimer: timer }) }, // 结束语音录制 endVoiceRecord: function() { if (this.data.isRecording) { this.recordManager.stop() if (this.data.recordingTimer) { clearInterval(this.data.recordingTimer) } this.setData({ isRecording: false, recordingTime: 0 }) } }, // 发送语音消息 sendAudioMessage: function(tempFilePath, duration) { const message = { id: 'audio-' + Date.now(), sender: 'user', type: 'audio', content: tempFilePath, duration: duration, timestamp: Date.now(), status: 'sending' } this.addMessageToList(message) // 模拟发送成功 setTimeout(() => { this.updateMessageStatus(message.id, 'success') // 模拟专家回复 setTimeout(() => { const reply = { id: 'exp-audio-' + Date.now(), sender: 'expert', type: 'text', content: '语音收到了,我会仔细听取分析。', timestamp: Date.now(), showTime: false } this.addMessageToList(reply) }, 1500) }, 800) }, // 预览图片 previewImage: function(e) { const url = e.currentTarget.dataset.url wx.previewImage({ current: url, urls: [url] }) }, // 下载文件 downloadFile: function(e) { const url = e.currentTarget.dataset.url wx.showLoading({ title: '下载中...' }) wx.downloadFile({ url: url, success: (res) => { wx.hideLoading() wx.showToast({ title: '下载成功', icon: 'success' }) // 保存到相册(如果是图片) if (url.match(/\.(jpg|jpeg|png|gif)$/i)) { wx.saveImageToPhotosAlbum({ filePath: res.tempFilePath, success: () => { wx.showToast({ title: '已保存到相册', icon: 'success' }) } }) } }, fail: () => { wx.hideLoading() wx.showToast({ title: '下载失败', icon: 'none' }) } }) }, // 键盘高度变化 onKeyboardHeightChange: function(res) { if (res.height > 0) { // 键盘弹出时隐藏多媒体面板 this.setData({ showMediaSheet: false }) // 滚动到底部 setTimeout(() => { this.scrollToBottom() }, 100) } }, // 滚动到底部 scrollToBottom: function() { if (this.data.messageList.length > 0) { const lastMessage = this.data.messageList[this.data.messageList.length - 1] this.setData({ scrollToView: lastMessage.id }) } }, // 格式化时间(微信样式) formatTime: function(timestamp) { const now = new Date() const date = new Date(timestamp) const diff = now - date // 今天 if (date.toDateString() === now.toDateString()) { return this.formatMessageTime(timestamp) } // 昨天 const yesterday = new Date(now) yesterday.setDate(yesterday.getDate() - 1) if (date.toDateString() === yesterday.toDateString()) { return '昨天 ' + this.formatMessageTime(timestamp) } // 一周内 if (diff < 7 * 24 * 60 * 60 * 1000) { const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'] return weekDays[date.getDay()] + ' ' + this.formatMessageTime(timestamp) } // 今年内 if (date.getFullYear() === now.getFullYear()) { return `${date.getMonth() + 1}月${date.getDate()}日 ${this.formatMessageTime(timestamp)}` } // 更早 return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()} ${this.formatMessageTime(timestamp)}` }, // 格式化消息时间(HH:mm) formatMessageTime: function(timestamp) { const date = new Date(timestamp) const hours = date.getHours().toString().padStart(2, '0') const minutes = date.getMinutes().toString().padStart(2, '0') return `${hours}:${minutes}` }, // 格式化文件大小 formatFileSize: function(bytes) { if (bytes === 0) return '未知大小' const units = ['B', 'KB', 'MB', 'GB'] let size = bytes let unitIndex = 0 while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024 unitIndex++ } return size.toFixed(1) + units[unitIndex] }, // 阻止事件冒泡 stopPropagation: function() { // 空函数,仅用于阻止事件冒泡 } })