|
|
|
|
@ -48,8 +48,10 @@ |
|
|
|
|
<!-- 画笔标注画布 --> |
|
|
|
|
<canvas v-show="isDrawingMode" ref="drawingCanvas" class="drawing-canvas" @mousedown="startDrawing" |
|
|
|
|
@mousemove="draw" @mouseup="stopDrawing" @mouseleave="stopDrawing"></canvas> |
|
|
|
|
<!-- 共享/采集卡画面 --> |
|
|
|
|
<video v-if="isSharing" ref="shareVideo" class="share-preview" autoplay playsinline :muted="false"></video> |
|
|
|
|
<!-- 共享/采集卡/本地摄像头画面 --> |
|
|
|
|
<video v-if="isSharing && (!sharedStreamId || sharedStreamId === 'local')" ref="shareVideo" class="share-preview" autoplay playsinline :muted="false"></video> |
|
|
|
|
<!-- 远端流双击切换到大屏 --> |
|
|
|
|
<div v-if="isSharing && sharedStreamId && sharedStreamId !== 'local'" ref="mainRemoteContainer" class="share-preview" style="width:100%;height:100%;background:#000;"></div> |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
<!-- 右侧本地摄像头 + 说话音量条 + 远端流 --> |
|
|
|
|
@ -84,7 +86,9 @@ |
|
|
|
|
<!-- 远端流列表 --> |
|
|
|
|
<div class="remote-stream-list"> |
|
|
|
|
<template v-for="stream in remoteStream"> |
|
|
|
|
<div class="remote-video-box remote-box" v-if="stream.subscribeID" :key="stream.subscribeID"> |
|
|
|
|
<div class="remote-video-box remote-box" v-if="stream.subscribeID" :key="stream.subscribeID" |
|
|
|
|
:title="sharedStreamId === stream.subscribeID ? '双击取消画面' : '双击将画面切换到采集卡窗口'" |
|
|
|
|
@dblclick="switchRemoteStreamToMain(stream)"> |
|
|
|
|
<div class="video-area remote-video-area"> |
|
|
|
|
<div class="video" :ref="`stream-${stream.subscribeID}`"></div> |
|
|
|
|
<div class="video-controls remote-controls"> |
|
|
|
|
@ -300,8 +304,10 @@ import CreateGroupDialog from '@/views/message/components/CreateGroupDialog'; |
|
|
|
|
import ConsultationCaseStatsDialog from './components/ConsultationCaseStatsDialog'; |
|
|
|
|
import { |
|
|
|
|
postConsultationCreate, |
|
|
|
|
postConsultationConnected, |
|
|
|
|
postConsultationInfo, |
|
|
|
|
postConsultationStop |
|
|
|
|
postConsultationStop, |
|
|
|
|
postConsultationQuit |
|
|
|
|
} from "@/api/videoCommunication"; |
|
|
|
|
import { patientAgeTypeList } from "@/api/cases/index.js"; |
|
|
|
|
export default { |
|
|
|
|
@ -383,6 +389,7 @@ export default { |
|
|
|
|
shareStream: null, |
|
|
|
|
captureCheckTimer: null, |
|
|
|
|
isLocalCameraShared: false, // 双击本地摄像头切换到采集卡窗口的状态 |
|
|
|
|
sharedStreamId: null, // 当前在主画面显示的流来源: null | 'local' | subscribeID |
|
|
|
|
|
|
|
|
|
// ==================== 设置模块 ==================== |
|
|
|
|
settingDialogVisible: false, |
|
|
|
|
@ -486,35 +493,39 @@ export default { |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
mounted() { |
|
|
|
|
|
|
|
|
|
if (this.$route.query.roomId_id) this.creatRoom() |
|
|
|
|
|
|
|
|
|
// this.initSDK() |
|
|
|
|
|
|
|
|
|
this.creatRoom() |
|
|
|
|
this.initPage(); |
|
|
|
|
|
|
|
|
|
}, |
|
|
|
|
methods: { |
|
|
|
|
async creatRoom() { |
|
|
|
|
try { |
|
|
|
|
// const res = await postConsultationCreate({ |
|
|
|
|
// avatar: '', |
|
|
|
|
// init_users: [], |
|
|
|
|
// invite_code: '1234', |
|
|
|
|
// name: '', |
|
|
|
|
// room_id: this.$route.query.roomId_id |
|
|
|
|
// }) |
|
|
|
|
// this.consultation_id = res.data.consultation_id |
|
|
|
|
// const ret = await postConsultationInfo({ |
|
|
|
|
// consultation_id: res.data.consultation_id, |
|
|
|
|
// from_history: 0, |
|
|
|
|
// room_id: '' |
|
|
|
|
// }) |
|
|
|
|
// this.meetingTitle = ret.data.name |
|
|
|
|
// this.meetingCode = ret.data.invite_code |
|
|
|
|
// this.participantCount = ret.data.user_num |
|
|
|
|
// this.userID = ret.data.user_id |
|
|
|
|
// this.roomID = ret.data.room_id |
|
|
|
|
let res = null |
|
|
|
|
if (this.$route.query.invite_type_detail == 1) { |
|
|
|
|
res = await postConsultationCreate({ |
|
|
|
|
avatar: '', |
|
|
|
|
init_users: [], |
|
|
|
|
invite_code: '1234', |
|
|
|
|
name: '', |
|
|
|
|
room_id: this.$route.query.roomId_id |
|
|
|
|
}) |
|
|
|
|
} else { |
|
|
|
|
res = await postConsultationConnected({ |
|
|
|
|
invite_type_detail: this.$route.query.invite_type_detail, |
|
|
|
|
consultation_id: 0, |
|
|
|
|
room_id: this.$route.query.room_id |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
this.consultation_id = res.data.consultation_id |
|
|
|
|
const ret = await postConsultationInfo({ |
|
|
|
|
consultation_id: res.data.consultation_id, |
|
|
|
|
from_history: 0, |
|
|
|
|
room_id: '' |
|
|
|
|
}) |
|
|
|
|
this.meetingTitle = ret.data.name |
|
|
|
|
this.meetingCode = ret.data.invite_code |
|
|
|
|
this.participantCount = ret.data.user_num |
|
|
|
|
this.userID = ret.data.user_list.filter((f) => f.name == this.$store.state.user.userInfo?.name)[0].id |
|
|
|
|
this.roomID = ret.data.room_id |
|
|
|
|
this.bindEvent(); |
|
|
|
|
this.initSDK() |
|
|
|
|
} catch (error) { |
|
|
|
|
@ -614,18 +625,15 @@ export default { |
|
|
|
|
this.join() |
|
|
|
|
}, |
|
|
|
|
join() { |
|
|
|
|
const userID = this.userID; |
|
|
|
|
const roomID = this.roomID; |
|
|
|
|
// if (!userID) { |
|
|
|
|
// this.$message({ message: "用户名不能为空", type: "warning" }); |
|
|
|
|
// return; |
|
|
|
|
// } |
|
|
|
|
// if (!roomID) { |
|
|
|
|
// this.$message({ message: "房间号不能为空", type: "warning" }); |
|
|
|
|
// return; |
|
|
|
|
// } |
|
|
|
|
const userName = this.$store.state.user.userInfo?.name || userID; |
|
|
|
|
window.hirtcwebsdk.join('1006110', 'ce84dbf44832bb97faa3bfd735f73c1b', '123123'); |
|
|
|
|
if (!this.userID) { |
|
|
|
|
this.$message({ message: "用户名不能为空", type: "warning" }); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
if (!this.roomID) { |
|
|
|
|
this.$message({ message: "房间号不能为空", type: "warning" }); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
window.hirtcwebsdk.join(this.roomID, 'ce84dbf44832bb97faa3bfd735f73c1b', this.$store.state.user.userInfo?.name); |
|
|
|
|
}, |
|
|
|
|
initLocalStream(type) { |
|
|
|
|
if (this.localCameraStream.type === type) { |
|
|
|
|
@ -1175,25 +1183,17 @@ export default { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 如果当前已显示本地摄像头画面 → 取消显示 |
|
|
|
|
if (this.isLocalCameraShared) { |
|
|
|
|
this.isLocalCameraShared = false; |
|
|
|
|
this.isSharing = false; |
|
|
|
|
this.$nextTick(() => { |
|
|
|
|
const videoEl = this.$refs.shareVideo; |
|
|
|
|
if (videoEl) { |
|
|
|
|
videoEl.srcObject = null; |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
if (this.isLocalCameraShared && this.sharedStreamId === 'local') { |
|
|
|
|
this.cancelMainDisplay(); |
|
|
|
|
this.$message.success('已取消采集卡窗口画面'); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 否则 → 切换到采集卡窗口显示 |
|
|
|
|
// 先关闭已有的共享/采集卡 |
|
|
|
|
this.closeCaptureCard(); |
|
|
|
|
this.stopShare(); |
|
|
|
|
// 否则 → 先清理当前大屏内容,再切换到本地摄像头 |
|
|
|
|
this.cancelMainDisplay(); |
|
|
|
|
|
|
|
|
|
this.isLocalCameraShared = true; |
|
|
|
|
this.sharedStreamId = 'local'; |
|
|
|
|
this.isSharing = true; |
|
|
|
|
this.$nextTick(() => { |
|
|
|
|
const videoEl = this.$refs.shareVideo; |
|
|
|
|
@ -1203,6 +1203,62 @@ export default { |
|
|
|
|
}); |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// ==================== 【3.6】双击远端流切换到采集卡画面(toggle) ==================== |
|
|
|
|
switchRemoteStreamToMain(stream) { |
|
|
|
|
const subscribeID = stream.subscribeID; |
|
|
|
|
if (!subscribeID || !stream.video) return; |
|
|
|
|
|
|
|
|
|
// 如果当前已显示该远端流 → 取消显示 |
|
|
|
|
if (this.sharedStreamId === subscribeID) { |
|
|
|
|
this.cancelMainDisplay(); |
|
|
|
|
this.$message.success('已取消采集卡窗口画面'); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 先清理当前大屏内容 |
|
|
|
|
this.cancelMainDisplay(); |
|
|
|
|
|
|
|
|
|
this.sharedStreamId = subscribeID; |
|
|
|
|
this.isSharing = true; |
|
|
|
|
|
|
|
|
|
this.$nextTick(() => { |
|
|
|
|
const mainContainer = this.$refs.mainRemoteContainer; |
|
|
|
|
if (!mainContainer) return; |
|
|
|
|
mainContainer.appendChild(stream.video); |
|
|
|
|
this.$message.success('远端画面已切换到采集卡窗口'); |
|
|
|
|
}); |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// ==================== 【3.7】取消主画面显示(统一清理) ==================== |
|
|
|
|
cancelMainDisplay() { |
|
|
|
|
const currentId = this.sharedStreamId; |
|
|
|
|
if (!currentId) return; |
|
|
|
|
|
|
|
|
|
if (currentId === 'local') { |
|
|
|
|
// 取消本地摄像头 → 清空 shareVideo |
|
|
|
|
this.isLocalCameraShared = false; |
|
|
|
|
this.$nextTick(() => { |
|
|
|
|
const videoEl = this.$refs.shareVideo; |
|
|
|
|
if (videoEl) { |
|
|
|
|
videoEl.srcObject = null; |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
} else { |
|
|
|
|
// 取消远端流 → 把 video 移回侧边栏原容器 |
|
|
|
|
const sidebarContainer = this.$refs[`stream-${currentId}`]; |
|
|
|
|
const mainContainer = this.$refs.mainRemoteContainer; |
|
|
|
|
if (mainContainer && sidebarContainer?.[0]) { |
|
|
|
|
const videoEl = mainContainer.querySelector('video'); |
|
|
|
|
if (videoEl) { |
|
|
|
|
sidebarContainer[0].appendChild(videoEl); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
this.sharedStreamId = null; |
|
|
|
|
this.isSharing = false; |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// ==================== 【4】采集卡设备 ==================== |
|
|
|
|
async switchCaptureCard(deviceId) { |
|
|
|
|
if (this.captureCheckTimer) clearTimeout(this.captureCheckTimer); |
|
|
|
|
@ -1265,6 +1321,7 @@ export default { |
|
|
|
|
closeCaptureCard() { |
|
|
|
|
try { |
|
|
|
|
this.isLocalCameraShared = false; |
|
|
|
|
this.sharedStreamId = null; |
|
|
|
|
if (this.captureCheckTimer) clearTimeout(this.captureCheckTimer); |
|
|
|
|
const videoEl = this.$refs.shareVideo; |
|
|
|
|
if (!videoEl) return; |
|
|
|
|
@ -1291,6 +1348,7 @@ export default { |
|
|
|
|
} |
|
|
|
|
try { |
|
|
|
|
this.isLocalCameraShared = false; // 开启屏幕共享时重置本地摄像头共享状态 |
|
|
|
|
this.sharedStreamId = null; |
|
|
|
|
const stream = await navigator.mediaDevices.getDisplayMedia({ |
|
|
|
|
video: true, |
|
|
|
|
audio: true, |
|
|
|
|
@ -1318,6 +1376,7 @@ export default { |
|
|
|
|
// 停止共享 |
|
|
|
|
stopShare() { |
|
|
|
|
this.isLocalCameraShared = false; |
|
|
|
|
this.sharedStreamId = null; |
|
|
|
|
if (this.shareStream) { |
|
|
|
|
this.shareStream.getTracks().forEach(t => { |
|
|
|
|
t.stop(); |
|
|
|
|
@ -2286,6 +2345,7 @@ export default { |
|
|
|
|
|
|
|
|
|
/* ==================== 本地摄像头控制增强 ==================== */ |
|
|
|
|
.video-controls { |
|
|
|
|
|
|
|
|
|
.audio-control, |
|
|
|
|
.video-control { |
|
|
|
|
cursor: pointer; |
|
|
|
|
|