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