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

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

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

@ -48,8 +48,10 @@
<!-- 画笔标注画布 --> <!-- 画笔标注画布 -->
<canvas v-show="isDrawingMode" ref="drawingCanvas" class="drawing-canvas" @mousedown="startDrawing" <canvas v-show="isDrawingMode" ref="drawingCanvas" class="drawing-canvas" @mousedown="startDrawing"
@mousemove="draw" @mouseup="stopDrawing" @mouseleave="stopDrawing"></canvas> @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> </div>
<!-- 右侧本地摄像头 + 说话音量条 + 远端流 --> <!-- 右侧本地摄像头 + 说话音量条 + 远端流 -->
@ -84,7 +86,9 @@
<!-- 远端流列表 --> <!-- 远端流列表 -->
<div class="remote-stream-list"> <div class="remote-stream-list">
<template v-for="stream in remoteStream"> <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-area remote-video-area">
<div class="video" :ref="`stream-${stream.subscribeID}`"></div> <div class="video" :ref="`stream-${stream.subscribeID}`"></div>
<div class="video-controls remote-controls"> <div class="video-controls remote-controls">
@ -300,8 +304,10 @@ import CreateGroupDialog from '@/views/message/components/CreateGroupDialog';
import ConsultationCaseStatsDialog from './components/ConsultationCaseStatsDialog'; import ConsultationCaseStatsDialog from './components/ConsultationCaseStatsDialog';
import { import {
postConsultationCreate, postConsultationCreate,
postConsultationConnected,
postConsultationInfo, postConsultationInfo,
postConsultationStop postConsultationStop,
postConsultationQuit
} from "@/api/videoCommunication"; } from "@/api/videoCommunication";
import { patientAgeTypeList } from "@/api/cases/index.js"; import { patientAgeTypeList } from "@/api/cases/index.js";
export default { export default {
@ -383,6 +389,7 @@ export default {
shareStream: null, shareStream: null,
captureCheckTimer: null, captureCheckTimer: null,
isLocalCameraShared: false, // isLocalCameraShared: false, //
sharedStreamId: null, // : null | 'local' | subscribeID
// ==================== ==================== // ==================== ====================
settingDialogVisible: false, settingDialogVisible: false,
@ -486,35 +493,39 @@ export default {
}, },
}, },
mounted() { mounted() {
this.creatRoom()
if (this.$route.query.roomId_id) this.creatRoom()
// this.initSDK()
this.initPage(); this.initPage();
}, },
methods: { methods: {
async creatRoom() { async creatRoom() {
try { try {
// const res = await postConsultationCreate({ let res = null
// avatar: '', if (this.$route.query.invite_type_detail == 1) {
// init_users: [], res = await postConsultationCreate({
// invite_code: '1234', avatar: '',
// name: '', init_users: [],
// room_id: this.$route.query.roomId_id invite_code: '1234',
// }) name: '',
// this.consultation_id = res.data.consultation_id room_id: this.$route.query.roomId_id
// const ret = await postConsultationInfo({ })
// consultation_id: res.data.consultation_id, } else {
// from_history: 0, res = await postConsultationConnected({
// room_id: '' invite_type_detail: this.$route.query.invite_type_detail,
// }) consultation_id: 0,
// this.meetingTitle = ret.data.name room_id: this.$route.query.room_id
// this.meetingCode = ret.data.invite_code })
// this.participantCount = ret.data.user_num }
// this.userID = ret.data.user_id this.consultation_id = res.data.consultation_id
// this.roomID = ret.data.room_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.bindEvent();
this.initSDK() this.initSDK()
} catch (error) { } catch (error) {
@ -614,18 +625,15 @@ export default {
this.join() this.join()
}, },
join() { join() {
const userID = this.userID; if (!this.userID) {
const roomID = this.roomID; this.$message({ message: "用户名不能为空", type: "warning" });
// if (!userID) { return;
// this.$message({ message: "", type: "warning" }); }
// return; if (!this.roomID) {
// } this.$message({ message: "房间号不能为空", type: "warning" });
// if (!roomID) { return;
// this.$message({ message: "", type: "warning" }); }
// return; window.hirtcwebsdk.join(this.roomID, 'ce84dbf44832bb97faa3bfd735f73c1b', this.$store.state.user.userInfo?.name);
// }
const userName = this.$store.state.user.userInfo?.name || userID;
window.hirtcwebsdk.join('1006110', 'ce84dbf44832bb97faa3bfd735f73c1b', '123123');
}, },
initLocalStream(type) { initLocalStream(type) {
if (this.localCameraStream.type === type) { if (this.localCameraStream.type === type) {
@ -1175,25 +1183,17 @@ export default {
} }
// //
if (this.isLocalCameraShared) { if (this.isLocalCameraShared && this.sharedStreamId === 'local') {
this.isLocalCameraShared = false; this.cancelMainDisplay();
this.isSharing = false;
this.$nextTick(() => {
const videoEl = this.$refs.shareVideo;
if (videoEl) {
videoEl.srcObject = null;
}
});
this.$message.success('已取消采集卡窗口画面'); this.$message.success('已取消采集卡窗口画面');
return; return;
} }
// //
// / this.cancelMainDisplay();
this.closeCaptureCard();
this.stopShare();
this.isLocalCameraShared = true; this.isLocalCameraShared = true;
this.sharedStreamId = 'local';
this.isSharing = true; this.isSharing = true;
this.$nextTick(() => { this.$nextTick(() => {
const videoEl = this.$refs.shareVideo; 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 ==================== // ==================== 4 ====================
async switchCaptureCard(deviceId) { async switchCaptureCard(deviceId) {
if (this.captureCheckTimer) clearTimeout(this.captureCheckTimer); if (this.captureCheckTimer) clearTimeout(this.captureCheckTimer);
@ -1265,6 +1321,7 @@ export default {
closeCaptureCard() { closeCaptureCard() {
try { try {
this.isLocalCameraShared = false; this.isLocalCameraShared = false;
this.sharedStreamId = null;
if (this.captureCheckTimer) clearTimeout(this.captureCheckTimer); if (this.captureCheckTimer) clearTimeout(this.captureCheckTimer);
const videoEl = this.$refs.shareVideo; const videoEl = this.$refs.shareVideo;
if (!videoEl) return; if (!videoEl) return;
@ -1291,6 +1348,7 @@ export default {
} }
try { try {
this.isLocalCameraShared = false; // this.isLocalCameraShared = false; //
this.sharedStreamId = null;
const stream = await navigator.mediaDevices.getDisplayMedia({ const stream = await navigator.mediaDevices.getDisplayMedia({
video: true, video: true,
audio: true, audio: true,
@ -1318,6 +1376,7 @@ export default {
// //
stopShare() { stopShare() {
this.isLocalCameraShared = false; this.isLocalCameraShared = false;
this.sharedStreamId = null;
if (this.shareStream) { if (this.shareStream) {
this.shareStream.getTracks().forEach(t => { this.shareStream.getTracks().forEach(t => {
t.stop(); t.stop();
@ -2286,6 +2345,7 @@ export default {
/* ==================== 本地摄像头控制增强 ==================== */ /* ==================== 本地摄像头控制增强 ==================== */
.video-controls { .video-controls {
.audio-control, .audio-control,
.video-control { .video-control {
cursor: pointer; cursor: pointer;

Loading…
Cancel
Save