fix: 聊天(已实现图文消息对话)
This commit is contained in:
@@ -40,6 +40,9 @@
|
||||
@scrolltoupper="loadHistory"
|
||||
>
|
||||
<view class="message-list">
|
||||
<view v-if="isEmpty" class="empty-state">
|
||||
没有更多消息了~
|
||||
</view>
|
||||
<view v-for="(msg, index) in messages" :key="index" class="message-item">
|
||||
<!-- 对方消息 -->
|
||||
<view v-if="msg.sender !== currentUserId" class="other-message">
|
||||
@@ -218,7 +221,7 @@
|
||||
<script>
|
||||
import { webSocketManager } from '@/utils/ws/websocket';
|
||||
import { ChatManager } from '@/store/chat/chat.js';
|
||||
import {sendToUserApi} from "@/request/api/im";
|
||||
import {sendToUserApi, upLoadChatFileApi} from "@/request/api/im";
|
||||
import AudioPlayer from './components/AudioPlayer.vue';
|
||||
|
||||
export default {
|
||||
@@ -248,10 +251,13 @@ export default {
|
||||
showRecordingModal: false,
|
||||
currentPlayingAudio: null,
|
||||
scrollTop: 0,
|
||||
scrollViewHeight: 0,
|
||||
oldScrollViewHeight: 0,
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
hasMore: true,
|
||||
loading: false, // 新增加载状态
|
||||
isEmpty: false, // 历史消息是否为空
|
||||
};
|
||||
},
|
||||
onLoad: function (option) {
|
||||
@@ -272,22 +278,14 @@ export default {
|
||||
|
||||
// 从ChatManager获取房间消息
|
||||
this.messages = ChatManager.getRoomMessages(option.room_id);
|
||||
|
||||
// 如果没有消息,添加示例消息
|
||||
if (this.messages.length === 0) {
|
||||
this.addExampleMessages();
|
||||
}
|
||||
|
||||
uni.setNavigationBarTitle({
|
||||
title: `与${this.doctorUserInfo.nick_name}医生的对话`,
|
||||
success: () => {
|
||||
console.log('修改标题成功');
|
||||
},
|
||||
fail: () => {
|
||||
console.log('修改标题失败');
|
||||
},
|
||||
complete: () => {
|
||||
console.log('修改标题结束');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -307,10 +305,14 @@ export default {
|
||||
onShow() {
|
||||
// 监听当前房间的新消息事件
|
||||
uni.$on('new-room-message', this.handleNewRoomMessage);
|
||||
uni.$on('load-room-message', this.handLoadMessageList);
|
||||
uni.$on('no-more-history', this.handNoMessageList);
|
||||
},
|
||||
onHide() {
|
||||
// 移除消息监听
|
||||
uni.$off('new-room-message', this.handleNewRoomMessage);
|
||||
uni.$off('load-room-message', this.handLoadMessageList);
|
||||
uni.$off('no-more-history', this.handNoMessageList);
|
||||
},
|
||||
mounted() {
|
||||
// 初始化录音管理器
|
||||
@@ -327,73 +329,32 @@ export default {
|
||||
|
||||
methods: {
|
||||
// 添加示例消息
|
||||
addExampleMessages() {
|
||||
const exampleMessages = [
|
||||
{
|
||||
id: '1',
|
||||
sender: 'doctor-1',
|
||||
type: 'text',
|
||||
content: '您好,请问有什么可以帮您的?',
|
||||
time: '09:30',
|
||||
timestamp: Date.now() - 3600000
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
sender: 'user-252',
|
||||
type: 'text',
|
||||
content: '我这两天有点发烧,还有咳嗽。',
|
||||
time: '09:31',
|
||||
timestamp: Date.now() - 3540000
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
sender: 'doctor-1',
|
||||
type: 'text',
|
||||
content: '您有测量体温吗?最近有没有接触过感冒的人?',
|
||||
time: '09:32',
|
||||
timestamp: Date.now() - 3480000
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
sender: 'user-252',
|
||||
type: 'image',
|
||||
content: 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk_upload_20250108/29bdb9560340373bb3096394a845afa5.jpg',
|
||||
time: '09:33',
|
||||
timestamp: Date.now() - 3420000
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
sender: 'doctor-1',
|
||||
type: 'video',
|
||||
content: 'https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/byted-player-videos/1.0.0/xgplayer-demo-720p.mp4',
|
||||
time: '09:35',
|
||||
timestamp: Date.now() - 3300000
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
sender: 'user-252',
|
||||
type: 'audio',
|
||||
content: 'https://er-sycdn.kuwo.cn/f7c3be4ff67cc7b135e38e8365479e06/687d941e/resource/30106/trackmedia/M800004emQMs09Z1lz.mp3',
|
||||
duration: 15,
|
||||
time: '09:36',
|
||||
timestamp: Date.now() - 3240000
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
sender: 'doctor-1',
|
||||
type: 'audio',
|
||||
content: 'https://er-sycdn.kuwo.cn/f7c3be4ff67cc7b135e38e8365479e06/687d941e/resource/30106/trackmedia/M800004emQMs09Z1lz.mp3',
|
||||
duration: 8,
|
||||
time: '09:37',
|
||||
timestamp: Date.now() - 3180000
|
||||
}
|
||||
];
|
||||
handNoMessageList() {
|
||||
// 提示没有更多消息了
|
||||
this.isEmpty = true;
|
||||
this.hasMore = false;
|
||||
},
|
||||
// 加载历史消息
|
||||
handLoadMessageList(type = 0) {
|
||||
this.loading = false;
|
||||
this.messages = ChatManager.roomMessages[this.doctorUserInfo.room_id]
|
||||
if (type === 1) {
|
||||
this.scrollToBottom();
|
||||
} else {
|
||||
this.scrollToOldLastMessage();
|
||||
}
|
||||
},
|
||||
|
||||
// 添加到ChatManager和本地消息列表
|
||||
exampleMessages.forEach(msg => {
|
||||
ChatManager.addMessageToRoom(this.doctorUserInfo.room_id, msg);
|
||||
});
|
||||
this.messages = [...exampleMessages];
|
||||
getScrollHeight() {
|
||||
|
||||
const query = uni.createSelectorQuery().in(this)
|
||||
// query.select('.chat-messages').boundingClientRect(res => {
|
||||
query.select('.message-list').boundingClientRect(res => {
|
||||
if (res) {
|
||||
this.oldScrollViewHeight = this.scrollViewHeight
|
||||
this.scrollViewHeight = res.height
|
||||
}
|
||||
}).exec()
|
||||
},
|
||||
|
||||
// 处理WebSocket消息
|
||||
@@ -404,77 +365,22 @@ export default {
|
||||
|
||||
// 处理当前房间的新消息事件
|
||||
handleNewRoomMessage(message) {
|
||||
// 处理不同类型的消息
|
||||
const msgType = this.getMessageType(message.message_type);
|
||||
const newMsg = {
|
||||
id: message.id,
|
||||
sender: message.sender_user_id,
|
||||
type: msgType,
|
||||
content: message.content,
|
||||
time: this.formatTime(message.send_time),
|
||||
timestamp: message.send_time
|
||||
};
|
||||
|
||||
// 对于音频和视频消息,可能有额外字段
|
||||
if (msgType === 'audio' || msgType === 'video') {
|
||||
newMsg.duration = message.duration || 0;
|
||||
}
|
||||
|
||||
// 添加到消息列表
|
||||
this.addMessage(newMsg);
|
||||
},
|
||||
|
||||
// 根据消息类型ID获取类型名称
|
||||
getMessageType(typeId) {
|
||||
const types = {
|
||||
0: 'text',
|
||||
1: 'image',
|
||||
2: 'audio',
|
||||
3: 'video',
|
||||
4: 'file',
|
||||
5: 'prescription'
|
||||
};
|
||||
return types[typeId] || 'text';
|
||||
console.log('handle', message)
|
||||
this.addMessage(message);
|
||||
this.scrollToBottom();
|
||||
},
|
||||
|
||||
// 添加消息到列表
|
||||
addMessage(message) {
|
||||
// 避免重复添加
|
||||
if (!this.messages.some(m => m.id === message.id)) {
|
||||
console.log('push', message)
|
||||
this.messages.push(message);
|
||||
this.scrollToBottom();
|
||||
}
|
||||
},
|
||||
|
||||
// 格式化时间
|
||||
formatTime(timestamp) {
|
||||
let date;
|
||||
|
||||
// 处理不同输入类型
|
||||
if (typeof timestamp === 'number') {
|
||||
// 数字直接作为时间戳处理
|
||||
date = new Date(timestamp);
|
||||
} else if (timestamp instanceof Date) {
|
||||
// Date对象直接使用
|
||||
date = timestamp;
|
||||
} else if (typeof timestamp === 'string') {
|
||||
// 字符串类型:尝试转换为时间戳
|
||||
const parsed = Date.parse(timestamp);
|
||||
if (isNaN(parsed)) {
|
||||
// 转换失败则使用当前时间
|
||||
date = new Date();
|
||||
} else {
|
||||
date = new Date(parsed);
|
||||
}
|
||||
} else {
|
||||
// 其他类型使用当前时间
|
||||
date = new Date();
|
||||
}
|
||||
|
||||
// 格式化时间为 HH:MM
|
||||
return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
|
||||
},
|
||||
|
||||
// 切换医生详情显示
|
||||
toggleDoctorDetail() {
|
||||
this.showDoctorDetail = !this.showDoctorDetail;
|
||||
@@ -491,11 +397,8 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendMessage({
|
||||
type: 'audio',
|
||||
content: 'https://er-sycdn.kuwo.cn/f7c3be4ff67cc7b135e38e8365479e06/687d941e/resource/30106/trackmedia/M800004emQMs09Z1lz.mp3',
|
||||
duration: Math.floor(res.duration / 1000),
|
||||
});
|
||||
// 上传录音文件
|
||||
this.uploadAudioFile(res.tempFilePath, Math.floor(res.duration / 1000));
|
||||
|
||||
this.recording = false;
|
||||
this.showRecordingModal = false;
|
||||
@@ -511,10 +414,43 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
// 上传音频文件并发送
|
||||
async uploadAudioFile(tempFilePath, duration) {
|
||||
uni.showLoading({ title: '上传中...', mask: true });
|
||||
try {
|
||||
uni.uploadFile({
|
||||
url: 'http://127.0.0.1:18001/api/mobile/chat-friends/upload-chat-file',
|
||||
filePath: tempFilePath,
|
||||
name: 'file',
|
||||
header: {
|
||||
"authorization": uni.getStorageSync('token')
|
||||
},
|
||||
formData: tempFilePath,
|
||||
success: (uploadFileRes) => {
|
||||
|
||||
this.sendMessage({
|
||||
type: 'audio',
|
||||
content: JSON.parse(uploadFileRes.data).result.url,
|
||||
duration,
|
||||
});
|
||||
},
|
||||
fail: (res) => {
|
||||
console.log(res, '上传失败')
|
||||
}
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('音频上传失败:', error);
|
||||
uni.showToast({ title: '音频上传失败', icon: 'none' });
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
},
|
||||
|
||||
// 发送消息
|
||||
sendMessage(message) {
|
||||
const now = new Date();
|
||||
const timeStr = this.formatTime(now);
|
||||
const timeStr = ChatManager.formatTime(now);
|
||||
|
||||
// 构建完整消息对象
|
||||
const fullMessage = {
|
||||
@@ -551,9 +487,8 @@ export default {
|
||||
};
|
||||
|
||||
sendToUserApi(requestData).then(res => {
|
||||
console.log('发送成功:', res);
|
||||
// 添加到本地消息列表
|
||||
this.messages.push(fullMessage);
|
||||
// this.messages.push(fullMessage);
|
||||
this.scrollToBottom();
|
||||
}).catch(error => {
|
||||
console.error('发送失败:', error);
|
||||
@@ -582,7 +517,6 @@ export default {
|
||||
this.startRecording();
|
||||
},
|
||||
fail: (e) => {
|
||||
console.log('失败', e)
|
||||
uni.showToast({
|
||||
title: '需要录音权限',
|
||||
icon: 'none'
|
||||
@@ -649,24 +583,47 @@ export default {
|
||||
this.showMoreActions = false;
|
||||
|
||||
if (type === 'image') {
|
||||
console.log(type, 'sssssssssssssssssssss')
|
||||
uni.chooseImage({
|
||||
count: 9,
|
||||
sourceType: ['album', 'camera'],
|
||||
success: res => {
|
||||
res.tempFilePaths.forEach(path => {
|
||||
this.sendMessage({
|
||||
type: 'image',
|
||||
content: path
|
||||
});
|
||||
res.tempFilePaths.forEach(file => {
|
||||
uni.uploadFile({
|
||||
url: 'http://127.0.0.1:18001/api/mobile/chat-friends/upload-chat-file',
|
||||
filePath: file,
|
||||
name: 'file',
|
||||
header: {
|
||||
"authorization": uni.getStorageSync('token')
|
||||
},
|
||||
formData: file,
|
||||
success: (uploadFileRes) => {
|
||||
|
||||
this.sendMessage({
|
||||
type: 'image',
|
||||
content: JSON.parse(uploadFileRes.data).result.url
|
||||
});
|
||||
},
|
||||
fail: (res) => {
|
||||
console.log(res, '上传失败')
|
||||
}
|
||||
})
|
||||
// upLoadChatFileApi
|
||||
});
|
||||
}
|
||||
},
|
||||
fail: (e) => {
|
||||
console.log('错误', e)
|
||||
},
|
||||
});
|
||||
} else if (type === 'video') {
|
||||
console.log(type, 'sssssssssssssssssssss')
|
||||
uni.chooseVideo({
|
||||
sourceType: ['album', 'camera'],
|
||||
compressed: true,
|
||||
maxDuration: 60,
|
||||
success: res => {
|
||||
console.log('vedio path', res)
|
||||
// upLoadChatFileApi
|
||||
this.sendMessage({
|
||||
type: 'video',
|
||||
content: res.tempFilePath,
|
||||
@@ -754,6 +711,14 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
scrollToOldLastMessage() {
|
||||
this.$nextTick(() => {
|
||||
this.getScrollHeight()
|
||||
const newHeight = this.scrollViewHeight;
|
||||
this.scrollTop = newHeight - this.oldScrollViewHeight; // 计算差值保持位置
|
||||
});
|
||||
},
|
||||
|
||||
// 加载历史消息
|
||||
async loadHistory() {
|
||||
if (!this.hasMore || this.loading) return;
|
||||
@@ -761,31 +726,7 @@ export default {
|
||||
this.loading = true;
|
||||
try {
|
||||
// 模拟加载历史消息
|
||||
const newMessages = await new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve([
|
||||
{
|
||||
id: 'h1',
|
||||
sender: 'doctor-1',
|
||||
type: 'text',
|
||||
content: '这是更早的消息',
|
||||
time: '09:20',
|
||||
timestamp: Date.now() - 7200000
|
||||
},
|
||||
{
|
||||
id: 'h2',
|
||||
sender: 'user-252',
|
||||
type: 'text',
|
||||
content: '是的,这是历史消息',
|
||||
time: '09:21',
|
||||
timestamp: Date.now() - 7140000
|
||||
}
|
||||
]);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
this.messages = [...newMessages, ...this.messages];
|
||||
this.hasMore = false; // 假设没有更多历史消息
|
||||
ChatManager.loadHistory(this.doctorUserInfo.room_id)
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
@@ -800,6 +741,14 @@ $doctor-bg: #e6f2ff;
|
||||
$patient-bg: #e6f9ff;
|
||||
$light-bg: #f5f9ff;
|
||||
|
||||
.empty-state {
|
||||
// 文字水平居中,灰色
|
||||
text-align: center;
|
||||
color: #999;
|
||||
padding: 20rpx 0;
|
||||
font-size: 16px;
|
||||
background-color: $light-bg;
|
||||
}
|
||||
.chat-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -81,4 +81,9 @@ export default {
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 即使不需要渲染内容,也需要保留一个空的 template -->
|
||||
<div v-if="false"></div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user