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

753 lines
18 KiB

// 获取应用实例
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() {
// 空函数,仅用于阻止事件冒泡
}
})