fix: 聊天(已实现发送文本音频消息)
This commit is contained in:
@@ -609,7 +609,7 @@
|
|||||||
.nav {
|
.nav {
|
||||||
width: 710rpx;
|
width: 710rpx;
|
||||||
background: #FFFFFF;
|
background: #FFFFFF;
|
||||||
border-radius: 8rpx;
|
border-radius: 15rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -623,6 +623,7 @@
|
|||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
background: url('/static/home/bg01.png') no-repeat;
|
background: url('/static/home/bg01.png') no-repeat;
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
|
||||||
.nav_service_img {
|
.nav_service_img {
|
||||||
width: 116rpx;
|
width: 116rpx;
|
||||||
@@ -670,7 +671,7 @@
|
|||||||
.list {
|
.list {
|
||||||
width: 710rpx;
|
width: 710rpx;
|
||||||
background: linear-gradient(180deg, #E2F1FF 0%, #FBFDFF 54%, #FFFFFF 100%);
|
background: linear-gradient(180deg, #E2F1FF 0%, #FBFDFF 54%, #FFFFFF 100%);
|
||||||
border-radius: 16rpx;
|
border-radius: 24rpx;
|
||||||
padding: 12rpx 30rpx 22rpx;
|
padding: 12rpx 30rpx 22rpx;
|
||||||
margin: 32rpx auto 52rpx;
|
margin: 32rpx auto 52rpx;
|
||||||
box-shadow: 0px 0px 16px 0px rgba(0, 0, 0, 0.08);
|
box-shadow: 0px 0px 16px 0px rgba(0, 0, 0, 0.08);
|
||||||
@@ -861,7 +862,7 @@
|
|||||||
.lists {
|
.lists {
|
||||||
width: 710rpx;
|
width: 710rpx;
|
||||||
background: linear-gradient(180deg, #E2F1FF 0%, #FBFDFF 54%, #FFFFFF 100%);
|
background: linear-gradient(180deg, #E2F1FF 0%, #FBFDFF 54%, #FFFFFF 100%);
|
||||||
border-radius: 16rpx;
|
border-radius: 24rpx;
|
||||||
padding: 30rpx;
|
padding: 30rpx;
|
||||||
margin: 32rpx auto 52rpx;
|
margin: 32rpx auto 52rpx;
|
||||||
box-shadow: 0px 0px 16px 0px rgba(0, 0, 0, 0.08);
|
box-shadow: 0px 0px 16px 0px rgba(0, 0, 0, 0.08);
|
||||||
|
|||||||
@@ -23,10 +23,10 @@
|
|||||||
<view class="info">
|
<view class="info">
|
||||||
<view class="infos_title">
|
<view class="infos_title">
|
||||||
<text class="infos">系统消息</text>
|
<text class="infos">系统消息</text>
|
||||||
<text class="time">{{system.time}}</text>
|
<text class="time">{{ system.time }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="preview" v-if="system.num >0">
|
<view class="preview" v-if="system.num >0">
|
||||||
您有{{system.num}}条未读信息
|
您有{{ system.num }}条未读信息
|
||||||
</view>
|
</view>
|
||||||
<view class="preview" v-if="system.num==undefined">
|
<view class="preview" v-if="system.num==undefined">
|
||||||
暂无新消息
|
暂无新消息
|
||||||
@@ -47,10 +47,10 @@
|
|||||||
<view class="info">
|
<view class="info">
|
||||||
<view class="infos_title">
|
<view class="infos_title">
|
||||||
<text class="infos">产品订单</text>
|
<text class="infos">产品订单</text>
|
||||||
<text class="time">{{order.time}}</text>
|
<text class="time">{{ order.time }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="preview" v-if="order.num>0">
|
<view class="preview" v-if="order.num>0">
|
||||||
您有{{order.num}}条未读信息
|
您有{{ order.num }}条未读信息
|
||||||
</view>
|
</view>
|
||||||
<view class="preview" v-if="order.num==undefined">
|
<view class="preview" v-if="order.num==undefined">
|
||||||
暂无新消息
|
暂无新消息
|
||||||
@@ -71,10 +71,10 @@
|
|||||||
<view class="info">
|
<view class="info">
|
||||||
<view class="infos_title">
|
<view class="infos_title">
|
||||||
<text class="infos">医生消息</text>
|
<text class="infos">医生消息</text>
|
||||||
<text class="time">{{doctor.time}}</text>
|
<text class="time">{{ doctor.time }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="preview" v-if="doctor.num>0">
|
<view class="preview" v-if="doctor.num>0">
|
||||||
您有{{doctor.num}}条新的消息
|
您有{{ doctor.num }}条新的消息
|
||||||
</view>
|
</view>
|
||||||
<view class="preview" v-if="doctor.num==undefined">
|
<view class="preview" v-if="doctor.num==undefined">
|
||||||
暂无新消息
|
暂无新消息
|
||||||
@@ -96,10 +96,10 @@
|
|||||||
<view class="info">
|
<view class="info">
|
||||||
<view class="infos_title">
|
<view class="infos_title">
|
||||||
<text class="infos">在线问诊</text>
|
<text class="infos">在线问诊</text>
|
||||||
<text class="time">{{doctor.time}}</text>
|
<text class="time">{{ doctor.time }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="preview" v-if="doctor.num>0">
|
<view class="preview" v-if="doctor.num>0">
|
||||||
您有{{doctor.num}}条新的消息
|
您有{{ doctor.num }}条新的消息
|
||||||
</view>
|
</view>
|
||||||
<view class="preview" v-if="doctor.num==undefined">
|
<view class="preview" v-if="doctor.num==undefined">
|
||||||
暂无新消息
|
暂无新消息
|
||||||
@@ -107,15 +107,15 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 模拟<u-divider>在线问诊</u-divider>效果-->
|
<!-- 模拟<u-divider>在线问诊</u-divider>效果-->
|
||||||
|
|
||||||
<!-- 标题 -->
|
<!-- 标题 -->
|
||||||
<view class="title">
|
<view class="title">
|
||||||
<text>在线问诊</text>
|
<text>在线问诊</text>
|
||||||
<!-- <view class="clear" @click="getClearInfo">-->
|
<!-- <view class="clear" @click="getClearInfo">-->
|
||||||
<!-- <text class="iconfont icon-qingchu"></text>-->
|
<!-- <text class="iconfont icon-qingchu"></text>-->
|
||||||
<!-- 清除未读-->
|
<!-- 清除未读-->
|
||||||
<!-- </view>-->
|
<!-- </view>-->
|
||||||
</view>
|
</view>
|
||||||
<!-- 在线问诊 - 用户-->
|
<!-- 在线问诊 - 用户-->
|
||||||
<view v-for="item in imChat" :key="item.id" class="list" @click="goChat(item)">
|
<view v-for="item in imChat" :key="item.id" class="list" @click="goChat(item)">
|
||||||
@@ -124,20 +124,21 @@
|
|||||||
<view class="img">
|
<view class="img">
|
||||||
<image :src="item.avatar" mode=""></image>
|
<image :src="item.avatar" mode=""></image>
|
||||||
</view>
|
</view>
|
||||||
<u-badge size="default" v-if="item.un_read_num>=0" :count="item.un_read_num" :is-dot="item.un_read_num==0?true:false"
|
<u-badge size="default" v-if="item.un_read_num>=0" :count="item.un_read_num"
|
||||||
|
:is-dot="item.un_read_num==0?true:false"
|
||||||
type="error" :offset="item.un_read_num>0?[-4,-8]:[0,0]"></u-badge>
|
type="error" :offset="item.un_read_num>0?[-4,-8]:[0,0]"></u-badge>
|
||||||
</view>
|
</view>
|
||||||
<!-- 描述 -->
|
<!-- 描述 -->
|
||||||
<view class="info">
|
<view class="info">
|
||||||
<view class="infos_title">
|
<view class="infos_title">
|
||||||
<text class="infos">{{ item.nick_name }}</text>
|
<text class="infos">{{ item.nick_name }}</text>
|
||||||
<text class="time">{{item.last_message_time}}</text>
|
<text class="time">{{ item.last_message_time }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="preview" v-if="item.un_read_num>0">
|
<view class="preview">
|
||||||
<p>{{ item.last_message }}</p>
|
<p>{{ item.last_message }}</p>
|
||||||
<p>您有{{item.un_read_num}}条新的消息</p>
|
<!-- <p v-if="item.un_read_num>0">您有{{item.un_read_num}}条新的消息</p>-->
|
||||||
</view>
|
</view>
|
||||||
<view class="preview" v-if="item.un_read_num==undefined">
|
<view class="preview" v-if="item.last_message===''">
|
||||||
暂无新消息
|
暂无新消息
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -150,20 +151,21 @@ import {
|
|||||||
getseesionInfo,
|
getseesionInfo,
|
||||||
getseesionRead
|
getseesionRead
|
||||||
} from '../../request/api/api'
|
} from '../../request/api/api'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
order: [],
|
order: [],
|
||||||
system: [],
|
system: [],
|
||||||
doctor:[],
|
doctor: [],
|
||||||
imChat:[
|
imChat: [
|
||||||
{
|
{
|
||||||
id: 'doctor-1',
|
id: 'doctor-39',
|
||||||
room_id: 'xk_chat_room_68786b4fd071233eb15dfa3e62c1',
|
room_id: 'xk_chat_room_68786b4fd071233eb15dfa3e62c1',
|
||||||
nick_name: '李二狗',
|
nick_name: '李二狗',
|
||||||
avatar: 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk_upload_20250108/29bdb9560340373bb3096394a845afa5.jpg',
|
avatar: 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk_upload_20250108/29bdb9560340373bb3096394a845afa5.jpg',
|
||||||
un_read_num: 0,
|
un_read_num: 99,
|
||||||
last_message: '[图片]',
|
last_message: '多喝水即可',
|
||||||
last_message_time: '2021-07-05 09:05:05'
|
last_message_time: '2021-07-05 09:05:05'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -174,7 +176,89 @@ export default {
|
|||||||
un_read_num: 0,
|
un_read_num: 0,
|
||||||
last_message: '[图片]',
|
last_message: '[图片]',
|
||||||
last_message_time: '2021-07-05 09:05:05'
|
last_message_time: '2021-07-05 09:05:05'
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
id: 'doctor-3',
|
||||||
|
room_id: 'xk_chat_room_68786b4fd071233eb15dfa3e62c1',
|
||||||
|
nick_name: '王五五',
|
||||||
|
avatar: '/static/xx/ysxx.png',
|
||||||
|
un_read_num: 0,
|
||||||
|
last_message: '[图片]',
|
||||||
|
last_message_time: '2021-07-05 09:05:05'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'doctor-4',
|
||||||
|
room_id: 'xk_chat_room_68786b4fd071233eb15dfa3e62c1',
|
||||||
|
nick_name: '王六六',
|
||||||
|
avatar: '/static/xx/ysxx.png',
|
||||||
|
un_read_num: 0,
|
||||||
|
last_message: '[图片]',
|
||||||
|
last_message_time: '2021-07-05 09:05:05'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'doctor-5',
|
||||||
|
room_id: 'xk_chat_room_68786b4fd071233eb15dfa3e62c1',
|
||||||
|
nick_name: '王六六1',
|
||||||
|
avatar: '/static/xx/ysxx.png',
|
||||||
|
un_read_num: 0,
|
||||||
|
last_message: '[音频]',
|
||||||
|
last_message_time: '2021-07-05 09:05:05'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'doctor-6',
|
||||||
|
room_id: 'xk_chat_room_68786b4fd071233eb15dfa3e62c1',
|
||||||
|
nick_name: '王六六2',
|
||||||
|
avatar: '/static/xx/ysxx.png',
|
||||||
|
un_read_num: 0,
|
||||||
|
last_message: '[视频]',
|
||||||
|
last_message_time: '2021-07-05 09:05:05'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'doctor-7',
|
||||||
|
room_id: 'xk_chat_room_68786b4fd071233eb15dfa3e62c1',
|
||||||
|
nick_name: '王六六3',
|
||||||
|
avatar: '/static/xx/ysxx.png',
|
||||||
|
un_read_num: 0,
|
||||||
|
last_message: '[文件]',
|
||||||
|
last_message_time: '2021-07-05 09:05:05'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'doctor-8',
|
||||||
|
room_id: 'xk_chat_room_68786b4fd071233eb15dfa3e62c1',
|
||||||
|
nick_name: '王六六',
|
||||||
|
avatar: '/static/xx/ysxx.png',
|
||||||
|
un_read_num: 0,
|
||||||
|
last_message: '[图片]',
|
||||||
|
last_message_time: '2021-07-05 09:05:05'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'doctor-9',
|
||||||
|
room_id: 'xk_chat_room_68786b4fd071233eb15dfa3e62c1',
|
||||||
|
nick_name: '王六六1',
|
||||||
|
avatar: '/static/xx/ysxx.png',
|
||||||
|
un_read_num: 0,
|
||||||
|
last_message: '[音频]',
|
||||||
|
last_message_time: '2021-07-05 09:05:05'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'doctor-10',
|
||||||
|
room_id: 'xk_chat_room_68786b4fd071233eb15dfa3e62c1',
|
||||||
|
nick_name: '王六六2',
|
||||||
|
avatar: '/static/xx/ysxx.png',
|
||||||
|
un_read_num: 0,
|
||||||
|
last_message: '[视频]',
|
||||||
|
last_message_time: '2021-07-05 09:05:05'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'doctor-11',
|
||||||
|
room_id: 'xk_chat_room_68786b4fd071233eb15dfa3e62c1',
|
||||||
|
nick_name: '王六六3',
|
||||||
|
avatar: '/static/xx/ysxx.png',
|
||||||
|
un_read_num: 0,
|
||||||
|
last_message: '[文件]',
|
||||||
|
last_message_time: '2021-07-05 09:05:05'
|
||||||
|
},
|
||||||
|
|
||||||
],
|
],
|
||||||
type: ""
|
type: ""
|
||||||
}
|
}
|
||||||
@@ -271,7 +355,7 @@ export default {
|
|||||||
// console.log(res, 'info');
|
// console.log(res, 'info');
|
||||||
this.order = res.data.data.order
|
this.order = res.data.data.order
|
||||||
this.system = res.data.data.system
|
this.system = res.data.data.system
|
||||||
this.doctor=res.data.data.doctor_news
|
this.doctor = res.data.data.doctor_news
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -281,7 +365,7 @@ export default {
|
|||||||
onShow() {
|
onShow() {
|
||||||
this.order = []
|
this.order = []
|
||||||
this.system = []
|
this.system = []
|
||||||
this.doctor=[]
|
this.doctor = []
|
||||||
this.getInfo()
|
this.getInfo()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,9 +27,14 @@ function request(url, params, method = 0) {
|
|||||||
if (url.split('/').indexOf('newApi') !== -1) {
|
if (url.split('/').indexOf('newApi') !== -1) {
|
||||||
baseURL = "https://shop.xiaokang88.com/member"
|
baseURL = "https://shop.xiaokang88.com/member"
|
||||||
}
|
}
|
||||||
|
if (url.split('/').indexOf('xkApi') !== -1) {
|
||||||
|
// baseURL = "https://api.xiaokang88.com/member"
|
||||||
|
baseURL = "https://127.0.0.1:18001/member"
|
||||||
|
}
|
||||||
url = url.replace('/oldApi', '');
|
url = url.replace('/oldApi', '');
|
||||||
url = url.replace('/newApi', '');
|
url = url.replace('/newApi', '');
|
||||||
url = url.replace('/imApi', '');
|
url = url.replace('/imApi', '');
|
||||||
|
url = url.replace('/xkApi', '');
|
||||||
|
|
||||||
// 加密
|
// 加密
|
||||||
if (params != null) {
|
if (params != null) {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
// chat.js
|
/**
|
||||||
// 聊天消息管理器
|
* 聊天消息管理器
|
||||||
|
* @type {{currentRoomId: null, unreadCounts: {}, messageCache: {}, roomMessages: {}, enterRoom(*): void, leaveRoom(): void, getCurrentRoomId(): *, increaseUnreadCount(*): void, resetUnreadCount(*): void, getUnreadCount(*): *, getAllUnreadCount(): *, notifyUnreadChange(): void, cacheMessage(*, *): void, addMessageToRoom(*, *): void, getRoomMessages(*): *, handleIncomingMessage(*): void}}
|
||||||
|
*/
|
||||||
const ChatManager = {
|
const ChatManager = {
|
||||||
// 当前房间ID
|
// 当前房间ID
|
||||||
currentRoomId: null,
|
currentRoomId: null,
|
||||||
@@ -7,11 +9,29 @@ const ChatManager = {
|
|||||||
// 存储未读消息数 { roomId: count }
|
// 存储未读消息数 { roomId: count }
|
||||||
unreadCounts: {},
|
unreadCounts: {},
|
||||||
|
|
||||||
|
// 消息缓存 { roomId: [message1, message2, ...] }
|
||||||
|
messageCache: {},
|
||||||
|
|
||||||
|
// 房间消息存储 { roomId: [message1, message2, ...] }
|
||||||
|
roomMessages: {},
|
||||||
|
|
||||||
// 进入聊天室
|
// 进入聊天室
|
||||||
enterRoom(roomId) {
|
enterRoom(roomId) {
|
||||||
this.currentRoomId = roomId;
|
this.currentRoomId = roomId;
|
||||||
uni.setStorageSync('currentRoomId', roomId);
|
uni.setStorageSync('currentRoomId', roomId);
|
||||||
console.log(`进入房间: ${roomId}`);
|
console.log(`进入房间: ${roomId}`);
|
||||||
|
|
||||||
|
// 重置当前房间的未读消息
|
||||||
|
this.resetUnreadCount(roomId);
|
||||||
|
|
||||||
|
// 如果有缓存消息,则触发事件,然后清除缓存
|
||||||
|
if (this.messageCache[roomId]) {
|
||||||
|
const cachedMessages = this.messageCache[roomId];
|
||||||
|
cachedMessages.forEach(message => {
|
||||||
|
this.addMessageToRoom(roomId, message);
|
||||||
|
});
|
||||||
|
delete this.messageCache[roomId];
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 离开聊天室
|
// 离开聊天室
|
||||||
@@ -63,17 +83,43 @@ const ChatManager = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 缓存消息(当收到消息但不在该房间时)
|
||||||
|
cacheMessage(roomId, message) {
|
||||||
|
if (!this.messageCache[roomId]) {
|
||||||
|
this.messageCache[roomId] = [];
|
||||||
|
}
|
||||||
|
this.messageCache[roomId].push(message);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 添加消息到房间
|
||||||
|
addMessageToRoom(roomId, message) {
|
||||||
|
if (!this.roomMessages[roomId]) {
|
||||||
|
this.roomMessages[roomId] = [];
|
||||||
|
}
|
||||||
|
this.roomMessages[roomId].push(message);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取房间消息
|
||||||
|
getRoomMessages(roomId) {
|
||||||
|
return this.roomMessages[roomId] || [];
|
||||||
|
},
|
||||||
|
|
||||||
// 处理收到的消息
|
// 处理收到的消息
|
||||||
handleIncomingMessage(message) {
|
handleIncomingMessage(message) {
|
||||||
|
const roomId = message.room_id;
|
||||||
const currentRoomId = this.getCurrentRoomId();
|
const currentRoomId = this.getCurrentRoomId();
|
||||||
|
|
||||||
|
// 添加消息到房间
|
||||||
|
this.addMessageToRoom(roomId, message);
|
||||||
|
|
||||||
// 如果当前在消息所属的房间,直接显示
|
// 如果当前在消息所属的房间,直接显示
|
||||||
if (currentRoomId === message.room_id) {
|
if (currentRoomId === roomId) {
|
||||||
// 通知聊天页面显示消息
|
// 通知聊天页面显示消息
|
||||||
uni.$emit('new-message', message);
|
uni.$emit('new-room-message', message);
|
||||||
} else {
|
} else {
|
||||||
// 不在当前房间,增加未读消息数
|
// 不在当前房间,缓存消息并增加未读消息数
|
||||||
this.increaseUnreadCount(message.room_id);
|
this.cacheMessage(roomId, message);
|
||||||
|
this.increaseUnreadCount(roomId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="chat-container">
|
<view class="chat-container">
|
||||||
<!-- 优化后的顶部导航栏 -->
|
<!-- 顶部导航栏 -->
|
||||||
<view class="chat-header" @click="toggleDoctorDetail">
|
<view class="chat-header" @click="toggleDoctorDetail">
|
||||||
<view class="header-center">
|
<view class="header-center">
|
||||||
<view class="doctor-info">
|
<view class="doctor-info">
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<u-icon name="videocam" size="24" color="#298dff" @click.stop="startCall('video')"></u-icon>
|
<u-icon name="videocam" size="24" color="#298dff" @click.stop="startCall('video')"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 医生详情信息 - 优化为下拉面板 -->
|
<!-- 医生详情信息 -->
|
||||||
<view v-if="showDoctorDetail" class="doctor-detail-panel">
|
<view v-if="showDoctorDetail" class="doctor-detail-panel">
|
||||||
<view class="detail-item">
|
<view class="detail-item">
|
||||||
<text class="detail-label">职称:</text>
|
<text class="detail-label">职称:</text>
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
v-for="(bar, i) in 5"
|
v-for="(bar, i) in 5"
|
||||||
:key="i"
|
:key="i"
|
||||||
class="wave-bar"
|
class="wave-bar"
|
||||||
:style="{ height: `${isPlaying(msg) ? Math.random() * 20 + 10 : 10}px` }"
|
:class="{'wave-bar-active': isPlaying(msg)}"
|
||||||
></view>
|
></view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
v-for="(bar, i) in 5"
|
v-for="(bar, i) in 5"
|
||||||
:key="i"
|
:key="i"
|
||||||
class="wave-bar"
|
class="wave-bar"
|
||||||
:style="{ height: `${isPlaying(msg) ? Math.random() * 20 + 10 : 10}px` }"
|
:class="{'wave-bar-active': isPlaying(msg)}"
|
||||||
></view>
|
></view>
|
||||||
</view>
|
</view>
|
||||||
<text class="duration">{{ msg.duration }}''</text>
|
<text class="duration">{{ msg.duration }}''</text>
|
||||||
@@ -112,45 +112,52 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 加载指示器 -->
|
||||||
|
<view v-if="loading" class="loading-indicator">
|
||||||
|
<u-loading mode="circle" color="#298dff"></u-loading>
|
||||||
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|
||||||
<!-- 输入区域 -->
|
<!-- 输入区域 -->
|
||||||
<view class="chat-input">
|
<view class="chat-input">
|
||||||
<!-- 更多操作 - 放在输入框上方 -->
|
<!-- 工具按钮行 - 单独一行 -->
|
||||||
<view v-if="showMoreActions" class="more-actions">
|
<view class="toolbar-row">
|
||||||
<view class="action-item" @click="openCamera">
|
<!-- <view class="tool-btn" @click="toggleVoiceInput">-->
|
||||||
<u-icon name="camera" size="32" color="#298dff"></u-icon>
|
<!-- <u-icon :name="voiceInputActive ? 'mic-off' : 'mic'" size="24" color="#298dff"></u-icon>-->
|
||||||
<text>拍照</text>
|
<!-- <text>{{ voiceInputActive ? '键盘' : '语音' }}</text>-->
|
||||||
</view>
|
<!-- </view>-->
|
||||||
<view class="action-item" @click="openMediaPicker('image')">
|
<view class="tool-btn" @click="openMediaPicker('image')">
|
||||||
<u-icon name="photo" size="32" color="#298dff"></u-icon>
|
<u-icon name="photo" size="24" color="#298dff"></u-icon>
|
||||||
<text>图片</text>
|
<text>图片</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="action-item" @click="openMediaPicker('video')">
|
<view class="tool-btn" @click="openCamera">
|
||||||
<u-icon name="videocam" size="32" color="#298dff"></u-icon>
|
<u-icon name="camera" size="24" color="#298dff"></u-icon>
|
||||||
<text>视频</text>
|
<text>拍照</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="action-item" @click="sendFile">
|
<!-- <view class="tool-btn" @click="openMediaPicker('video')">-->
|
||||||
<u-icon name="file-text" size="32" color="#298dff"></u-icon>
|
<!-- <u-icon name="videocam" size="24" color="#298dff"></u-icon>-->
|
||||||
|
<!-- <text>视频</text>-->
|
||||||
|
<!-- </view>-->
|
||||||
|
<view class="tool-btn" @click="sendFile">
|
||||||
|
<u-icon name="file-text" size="24" color="#298dff"></u-icon>
|
||||||
<text>文件</text>
|
<text>文件</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="action-item" @click="sendPrescription">
|
<view class="tool-btn" @click="sendPrescription">
|
||||||
<u-icon name="file-medical" size="32" color="#298dff"></u-icon>
|
<u-icon name="file-medical" size="24" color="#298dff"></u-icon>
|
||||||
<text>处方</text>
|
<text>处方</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="input-area">
|
<!-- 输入框行 -->
|
||||||
<view class="action-buttons">
|
<view class="input-row">
|
||||||
<view class="action-btn" @click="toggleVoiceInput">
|
<!-- 更多操作按钮 -->
|
||||||
<u-icon :name="voiceInputActive ? 'mic-off' : 'mic'" size="24" color="#298dff"></u-icon>
|
<!-- <view class="action-btn" @click="showMoreActions = !showMoreActions">-->
|
||||||
</view>
|
<!-- <u-icon :name="showMoreActions ? 'close-circle' : 'plus-circle'" size="24" color="#298dff"></u-icon>-->
|
||||||
<view class="action-btn" @click="openMediaPicker('image')">
|
<!-- </view>-->
|
||||||
<u-icon name="photo" size="24" color="#298dff"></u-icon>
|
<view class="action-btn" @click="toggleVoiceInput">
|
||||||
</view>
|
<u-icon :name="voiceInputActive ? 'mic-off' : 'mic'" size="24" color="#298dff"></u-icon>
|
||||||
<view class="action-btn" @click="showMoreActions = !showMoreActions">
|
<!-- <text>{{ voiceInputActive ? '键盘' : '语音' }}</text>-->
|
||||||
<u-icon :name="showMoreActions ? 'close-circle' : 'plus-circle'" size="24" color="#298dff"></u-icon>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 文本输入 -->
|
<!-- 文本输入 -->
|
||||||
@@ -192,13 +199,19 @@
|
|||||||
<view v-if="showRecordingModal" class="recording-modal">
|
<view v-if="showRecordingModal" class="recording-modal">
|
||||||
<view class="recording-content">
|
<view class="recording-content">
|
||||||
<view class="recording-icon">
|
<view class="recording-icon">
|
||||||
<u-icon name="mic" size="50" color="#fff"></u-icon>
|
<u-icon name="mic" size="100" style="margin-top: 20rpx;" color="#fff"></u-icon>
|
||||||
<view class="pulse-animation"></view>
|
<view class="pulse-animation"></view>
|
||||||
</view>
|
</view>
|
||||||
<text class="recording-text">录音中... {{ recordingTime }}秒</text>
|
<text class="recording-text">录音中... {{ recordingTime }}秒</text>
|
||||||
<text class="recording-tip">上划取消发送</text>
|
<text class="recording-tip">上划取消发送</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 音频播放器组件 -->
|
||||||
|
<audio-player ref="audioPlayer"
|
||||||
|
@play="handleAudioPlay"
|
||||||
|
@pause="handleAudioPause"
|
||||||
|
@ended="handleAudioEnd" />
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -206,8 +219,12 @@
|
|||||||
import { webSocketManager } from '@/utils/ws/websocket';
|
import { webSocketManager } from '@/utils/ws/websocket';
|
||||||
import { ChatManager } from '@/store/chat/chat.js';
|
import { ChatManager } from '@/store/chat/chat.js';
|
||||||
import {sendToUserApi} from "@/request/api/im";
|
import {sendToUserApi} from "@/request/api/im";
|
||||||
|
import AudioPlayer from './components/AudioPlayer.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
AudioPlayer
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
doctorUserInfo: {
|
doctorUserInfo: {
|
||||||
@@ -217,11 +234,101 @@ export default {
|
|||||||
room_id: ''
|
room_id: ''
|
||||||
},
|
},
|
||||||
showDoctorDetail: false, // 控制医生详情展开
|
showDoctorDetail: false, // 控制医生详情展开
|
||||||
currentUserId: 'user-252',
|
currentUserId: '',
|
||||||
doctorId: 'doctor-1',
|
doctorId: 'doctor-1',
|
||||||
doctorAvatar: 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk_upload_20250108/29bdb9560340373bb3096394a845afa5.jpg',
|
doctorAvatar: 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk_upload_20250108/29bdb9560340373bb3096394a845afa5.jpg',
|
||||||
userAvatar: 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk_upload_20250108/29bdb9560340373bb3096394a845afa5.jpg',
|
userAvatar: 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk_upload_20250108/29bdb9560340373bb3096394a845afa5.jpg',
|
||||||
messages: [
|
messages: [],
|
||||||
|
newMessage: '',
|
||||||
|
voiceInputActive: false,
|
||||||
|
showMoreActions: false,
|
||||||
|
recording: false,
|
||||||
|
recordingTime: 0,
|
||||||
|
recordingTimer: null,
|
||||||
|
showRecordingModal: false,
|
||||||
|
currentPlayingAudio: null,
|
||||||
|
scrollTop: 0,
|
||||||
|
page: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
hasMore: true,
|
||||||
|
loading: false, // 新增加载状态
|
||||||
|
};
|
||||||
|
},
|
||||||
|
onLoad: function (option) {
|
||||||
|
this.doctorUserInfo = option;
|
||||||
|
if (option.avatar) {
|
||||||
|
this.doctorAvatar = option.avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentUserId = 'user-' + uni.getStorageSync('user_id') || 'user-2512';
|
||||||
|
|
||||||
|
this.doctorId = option.user_id;
|
||||||
|
|
||||||
|
// 进入聊天室时缓存房间ID
|
||||||
|
ChatManager.enterRoom(option.room_id);
|
||||||
|
|
||||||
|
// 重置当前房间的未读消息
|
||||||
|
ChatManager.resetUnreadCount(option.room_id);
|
||||||
|
|
||||||
|
// 从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('修改标题结束');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设置滚动到底部
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.scrollToBottom();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onUnload() {
|
||||||
|
// 离开聊天室时清除房间缓存
|
||||||
|
ChatManager.leaveRoom();
|
||||||
|
// 停止音频播放
|
||||||
|
if (this.$refs.audioPlayer) {
|
||||||
|
this.$refs.audioPlayer.stop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onShow() {
|
||||||
|
// 监听当前房间的新消息事件
|
||||||
|
uni.$on('new-room-message', this.handleNewRoomMessage);
|
||||||
|
},
|
||||||
|
onHide() {
|
||||||
|
// 移除消息监听
|
||||||
|
uni.$off('new-room-message', this.handleNewRoomMessage);
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// 初始化录音管理器
|
||||||
|
this.recorderManager = uni.getRecorderManager();
|
||||||
|
this.initRecorder();
|
||||||
|
|
||||||
|
// 监听WebSocket消息
|
||||||
|
webSocketManager.addMessageHandler(this.handleSocketMessage);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// 移除WebSocket监听
|
||||||
|
webSocketManager.removeMessageHandler(this.handleSocketMessage);
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
// 添加示例消息
|
||||||
|
addExampleMessages() {
|
||||||
|
const exampleMessages = [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
sender: 'doctor-1',
|
sender: 'doctor-1',
|
||||||
@@ -266,7 +373,7 @@ export default {
|
|||||||
id: '6',
|
id: '6',
|
||||||
sender: 'user-252',
|
sender: 'user-252',
|
||||||
type: 'audio',
|
type: 'audio',
|
||||||
content: 'https://lv-sycdn.kuwo.cn/35581c6b5aa0e579312193399190e51a/687a023d/resource/30106/trackmedia/M800001zMQr71F1Qo8.mp3',
|
content: 'https://er-sycdn.kuwo.cn/f7c3be4ff67cc7b135e38e8365479e06/687d941e/resource/30106/trackmedia/M800004emQMs09Z1lz.mp3',
|
||||||
duration: 15,
|
duration: 15,
|
||||||
time: '09:36',
|
time: '09:36',
|
||||||
timestamp: Date.now() - 3240000
|
timestamp: Date.now() - 3240000
|
||||||
@@ -275,107 +382,46 @@ export default {
|
|||||||
id: '7',
|
id: '7',
|
||||||
sender: 'doctor-1',
|
sender: 'doctor-1',
|
||||||
type: 'audio',
|
type: 'audio',
|
||||||
content: 'https://lv-sycdn.kuwo.cn/35581c6b5aa0e579312193399190e51a/687a023d/resource/30106/trackmedia/M800001zMQr71F1Qo8.mp3',
|
content: 'https://er-sycdn.kuwo.cn/f7c3be4ff67cc7b135e38e8365479e06/687d941e/resource/30106/trackmedia/M800004emQMs09Z1lz.mp3',
|
||||||
duration: 8,
|
duration: 8,
|
||||||
time: '09:37',
|
time: '09:37',
|
||||||
timestamp: Date.now() - 3180000
|
timestamp: Date.now() - 3180000
|
||||||
}
|
}
|
||||||
],
|
];
|
||||||
newMessage: '',
|
|
||||||
voiceInputActive: false,
|
|
||||||
showMoreActions: false,
|
|
||||||
recording: false,
|
|
||||||
recordingTime: 0,
|
|
||||||
recordingTimer: null,
|
|
||||||
showRecordingModal: false,
|
|
||||||
audioContexts: {},
|
|
||||||
currentPlayingAudio: null,
|
|
||||||
scrollTop: 0,
|
|
||||||
scrollToBottomFlag: true,
|
|
||||||
page: 1,
|
|
||||||
pageSize: 20,
|
|
||||||
hasMore: true,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
onLoad: function (option) {
|
|
||||||
this.doctorUserInfo = option;
|
|
||||||
if (option.avatar) {
|
|
||||||
this.doctorAvatar = option.avatar;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 进入聊天室时缓存房间ID
|
// 添加到ChatManager和本地消息列表
|
||||||
ChatManager.enterRoom(option.room_id);
|
exampleMessages.forEach(msg => {
|
||||||
|
ChatManager.addMessageToRoom(this.doctorUserInfo.room_id, msg);
|
||||||
// 重置当前房间的未读消息
|
});
|
||||||
ChatManager.resetUnreadCount(option.room_id);
|
this.messages = [...exampleMessages];
|
||||||
},
|
|
||||||
onUnload() {
|
|
||||||
// 离开聊天室时清除房间缓存
|
|
||||||
ChatManager.leaveRoom();
|
|
||||||
},
|
|
||||||
onShow() {
|
|
||||||
// 监听新消息事件
|
|
||||||
uni.$on('new-message', this.handleNewMessage);
|
|
||||||
},
|
|
||||||
onHide() {
|
|
||||||
// 移除消息监听
|
|
||||||
uni.$off('new-message', this.handleNewMessage);
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
// 初始化录音管理器
|
|
||||||
this.recorderManager = uni.getRecorderManager();
|
|
||||||
this.initRecorder();
|
|
||||||
|
|
||||||
// 设置滚动到底部
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.scrollToBottom();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 监听WebSocket消息
|
|
||||||
webSocketManager.addMessageHandler(this.handleSocketMessage);
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
// 移除WebSocket监听
|
|
||||||
webSocketManager.removeMessageHandler(this.handleSocketMessage);
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
// 处理WebSocket消息
|
|
||||||
handleSocketMessage(data) {
|
|
||||||
// 只处理当前房间的消息
|
|
||||||
if (data.room_id === this.doctorUserInfo.room_id) {
|
|
||||||
// 处理不同类型的消息
|
|
||||||
switch (data.message_type) {
|
|
||||||
case 0: // 文本
|
|
||||||
this.handleTextMessage(data);
|
|
||||||
break;
|
|
||||||
case 1: // 图片
|
|
||||||
this.handleImageMessage(data);
|
|
||||||
break;
|
|
||||||
case 2: // 音频
|
|
||||||
this.handleAudioMessage(data);
|
|
||||||
break;
|
|
||||||
case 3: // 视频
|
|
||||||
this.handleVideoMessage(data);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.warn('未知消息类型:', data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 处理新消息事件
|
// 处理WebSocket消息
|
||||||
handleNewMessage(message) {
|
handleSocketMessage(data) {
|
||||||
if (message.room_id === this.doctorUserInfo.room_id) {
|
// 将消息交给ChatManager统一处理
|
||||||
this.addMessage({
|
ChatManager.handleIncomingMessage(data);
|
||||||
id: message.id,
|
},
|
||||||
sender: message.sender_user_id,
|
|
||||||
type: this.getMessageType(message.message_type),
|
// 处理当前房间的新消息事件
|
||||||
content: message.message_content,
|
handleNewRoomMessage(message) {
|
||||||
time: this.formatTime(message.timestamp),
|
// 处理不同类型的消息
|
||||||
timestamp: message.timestamp
|
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获取类型名称
|
// 根据消息类型ID获取类型名称
|
||||||
@@ -391,56 +437,6 @@ export default {
|
|||||||
return types[typeId] || 'text';
|
return types[typeId] || 'text';
|
||||||
},
|
},
|
||||||
|
|
||||||
// 处理文本消息
|
|
||||||
handleTextMessage(data) {
|
|
||||||
this.addMessage({
|
|
||||||
id: data.id,
|
|
||||||
sender: data.sender_user_id,
|
|
||||||
type: 'text',
|
|
||||||
content: data.message_content,
|
|
||||||
time: this.formatTime(data.timestamp),
|
|
||||||
timestamp: data.timestamp
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// 处理图片消息
|
|
||||||
handleImageMessage(data) {
|
|
||||||
this.addMessage({
|
|
||||||
id: data.id,
|
|
||||||
sender: data.sender_user_id,
|
|
||||||
type: 'image',
|
|
||||||
content: data.file_url,
|
|
||||||
time: this.formatTime(data.timestamp),
|
|
||||||
timestamp: data.timestamp
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// 处理音频消息
|
|
||||||
handleAudioMessage(data) {
|
|
||||||
this.addMessage({
|
|
||||||
id: data.id,
|
|
||||||
sender: data.sender_user_id,
|
|
||||||
type: 'audio',
|
|
||||||
content: data.file_url,
|
|
||||||
duration: data.duration,
|
|
||||||
time: this.formatTime(data.timestamp),
|
|
||||||
timestamp: data.timestamp
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// 处理视频消息
|
|
||||||
handleVideoMessage(data) {
|
|
||||||
this.addMessage({
|
|
||||||
id: data.id,
|
|
||||||
sender: data.sender_user_id,
|
|
||||||
type: 'video',
|
|
||||||
content: data.file_url,
|
|
||||||
duration: data.duration,
|
|
||||||
time: this.formatTime(data.timestamp),
|
|
||||||
timestamp: data.timestamp
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// 添加消息到列表
|
// 添加消息到列表
|
||||||
addMessage(message) {
|
addMessage(message) {
|
||||||
// 避免重复添加
|
// 避免重复添加
|
||||||
@@ -452,7 +448,30 @@ export default {
|
|||||||
|
|
||||||
// 格式化时间
|
// 格式化时间
|
||||||
formatTime(timestamp) {
|
formatTime(timestamp) {
|
||||||
const date = new Date(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')}`;
|
return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -474,7 +493,7 @@ export default {
|
|||||||
|
|
||||||
this.sendMessage({
|
this.sendMessage({
|
||||||
type: 'audio',
|
type: 'audio',
|
||||||
content: 'https://lv-sycdn.kuwo.cn/35581c6b5aa0e579312193399190e51a/687a023d/resource/30106/trackmedia/M800001zMQr71F1Qo8.mp3',
|
content: 'https://er-sycdn.kuwo.cn/f7c3be4ff67cc7b135e38e8365479e06/687d941e/resource/30106/trackmedia/M800004emQMs09Z1lz.mp3',
|
||||||
duration: Math.floor(res.duration / 1000),
|
duration: Math.floor(res.duration / 1000),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -507,10 +526,9 @@ export default {
|
|||||||
time: timeStr,
|
time: timeStr,
|
||||||
timestamp: now.getTime()
|
timestamp: now.getTime()
|
||||||
};
|
};
|
||||||
//
|
|
||||||
// // 添加到本地消息列表
|
// 添加到ChatManager
|
||||||
// this.messages.push(fullMessage);
|
ChatManager.addMessageToRoom(this.doctorUserInfo.room_id, fullMessage);
|
||||||
// this.scrollToBottom();
|
|
||||||
|
|
||||||
// 构建请求数据
|
// 构建请求数据
|
||||||
const messageTypeMap = {
|
const messageTypeMap = {
|
||||||
@@ -531,7 +549,7 @@ export default {
|
|||||||
isFromHttp: true,
|
isFromHttp: true,
|
||||||
duration: message.duration || 0
|
duration: message.duration || 0
|
||||||
};
|
};
|
||||||
console.log('发送数据:', requestData)
|
|
||||||
sendToUserApi(requestData).then(res => {
|
sendToUserApi(requestData).then(res => {
|
||||||
console.log('发送成功:', res);
|
console.log('发送成功:', res);
|
||||||
// 添加到本地消息列表
|
// 添加到本地消息列表
|
||||||
@@ -563,7 +581,8 @@ export default {
|
|||||||
success: () => {
|
success: () => {
|
||||||
this.startRecording();
|
this.startRecording();
|
||||||
},
|
},
|
||||||
fail: () => {
|
fail: (e) => {
|
||||||
|
console.log('失败', e)
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '需要录音权限',
|
title: '需要录音权限',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
@@ -598,62 +617,31 @@ export default {
|
|||||||
this.recorderManager.stop();
|
this.recorderManager.stop();
|
||||||
},
|
},
|
||||||
|
|
||||||
// 播放音频 - 修复后的逻辑
|
// 播放音频
|
||||||
playAudio(msg) {
|
playAudio(msg) {
|
||||||
if (!msg.content) return;
|
if (this.$refs.audioPlayer) {
|
||||||
|
this.$refs.audioPlayer.play({
|
||||||
// 如果当前正在播放的是同一个音频
|
id: msg.id,
|
||||||
if (this.currentPlayingAudio === msg.id) {
|
src: msg.content
|
||||||
const audioContext = this.audioContexts[msg.id];
|
});
|
||||||
if (audioContext.paused) {
|
|
||||||
audioContext.play();
|
|
||||||
} else {
|
|
||||||
audioContext.pause();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 停止当前播放的音频
|
|
||||||
if (this.currentPlayingAudio) {
|
|
||||||
const prevAudio = this.audioContexts[this.currentPlayingAudio];
|
|
||||||
prevAudio.stop();
|
|
||||||
prevAudio.destroy();
|
|
||||||
delete this.audioContexts[this.currentPlayingAudio];
|
|
||||||
this.currentPlayingAudio = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建新的音频上下文
|
|
||||||
const audioContext = uni.createInnerAudioContext();
|
|
||||||
audioContext.src = msg.content;
|
|
||||||
audioContext.autoplay = true;
|
|
||||||
|
|
||||||
audioContext.onPlay(() => {
|
|
||||||
this.currentPlayingAudio = msg.id;
|
|
||||||
});
|
|
||||||
|
|
||||||
audioContext.onPause(() => {
|
|
||||||
// 暂停时不改变currentPlayingAudio,以便继续播放
|
|
||||||
});
|
|
||||||
|
|
||||||
audioContext.onEnded(() => {
|
|
||||||
this.currentPlayingAudio = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
audioContext.onError((err) => {
|
|
||||||
console.error('音频播放失败:', err);
|
|
||||||
uni.showToast({ title: '播放失败', icon: 'none' });
|
|
||||||
this.currentPlayingAudio = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.audioContexts[msg.id] = audioContext;
|
|
||||||
this.currentPlayingAudio = msg.id;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 检查是否正在播放
|
// 检查是否正在播放
|
||||||
isPlaying(msg) {
|
isPlaying(msg) {
|
||||||
return this.currentPlayingAudio === msg.id &&
|
return this.$refs.audioPlayer && this.$refs.audioPlayer.isPlaying(msg.id);
|
||||||
this.audioContexts[msg.id] &&
|
},
|
||||||
!this.audioContexts[msg.id].paused;
|
|
||||||
|
// 音频播放事件
|
||||||
|
handleAudioPlay(audioId) {
|
||||||
|
// 更新UI
|
||||||
|
this.currentPlayingAudio = audioId;
|
||||||
|
},
|
||||||
|
handleAudioPause() {
|
||||||
|
this.currentPlayingAudio = null;
|
||||||
|
},
|
||||||
|
handleAudioEnd() {
|
||||||
|
this.currentPlayingAudio = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
// 打开媒体选择器
|
// 打开媒体选择器
|
||||||
@@ -691,7 +679,6 @@ export default {
|
|||||||
|
|
||||||
// 打开相机拍照
|
// 打开相机拍照
|
||||||
openCamera() {
|
openCamera() {
|
||||||
this.showMoreActions = false;
|
|
||||||
uni.chooseImage({
|
uni.chooseImage({
|
||||||
count: 1,
|
count: 1,
|
||||||
sourceType: ['camera'],
|
sourceType: ['camera'],
|
||||||
@@ -706,7 +693,6 @@ export default {
|
|||||||
|
|
||||||
// 发送文件
|
// 发送文件
|
||||||
sendFile() {
|
sendFile() {
|
||||||
this.showMoreActions = false;
|
|
||||||
uni.chooseMessageFile({
|
uni.chooseMessageFile({
|
||||||
count: 1,
|
count: 1,
|
||||||
type: 'all',
|
type: 'all',
|
||||||
@@ -724,7 +710,6 @@ export default {
|
|||||||
|
|
||||||
// 发送处方
|
// 发送处方
|
||||||
sendPrescription() {
|
sendPrescription() {
|
||||||
this.showMoreActions = false;
|
|
||||||
// 模拟处方数据
|
// 模拟处方数据
|
||||||
const prescription = {
|
const prescription = {
|
||||||
id: 'RX20230718001',
|
id: 'RX20230718001',
|
||||||
@@ -760,7 +745,6 @@ export default {
|
|||||||
// 切换语音输入
|
// 切换语音输入
|
||||||
toggleVoiceInput() {
|
toggleVoiceInput() {
|
||||||
this.voiceInputActive = !this.voiceInputActive;
|
this.voiceInputActive = !this.voiceInputActive;
|
||||||
this.showMoreActions = false;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 滚动到底部
|
// 滚动到底部
|
||||||
@@ -771,33 +755,40 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 加载历史消息
|
// 加载历史消息
|
||||||
loadHistory() {
|
async loadHistory() {
|
||||||
if (!this.hasMore) return;
|
if (!this.hasMore || this.loading) return;
|
||||||
|
|
||||||
// 模拟加载历史消息
|
this.loading = true;
|
||||||
setTimeout(() => {
|
try {
|
||||||
const newMessages = [
|
// 模拟加载历史消息
|
||||||
{
|
const newMessages = await new Promise(resolve => {
|
||||||
id: 'h1',
|
setTimeout(() => {
|
||||||
sender: 'doctor-1',
|
resolve([
|
||||||
type: 'text',
|
{
|
||||||
content: '这是更早的消息',
|
id: 'h1',
|
||||||
time: '09:20',
|
sender: 'doctor-1',
|
||||||
timestamp: Date.now() - 7200000
|
type: 'text',
|
||||||
},
|
content: '这是更早的消息',
|
||||||
{
|
time: '09:20',
|
||||||
id: 'h2',
|
timestamp: Date.now() - 7200000
|
||||||
sender: 'user-252',
|
},
|
||||||
type: 'text',
|
{
|
||||||
content: '是的,这是历史消息',
|
id: 'h2',
|
||||||
time: '09:21',
|
sender: 'user-252',
|
||||||
timestamp: Date.now() - 7140000
|
type: 'text',
|
||||||
}
|
content: '是的,这是历史消息',
|
||||||
];
|
time: '09:21',
|
||||||
|
timestamp: Date.now() - 7140000
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
|
||||||
this.messages = [...newMessages, ...this.messages];
|
this.messages = [...newMessages, ...this.messages];
|
||||||
this.hasMore = false; // 假设没有更多历史消息
|
this.hasMore = false; // 假设没有更多历史消息
|
||||||
}, 500);
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -903,7 +894,7 @@ $light-bg: #f5f9ff;
|
|||||||
.chat-messages {
|
.chat-messages {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
padding-bottom: 80px;
|
padding-bottom: 120px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: #f5f9ff;
|
background-color: #f5f9ff;
|
||||||
}
|
}
|
||||||
@@ -1050,13 +1041,19 @@ $light-bg: #f5f9ff;
|
|||||||
background-color: #a0c8ff;
|
background-color: #a0c8ff;
|
||||||
margin: 0 2px;
|
margin: 0 2px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
transition: all 0.3s ease;
|
height: 10px;
|
||||||
|
transition: height 0.3s ease;
|
||||||
|
|
||||||
&:nth-child(1) { height: 14px; animation: wave 1s infinite 0.1s; }
|
&.wave-bar-active {
|
||||||
&:nth-child(2) { height: 18px; animation: wave 1s infinite 0.2s; }
|
animation: wave 1s infinite;
|
||||||
&:nth-child(3) { height: 22px; animation: wave 1s infinite 0.3s; }
|
height: 24px;
|
||||||
&:nth-child(4) { height: 18px; animation: wave 1s infinite 0.4s; }
|
|
||||||
&:nth-child(5) { height: 14px; animation: wave 1s infinite 0.5s; }
|
&:nth-child(1) { animation-delay: 0.1s; }
|
||||||
|
&:nth-child(2) { animation-delay: 0.2s; }
|
||||||
|
&:nth-child(3) { animation-delay: 0.3s; }
|
||||||
|
&:nth-child(4) { animation-delay: 0.4s; }
|
||||||
|
&:nth-child(5) { animation-delay: 0.5s; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1068,7 +1065,7 @@ $light-bg: #f5f9ff;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes wave {
|
@keyframes wave {
|
||||||
0%, 100% { height: 14px; }
|
0%, 100% { height: 10px; }
|
||||||
50% { height: 24px; }
|
50% { height: 24px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1082,26 +1079,51 @@ $light-bg: #f5f9ff;
|
|||||||
.chat-input {
|
.chat-input {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-top: 1px solid #eaeef5;
|
border-top: 1px solid #eaeef5;
|
||||||
padding: 16px;
|
padding: 10px 16px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 100;
|
z-index: 1000;
|
||||||
box-shadow: 0 -4px 12px rgba(41, 141, 255, 0.1);
|
box-shadow: 0 -4px 12px rgba(41, 141, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-area {
|
/* 工具按钮行 */
|
||||||
|
.toolbar-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
padding: 8px 0;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f7ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-btn {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: #f0f7ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #555;
|
||||||
|
margin-top: 4px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 输入框行 */
|
||||||
|
.input-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-buttons {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
width: 46px;
|
width: 46px;
|
||||||
height: 46px;
|
height: 46px;
|
||||||
@@ -1193,38 +1215,6 @@ $light-bg: #f5f9ff;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.more-actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-around;
|
|
||||||
padding: 20px 0;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 16px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 12px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
width: 20%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background-color: #f0f7ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
text {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #555;
|
|
||||||
margin-top: 6px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.recording-modal {
|
.recording-modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -1243,7 +1233,7 @@ $light-bg: #f5f9ff;
|
|||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 280px;
|
width: 280px;
|
||||||
box-shadow: 0 10px 30px rgba(41, 141, 255, 0.4);
|
//box-shadow: 0 10px 30px rgba(41, 141, 255, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.recording-icon {
|
.recording-icon {
|
||||||
@@ -1290,4 +1280,11 @@ $light-bg: #f5f9ff;
|
|||||||
50% { transform: scale(1.1); opacity: 1; }
|
50% { transform: scale(1.1); opacity: 1; }
|
||||||
100% { transform: scale(0.9); opacity: 0.7; }
|
100% { transform: scale(0.9); opacity: 0.7; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 加载指示器 */
|
||||||
|
.loading-indicator {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
84
subPackages/chat/components/AudioPlayer.vue
Normal file
84
subPackages/chat/components/AudioPlayer.vue
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<!-- /components/AudioPlayer.vue -->
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentAudio: null, // 当前播放的音频ID
|
||||||
|
audioContext: null, // 微信音频上下文
|
||||||
|
playing: false // 是否正在播放
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 播放音频
|
||||||
|
play({ id, src }) {
|
||||||
|
// 如果当前正在播放同一音频,则暂停
|
||||||
|
if (this.currentAudio === id && this.playing) {
|
||||||
|
this.pause();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果当前播放的是其他音频,先停止
|
||||||
|
if (this.currentAudio && this.currentAudio !== id) {
|
||||||
|
this.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新的音频上下文
|
||||||
|
this.audioContext = wx.createInnerAudioContext();
|
||||||
|
this.audioContext.src = src;
|
||||||
|
this.audioContext.play();
|
||||||
|
|
||||||
|
this.currentAudio = id;
|
||||||
|
this.playing = true;
|
||||||
|
this.$emit('play', id);
|
||||||
|
|
||||||
|
// 监听音频结束事件
|
||||||
|
this.audioContext.onEnded(() => {
|
||||||
|
this.playing = false;
|
||||||
|
this.currentAudio = null;
|
||||||
|
this.$emit('ended');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听音频错误事件
|
||||||
|
this.audioContext.onError((err) => {
|
||||||
|
console.error('音频播放失败:', err);
|
||||||
|
this.playing = false;
|
||||||
|
this.currentAudio = null;
|
||||||
|
this.$emit('error', err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 暂停音频
|
||||||
|
pause() {
|
||||||
|
if (this.audioContext && this.playing) {
|
||||||
|
this.audioContext.pause();
|
||||||
|
this.playing = false;
|
||||||
|
this.$emit('pause');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 停止音频
|
||||||
|
stop() {
|
||||||
|
if (this.audioContext) {
|
||||||
|
this.audioContext.stop();
|
||||||
|
this.audioContext.destroy();
|
||||||
|
this.audioContext = null;
|
||||||
|
this.playing = false;
|
||||||
|
this.currentAudio = null;
|
||||||
|
this.$emit('ended');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 检查指定音频是否正在播放
|
||||||
|
isPlaying(audioId) {
|
||||||
|
return this.currentAudio === audioId && this.playing;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// 组件销毁时停止音频
|
||||||
|
if (this.audioContext) {
|
||||||
|
this.audioContext.stop();
|
||||||
|
this.audioContext.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user