main
SWX\10484 3 days ago
parent ae96b80b61
commit 16d59708f2
  1. 16
      src/api/videoCommunication.js
  2. 80
      src/views/videoCommunication/index.vue
  3. 166
      src/views/videoCommunication/realTimeConsultation.vue

@ -43,6 +43,14 @@ export function postConsultationCreate(data) {
data
})
}
// 视讯-加入房间
export function postConsultationConnected(data) {
return request({
url: '/consultation/connected',
method: 'post',
data
})
}
// 视讯-加入会议
export function postConsultationInfo(data) {
return request({
@ -59,6 +67,14 @@ export function postConsultationStop(data) {
data
})
}
// 视讯-退出会议
export function postConsultationQuit(data) {
return request({
url: '/consultation/quit',
method: 'post',
data
})
}
// 视讯-会议结果提交
export function postConsultationPatients(data) {
return request({

@ -8,24 +8,15 @@
</div>
<!-- 口令入会区域 -->
<div class="join-area">
<el-input
v-model="room_id"
placeholder="请输入数字口令"
class="join-input"
@keyup.enter="joinMeeting"
/>
<el-input v-model="room_id" placeholder="请输入数字口令" class="join-input" @keyup.enter="joinMeeting" />
<el-button type="success" class="join-btn" @click="joinMeeting">
入会
</el-button>
</div>
<!-- 四种模式卡片 -->
<div class="mode-list">
<div
v-for="(mode, index) in meetingModes"
:key="index"
:class="['mode-item', mode.color]"
@click="handleModeClick(mode)"
>
<div v-for="(mode, index) in meetingModes" :key="index" :class="['mode-item', mode.color]"
@click="handleModeClick(mode)">
<div class="title">
{{ mode.title }}
</div>
@ -40,23 +31,13 @@
</div>
<!-- 会诊表格 -->
<div class="table-wrapper">
<el-table
v-loading="loading"
:data="list"
:show-header="false"
stripe
height="calc(100vh - 550px)"
>
<el-table v-loading="loading" :data="list" :show-header="false" stripe height="calc(100vh - 550px)">
<el-table-column label="头像" prop="avatar" align="left" width="40">
<template slot-scope="scope">
<!-- MINIO_ENDPOINT_HTTPS+scope.row.avatar -->
<el-avatar
:size="23"
:src="
$store.state.user.netConfig.MINIO_ENDPOINT_HTTPS +
<el-avatar :size="23" :src="$store.state.user.netConfig.MINIO_ENDPOINT_HTTPS +
scope.row.avatar
"
/>
" />
</template>
</el-table-column>
<el-table-column label="会诊名称" prop="name" align="left">
@ -66,11 +47,7 @@
</span>
</template>
</el-table-column>
<el-table-column
label="会议类型名称"
prop="meet_type_name"
align="left"
>
<el-table-column label="会议类型名称" prop="meet_type_name" align="left">
<template slot-scope="scope">
<span class="table-column" style="font-weight: bold">
{{ scope.row.meet_type_name }}
@ -81,31 +58,18 @@
<!-- 状态status 1-开始 0-结束 -->
<el-table-column label="操作" align="right">
<template slot-scope="scope">
<el-button
type="text"
icon="el-icon-more"
@click="handleDetail(scope.row)"
/>
<el-button type="text" icon="el-icon-more" @click="handleDetail(scope.row)" />
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页 -->
<pagination
v-show="queryParams.total > 0"
:total="queryParams.total"
:page.sync="queryParams.page"
:limit.sync="queryParams.size"
@pagination="getList"
/>
<pagination v-show="queryParams.total > 0" :total="queryParams.total" :page.sync="queryParams.page"
:limit.sync="queryParams.size" @pagination="getList" />
</el-card>
<!-- 会诊详情弹框 -->
<el-dialog
:title="meetingDetail.name"
:visible.sync="meetingDetailVisible"
width="35%"
:close-on-click-modal="false"
>
<el-dialog :title="meetingDetail.name" :visible.sync="meetingDetailVisible" width="35%"
:close-on-click-modal="false">
<el-form :model="meetingDetail" label-width="110px">
<el-form-item>
{{ getStatusText(meetingDetail.status) }}
@ -133,15 +97,11 @@
<el-row>
<el-col :span="3">
<el-form-item>
<el-avatar
:src="
meetingDetail.user_list && meetingDetail.user_list[0]
<el-avatar :src="meetingDetail.user_list && meetingDetail.user_list[0]
? $store.state.user.netConfig.MINIO_ENDPOINT_HTTPS +
meetingDetail.user_list[0].avatar
: ''
"
class="person-avatar"
/>
" class="person-avatar" />
</el-form-item>
</el-col>
<el-col :span="21">
@ -156,13 +116,9 @@
<el-row v-for="(item, index) in meetingDetail.user_list" :key="index">
<el-col :span="3">
<el-form-item>
<el-avatar
:src="
$store.state.user.netConfig.MINIO_ENDPOINT_HTTPS +
<el-avatar :src="$store.state.user.netConfig.MINIO_ENDPOINT_HTTPS +
item.avatar
"
class="person-avatar"
/>
" class="person-avatar" />
</el-form-item>
</el-col>
<el-col :span="6">
@ -265,6 +221,7 @@ export default {
consultation_id: 0,
from_history: 0,
room_id: res.data.room_id,
invite_type_detail: 4
},
});
})
@ -289,6 +246,7 @@ export default {
query: {
name: this.userInfo.group,
roomId_id: this.generateRoomId(mode.type, this.userInfo.id),
invite_type_detail: 1
},
});
},
@ -336,6 +294,7 @@ export default {
margin-right: 8px;
}
}
.table-column {
color: #009696;
}
@ -381,6 +340,7 @@ export default {
overflow: hidden;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
&:hover {
transform: translateY(-1px);
}

@ -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.6toggle ====================
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;

Loading…
Cancel
Save