commit: 二期,语音聊天功能(bs端实现)

This commit is contained in:
republicline 2024-11-15 09:38:17 +08:00
parent 3480ee2af7
commit 121ae430cb
12 changed files with 8154 additions and 17 deletions

7876
src/assets/font/all.css Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,6 @@
import {createApp} from 'vue'
import main from './main.vue'
import router from './router'
import {createPinia} from 'pinia'
@ -7,7 +8,7 @@ import ElementPlus, {ElDialog} from 'element-plus';
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import TableAbility from '@/components/table-ability.vue'
import './assets/font/all.css'
import 'element-plus/dist/index.css';
import './assets/css/global.scss';
import './assets/font/iconfont.css';

View File

@ -204,7 +204,7 @@ export const useRemoteWsStore = defineStore("remoteWs", {
patient.chatWS.onmessage = (e: any) => {
if (e && e.data) {
const data = JSON.parse(e.data);
if (data.msgType == "msg") {
if (data.msgType == "msg" || data.msgType == "audio") {
cb(e)
} else {
patient.chatWS.send(JSON.stringify({msgType: "heartbeat"}))
@ -268,6 +268,28 @@ export const useRemoteWsStore = defineStore("remoteWs", {
})
}
},
// 发送语音消息
sendAudio(name: string, id: string, date: string, audio: any, index: number, cb: any) {
const patient: any = this.patient[name + id + date + index]
if (patient) {
const params = {
patientName: name,
idNum: id,
date: date,
content: audio,
msgType: "audio"
}
patient.chatWS.send(JSON.stringify(params))
cb({
status: 0
})
} else {
cb({
status: 1,
msg: "已断开连接"
})
}
},
disconnectChat(name: string, id: string, date: string, index: number) {
const patient: any = this.patient[name + id + date + index]
if (patient && patient.chatWS) {

View File

@ -66,6 +66,7 @@
</ul>
</div>
<div class="right-box">
<!--带视频组件的聊天框-->
<!--<div class="video-box" @click="playPause">-->
<!-- <div class="icon-box">-->
<!-- <el-icon v-if="isVideoPlay">-->
@ -80,16 +81,40 @@
<!-- <source src="@/assets/medical.mp4" type="video/mp4"/>-->
<!-- </video>-->
<!--</div>-->
<!--<div class="message-box">-->
<!-- <ul ref="msgLog" class="message-log">-->
<!-- <li v-for="(item, index) in mssageList" :key="'msg-log-' + index"-->
<!-- :class="{ 'align-right': item.createUser == userInfo.account }">-->
<!-- <span>{{ item.content }}</span>-->
<!-- </li>-->
<!-- </ul>-->
<!-- <div class="send-box">-->
<!-- <el-input v-model="msgVal" placeholder="请输入消息"/>-->
<!-- <el-button color="#006080" @click="sendMsg">发送消息</el-button>-->
<!-- </div>-->
<!--</div>-->
<!-- 聊天框, 添加音频组件 -->
<div class="message-box">
<ul ref="msgLog" class="message-log">
<li v-for="(item, index) in mssageList" :key="'msg-log-' + index"
:class="{ 'align-right': item.createUser == userInfo.userInfo.username }">
<span>{{ item.content }}</span>
:class="{ 'align-right': item.createUser == userInfo.account }">
<span v-if="item.msgType === 'msg'">{{ item.content }}</span>
<audio v-if="item.msgType === 'audio'" controls>
21312
<source :src="item.content" type="audio/mpeg"/>
您的浏览器不支持音频元素
</audio>
</li>
</ul>
<div class="send-box">
<el-input v-model="msgVal" placeholder="请输入消息"/>
<el-button color="#006080" @click="sendMsg">发送消息</el-button>
<el-input style="width: 60%" v-model="msgVal" placeholder="请输入消息"/>
<el-button style="color: #006080; width: 20%; margin-left: 10px;" @click="sendMsg">发送消息</el-button>
<el-button style="color: #006080; width: 20%" class="mic-btn" @mousedown="startRecording" @mouseup="stopRecording"
@mouseleave="stopRecording">录音</el-button>
</div>
<div v-if="isRecording" class="mic-icon">
<i class="fa-solid fa-microphone"></i> <!-- 麦克风图标 -->
正在录音... {{ remainingTime }} 秒剩余
</div>
</div>
</div>
@ -235,8 +260,9 @@ const medicineCustom: any[] = [
]
const remoteWsStore = useRemoteWsStore()
const currentRemote = ref(remoteWsStore.getRemoteTask()[remoteWsStore.getCurrentTaskIndex()])
const userInfo = useUserStore()
const userInfoStore = useUserStore()
const userInfo = ref(userInfoStore.getlogin())
const chartDom1 = ref(),
chartDom2 = ref(),
@ -250,18 +276,14 @@ const chartDom1 = ref(),
const isPatientDialog = ref(false)
const database = ref('')
const databaseOptions = ref([] as { value: string, label: string }[])
const messageSum = ref(10)
const userName = ref(userInfo.userInfo.name)
const setDatabaseDialog = ref(false);
const featureTable = ref([] as any[]);
let chartNowData = reactive({ID: 0});
const lungAlarm = ref(false); //
const heartAlarm = ref(false); //
const isAIDose = ref(0); // AI
const isVideoPlay = ref(false); //
const videoSrc = ref('https://www.runoob.com/try/demo_source/mov_bbb.mp4');
const mssageList = ref([] as any);
const msgVal = ref('');
// const videoSrc = ref('https://www.runoob.com/try/demo_source/mov_bbb.mp4');
const unusual = ref([] as any);
const fixedTableData = ref([] as any[]);
const varTableData = ref([] as any[]);
@ -419,7 +441,7 @@ const subscribeVital = () => {
chartDom3.value.updateChartData(data.vitalSignsList[0]);
chartDom4.value.updateChartData(data.vitalSignsList[0]);
isAIDose.value = data.flags.aiFlag === '1' ? 1 : 0;
console.log('data >>>>>', data);
// console.log('data >>>>>', data);
if (!data.rateModTime) {
updateMedicineTable(data.medicineList);
return
@ -718,6 +740,123 @@ function startAI() {
});
}
/*
* 聊天室
*/
const msgVal = ref('');
const mssageList = ref([] as any);
const isRecording = ref(false); //
const mediaRecorder = ref<MediaRecorder | null>(null);
const audioChunks = ref<Blob[]>([]);
const remainingTime = ref(10); // 10
// Blob Base64
const convertBlobToBase64 = (blob: Blob): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader(); // FileReader
// onloadend
reader.onloadend = () => {
// result null
if (reader.result) {
// const base64String = (reader.result as string).split(',')[1]; // Base64
const base64String = (reader.result as string); // Base64
resolve(base64String); // Base64
} else {
reject(new Error("读取 Base64 失败")); //
}
};
//
reader.onerror = (error) => {
reject(error); //
};
// Blob URL
reader.readAsDataURL(blob);
});
};
//
const startRecording = async () => {
if (isRecording.value) return; //
isRecording.value = true; //
remainingTime.value = 10; // 10
const stream = await navigator.mediaDevices.getUserMedia({audio: true});
mediaRecorder.value = new MediaRecorder(stream);
//
mediaRecorder.value.ondataavailable = (event) => {
console.log("录音中...");
audioChunks.value.push(event.data);
console.log("当前音频数据块:", event.data); //
};
//
mediaRecorder.value.onstop = async () => {
isRecording.value = false; //
if (audioChunks.value.length === 0) {
console.error("没有音频数据可用于创建 Blob");
return;
}
// Blob
const audioBlob = new Blob(audioChunks.value, {type: 'audio/webm; codecs=opus'});
try {
// Base64
const base64Audio = await convertBlobToBase64(audioBlob);
console.log("转换后的 Base64 字符串:", base64Audio);
// WebSocket
const index = remoteWsStore.getCurrentTaskIndex();
//
remoteWsStore.sendAudio(
currentRemote.value.patient,
currentRemote.value.patientId,
currentRemote.value.date,
base64Audio.replace(/\s+/g, ''), //
index,
function (res: any) {
if (res.code == 1) {
ElMessage.error(res.msg);
}
}
);
} catch (error) {
console.error("转换为 Base64 失败:", error);
}
audioChunks.value = []; // :
};
//
mediaRecorder.value.start();
console.log("录音已开始"); //
// 10
const timer = setInterval(() => {
if (remainingTime.value > 0) {
remainingTime.value--; // 1
} else {
stopRecording(); // 0
console.log("录音时间到,已自动停止!");
clearInterval(timer); //
}
}, 1000); //
};
//
const stopRecording = () => {
if (mediaRecorder.value) {
mediaRecorder.value.stop();
isRecording.value = false; //
}
};
</script>
<style lang="scss" scoped>
@ -1007,34 +1146,107 @@ function startAI() {
}
}
// v2msg-box
//.message-box {
// width: 100%;
// // height: 270px;
// height: 100%;
// // margin-bottom: 5px;
//
// .message-log {
// width: 100%;
// height: calc(100% - 40px);
// max-height: 109px;
// padding: 16px 20px;
// box-sizing: border-box;
// border: 1px solid #c8c8c8;
// background: #f8f8f8;
// overflow-y: auto;
//
// li {
// width: 100%;
// font-size: 14px;
// line-height: 1.6;
// margin: 5px 0;
//
// &.align-right {
// text-align: right;
//
// span {
// background: $main-color;
// }
// }
//
// span {
// display: inline-block;
// max-width: 80%;
// padding: 6px 8px;
// box-sizing: border-box;
// border-radius: 8px;
// color: white;
// letter-spacing: 1px;
// background: #92b3c1;
// text-align: left;
// }
// }
// }
//
// .send-box {
// width: 100%;
// height: 40px;
// display: flex;
// justify-content: space-between;
// align-items: flex-end;
//
// .el-input {
// width: calc(100% - 110px);
// height: 32px;
//
// :deep(.el-input__wrapper) {
// background-color: #F2F3F5;
// border-color: #C1C1C1;
// }
// }
//
// .el-button {
// padding: 0;
// width: 100px;
// line-height: 30px;
// }
// }
//}
.message-box {
width: 100%;
// height: 270px;
height: 300px;
height: 100%;
// margin-bottom: 5px;
.message-log {
width: 100%;
height: calc(100% - 40px);
max-height: 109px;
height: 100%;
max-height: calc(100% - 35px);
padding: 16px 20px;
box-sizing: border-box;
border: 1px solid #c8c8c8;
background: #f8f8f8;
overflow-y: auto;
li {
width: 100%;
font-size: 14px;
line-height: 1.6;
margin: 5px 0;
&.align-right {
text-align: right;
span {
background: $main-color;
}
}
span {
@ -1045,6 +1257,7 @@ function startAI() {
border-radius: 8px;
color: white;
letter-spacing: 1px;
background: $main-color;
background: #92b3c1;
text-align: left;
}
@ -1123,4 +1336,29 @@ function startAI() {
}
}
//
.send-box {
position: relative; /* 设置相对定位用于绝对定位子元素的参考 */
}
.mic-icon {
position: fixed; /* 使用固定定位 */
bottom: 20px; /* 距离底部20px */
left: 50%; /* 水平居中 */
transform: translateX(-50%); /* 使图标真正居中 */
background-color: rgba(255, 255, 255, 0.9); /* 背景颜色 */
border-radius: 5px; /* 圆角 */
padding: 10px; /* 内边距 */
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); /* 阴影效果 */
z-index: 1000; /* 确保图标在其他元素之上 */
display: flex; /* 使用 flexbox 布局 */
align-items: center; /* 垂直居中 */
}
.mic-icon .fa-microphone {
color: red; /* 麦克风图标颜色 */
font-size: 80px; /* 调整图标大小 */
margin-right: 5px; /* 图标与文本间的间距 */
}
</style>