3540 lines
115 KiB
Vue
3540 lines
115 KiB
Vue
<template>
|
||
<div class="detection-container-box">
|
||
<Header />
|
||
<div class="displaycontainer">
|
||
<div class="displayleft">
|
||
<img src="@/assets/detection/progress.png" alt="" style=" margin-left:10px;margin-right:15px">
|
||
<div style="
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
font-style: normal;
|
||
color: #3BF2C6;"
|
||
>检测中...</div>
|
||
<div class="patientInfotop1">{{ patientInfo.name }}</div>
|
||
<div style="font-size: 20px;"> {{ patientInfo.gender }}</div>
|
||
<div class="username-line"></div>
|
||
<div style="font-size: 20px;">{{ calculateAge(patientInfo.birth_date) }}岁</div>
|
||
<el-button type="primary" class="endbutton" @click="endClick">结束检测</el-button>
|
||
</div>
|
||
<div class="displayleft">
|
||
<div class="icon-box" :title="remoteStatus =='未连接'?'截图':'截图(有遥控器)'" @click="saveDetectionData">
|
||
<img src="@/assets/detection/screenshot.png" alt="">
|
||
</div>
|
||
<div class="icon-box" @click="isPhotoAlbum = true" title="相册">
|
||
<img src="@/assets/detection/photoalbum.svg" alt="">
|
||
</div>
|
||
<div v-if="!isStartVideo" class="icon-box" @click="startVideoClick"
|
||
:title="remoteStatus =='未连接'?'开始录像':'开始录像(有遥控器)'">
|
||
<img src="@/assets/detection/startvideo.png" alt="">
|
||
</div>
|
||
<div v-if="isStartVideo" class="icon-box" @click="stopVideoClick" title="结束录像">
|
||
<img src="@/assets/detection/endvideo.png" alt="">
|
||
</div>
|
||
<div class="startvideo-box" v-if="isStartVideo">
|
||
<img src="@/assets/detection/conduct.png" alt="" class="conductanimated">
|
||
录像中: {{ formattedTime }}
|
||
</div>
|
||
<div class="startvideo-box" v-if="!isStartVideo">
|
||
</div>
|
||
</div>
|
||
<div class="displayleft">
|
||
<div class="icon-box" title="相机参数设置" @click="cameraUpdate">
|
||
<img src="@/assets/detection/settings.png" alt="" >
|
||
</div>
|
||
<div class="icon-box" title="查看档案" @click="routerClick">
|
||
<img src="@/assets/detection/archive.png" alt="" >
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div style="width:100%;height: calc(100% - 125px);" ref="contenGridRef">
|
||
<!-- 主内容区域 -->
|
||
<el-row :gutter="15" style="padding: 10px;padding-top:0" >
|
||
<el-col :span="6" style="flex: 0 0 24%;height: calc(100% - 0px);">
|
||
<div class="body-posture-box">
|
||
<div class="body-title-display">
|
||
<div class="body-son-display">
|
||
<img src="@/assets/detection/title1.png" alt="" style="margin-right: 8px;">
|
||
<div class="body-posture-text">身体姿态</div>
|
||
</div>
|
||
<div class="body-son-display">
|
||
<img :src="femtoboltStatus == '已连接'?refresh_active:refresh" alt="" style="margin-right: 8px;cursor: pointer;"
|
||
@click="refreshClick('femtobolt')" title="重启深度相机">
|
||
<div class="connecttext" :style="{ color: femtoboltStatus == '已连接' ? '#3bf2c6' : '#808080' }">
|
||
{{ femtoboltStatus }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div ref="wholeBodyRef" style="display: flex;justify-content: center;height:calc( 100% - 50px);padding-top: 0px;"
|
||
:style="{alignItems:(femtoboltStatus === '已连接' && depthCameraImgSrc)?'':'center'}">
|
||
<img v-if="(femtoboltStatus === '已连接' && depthCameraImgSrc)" :src="depthCameraImgSrc" alt="深度相机视频流"
|
||
style="width: 100%;height: calc(100%);">
|
||
<!-- object-fit:contain; -->
|
||
<div v-else style="width:90px;height:60px">
|
||
<img :src="noImageSvg" style="margin-left: 15px;">
|
||
<div style="font-size:14px;color:#ffffff99;text-align: center;">连接已断开!</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-col>
|
||
<el-col :span="12" style="flex: 0 0 52%;max-width: 52%;height: calc(100% - 0px);">
|
||
<!-- 头部姿态模块 -->
|
||
<div class="body-header-box">
|
||
<div class="body-title-display">
|
||
<div class="body-son-display">
|
||
<img src="@/assets/detection/title2.png" alt="" style="margin-right: 8px;">
|
||
<div class="body-posture-text">头部姿态</div>
|
||
<div class="calibration-zero" @click="calibrationClick" style="margin-left:20px">
|
||
<img src="@/assets/detection/calibration.png" style="margin-right:7px">
|
||
校准
|
||
</div>
|
||
<div class="calibration-zero" @click="clearAndStartTracking">
|
||
<img src="@/assets/detection/zero.png" style="margin-right:7px">
|
||
清零
|
||
</div>
|
||
</div>
|
||
<div class="body-son-display">
|
||
<img :src="imuStatus == '已连接'?refresh_active:refresh" alt="" style="margin-right: 8px;cursor: pointer;" title="重启IMU"
|
||
@click="refreshClick('imu')">
|
||
<div class="connecttext" :style="{ color: imuStatus == '已连接' ? '#3bf2c6' : '#808080' }">
|
||
{{ imuStatus }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 头部姿态3D模型 -->
|
||
<div class="body-header-bottombox" ref="imuHeaderRef">
|
||
<div class="body-header-bottombox-left">
|
||
<div class="body-header-bottombox-lefttext" style="height: 25%;"></div>
|
||
<div class="body-header-bottombox-lefttext" >
|
||
<div class="currencytext1">左最大旋转角度</div>
|
||
<div class="currencytext2">{{
|
||
headPoseMaxValues.rotationLeftMax.toFixed(1) }}°</div>
|
||
</div>
|
||
<div class="body-header-bottombox-lefttext">
|
||
<div>
|
||
<div class="currencytext1">倾斜角</div>
|
||
<div class="currencytext2">{{ headlist.tilt }}°</div>
|
||
</div>
|
||
<div style="margin-top: 0px;">
|
||
<span>
|
||
<span class="currencytext4">左</span>
|
||
<span class="currencytext3">
|
||
{{ headPoseMaxValues.tiltLeftMax.toFixed(1) }}°
|
||
</span>
|
||
</span>
|
||
<span>|</span>
|
||
<span style="padding-left: 20px;">
|
||
<span class="currencytext4">右</span>
|
||
<span class="currencytext3">
|
||
{{ headPoseMaxValues.tiltRightMax.toFixed(1) }}°
|
||
</span>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="body-header-bottombox-content">
|
||
<div style="text-align: center; width: 100%;" >
|
||
<span class="currencytext1">旋转角:</span>
|
||
<span class="currencytext2">{{ headlist.rotation }}°</span>
|
||
</div>
|
||
<div style="width: 100%;height: 80%;">
|
||
<Model :rotation="Number(headlist.rotation)" :tilt="Number(headlist.tilt)" :pitch="Number(headlist.pitch)*(-1)" :gender="patientInfo.gender || '男'" />
|
||
</div>
|
||
|
||
</div>
|
||
<div class="body-header-bottombox-right">
|
||
<div class="body-header-bottombox-righttext" style="height: 25%;"></div>
|
||
<div class="body-header-bottombox-righttext" >
|
||
<div class="currencytext1">右最大旋转角度</div>
|
||
<div class="currencytext2">
|
||
{{ headPoseMaxValues.rotationRightMax.toFixed(1) }}°
|
||
</div>
|
||
</div>
|
||
<div class="body-header-bottombox-righttext">
|
||
<div>
|
||
<div class="currencytext1">俯仰角</div>
|
||
<div class="currencytext2">{{ headlist.pitch }}°</div>
|
||
</div>
|
||
<div style="margin-top: 0px;">
|
||
<span>
|
||
<span class="currencytext4">俯</span>
|
||
<span class="currencytext3">
|
||
{{ headPoseMaxValues.pitchDownMax.toFixed(1) }}°
|
||
</span>
|
||
</span>
|
||
<span>|</span>
|
||
<span style="padding-left: 20px;">
|
||
<span class="currencytext4">仰</span>
|
||
<span style="text-align:right">
|
||
{{ headPoseMaxValues.pitchUpMax.toFixed(1) }}°
|
||
</span>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="body-foot-box">
|
||
<div class="body-title-display">
|
||
<div class="body-son-display">
|
||
<img src="@/assets/detection/title3.png" alt="" style="margin-right: 8px;">
|
||
<div class="body-posture-text">足部压力</div>
|
||
</div>
|
||
<div class="body-son-display">
|
||
<img :src="pressureStatus == '已连接'?refresh_active:refresh" alt="" style="margin-right: 8px;cursor: pointer;" title="重启足底压力板"
|
||
@click="refreshClick('pressure')">
|
||
<div class="connecttext" :style="{ color: pressureStatus == '已连接' ? '#3bf2c6' : '#808080' }">
|
||
{{ pressureStatus }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="body-footbottom-box" ref="pressureRef">
|
||
<div class="body-footbottom-left">
|
||
<div style="width:100%;height: 50px;"></div>
|
||
<div class="body-footbottom-leftbottom">
|
||
<div class="body-footbottom-leftbox">
|
||
<span class="currencytext1">左前足</span>
|
||
<span class="currencytext2">
|
||
{{ footPressure.left_front }}%
|
||
</span>
|
||
</div>
|
||
<div class="body-footbottom-leftbox">
|
||
<span class="currencytext1">左后足</span>
|
||
<span class="currencytext2">
|
||
{{ footPressure.left_rear }}%
|
||
</span>
|
||
</div>
|
||
<div class="body-footbottom-leftbox">
|
||
<span class="currencytext1">左足总压力</span>
|
||
<span class="currencytext2">
|
||
{{ footPressure.left_total}}%
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="body-footbottom-center">
|
||
<div class="body-footbottom-topbox">
|
||
<div class="currencytext1" style="font-size:22px;text-align:center;">左足</div>
|
||
<div class="currencytext1" style="font-size:22px;text-align:center;">右足</div>
|
||
</div>
|
||
<div style="position: relative;width: 100%;height:calc(100% - 60px) ;"
|
||
:class="(pressureStatus === '已连接' && footImgSrc)?'':'noImageSvg-bg'">
|
||
|
||
<img v-if="(pressureStatus === '已连接' && footImgSrc)" :src="footImgSrc" style="width: 100%;height: 100%;" alt="">
|
||
<div v-else style="width:90px;height:60px">
|
||
<img :src="noImageSvg" style="margin-left: 15px;">
|
||
<div style="font-size:14px;color:#ffffff99;text-align: center;">连接已断开!</div>
|
||
</div>
|
||
<div class="xline"></div>
|
||
<div class="yline"></div>
|
||
</div>
|
||
</div>
|
||
<div class="body-footbottom-left">
|
||
<div style="width:100%;height: 50px;"></div>
|
||
<div class="body-footbottom-leftbottom">
|
||
<div class="body-footbottom-leftbox">
|
||
<span class="currencytext1">右前足</span>
|
||
<span class="currencytext2">
|
||
{{ footPressure.right_front }}%
|
||
</span>
|
||
</div>
|
||
<div class="body-footbottom-leftbox">
|
||
<span class="currencytext1">右后足</span>
|
||
<span class="currencytext2">
|
||
{{ footPressure.right_rear }}%
|
||
</span>
|
||
</div>
|
||
<div class="body-footbottom-leftbox">
|
||
<span class="currencytext1">右足总压力</span>
|
||
<span class="currencytext2">
|
||
{{ footPressure.right_total}}%
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</el-col>
|
||
<el-col v-if=" camera1Status === '已连接' && camera2Status === '已连接' "
|
||
:span="6" style="flex: 0 0 24%;height: calc(100% - 0px); position: relative;">
|
||
<div class="body-userinfo-box" :class="isExpand == true?'body-userinfo-expandbox':''">
|
||
<div class="body-title-display">
|
||
<div class="body-son-display">
|
||
<img src="@/assets/detection/title4.png" alt="" style="margin-right: 8px;">
|
||
<div class="body-posture-text">患者信息</div>
|
||
</div>
|
||
<div class="body-son-display"></div>
|
||
</div>
|
||
<div class="body-userinfo-content">
|
||
<div class="body-userinfo-content-top">
|
||
|
||
<img src="@/assets/detection/useredit.png" alt="" title="编辑患者信息"
|
||
class="userinfo-edit-img" style="cursor: pointer;" @click="handleEditUserInfo">
|
||
<div class="useravatar-box">
|
||
<img src="@/assets/detection/useravatar.svg" alt="">
|
||
</div>
|
||
<div>
|
||
<div class="userinfo-text-top">
|
||
<div class="userinfo-text1" style="margin-right:20px ;">{{ patientInfo.name }}</div>
|
||
<div class="userinfo-text2"> {{ patientInfo.gender }}</div>
|
||
<div class="userinfo-line"></div>
|
||
<div class="userinfo-text2">{{ calculateAge(patientInfo.birth_date) }}岁</div>
|
||
</div>
|
||
<div class="userinfo-text3">
|
||
ID:{{ patientInfo.id }}
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
<div class="body-userinfo-content-bottom" v-if="isExpand == false">
|
||
<img src="@/assets/detection/userinfo.png" alt=""
|
||
class="userinfo-edit-img" style="cursor: pointer;"
|
||
@click="viewClick(true)">
|
||
<div class="userinfo-disyplay1" >
|
||
<div class="userinfo-text4">电话</div>
|
||
<div class="userinfo-text5">
|
||
<span v-if="patientInfo && patientInfo.phone">
|
||
{{ patientInfo.phone ==''||patientInfo.phone ==null ?'—':patientInfo.phone}}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<div class="userinfo-disyplay2">
|
||
<div class="userinfo-text4">身高</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.height ==''||patientInfo.height ==null ?'—':patientInfo.height}}cm
|
||
</div>
|
||
</div>
|
||
<div class="userinfo-disyplay1" style="padding-top: 20px;">
|
||
<div class="userinfo-text4">体重</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.weight ==''||patientInfo.weight ==null ?'—':patientInfo.weight}}kg
|
||
</div>
|
||
</div>
|
||
<div class="userinfo-disyplay2">
|
||
<div class="userinfo-text4">鞋码</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.shoe_size ==''||patientInfo.shoe_size ==null ?'—':patientInfo.shoe_size}}码</div>
|
||
</div>
|
||
|
||
</div>
|
||
<div class="body-userinfo-content-bottom2" v-if="isExpand == true">
|
||
<img src="@/assets/detection/userinfo.png" alt=""
|
||
class="userinfo-edit-img" style="cursor: pointer;"
|
||
@click="viewClick(false)">
|
||
<div class="userinfo-disyplaypadding1 ">
|
||
<div class="userinfo-text4 padding10">出生日期</div>
|
||
<div class="userinfo-text5">
|
||
<span v-if="patientInfo && patientInfo.birth_date">
|
||
{{ formatDate(patientInfo.birth_date) }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding2">
|
||
<div class="userinfo-text4 padding10">身高</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.height ==''||patientInfo.height ==null ?'—':patientInfo.height}}cm
|
||
</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding1">
|
||
<div class="userinfo-text4 padding10">体重</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.weight ==''||patientInfo.weight ==null ?'—':patientInfo.weight}}kg
|
||
</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding2">
|
||
<div class="userinfo-text4 padding10">鞋码</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.shoe_size ==''||patientInfo.shoe_size ==null ?'—':patientInfo.shoe_size}}码</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding1">
|
||
<div class="userinfo-text4 padding10">电话</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.phone ==''||patientInfo.phone ==null ?'—':patientInfo.phone}}
|
||
</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding2">
|
||
<div class="userinfo-text4 padding10">民族</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.nationality ==''||patientInfo.nationality ==null ?'—':patientInfo.nationality}}</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding1">
|
||
<div class="userinfo-text4 padding10">身份证号</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.idcode ==''||patientInfo.idcode ==null ?'—':patientInfo.idcode}}
|
||
</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding2">
|
||
<div class="userinfo-text4 padding10">职业</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.occupation ==''||patientInfo.occupation ==null ?'—':patientInfo.occupation}}</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding3">
|
||
<div class="userinfo-text4 padding10">居住地</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.residence ==''||patientInfo.residence ==null ?'—':patientInfo.residence}}</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding3">
|
||
<div class="userinfo-text4 padding10">邮箱</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.email ==''||patientInfo.email ==null ?'—':patientInfo.email}}</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="body-video-box" style="position: absolute;top: 347px; width: calc(100% - 15px);">
|
||
<div class="body-title-display">
|
||
<div class="body-son-display">
|
||
<img src="@/assets/detection/title5.png" alt="" style="margin-right: 8px;">
|
||
<div class="body-posture-text">视频</div>
|
||
</div>
|
||
<div class="body-son-display">
|
||
<img :src="cameraStatus == '已连接'?refresh_active:refresh" alt="" style="margin-right: 8px;cursor: pointer;"
|
||
title="重启视频"
|
||
@click="refreshClick('camera')">
|
||
<div class="connecttext" :style="{ color: cameraStatus == '已连接' ? '#3bf2c6' : '#808080' }">
|
||
{{ cameraStatus }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="body-video-content">
|
||
<div class="body-video-imgbox1" ref="camera1Ref" :class="(camera1Status === '已连接' && camera1ImgSrc)?'':'noImageSvg-bg'">
|
||
<div v-if="(camera1Status === '已连接' && camera1ImgSrc)"
|
||
@click="isBig1 = true" class="big-img">
|
||
<img src="@/assets/detection/big.png">
|
||
</div>
|
||
<img v-if="(camera1Status === '已连接' && camera1ImgSrc)" :src="camera1ImgSrc" alt="camera1"
|
||
style="width: 100%; height: 100%;" />
|
||
<div v-else style="width:90px;height:60px">
|
||
<img :src="noImageSvg" style="margin-left: 15px;">
|
||
<div style="font-size:14px;color:#ffffff99;text-align: center;">连接已断开!</div>
|
||
</div>
|
||
</div>
|
||
<div class="body-video-imgbox2" ref="camera2Ref" :class="(camera2Status === '已连接' && camera2ImgSrc)?'':'noImageSvg-bg'">
|
||
<div v-if="(camera2Status === '已连接' && camera2ImgSrc)"
|
||
@click="isBig2 = true" class="big-img">
|
||
<img src="@/assets/detection/big.png">
|
||
</div>
|
||
<img v-if="(camera2Status === '已连接' && camera2ImgSrc)" :src="camera2ImgSrc" alt="camera2"
|
||
style="width: 100%; height: 100%;" />
|
||
<div v-else style="width:90px;height:60px">
|
||
<img :src="noImageSvg" style="margin-left: 15px;">
|
||
<div style="font-size:14px;color:#ffffff99;text-align: center;">连接已断开!</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-col>
|
||
|
||
|
||
<el-col v-if="camera1Status === '已连接' || camera2Status === '已连接'"
|
||
:span="6" style="flex: 0 0 24%;height: calc(100% - 0px);">
|
||
<div class="body-userinfo-box1">
|
||
<div class="body-title-display">
|
||
<div class="body-son-display">
|
||
<img src="@/assets/detection/title4.png" alt="" style="margin-right: 8px;">
|
||
<div class="body-posture-text">患者信息</div>
|
||
</div>
|
||
<div class="body-son-display"></div>
|
||
</div>
|
||
<div class="body-userinfo-content">
|
||
<div class="body-userinfo-content-top">
|
||
|
||
<img src="@/assets/detection/useredit.png" alt="" title="编辑患者信息"
|
||
class="userinfo-edit-img" style="cursor: pointer;" @click="handleEditUserInfo">
|
||
<div class="useravatar-box">
|
||
<img src="@/assets/detection/useravatar.svg" alt="">
|
||
</div>
|
||
<div>
|
||
<div class="userinfo-text-top">
|
||
<div class="userinfo-text1" style="margin-right:20px ;">{{ patientInfo.name }}</div>
|
||
<div class="userinfo-text2"> {{ patientInfo.gender }}</div>
|
||
<div class="userinfo-line"></div>
|
||
<div class="userinfo-text2">{{ calculateAge(patientInfo.birth_date) }}岁</div>
|
||
</div>
|
||
<div class="userinfo-text3">
|
||
ID:{{ patientInfo.id }}
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
<div class="body-userinfo-content-bottom1">
|
||
<div class="userinfo-disyplaypadding4">
|
||
<div class="userinfo-text4">出生日期</div>
|
||
<div class="userinfo-text5">
|
||
<span v-if="patientInfo && patientInfo.birth_date">
|
||
{{ formatDate(patientInfo.birth_date) }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding5">
|
||
<div class="userinfo-text4">身高</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.height ==''||patientInfo.height ==null ?'—':patientInfo.height}}cm
|
||
</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding4">
|
||
<div class="userinfo-text4">体重</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.weight ==''||patientInfo.weight ==null ?'—':patientInfo.weight}}kg
|
||
</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding5">
|
||
<div class="userinfo-text4">鞋码</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.shoe_size ==''||patientInfo.shoe_size ==null ?'—':patientInfo.shoe_size}}码</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding4">
|
||
<div class="userinfo-text4">电话</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.phone ==''||patientInfo.phone ==null ?'—':patientInfo.phone}}
|
||
</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding5">
|
||
<div class="userinfo-text4">民族</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.nationality ==''||patientInfo.nationality ==null ?'—':patientInfo.nationality}}</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding4">
|
||
<div class="userinfo-text4">身份证号</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.idcode ==''||patientInfo.idcode ==null ?'—':patientInfo.idcode}}
|
||
</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding5">
|
||
<div class="userinfo-text4">职业</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.occupation ==''||patientInfo.occupation ==null ?'—':patientInfo.occupation}}</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding6">
|
||
<div class="userinfo-text4">居住地</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.residence ==''||patientInfo.residence ==null ?'—':patientInfo.residence}}</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding6">
|
||
<div class="userinfo-text4">邮箱</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.email ==''||patientInfo.email ==null ?'—':patientInfo.email}}</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="body-video-box1">
|
||
<div class="body-title-display">
|
||
<div class="body-son-display">
|
||
<img src="@/assets/detection/title5.png" alt="" style="margin-right: 8px;">
|
||
<div class="body-posture-text">视频</div>
|
||
</div>
|
||
<div class="body-son-display">
|
||
<img :src="cameraStatus == '已连接'?refresh_active:refresh" alt="" style="margin-right: 8px;cursor: pointer;"
|
||
title="重启视频"
|
||
@click="refreshClick('camera')">
|
||
<div class="connecttext" :style="{ color: cameraStatus == '已连接' ? '#3bf2c6' : '#808080' }">
|
||
{{ cameraStatus }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="body-video-content">
|
||
<div v-show="camera1Status === '已连接'" class="body-video-imgbox3" ref="camera1Ref" :class="(camera1Status === '已连接' && camera1ImgSrc)?'':'noImageSvg-bg'">
|
||
<div v-if="(camera1Status === '已连接' && camera1ImgSrc)"
|
||
@click="isBig1 = true" class="big-img">
|
||
<img src="@/assets/detection/big.png">
|
||
</div>
|
||
<img v-if="(camera1Status === '已连接' && camera1ImgSrc)" :src="camera1ImgSrc" alt="camera1"
|
||
style="width: 100%; height: 100%;" />
|
||
<div v-else style="width:90px;height:60px">
|
||
<img :src="noImageSvg" style="margin-left: 15px;">
|
||
<div style="font-size:14px;color:#ffffff99;text-align: center;">连接已断开!</div>
|
||
</div>
|
||
</div>
|
||
<div v-show="camera2Status === '已连接'" class="body-video-imgbox3" ref="camera2Ref" :class="(camera2Status === '已连接' && camera2ImgSrc)?'':'noImageSvg-bg'">
|
||
<div v-if="(camera2Status === '已连接' && camera2ImgSrc)"
|
||
@click="isBig2 = true" class="big-img">
|
||
<img src="@/assets/detection/big.png">
|
||
</div>
|
||
<img v-if="(camera2Status === '已连接' && camera2ImgSrc)" :src="camera2ImgSrc" alt="camera2"
|
||
style="width: 100%; height: 100%;" />
|
||
<div v-else style="width:90px;height:60px">
|
||
<img :src="noImageSvg" style="margin-left: 15px;">
|
||
<div style="font-size:14px;color:#ffffff99;text-align: center;">连接已断开!</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-col>
|
||
<el-col v-if="camera1Status !== '已连接' && camera2Status !== '已连接'" :span="6" style="flex: 0 0 24%;height: calc(100% - 0px);">
|
||
<div class="body-userinfo-box3">
|
||
<div class="body-title-display">
|
||
<div class="body-son-display">
|
||
<img src="@/assets/detection/title4.png" alt="" style="margin-right: 8px;">
|
||
<div class="body-posture-text">患者信息</div>
|
||
</div>
|
||
<div class="body-son-display"></div>
|
||
</div>
|
||
<div class="body-userinfo-content">
|
||
<div class="body-userinfo-content-top">
|
||
|
||
<img src="@/assets/detection/useredit.png" alt="" title="编辑患者信息"
|
||
class="userinfo-edit-img" style="cursor: pointer;" @click="handleEditUserInfo">
|
||
<div class="useravatar-box">
|
||
<img src="@/assets/detection/useravatar.svg" alt="">
|
||
</div>
|
||
<div>
|
||
<div class="userinfo-text-top">
|
||
<div class="userinfo-text1" style="margin-right:20px ;">{{ patientInfo.name }}</div>
|
||
<div class="userinfo-text2"> {{ patientInfo.gender }}</div>
|
||
<div class="userinfo-line"></div>
|
||
<div class="userinfo-text2">{{ calculateAge(patientInfo.birth_date) }}岁</div>
|
||
</div>
|
||
<div class="userinfo-text3">
|
||
ID:{{ patientInfo.id }}
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
<div class="body-userinfo-content-bottom0">
|
||
<div class="userinfo-disyplaypadding1 ">
|
||
<div class="userinfo-text4 padding10">出生日期</div>
|
||
<div class="userinfo-text5">
|
||
<span v-if="patientInfo && patientInfo.birth_date">
|
||
{{ formatDate(patientInfo.birth_date) }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding2">
|
||
<div class="userinfo-text4 padding10">身高</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.height ==''||patientInfo.height ==null ?'—':patientInfo.height}}cm
|
||
</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding1">
|
||
<div class="userinfo-text4 padding10">体重</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.weight ==''||patientInfo.weight ==null ?'—':patientInfo.weight}}kg
|
||
</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding2">
|
||
<div class="userinfo-text4 padding10">鞋码</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.shoe_size ==''||patientInfo.shoe_size ==null ?'—':patientInfo.shoe_size}}码</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding1">
|
||
<div class="userinfo-text4 padding10">电话</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.phone ==''||patientInfo.phone ==null ?'—':patientInfo.phone}}
|
||
</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding2">
|
||
<div class="userinfo-text4 padding10">民族</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.nationality ==''||patientInfo.nationality ==null ?'—':patientInfo.nationality}}</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding1">
|
||
<div class="userinfo-text4 padding10">身份证号</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.idcode ==''||patientInfo.idcode ==null ?'—':patientInfo.idcode}}
|
||
</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding2">
|
||
<div class="userinfo-text4 padding10">职业</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.occupation ==''||patientInfo.occupation ==null ?'—':patientInfo.occupation}}</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding3">
|
||
<div class="userinfo-text4 padding10">居住地</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.residence ==''||patientInfo.residence ==null ?'—':patientInfo.residence}}</div>
|
||
</div>
|
||
<div class="userinfo-disyplaypadding3">
|
||
<div class="userinfo-text4 padding10">邮箱</div>
|
||
<div class="userinfo-text5">
|
||
{{ patientInfo.email ==''||patientInfo.email ==null ?'—':patientInfo.email}}</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div v-show="false" class="body-video-box">
|
||
<div class="body-title-display">
|
||
<div class="body-son-display">
|
||
<img src="@/assets/detection/title5.png" alt="" style="margin-right: 8px;">
|
||
<div class="body-posture-text">视频</div>
|
||
</div>
|
||
<div class="body-son-display">
|
||
<img :src="cameraStatus == '已连接'?refresh_active:refresh" alt="" style="margin-right: 8px;cursor: pointer;"
|
||
title="重启视频"
|
||
@click="refreshClick('camera')">
|
||
<div class="connecttext" :style="{ color: cameraStatus == '已连接' ? '#3bf2c6' : '#808080' }">
|
||
{{ cameraStatus }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="body-video-content">
|
||
<div class="body-video-imgbox1" ref="camera1Ref" :class="(camera1Status === '已连接' && camera1ImgSrc)?'':'noImageSvg-bg'">
|
||
<div v-if="(camera1Status === '已连接' && camera1ImgSrc)"
|
||
@click="isBig1 = true" class="big-img">
|
||
<img src="@/assets/detection/big.png">
|
||
</div>
|
||
<img v-if="(camera1Status === '已连接' && camera1ImgSrc)" :src="camera1ImgSrc" alt="camera1"
|
||
style="width: 100%; height: 100%;" />
|
||
<div v-else style="width:90px;height:60px">
|
||
<img :src="noImageSvg" style="margin-left: 15px;">
|
||
<div style="font-size:14px;color:#ffffff99;text-align: center;">连接已断开!</div>
|
||
</div>
|
||
</div>
|
||
<div class="body-video-imgbox2" ref="camera2Ref" :class="(camera2Status === '已连接' && camera2ImgSrc)?'':'noImageSvg-bg'">
|
||
<div v-if="(camera2Status === '已连接' && camera2ImgSrc)"
|
||
@click="isBig2 = true" class="big-img">
|
||
<img src="@/assets/detection/big.png">
|
||
</div>
|
||
<img v-if="(camera2Status === '已连接' && camera2ImgSrc)" :src="camera2ImgSrc" alt="camera2"
|
||
style="width: 100%; height: 100%;" />
|
||
<div v-else style="width:90px;height:60px">
|
||
<img :src="noImageSvg" style="margin-left: 15px;">
|
||
<div style="font-size:14px;color:#ffffff99;text-align: center;">连接已断开!</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
<!-- 无操作退出提示 -->
|
||
<div class="pop-up-mask" v-if="isTip">
|
||
<div class="pop-up-tip-container">
|
||
<div class="pop-up-tip-header">
|
||
<div>提示</div>
|
||
<img src="@/assets/header/closepage.png" alt="" style="cursor: pointer;" @click="handleCancel">
|
||
</div>
|
||
<div class="pop-up-tip-text" v-if="!isVideoOperation">本次检测未截图或录像操作,不予存档记录!</div>
|
||
<div class="pop-up-tip-text" v-if="isVideoOperation">本次检测未截图操作,存档记录不可生成报告!</div>
|
||
<div class="tipconfirmbutton-box">
|
||
<el-button type="primary" class="tipconfirmbutton" @click="closeTipClick">确定</el-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 诊断信息 -->
|
||
<div class="pop-up-mask" v-if="isDiagnosticMessage">
|
||
<DiagnosticMessage @closeDiagnosticMessage="closeDiagnosticMessage"
|
||
:selectedPatient="patientInfo" v-if="isDiagnosticMessage"/>
|
||
</div>
|
||
|
||
<div class="pop-up-mask" v-if="isCloseCreat">
|
||
<PatientCreate @closecreatbox="closecreatbox" :patienttype="'edit'"
|
||
:selectedPatient="selectedPatient" v-if="isCloseCreat"/>
|
||
</div>
|
||
<div class="pop-up-mask" v-if="cameraDialogVisible">
|
||
<div class="pop-up-camera-container">
|
||
<div class="pop-up-camera-header">
|
||
<div>相机参数设置</div>
|
||
<img src="@/assets/close.png" alt="" style="cursor: pointer;" @click="handleCameraCancel">
|
||
</div>
|
||
<div class="pop-up-camera-body">
|
||
<div class="pop-up-camera-display">
|
||
<div class="pop-up-camera-line"></div>
|
||
<div class="pop-up-camera-title">深度相机</div>
|
||
</div>
|
||
<div class="pop-up-camera-display" style="padding-top: 10px;">
|
||
<div class="pop-up-camera-name">距离范围</div>
|
||
<el-input v-model="cameraForm.femtobolt.depth_range_min" placeholder="请输入最小值"
|
||
style="width: 200px;" />
|
||
<div style="margin: 0 10px;">至</div>
|
||
<el-input v-model="cameraForm.femtobolt.depth_range_max" placeholder="请输入最大值"
|
||
style="width: 200px;"/>
|
||
<el-checkbox v-model="cameraForm.femtobolt.enable" label="有效" size="large" style="width: 60px;margin-left:10px ;" />
|
||
|
||
</div>
|
||
<div class="pop-up-camera-display" style="padding-top: 30px;padding-bottom: 10px;">
|
||
<div class="pop-up-camera-line"></div>
|
||
<div class="pop-up-camera-title">足部相机</div>
|
||
</div>
|
||
<div class="pop-up-camera-display" style="padding-top: 0px;">
|
||
<div class="pop-up-camera-name">相机上</div>
|
||
<el-radio-group v-model="cameraForm.camera1.device_index">
|
||
<div style="display: flex;justify-content: space-between;width: 435px;">
|
||
<el-radio :value="0" border>1号</el-radio>
|
||
<el-radio :value="1" border>2号</el-radio>
|
||
<el-radio :value="2" border>3号</el-radio>
|
||
<el-radio :value="3" border>4号</el-radio>
|
||
<el-radio :value="4" border>5号</el-radio>
|
||
</div>
|
||
</el-radio-group>
|
||
<el-checkbox v-model="cameraForm.camera1.enable" label="有效" size="large" style="width: 60px;margin-left:10px ;" />
|
||
</div>
|
||
<div class="pop-up-camera-display" style="padding-top: 20px;">
|
||
<div class="pop-up-camera-name">相机下</div>
|
||
<el-radio-group v-model="cameraForm.camera2.device_index">
|
||
<div style="display: flex;justify-content: space-between;width: 435px;">
|
||
<el-radio :value="0" border>1号</el-radio>
|
||
<el-radio :value="1" border>2号</el-radio>
|
||
<el-radio :value="2" border>3号</el-radio>
|
||
<el-radio :value="3" border>4号</el-radio>
|
||
<el-radio :value="4" border>5号</el-radio>
|
||
</div>
|
||
</el-radio-group>
|
||
<el-checkbox v-model="cameraForm.camera2.enable" label="有效" size="large" style="width: 60px;margin-left:10px ;" />
|
||
</div>
|
||
<div class="pop-up-camera-display" style="padding-top: 30px;padding-bottom: 00px;">
|
||
<div class="pop-up-camera-line"></div>
|
||
<div class="pop-up-camera-title">遥控器</div>
|
||
</div>
|
||
<div class="pop-up-camera-display" style="padding-top: 10px;">
|
||
<div class="pop-up-camera-name">串口号</div>
|
||
<el-select v-model="cameraForm.remote.port" placeholder="请选择" style="width: 434px;">
|
||
<el-option v-for="item in remotePortData" :label="item" :value="item" />
|
||
</el-select>
|
||
<el-checkbox v-model="cameraForm.remote.enable" label="有效" size="large" style="width: 60px;margin-left:10px ;" />
|
||
|
||
</div>
|
||
|
||
<div class="pop-up-camera-display" style="padding-top: 30px;padding-bottom: 00px;">
|
||
<div class="pop-up-camera-line"></div>
|
||
<div class="pop-up-camera-title">IMU设备</div>
|
||
</div>
|
||
<div class="pop-up-camera-display" style="padding-top: 10px;">
|
||
<div class="pop-up-camera-name">Mac地址</div>
|
||
<el-input v-model="cameraForm.imu.mac_address" placeholder="请输入"
|
||
style="width: 434px;" />
|
||
<el-checkbox v-model="cameraForm.imu.enable" label="有效" size="large" style="width: 60px;margin-left:10px ;" />
|
||
|
||
</div>
|
||
|
||
<div class="form-actions-display">
|
||
<el-button @click="handleCameraCancel" class="formreturnCancel">退出</el-button>
|
||
<el-button type="primary" class="formsaveCancel"
|
||
@click="cameraSubmit('cameraForm')">
|
||
保存
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="pop-up-mask" v-if="isPhotoAlbum">
|
||
<PhotoAlbum @closePhotoAlbum="closePhotoAlbum"
|
||
:selectedPatient="patientInfo" v-if="isPhotoAlbum"/>
|
||
</div>
|
||
|
||
<PatientProfile v-if="historyDialogVisible"
|
||
:archiveType="true"
|
||
:selectedPatient="selectedPatient"
|
||
@closePatientProfile="closePatientProfile"/>
|
||
|
||
<div v-if="isBig1" style="position: fixed;top: 122px;right: 0;
|
||
width: 100%;height:calc(100% - 122px);z-index: 9999;
|
||
background: linear-gradient(135deg, #1a1e2a 0%, #222b38 100%);
|
||
border: 1px solid #262d40;">
|
||
<svg @click="isBig1=false" style="position: absolute;right: 10px;top:10px;cursor: pointer;" t="1760175800150" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5743" width="24" height="24"><path d="M796 163.1L511.1 448l-285-285-63.9 64 285 285-285 285 63.8 63.8 285-285 285 285 63.8-63.8-285-285 285-285-63.8-63.9z" fill="#ffffff" p-id="5744"></path></svg>
|
||
<img v-if="isBig1" :src="camera1ImgSrc" alt=""
|
||
style="width: 100%;height: calc(100%);object-fit:contain;background:#323232;" />
|
||
</div>
|
||
|
||
<div v-if="isBig2" style="position: fixed;top: 122px;right: 0;
|
||
width: 100%;height:calc(100% - 122px);z-index: 9999;
|
||
background: linear-gradient(135deg, #1a1e2a 0%, #222b38 100%);
|
||
border: 1px solid #262d40;">
|
||
<svg @click="isBig2=false" style="position: absolute;right: 10px;top:10px;cursor: pointer;" t="1760175800150" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5743" width="24" height="24"><path d="M796 163.1L511.1 448l-285-285-63.9 64 285 285-285 285 63.8 63.8 285-285 285 285 63.8-63.8-285-285 285-285-63.8-63.9z" fill="#ffffff" p-id="5744"></path></svg>
|
||
<img v-if="isBig2" :src="camera2ImgSrc" alt=""
|
||
style="width: 100%;height: calc(100%);object-fit:contain;background:#323232;" />
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
|
||
import { ElMessage } from 'element-plus'
|
||
import { useRouter, useRoute } from 'vue-router'
|
||
import { io } from 'socket.io-client'
|
||
import Header from '@/views/Header.vue'
|
||
import { useAuthStore } from '../stores/index.js'
|
||
import * as echarts from 'echarts'
|
||
import { getBackendUrl, patientAPI } from '../services/api.js'
|
||
import noImageSvg from '@/assets/detection/no-image.png'
|
||
import DiagnosticMessage from '@/views/DiagnosticMessage.vue'
|
||
import PatientCreate from '@/views/PatientCreate.vue'
|
||
import PatientProfile from '@/views/PatientProfile.vue'
|
||
import Model from './model.vue'
|
||
import ViewUserInfo from '@/views/ViewUser.vue'
|
||
import PhotoAlbum from '@/views/PhotoAlbum.vue'
|
||
|
||
import refresh from '@/assets/detection/refresh.png'
|
||
import refresh_active from '@/assets/detection/refresh_active.png'
|
||
const emit = defineEmits([ 'endChange']);
|
||
const props = defineProps({
|
||
selectedPatient: {
|
||
required: false,
|
||
type: Object,
|
||
default: null
|
||
},
|
||
})
|
||
const remotePortData =ref([]) // 遥控器串口数据
|
||
const videoNum = ref(2) // 视频数量
|
||
const videoTop = ref(1) // 视频位置
|
||
const isExpand = ref(false) // 是否展开
|
||
const isViewUser = ref(false) // 查看信息
|
||
const isCloseCreat =ref(false) // 是否打开患者信息编辑框
|
||
const isoperation = ref(false) // 是否保存数据
|
||
const isVideoOperation = ref(false) // 是否录制视频
|
||
const isTip =ref(false)
|
||
const isStartVideo = ref(false)
|
||
function startVideoClick() {
|
||
startRecord()
|
||
isVideoOperation.value = true
|
||
isStartVideo.value = true
|
||
}
|
||
function stopVideoClick() {
|
||
stopRecord()
|
||
isStartVideo.value = false
|
||
}
|
||
function endClick() {
|
||
if(isoperation.value == true){
|
||
isDiagnosticMessage.value = true
|
||
}else{
|
||
isTip.value = true
|
||
}
|
||
}
|
||
function handleCancel(){
|
||
isTip.value = false
|
||
}
|
||
function handleCameraCancel(){
|
||
cameraDialogVisible.value = false
|
||
}
|
||
async function closeTipClick(){
|
||
try {
|
||
if (timerId.value) {
|
||
clearInterval(timerId.value)
|
||
timerId.value = null
|
||
}
|
||
if (isRecording.value === true) {
|
||
stopRecord()
|
||
}
|
||
await stopDetection({})
|
||
disconnectWebSocket()
|
||
window.removeEventListener('beforeunload', handleBeforeUnload)
|
||
emit('endChange',false)
|
||
routeTo('/')
|
||
} catch (error) {
|
||
emit('endChange',false)
|
||
}
|
||
}
|
||
|
||
const isDiagnosticMessage = ref(false)
|
||
|
||
const isBig1 =ref(false)
|
||
const isBig2 =ref(false)
|
||
const authStore = useAuthStore()
|
||
const router = useRouter()
|
||
const isRecording = ref(false)
|
||
const isConnected = ref(false)
|
||
const rtspImgSrc = ref('')
|
||
const camera1ImgSrc = ref('')
|
||
const camera2ImgSrc = ref('')
|
||
const depthCameraImgSrc = ref('') // 深度相机视频流
|
||
const screenshotLoading = ref(false)
|
||
const cameraDialogVisible =ref(false) // 设置相机参数弹框
|
||
const contenGridRef =ref(null) // 实时检查整体box
|
||
const wholeBodyRef = ref(null) // 身体姿态ref
|
||
const camera1Ref = ref(null) // 视频1ref
|
||
const camera2Ref = ref(null) // 视频2ref
|
||
const pressureRef = ref(null) // 足底压力ref
|
||
const imuHeaderRef = ref(null) // IMUref
|
||
const historyDialogVisible = ref(false)
|
||
// 患者信息(从页面获取或通过API获取)
|
||
const patientInfo = ref({
|
||
id: '',
|
||
name: '',
|
||
gender: '',
|
||
birth_date: '',
|
||
age: '',
|
||
height: '',
|
||
weight: '',
|
||
shoe_size: '',
|
||
phone: '',
|
||
nationality: '',
|
||
created_at: '',
|
||
sessionId: null // 检查记录ID,实际使用时应从路由或API获取
|
||
})
|
||
|
||
// WebSocket相关变量
|
||
let socket = null
|
||
let devicesSocket = null
|
||
let cameraSocket = null
|
||
let femtoboltSocket = null
|
||
let imuSocket = null
|
||
let pressureSocket = null
|
||
let restartSocket = null
|
||
let frameCount = 0
|
||
let latestFrameCamera1 = ''
|
||
let latestFrameCamera2 = ''
|
||
let rafScheduled = false
|
||
let lastRenderTs = 0
|
||
const MAX_RENDER_FPS = 30
|
||
|
||
// 后端服务器地址配置
|
||
const BACKEND_URL = getBackendUrl()
|
||
const resDialogVisible = ref(false)
|
||
const dialogVisible = ref(false)
|
||
// 表单引用
|
||
const patientFormRef = ref()
|
||
// 表单数据
|
||
const saveLoading = ref(false)
|
||
const patientForm = ref({
|
||
id: '',
|
||
name: '',
|
||
gender: '',
|
||
birth_date: '',
|
||
nationality: '',
|
||
residence: '',
|
||
height: '',
|
||
weight: '',
|
||
shoe_size: '',
|
||
phone: '',
|
||
occupation: '',
|
||
workplace: '',
|
||
email: ''
|
||
})
|
||
const occupationOptions = ref(["学生", "教师", "医生", "护士", "工程师", "程序员", "设计师",
|
||
"会计师", "律师", "警察", "消防员", "军人", "公务员", "销售", "市场营销",
|
||
"人力资源", "行政", "财务", "咨询师", "建筑师", "科研人员", "记者", "编辑",
|
||
"作家", "艺术家", "音乐家", "演员", "导演", "摄影师", "厨师", "服务员",
|
||
"司机", "快递员", "外卖员", "农民", "工人", "电工", "焊工", "机械师",
|
||
"飞行员", "空乘", "船员", "导游", "翻译", "心理咨询师", "社会工作者",
|
||
"运动员", "教练", "经纪人", "投资人", "企业家", "自由职业者"])
|
||
const nationalityOptions = ref(["汉族", "满族", "蒙古族", "回族", "藏族", "维吾尔族", "苗族", "彝族", "壮族",
|
||
"布依族", "朝鲜族", "侗族", "瑶族", "白族", "土家族", "哈尼族", "哈萨克族", "傣族",
|
||
"黎族", "傈僳族", "佤族", "畲族", "高山族", "拉祜族", "水族", "东乡族", "纳西族",
|
||
"景颇族", "柯尔克孜族", "土族", "达斡尔族", "仫佬族", "羌族", "布朗族", "撒拉族",
|
||
"毛南族", "仡佬族", "锡伯族", "阿昌族", "普米族", "塔吉克族", "怒族", "乌孜别克族",
|
||
"俄罗斯族", "鄂温克族", "德昂族", "保安族", "裕固族", "京族", "塔塔尔族", "独龙族",
|
||
"鄂伦春族", "赫哲族", "门巴族", "珞巴族", "基诺族"])
|
||
const diagnosticForm = ref({
|
||
diagnosis_info: '',
|
||
treatment_info: '',
|
||
suggestion_info: ''
|
||
})
|
||
const cameraForm = ref({ // 相机参数
|
||
camera1:{
|
||
device_index: '', // 序号
|
||
},
|
||
camera2:{
|
||
device_index: '', // 序号
|
||
},
|
||
femtobolt:{
|
||
algorithm_type: '', // 算法类型
|
||
depth_mode: '', // 相机模式
|
||
depth_range_min: '', // 距离范围最小值
|
||
depth_range_max: '', // 距离范围最大值
|
||
},
|
||
imu:{
|
||
mac_address: '', // IMU串口号
|
||
enable: false
|
||
},
|
||
remote:{
|
||
port: '', // 遥控器串口号
|
||
enable: false
|
||
},
|
||
|
||
})
|
||
const calculatedAge = ref(null)
|
||
//修改
|
||
|
||
// 模拟历史数据
|
||
const historyData = ref([])
|
||
const chartoption = ref({
|
||
backgroundColor: '#242424',
|
||
grid: { top: 0, right: 0, bottom: 0, left: 0 },
|
||
animation: false,
|
||
series: [
|
||
{
|
||
type: 'gauge',
|
||
radius: '180%',
|
||
center: ['50%', '91%'],
|
||
startAngle: 180,
|
||
endAngle: 0,
|
||
min: -90,
|
||
max: 90,
|
||
splitNumber: 10,
|
||
itemStyle: {
|
||
color: '#58D9F9',
|
||
shadowColor: 'rgba(0,138,255,0.45)',
|
||
shadowBlur: 10,
|
||
shadowOffsetX: 2,
|
||
shadowOffsetY: 2
|
||
},
|
||
progress: {
|
||
show: false,
|
||
},
|
||
pointer: {
|
||
length: '85%',
|
||
width: 2,
|
||
offsetCenter: [0, '5%'],
|
||
itemStyle: {
|
||
color: '#ff8d00'
|
||
},
|
||
},
|
||
anchor: {
|
||
show: true,
|
||
showAbove: true,
|
||
size: 18,
|
||
icon: 'circle',
|
||
itemStyle: {
|
||
borderWidth: 3,
|
||
color: '#ff8d00',
|
||
borderColor: '#ff8d00',
|
||
}
|
||
},
|
||
axisLine: {
|
||
roundCap: true,
|
||
lineStyle: {
|
||
width: 3,
|
||
color: [[1, '#0089ff']]
|
||
}
|
||
},
|
||
axisTick: {
|
||
splitNumber: 2,
|
||
lineStyle: {
|
||
width: 2,
|
||
color: '#ffffff'
|
||
}
|
||
},
|
||
splitLine: {
|
||
length: 12,
|
||
lineStyle: {
|
||
width: 4,
|
||
color: '#0089ff'
|
||
}
|
||
},
|
||
axisLabel: {
|
||
//刻度颜色
|
||
distance: 10,
|
||
color: '#ffffff',
|
||
fontSize: 10
|
||
},
|
||
title: {
|
||
show: false
|
||
},
|
||
detail: {
|
||
show: false,
|
||
},
|
||
data: [
|
||
{
|
||
value: 0
|
||
}
|
||
]
|
||
}
|
||
]
|
||
})
|
||
// 四个设备的独立连接状态
|
||
const camera1Status = ref('未连接')
|
||
const camera2Status = ref('未连接')
|
||
const cameraStatus = computed(() => (camera1Status.value === '已连接' || camera2Status.value === '已连接') ? '已连接' : '未连接')
|
||
const femtoboltStatus = ref('未连接') // 深度相机(FemtoBolt)设备状态
|
||
const imuStatus = ref('未连接') // IMU设备状态
|
||
const pressureStatus = ref('未连接') // 压力传感器设备状态
|
||
const remoteStatus = ref('未连接') // 遥控器设备状态
|
||
|
||
// 为了向后兼容,保留videoStatus但映射到cameraStatus
|
||
const videoStatus = computed(() => cameraStatus.value)
|
||
// 计时器状态
|
||
const seconds = ref(0);
|
||
const isRunning = ref(false);
|
||
const timerId = ref(null);
|
||
const blinkState = ref(false);
|
||
const formattedTime = computed(() => {
|
||
|
||
const mins = Math.floor(seconds.value / 60);
|
||
const secs = seconds.value % 60;
|
||
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
||
});
|
||
const headlist = ref({
|
||
rotation: 0,
|
||
tilt: 0,
|
||
pitch: 0
|
||
})
|
||
// 开始计时器
|
||
const startTimer = () => {
|
||
if (isRunning.value) return;
|
||
|
||
isRunning.value = true;
|
||
seconds.value = 0;
|
||
|
||
// 使用高性能计时器
|
||
const startTime = Date.now();
|
||
|
||
timerId.value = setInterval(() => {
|
||
// 计算经过的时间(毫秒)
|
||
const elapsed = Date.now() - startTime;
|
||
// 转换为秒并四舍五入
|
||
seconds.value = Math.round(elapsed / 1000);
|
||
|
||
// 检测时长超过10分钟(1800秒)自动停止检测
|
||
if (seconds.value >= 1800) {
|
||
console.log('⏰ 检测时长超过30分钟,自动停止检测');
|
||
ElMessage.warning('检测时长已达到30分钟,自动停止检测');
|
||
stopRecord()
|
||
return;
|
||
}
|
||
|
||
// 触发闪烁效果
|
||
blinkState.value = !blinkState.value;
|
||
}, 1000);
|
||
};
|
||
// 重置计时器
|
||
const resetTimer = () => {
|
||
if (timerId.value) {
|
||
clearInterval(timerId.value);
|
||
timerId.value = null;
|
||
}
|
||
isRunning.value = false;
|
||
seconds.value = 0;
|
||
blinkState.value = false;
|
||
};
|
||
const validateForm = async () => {
|
||
try {
|
||
await patientFormRef.value.validate()
|
||
return true
|
||
} catch (error) {
|
||
ElMessage.error('请完善必填信息')
|
||
return false
|
||
}
|
||
}
|
||
//保存基础信息
|
||
const handleSave = async () => {
|
||
if (!(await validateForm())) return
|
||
saveLoading.value = true
|
||
try {
|
||
await savePatient()
|
||
ElMessage.success('修改成功')
|
||
dialogVisible.value = false
|
||
saveLoading.value = false
|
||
loadPatientInfo()
|
||
} catch (error) {
|
||
ElMessage.error('修改失败:' + error.message)
|
||
saveLoading.value = false
|
||
} finally {
|
||
saveLoading.value = false
|
||
}
|
||
}
|
||
const savePatient = async () => {
|
||
const patientData = {
|
||
id: patientForm.value.id,
|
||
name: patientForm.value.name,
|
||
gender: patientForm.value.gender,
|
||
age: calculatedAge.value,
|
||
birth_date: patientForm.value.birth_date,
|
||
height: patientForm.value.height,
|
||
weight: patientForm.value.weight,
|
||
shoe_size: patientForm.value.shoe_size,
|
||
phone: patientForm.value.phone,
|
||
occupation: patientForm.value.occupation,
|
||
email: patientForm.value.email,
|
||
nationality: patientForm.value.nationality,
|
||
residence: patientForm.value.residence,
|
||
workplace: patientForm.value.workplace
|
||
}
|
||
try {
|
||
const response = await patientAPI.updatePatient(patientForm.value.id, patientData)
|
||
if (response.success) {
|
||
return response.data
|
||
} else {
|
||
throw new Error(response.message || '修改失败')
|
||
}
|
||
} catch (error) {
|
||
throw error
|
||
}
|
||
}
|
||
const isPreventCombo = ref(false)
|
||
function routeTo(path = '/') {
|
||
if (isPreventCombo.value === true) {
|
||
ElMessage.warning(`请勿连续点击回退按钮!`)
|
||
return
|
||
}
|
||
isPreventCombo.value = true
|
||
setTimeout(() => {
|
||
isPreventCombo.value = false
|
||
}, 2000)
|
||
router.push(path)
|
||
}
|
||
function cameraUpdate() { // 相机设置数据更新弹框
|
||
cameraForm.value = { // 相机参数
|
||
camera1:{
|
||
device_index: '', // 序号1
|
||
},
|
||
camera2:{
|
||
device_index: '', // 序号2
|
||
},
|
||
femtobolt:{
|
||
algorithm_type: '', // 算法类型
|
||
depth_mode: '', // 相机模式
|
||
depth_range_min: '', // 距离范围最小值
|
||
depth_range_max: '', // 距离范围最大值
|
||
},
|
||
imu:{
|
||
mac_address: '', // IMU串口号
|
||
enable: false
|
||
},
|
||
remote:{
|
||
port: '', // IMU串口号
|
||
}
|
||
}
|
||
// 加载相机参数信息
|
||
getDevicesInit()
|
||
|
||
}
|
||
const calculateAge = (birthDate) => {
|
||
if (!birthDate) return '—'
|
||
const today = new Date()
|
||
const birth = new Date(birthDate)
|
||
let age = today.getFullYear() - birth.getFullYear()
|
||
const monthDiff = today.getMonth() - birth.getMonth()
|
||
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
|
||
age--
|
||
}
|
||
return age
|
||
}
|
||
const tempInfo = ref({})
|
||
// WebSocket连接函数
|
||
function connectWebSocket() {
|
||
try {
|
||
console.log('正在连接到', BACKEND_URL)
|
||
|
||
// 如果已有连接,先断开
|
||
if (socket) {
|
||
socket.disconnect()
|
||
socket = null
|
||
}
|
||
if (cameraSocket) {
|
||
cameraSocket.disconnect()
|
||
cameraSocket = null
|
||
}
|
||
if (femtoboltSocket) {
|
||
femtoboltSocket.disconnect()
|
||
femtoboltSocket = null
|
||
}
|
||
if (imuSocket) {
|
||
imuSocket.disconnect()
|
||
imuSocket = null
|
||
}
|
||
if (pressureSocket) {
|
||
pressureSocket.disconnect()
|
||
pressureSocket = null
|
||
}
|
||
if (restartSocket) {
|
||
restartSocket.disconnect()
|
||
restartSocket = null
|
||
}
|
||
|
||
|
||
// 创建主Socket.IO连接
|
||
socket = io(BACKEND_URL, {
|
||
transports: ['websocket'],
|
||
timeout: 10000,
|
||
forceNew: true,
|
||
reconnection: true,
|
||
reconnectionAttempts: 5,
|
||
reconnectionDelay: 1000
|
||
})
|
||
|
||
// 创建统一设备命名空间连接
|
||
devicesSocket = io(BACKEND_URL + '/devices', {
|
||
transports: ['websocket'],
|
||
timeout: 10000,
|
||
forceNew: true
|
||
})
|
||
|
||
// 为了保持兼容性,将统一的设备socket赋值给各个设备变量
|
||
cameraSocket = devicesSocket
|
||
femtoboltSocket = devicesSocket
|
||
imuSocket = devicesSocket
|
||
pressureSocket = devicesSocket
|
||
restartSocket = devicesSocket
|
||
// 主连接事件
|
||
socket.on('connect', () => {
|
||
console.log('✅ 主WebSocket连接成功!Socket ID:', socket.id)
|
||
isConnected.value = true
|
||
//绘制头部仪表盘
|
||
initchart()
|
||
})
|
||
|
||
socket.on('connect_error', (error) => {
|
||
console.error('❌ 主连接失败:', error.message)
|
||
isConnected.value = false
|
||
})
|
||
|
||
socket.on('disconnect', (reason) => {
|
||
console.log('⚠️ 主连接断开:', reason)
|
||
isConnected.value = false
|
||
})
|
||
|
||
socket.on('reconnect', (attemptNumber) => {
|
||
console.log('🔄 主WebSocket重连成功,尝试次数:', attemptNumber)
|
||
isConnected.value = true
|
||
})
|
||
|
||
socket.on('reconnect_attempt', (attemptNumber) => {
|
||
console.log('🔄 正在尝试重连主连接...', attemptNumber)
|
||
})
|
||
|
||
socket.on('reconnect_failed', () => {
|
||
console.error('❌ 主WebSocket重连失败')
|
||
isConnected.value = false
|
||
})
|
||
|
||
|
||
|
||
socket.on('error', (error) => {
|
||
console.error('❌ 主Socket错误:', error)
|
||
})
|
||
|
||
// 统一设备命名空间事件监听
|
||
devicesSocket.on('connect', () => {
|
||
console.log('🔗 设备命名空间连接成功')
|
||
|
||
const enabledCameras = (() => {
|
||
const list = []
|
||
const idx1 = String((cameraForm.value && cameraForm.value.camera1 && cameraForm.value.camera1.device_index) || '').trim()
|
||
const idx2 = String((cameraForm.value && cameraForm.value.camera2 && cameraForm.value.camera2.device_index) || '').trim()
|
||
if (idx1 !== '') list.push('camera1')
|
||
if (idx2 !== '') list.push('camera2')
|
||
if (list.length === 0) list.push('camera1')
|
||
return list
|
||
})()
|
||
enabledCameras.forEach(t => devicesSocket.emit('subscribe_device', { device_type: t }))
|
||
devicesSocket.emit('subscribe_device', { device_type: 'femtobolt' })
|
||
devicesSocket.emit('subscribe_device', { device_type: 'imu' })
|
||
devicesSocket.emit('subscribe_device', { device_type: 'pressure' })
|
||
devicesSocket.emit('subscribe_device', { device_type: 'remote' })
|
||
|
||
// 设备连接成功后启动数据推送
|
||
startDeviceDataPush()
|
||
})
|
||
|
||
devicesSocket.on('disconnect', () => {
|
||
console.log('🔗 设备命名空间断开连接')
|
||
// 断开连接时重置所有设备状态
|
||
camera1Status.value = '未连接'
|
||
camera2Status.value = '未连接'
|
||
femtoboltStatus.value = '未连接'
|
||
imuStatus.value = '未连接'
|
||
pressureStatus.value = '未连接'
|
||
})
|
||
|
||
devicesSocket.on('connect_error', (error) => {
|
||
console.error('❌ 设备命名空间连接失败:', error.message)
|
||
})
|
||
|
||
// 监听设备重启消息事件
|
||
devicesSocket.on('device_restart_message', (data) => {
|
||
ElMessage.success({
|
||
message: data.message,
|
||
duration: 1000
|
||
})
|
||
})
|
||
|
||
// 监听各设备数据事件
|
||
devicesSocket.on('camera_frame', (data) => {
|
||
frameCount++
|
||
// 区分 camera1 / camera2 帧
|
||
const devId = (data && data.device_id) ? String(data.device_id).toLowerCase() : ''
|
||
if (!tempInfo.value.camera_frames) {
|
||
tempInfo.value.camera_frames = {}
|
||
}
|
||
if (devId === 'camera1') {
|
||
tempInfo.value.camera_frames['camera1'] = data
|
||
tempInfo.value.camera1_frame = data
|
||
displayCameraFrameById('camera1', data.image)
|
||
} else if (devId === 'camera2') {
|
||
tempInfo.value.camera_frames['camera2'] = data
|
||
tempInfo.value.camera2_frame = data
|
||
displayCameraFrameById('camera2', data.image)
|
||
}
|
||
})
|
||
|
||
devicesSocket.on('femtobolt_frame', (data) => {
|
||
tempInfo.value.femtobolt_frame = data
|
||
displayDepthCameraFrame(data.depth_image || data.image)
|
||
})
|
||
|
||
devicesSocket.on('imu_data', (data) => {
|
||
tempInfo.value.imu_data = data
|
||
handleIMUData(data)
|
||
})
|
||
|
||
devicesSocket.on('pressure_data', (data) => {
|
||
tempInfo.value.pressure_data = data
|
||
handlePressureData(data)
|
||
})
|
||
|
||
// 监听遥控器事件:根据编码触发页面方法
|
||
devicesSocket.on('remote_control', (data) => {
|
||
const code = String((data && data.code) || '').toUpperCase()
|
||
switch (code) {
|
||
case '11':
|
||
startVideoClick()
|
||
break
|
||
case '14':
|
||
stopVideoClick()
|
||
break
|
||
case '12':
|
||
clearAndStartTracking()
|
||
break
|
||
case '0F':
|
||
saveDetectionData()
|
||
break
|
||
default:
|
||
break
|
||
}
|
||
})
|
||
|
||
// 监听测试状态事件
|
||
devicesSocket.on('test_status', (data) => {
|
||
console.log('📊 测试状态:', data)
|
||
if (data.status === 'started') {
|
||
console.log('✅ 设备数据推送已开始')
|
||
} else if (data.status === 'stopped') {
|
||
console.log('⏹️ 设备数据推送已停止')
|
||
} else if (data.status === 'error') {
|
||
console.error('❌ 设备数据推送错误:', data.message)
|
||
}
|
||
})
|
||
|
||
// 监听各设备独立连接状态事件
|
||
devicesSocket.on('device_status', (data) => {
|
||
console.log('📱 设备状态更新:', data)
|
||
const { device_type, status } = data
|
||
const statusText = status ? '已连接' : '未连接'
|
||
|
||
switch (device_type) {
|
||
case 'camera1':
|
||
camera1Status.value = statusText
|
||
console.log(`📷 相机1状态: ${statusText}`)
|
||
break
|
||
case 'camera2':
|
||
camera2Status.value = statusText
|
||
console.log(`📷 相机2状态: ${statusText}`)
|
||
break
|
||
case 'femtobolt':
|
||
femtoboltStatus.value = statusText
|
||
console.log(`🔍 深度相机状态: ${statusText}`)
|
||
break
|
||
case 'imu':
|
||
imuStatus.value = statusText
|
||
console.log(`🧭 IMU状态: ${statusText}`)
|
||
break
|
||
case 'pressure':
|
||
pressureStatus.value = statusText
|
||
console.log(`⚖️ 压力传感器状态: ${statusText}`)
|
||
break
|
||
case 'remote':
|
||
remoteStatus.value = statusText
|
||
console.log(`📡 遥控器状态: ${statusText}`)
|
||
break
|
||
default:
|
||
console.warn('⚠️ 未知设备类型:', device_type)
|
||
}
|
||
})
|
||
|
||
} catch (error) {
|
||
console.error('💥 连接异常:', error.message)
|
||
isConnected.value = false
|
||
}
|
||
}
|
||
|
||
// 启动设备数据推送
|
||
function startDeviceDataPush() {
|
||
if (devicesSocket && devicesSocket.connected) {
|
||
console.log('🚀 发送启动设备数据推送请求...')
|
||
devicesSocket.emit('start_push_data')
|
||
} else {
|
||
console.warn('⚠️ 设备Socket未连接,无法启动设备数据推送')
|
||
}
|
||
}
|
||
|
||
// 断开WebSocket连接函数
|
||
function disconnectWebSocket() {
|
||
try {
|
||
if (socket) {
|
||
if (socket.connected) {
|
||
console.log('正在主动断开WebSocket连接...')
|
||
|
||
// 移除所有事件监听器
|
||
socket.removeAllListeners()
|
||
|
||
// 断开主连接
|
||
socket.disconnect()
|
||
|
||
console.log('✅ 主WebSocket连接已断开')
|
||
}
|
||
socket = null
|
||
isConnected.value = false
|
||
}
|
||
|
||
// 断开统一设备命名空间连接
|
||
if (devicesSocket) {
|
||
if (devicesSocket.connected) {
|
||
// 取消订阅所有设备
|
||
// try {
|
||
// devicesSocket.emit('unsubscribe_device', { device_type: 'camera1' })
|
||
// devicesSocket.emit('unsubscribe_device', { device_type: 'camera2' })
|
||
// devicesSocket.emit('unsubscribe_device', { device_type: 'femtobolt' })
|
||
// devicesSocket.emit('unsubscribe_device', { device_type: 'imu' })
|
||
// devicesSocket.emit('unsubscribe_device', { device_type: 'pressure' })
|
||
// } catch (e) {
|
||
// console.warn('取消设备订阅时出错:', e)
|
||
// }
|
||
|
||
// 移除所有事件监听器
|
||
devicesSocket.removeAllListeners()
|
||
|
||
// 断开连接
|
||
devicesSocket.disconnect()
|
||
|
||
console.log('🔗 统一设备命名空间连接已断开')
|
||
}
|
||
|
||
devicesSocket = null
|
||
cameraSocket = null
|
||
femtoboltSocket = null
|
||
imuSocket = null
|
||
pressureSocket = null
|
||
restartSocket = null
|
||
}
|
||
|
||
// 重置所有设备状态
|
||
camera1Status.value = '未连接'
|
||
camera2Status.value = '未连接'
|
||
femtoboltStatus.value = '未连接'
|
||
imuStatus.value = '未连接'
|
||
pressureStatus.value = '未连接'
|
||
|
||
} catch (error) {
|
||
console.warn('断开WebSocket连接时出错:', error)
|
||
}
|
||
}
|
||
|
||
// WebSocket重连函数
|
||
function reconnectWebSocket() {
|
||
console.log('开始重新连接WebSocket...')
|
||
|
||
// 先断开现有连接
|
||
disconnectWebSocket()
|
||
|
||
// 延迟一段时间后重新连接
|
||
setTimeout(() => {
|
||
connectWebSocket()
|
||
console.log('WebSocket重连完成')
|
||
}, 1000)
|
||
}
|
||
|
||
|
||
|
||
|
||
// 简单的帧显示函数
|
||
function displayFrame(base64Image) {
|
||
// 兼容旧调用:默认作为 camera1 更新
|
||
displayCameraFrameById('camera1', base64Image)
|
||
}
|
||
|
||
function displayCameraFrameById(deviceId, base64Image) {
|
||
if (base64Image && base64Image.length > 0) {
|
||
if (String(deviceId).toLowerCase() === 'camera2') {
|
||
latestFrameCamera2 = base64Image
|
||
} else {
|
||
latestFrameCamera1 = base64Image
|
||
}
|
||
if (!rafScheduled) {
|
||
rafScheduled = true
|
||
requestAnimationFrame(() => {
|
||
const now = (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now()
|
||
const minInterval = 1000 / MAX_RENDER_FPS
|
||
if (now - lastRenderTs >= minInterval) {
|
||
if (latestFrameCamera1) {
|
||
const url1 = 'data:image/jpeg;base64,' + latestFrameCamera1
|
||
camera1ImgSrc.value = url1
|
||
rtspImgSrc.value = url1
|
||
}
|
||
if (latestFrameCamera2) {
|
||
const url2 = 'data:image/jpeg;base64,' + latestFrameCamera2
|
||
camera2ImgSrc.value = url2
|
||
}
|
||
lastRenderTs = now
|
||
}
|
||
rafScheduled = false
|
||
})
|
||
}
|
||
} else {
|
||
console.warn('⚠️ 收到空的视频帧数据')
|
||
}
|
||
}
|
||
|
||
// 深度相机帧显示函数
|
||
function displayDepthCameraFrame(base64Image) {
|
||
if (base64Image && base64Image.length > 0) {
|
||
depthCameraImgSrc.value = 'data:image/jpeg;base64,' + base64Image
|
||
} else {
|
||
console.warn('⚠️ 收到空的深度相机帧数据')
|
||
}
|
||
}
|
||
|
||
|
||
// 头部姿态最值跟踪数据
|
||
const headPoseMaxValues = ref({
|
||
rotationLeftMax: 0, // 旋转-左旋最大值
|
||
rotationRightMax: 0, // 旋转-右旋最大值
|
||
tiltLeftMax: 0, // 倾斜-左倾最大值
|
||
tiltRightMax: 0, // 倾斜-右倾最大值
|
||
pitchUpMax: 0, // 俯仰-上仰最大值
|
||
pitchDownMax: 0 // 俯仰-下俯最大值
|
||
})
|
||
|
||
// 头部姿态历史最值记录数组
|
||
const headPoseHistory = ref([])
|
||
const headPoseData = ref({})
|
||
|
||
// IMU更新节流与抖动阈值(降低频繁DOM与图表更新导致的卡顿)
|
||
let lastIMUUpdateTs = 0
|
||
const IMU_MIN_INTERVAL_MS = 33 // 约30Hz
|
||
let lastIMUValues = { rotation: null, tilt: null, pitch: null }
|
||
const IMU_CHANGE_EPS = 0.1 // 小于0.1°的变化忽略
|
||
|
||
|
||
// 处理IMU头部姿态数据
|
||
function handleIMUData(data) {
|
||
try {
|
||
if (!data) return
|
||
|
||
// 兼容两种载荷结构:
|
||
// 1) { rotation, tilt, pitch }
|
||
// 2) { head_pose: { rotation, tilt, pitch } }
|
||
const rotation = (data.rotation ?? (data.head_pose && data.head_pose.rotation))
|
||
const tilt = (data.tilt ?? (data.head_pose && data.head_pose.tilt))
|
||
const pitch = (data.pitch ?? (data.head_pose && data.head_pose.pitch))
|
||
|
||
if (rotation === undefined || tilt === undefined || pitch === undefined) {
|
||
return
|
||
}
|
||
headlist.value.rotation = rotation // 旋转角度
|
||
headlist.value.tilt = tilt // 倾斜角度
|
||
headlist.value.pitch = pitch // 俯仰角度
|
||
const now = (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now()
|
||
|
||
// 若距离上次更新时间很短且变化不明显,则跳过避免频繁DOM更新
|
||
const tooSoon = (now - lastIMUUpdateTs) < IMU_MIN_INTERVAL_MS
|
||
const notSignificant = (
|
||
lastIMUValues.rotation !== null && Math.abs(rotation - lastIMUValues.rotation) < IMU_CHANGE_EPS &&
|
||
lastIMUValues.tilt !== null && Math.abs(tilt - lastIMUValues.tilt) < IMU_CHANGE_EPS &&
|
||
lastIMUValues.pitch !== null && Math.abs(pitch - lastIMUValues.pitch) < IMU_CHANGE_EPS
|
||
)
|
||
if (tooSoon && notSignificant) return
|
||
|
||
lastIMUUpdateTs = now
|
||
lastIMUValues = { rotation, tilt, pitch }
|
||
|
||
const rVal = Math.round(rotation * 10) / 10
|
||
const pVal = Math.round(pitch * 10) / 10
|
||
const tVal = Math.round(tilt * 10) / 10
|
||
|
||
if (rotationCharts && !rotationCharts.isDisposed()) {
|
||
try {
|
||
rotationCharts.setOption({
|
||
series: [{ data: [{ value: rVal }] }]
|
||
})
|
||
} catch (e) {
|
||
console.warn('rotationCharts setOption error:', e);
|
||
}
|
||
}
|
||
if (pitchCharts && !pitchCharts.isDisposed()) {
|
||
try {
|
||
pitchCharts.setOption({
|
||
series: [{ data: [{ value: pVal }] }]
|
||
})
|
||
} catch (e) {
|
||
console.warn('pitchCharts setOption error:', e);
|
||
}
|
||
}
|
||
if (tiltCharts && !tiltCharts.isDisposed()) {
|
||
try {
|
||
tiltCharts.setOption({
|
||
series: [{ data: [{ value: tVal }] }]
|
||
})
|
||
} catch (e) {
|
||
console.warn('tiltCharts setOption error:', e);
|
||
}
|
||
}
|
||
|
||
// 更新最值跟踪逻辑使用原始数值(不做四舍五入)
|
||
updateHeadPoseMaxValues({ rotation, tilt, pitch })
|
||
} catch (error) {
|
||
console.error('❌ 处理IMU数据失败:', error)
|
||
}
|
||
}
|
||
|
||
// 更新头部姿态最值
|
||
function updateHeadPoseMaxValues(headPose) {
|
||
try {
|
||
// 更新旋转角最值
|
||
if (headPose.rotation > 0) {
|
||
// 左旋(负值),取绝对值的最大值
|
||
headPoseMaxValues.value.rotationLeftMax = Math.max(
|
||
headPoseMaxValues.value.rotationLeftMax,
|
||
headPose.rotation
|
||
)
|
||
} else if (headPose.rotation < 0) {
|
||
// 右旋(正值)
|
||
headPoseMaxValues.value.rotationRightMax = Math.max(
|
||
headPoseMaxValues.value.rotationRightMax,
|
||
Math.abs(headPose.rotation)
|
||
|
||
)
|
||
}
|
||
|
||
// 更新倾斜角最值
|
||
if (headPose.tilt > 0) {
|
||
// 左倾(负值),取绝对值的最大值
|
||
headPoseMaxValues.value.tiltLeftMax = Math.max(
|
||
headPoseMaxValues.value.tiltLeftMax,
|
||
headPose.tilt
|
||
|
||
)
|
||
} else if (headPose.tilt < 0) {
|
||
// 右倾(正值)
|
||
headPoseMaxValues.value.tiltRightMax = Math.max(
|
||
headPoseMaxValues.value.tiltRightMax,
|
||
Math.abs(headPose.tilt)
|
||
)
|
||
}
|
||
|
||
// 更新俯仰角最值
|
||
if (headPose.pitch >0) {
|
||
// 下俯(负值),取绝对值的最大值
|
||
headPoseMaxValues.value.pitchDownMax = Math.max(
|
||
headPoseMaxValues.value.pitchDownMax,
|
||
headPose.pitch
|
||
|
||
)
|
||
} else if (headPose.pitch < 0) {
|
||
// 上仰(正值)
|
||
headPoseMaxValues.value.pitchUpMax = Math.max(
|
||
headPoseMaxValues.value.pitchUpMax,
|
||
Math.abs(headPose.pitch)
|
||
)
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('❌ 更新头部姿态最值失败:', error)
|
||
}
|
||
}
|
||
|
||
// 清零最值并开始跟踪
|
||
function clearAndStartTracking() {
|
||
try {
|
||
// 重置所有最值为0
|
||
headPoseMaxValues.value = {
|
||
rotationLeftMax: 0,
|
||
rotationRightMax: 0,
|
||
tiltLeftMax: 0,
|
||
tiltRightMax: 0,
|
||
pitchUpMax: 0,
|
||
pitchDownMax: 0
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error('清零失败')
|
||
}
|
||
}
|
||
|
||
// 保存当前最值到历史记录
|
||
function saveMaxValuesToHistory() {
|
||
try {
|
||
// 创建当前最值的副本
|
||
const currentMaxValues = {
|
||
id: headPoseHistory.value.length + 1,
|
||
rotationLeftMax: Number(headPoseMaxValues.value.rotationLeftMax.toFixed(1)),
|
||
rotationRightMax: Number(headPoseMaxValues.value.rotationRightMax.toFixed(1)),
|
||
tiltLeftMax: Number(headPoseMaxValues.value.tiltLeftMax.toFixed(1)),
|
||
tiltRightMax: Number(headPoseMaxValues.value.tiltRightMax.toFixed(1)),
|
||
pitchUpMax: Number(headPoseMaxValues.value.pitchUpMax.toFixed(1)),
|
||
pitchDownMax: Number(headPoseMaxValues.value.pitchDownMax.toFixed(1)),
|
||
timestamp: new Date().toLocaleString()
|
||
}
|
||
|
||
// 添加到历史记录
|
||
headPoseHistory.value.push(currentMaxValues)
|
||
|
||
|
||
// console.log('💾 头部姿态最值已保存:', currentMaxValues)
|
||
// ElMessage.success(`头部姿态最值已保存(第${currentMaxValues.id}组)`)
|
||
|
||
// 更新历史数据表格(如果存在的话)
|
||
updateHistoryTable()
|
||
} catch (error) {
|
||
// console.error('❌ 保存最值失败:', error)
|
||
ElMessage.error('保存最值失败')
|
||
}
|
||
}
|
||
|
||
// 更新历史数据表格
|
||
function updateHistoryTable() {
|
||
try {
|
||
// 将头部姿态最值数据合并到现有的历史数据中
|
||
if (headPoseHistory.value.length > 0) {
|
||
const latestData = headPoseHistory.value[headPoseHistory.value.length - 1]
|
||
|
||
// 更新historyData数组,添加头部姿态最值数据
|
||
const newHistoryItem = {
|
||
id: latestData.id,
|
||
rotLeft: latestData.rotationLeftMax,
|
||
rotRight: latestData.rotationRightMax,
|
||
tiltLeft: latestData.tiltLeftMax,
|
||
tiltRight: latestData.tiltRightMax,
|
||
pitchDown: latestData.pitchDownMax,
|
||
pitchUp: latestData.pitchUpMax,
|
||
timestamp: latestData.timestamp
|
||
}
|
||
// 如果historyData数组存在,则添加新数据
|
||
if (historyData.value) {
|
||
historyData.value.push(newHistoryItem)
|
||
}
|
||
historyData.value.sort((a, b) => b.id - a.id);
|
||
console.log('📋 历史数据表格已更新')
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ 更新历史数据表格失败:', error)
|
||
}
|
||
}
|
||
const footPressure = ref({
|
||
left_front: '',
|
||
left_rear: '',
|
||
right_front: '',
|
||
right_rear: '',
|
||
left_total: '',
|
||
right_total: '',
|
||
total_pressure: ''
|
||
})
|
||
const footImgSrc = ref('')
|
||
// 处理压力传感器足部压力数据
|
||
function handlePressureData(data) {
|
||
try {
|
||
if (data && data.foot_pressure) {
|
||
const pressureData = data.foot_pressure
|
||
|
||
// 更新足部压力数据
|
||
// 显示分区压力值
|
||
if (pressureData.pressure_zones) {
|
||
footPressure.value = pressureData.pressure_zones
|
||
}
|
||
|
||
|
||
// 处理压力图片
|
||
if (pressureData.pressure_image) {
|
||
// console.log(' 📊 接收到压力分布图片 (base64格式)')
|
||
// 这里可以将图片显示在界面上
|
||
if (pressureData.pressure_image && pressureData.pressure_image.length > 0) {
|
||
footImgSrc.value = pressureData.pressure_image
|
||
} else {
|
||
console.warn('⚠️ 收到空的压力传感器数据图')
|
||
}
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ 处理压力传感器数据失败:', error)
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
// 保存检测数据
|
||
async function saveDetectionData() {
|
||
if (screenshotLoading.value) return
|
||
let titile_height = 24
|
||
try {
|
||
screenshotLoading.value = true
|
||
// 检查是否有活跃的会话ID
|
||
if (!patientInfo.value.sessionId) {
|
||
throw new Error('请先开始检测再进行数据保存')
|
||
}
|
||
const base64 = 'data:image/jpeg;base64,'
|
||
|
||
let body_image = ""
|
||
if(tempInfo.value.femtobolt_frame != null
|
||
&& tempInfo.value.femtobolt_frame.depth_image != null){
|
||
body_image = base64 + tempInfo.value.femtobolt_frame.depth_image
|
||
}
|
||
|
||
let pressure_image = ""
|
||
let foot_data = ""
|
||
if(tempInfo.value.pressure_data != null
|
||
&& tempInfo.value.pressure_data.foot_pressure != null
|
||
&& tempInfo.value.pressure_data.foot_pressure.pressure_image != null){
|
||
pressure_image = tempInfo.value.pressure_data.foot_pressure.pressure_image
|
||
foot_data = tempInfo.value.pressure_data.foot_pressure.pressure_zones
|
||
}
|
||
|
||
let pressureBox = pressureRef.value?.getBoundingClientRect()
|
||
let imuHeaderBox = imuHeaderRef.value?.getBoundingClientRect()
|
||
|
||
let titile_height = 24
|
||
pressure_image =[
|
||
Math.round(pressureBox.x), Math.round(pressureBox.y)+ titile_height,
|
||
Math.round(pressureBox.width), Math.round(pressureBox.height)
|
||
]
|
||
let head_data_image = []
|
||
head_data_image =[
|
||
Math.round(imuHeaderBox.x), Math.round(imuHeaderBox.y)+ titile_height,
|
||
Math.round(imuHeaderBox.width), Math.round(imuHeaderBox.height)
|
||
]
|
||
let foot1_image=""
|
||
if(tempInfo.value.camera1_frame != null
|
||
&& tempInfo.value.camera1_frame.image != null ){
|
||
foot1_image=base64 + tempInfo.value.camera1_frame.image
|
||
}
|
||
let foot2_image=""
|
||
if(tempInfo.value.camera2_frame != null
|
||
&& tempInfo.value.camera2_frame.image != null ){
|
||
foot2_image=base64 + tempInfo.value.camera2_frame.image
|
||
}
|
||
|
||
|
||
let head_pose={}
|
||
if(tempInfo.value.imu_data != null ){
|
||
head_pose=tempInfo.value.imu_data
|
||
}
|
||
if(headPoseMaxValues !=null){
|
||
head_pose.headPoseMaxValues = headPoseMaxValues.value
|
||
}
|
||
let screen_location = contenGridRef.value.getBoundingClientRect()
|
||
// 调用后端API保存截图
|
||
const result = await sendDetectionData({
|
||
|
||
session_id: patientInfo.value.sessionId,
|
||
patient_id: patientInfo.value.id,
|
||
screen_location:[Math.round(screen_location.x), Math.round(screen_location.y) + titile_height, Math.round(screen_location.width), Math.round(screen_location.height)],
|
||
head_pose:head_pose,
|
||
body_pose:null,
|
||
body_image: body_image,
|
||
|
||
foot_data:foot_data,
|
||
foot1_image:foot1_image,
|
||
foot2_image:foot2_image,
|
||
foot_data_image:pressure_image,
|
||
head_data_image:head_data_image,
|
||
screen_image:null
|
||
|
||
})
|
||
isoperation.value = true
|
||
// 显示成功消息和文件路径
|
||
ElMessage.success({
|
||
message: `检测数据保存成功!`,
|
||
duration: 500
|
||
})
|
||
|
||
|
||
} catch (error) {
|
||
console.error('❌ 检测数据保存失败:', error)
|
||
|
||
// 根据错误类型显示不同的错误消息
|
||
let errorMessage = '检测数据保存失败'
|
||
if (error.message.includes('网络连接失败')) {
|
||
errorMessage = '网络连接失败,请检查后端服务是否正常运行'
|
||
} else if (error.message.includes('服务器错误')) {
|
||
errorMessage = error.message
|
||
} else if (error.message.includes('未找到检测数据区域')) {
|
||
errorMessage = '检测数据区域不存在,请刷新页面重试'
|
||
} else if (error.message.includes('未找到检测数据')) {
|
||
errorMessage = '检测数据不存在,请刷新页面重试'
|
||
} else {
|
||
errorMessage = `检测数据保存失败: ${error.message}`
|
||
}
|
||
|
||
ElMessage.error({
|
||
message: errorMessage,
|
||
duration: 5000
|
||
})
|
||
|
||
} finally {
|
||
screenshotLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 调用后端API保存检测数据
|
||
async function sendDetectionData(data) {
|
||
try {
|
||
const response = await fetch(`${BACKEND_URL}/api/detection/${patientInfo.value.sessionId}/save-data`
|
||
, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(data)
|
||
})
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||
}
|
||
|
||
const result = await response.json()
|
||
|
||
if (result.success) {
|
||
console.log('📸 截图保存成功:', result.filepath)
|
||
return result
|
||
} else {
|
||
throw new Error(result.message || '保存失败')
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('💥 保存截图API调用失败:', error)
|
||
|
||
if (error.name === 'TypeError' && error.message.includes('fetch')) {
|
||
throw new Error('网络连接失败,请检查后端服务是否正常运行')
|
||
} else if (error.message.includes('HTTP')) {
|
||
throw new Error(`服务器错误: ${error.message}`)
|
||
} else {
|
||
throw new Error(error.message)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 处理开始/停止按钮点击
|
||
// async function handleStartStop() {
|
||
// if (isRecording.value) {
|
||
// // 停止录制视频
|
||
// await stopRecord()
|
||
// } else {
|
||
// // 开始录制视频
|
||
// await startRecord()
|
||
// }
|
||
// }
|
||
// 开始检测
|
||
async function startDetection() {
|
||
try {
|
||
console.log('🚀 正在开始检测...')
|
||
|
||
// 调用后端API开始检测
|
||
const response = await fetch(`${BACKEND_URL}/api/detection/start`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
patient_id: patientId.value,
|
||
// 可以添加其他检测参数
|
||
creator_id: creatorId.value,
|
||
})
|
||
})
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||
}
|
||
const result = await response.json()
|
||
if (result.success) {
|
||
console.log('✅ 检测开始成功')
|
||
// 保存会话ID和检测开始时间
|
||
patientInfo.value.sessionId = result.session_id
|
||
} else {
|
||
throw new Error(result.message || '开始检测失败')
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('💥 开始检测失败:', error)
|
||
ElMessage.error(`开始检测失败: ${error.message}`)
|
||
throw error
|
||
}
|
||
}
|
||
|
||
// 停止检测
|
||
async function stopDetection(summary = {}) {
|
||
try {
|
||
const payload = summary || {}
|
||
const response = await fetch(`${BACKEND_URL}/api/detection/${patientInfo.value.sessionId}/stop`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(payload)
|
||
})
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ 停止检测失败:', error)
|
||
ElMessage.error(`停止检测失败: ${error.message}`)
|
||
}
|
||
}
|
||
// 格式化日期方法
|
||
const formatDate = (dateString) => {
|
||
if (!dateString) return '-'
|
||
const date = new Date(dateString)
|
||
return date.toLocaleDateString('zh-CN')
|
||
}
|
||
const patientId = ref("")
|
||
|
||
// 加载患者信息
|
||
const loadPatientInfo = async () => {
|
||
try {
|
||
// 从路由参数获取患者ID
|
||
|
||
if (patientId.value == '' || patientId.value == null) {
|
||
console.warn('未找到患者ID参数')
|
||
return
|
||
}
|
||
// 调用API获取患者信息
|
||
const response = await fetch(`${BACKEND_URL}/api/patients/${patientId.value}`)
|
||
if (response.ok) {
|
||
const result = await response.json()
|
||
if (result.success) {
|
||
patientInfo.value = { ...result.data, sessionId: null }
|
||
|
||
console.log('患者信息加载成功:', patientInfo.value)
|
||
} else {
|
||
throw new Error(result.message)
|
||
}
|
||
} else {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||
}
|
||
} catch (error) {
|
||
console.error('加载患者信息失败:', error)
|
||
ElMessage.warning('加载患者信息失败,请检查网络连接')
|
||
}
|
||
}
|
||
|
||
// 处理页面关闭或刷新事件
|
||
const handleBeforeUnload = (event) => {
|
||
console.log('页面即将关闭,正在清理资源...')
|
||
// 断开WebSocket连接
|
||
disconnectWebSocket()
|
||
|
||
console.log('✅ 资源清理完成')
|
||
}
|
||
const creatorId = ref('')
|
||
let rotationCharts = null;
|
||
let pitchCharts = null;
|
||
let tiltCharts = null;
|
||
const initchart = () => {
|
||
// 确保 DOM 元素已经渲染
|
||
nextTick(() => {
|
||
const chartDom = document.getElementById('rotationChartId');
|
||
if (chartDom) {
|
||
// 如果图表已经存在,先销毁
|
||
if (rotationCharts) {
|
||
try {
|
||
rotationCharts.dispose();
|
||
} catch (e) {
|
||
console.warn('rotationCharts dispose error:', e);
|
||
}
|
||
rotationCharts = null;
|
||
}
|
||
rotationCharts = echarts.init(chartDom);
|
||
rotationCharts.setOption(chartoption.value);
|
||
} else {
|
||
console.warn('找不到 ID 为 rotationChartId 的 DOM 元素');
|
||
}
|
||
const chartDom2 = document.getElementById('pitchChartId');
|
||
if (chartDom2) {
|
||
// 如果图表已经存在,先销毁
|
||
if (pitchCharts) {
|
||
try {
|
||
pitchCharts.dispose();
|
||
} catch (e) {
|
||
console.warn('pitchCharts dispose error:', e);
|
||
}
|
||
pitchCharts = null;
|
||
}
|
||
pitchCharts = echarts.init(chartDom2);
|
||
pitchCharts.setOption(chartoption.value);
|
||
} else {
|
||
console.warn('找不到 ID 为 pitchChartId 的 DOM 元素');
|
||
}
|
||
const chartDom3 = document.getElementById('tiltChartId');
|
||
if (chartDom3) {
|
||
// 如果图表已经存在,先销毁
|
||
if (tiltCharts) {
|
||
try {
|
||
tiltCharts.dispose();
|
||
} catch (e) {
|
||
console.warn('tiltCharts dispose error:', e);
|
||
}
|
||
tiltCharts = null;
|
||
}
|
||
tiltCharts = echarts.init(chartDom3);
|
||
tiltCharts.setOption(chartoption.value);
|
||
} else {
|
||
console.warn('找不到 ID 为 tiltChartId 的 DOM 元素');
|
||
}
|
||
// 添加窗口大小调整监听器
|
||
window.addEventListener('resize', () => {
|
||
if (rotationCharts && !rotationCharts.isDisposed()) {
|
||
try {
|
||
rotationCharts.resize();
|
||
} catch (e) {
|
||
console.warn('rotationCharts resize error:', e);
|
||
}
|
||
}
|
||
if (pitchCharts && !pitchCharts.isDisposed()) {
|
||
try {
|
||
pitchCharts.resize();
|
||
} catch (e) {
|
||
console.warn('pitchCharts resize error:', e);
|
||
}
|
||
}
|
||
if (tiltCharts && !tiltCharts.isDisposed()) {
|
||
try {
|
||
tiltCharts.resize();
|
||
} catch (e) {
|
||
console.warn('tiltCharts resize error:', e);
|
||
}
|
||
}
|
||
});
|
||
});
|
||
}
|
||
const calibrationClick = async () => {
|
||
const response = await fetch(`${BACKEND_URL}/api/devices/calibrate/imu`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({})
|
||
})
|
||
clearAndStartTracking()
|
||
if (response.ok) {
|
||
const result = await response.json()
|
||
if (result.success) {
|
||
ElMessage.success(result.message)
|
||
} else {
|
||
ElMessage.error(result.message)
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
const cameraSubmit = async () => {
|
||
if(cameraForm.value.camera1.device_index == cameraForm.value.camera2.device_index){
|
||
ElMessage.error('相机上和相机下的号码不能相同!')
|
||
return
|
||
}
|
||
const response = await fetch(`${BACKEND_URL}/api/config/devices/all`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(cameraForm.value)
|
||
})
|
||
if (response.ok) {
|
||
const result = await response.json()
|
||
if (result.success) {
|
||
ElMessage.success(result.message)
|
||
cameraDialogVisible.value = false
|
||
} else {
|
||
ElMessage.error(result.message)
|
||
}
|
||
}else {
|
||
const result = await response.json()
|
||
ElMessage.error(result.message)
|
||
}
|
||
}
|
||
|
||
// 加载相机参数信息
|
||
const getDevicesInit = async () => {
|
||
try {
|
||
// 调用API获取患者信息
|
||
const response = await fetch(`${BACKEND_URL}/api/config/devices`)
|
||
if (response.ok) {
|
||
const result = await response.json()
|
||
if (result.success) {
|
||
console.log('相机参数加载成功:', result.data)
|
||
cameraForm.value = result.data
|
||
cameraDialogVisible.value = true
|
||
// console.log('相机参数加载成功:', patientInfo.value)
|
||
} else {
|
||
throw new Error(result.message)
|
||
}
|
||
} else {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||
}
|
||
} catch (error) {
|
||
console.error('加载相机参数失败:', error)
|
||
ElMessage.warning('加载相机参数失败,请检查网络连接')
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
for(let i = 0; i < 20; i++){
|
||
let port = "COM" + (i + 1)
|
||
remotePortData.value.push(port)
|
||
}
|
||
if (authStore.currentUser) {
|
||
console.log(authStore.currentUser)
|
||
creatorId.value = authStore.currentUser.id
|
||
}
|
||
patientId.value = props.selectedPatient.id
|
||
//patientId.value = '202511150005'
|
||
// 加载患者信息
|
||
loadPatientInfo()
|
||
// 启动检测
|
||
startDetection()
|
||
// 页面加载时自动连接WebSocket
|
||
connectWebSocket()
|
||
|
||
// 监听页面关闭或刷新事件
|
||
window.addEventListener('beforeunload', handleBeforeUnload)
|
||
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
console.log('🔄 Detection组件正在卸载,开始清理资源...')
|
||
|
||
try {
|
||
// 清理定时器
|
||
if (timerId.value) {
|
||
clearInterval(timerId.value)
|
||
timerId.value = null
|
||
console.log('✅ 定时器已清理')
|
||
}
|
||
|
||
// 断开WebSocket连接
|
||
disconnectWebSocket()
|
||
|
||
// 清理图表资源
|
||
if (tiltCharts) {
|
||
try {
|
||
tiltCharts.dispose()
|
||
console.log('✅ tiltCharts已清理')
|
||
} catch (e) {
|
||
console.warn('tiltCharts dispose error in onUnmounted:', e)
|
||
}
|
||
tiltCharts = null
|
||
}
|
||
|
||
if (rotationCharts) {
|
||
try {
|
||
rotationCharts.dispose()
|
||
console.log('✅ rotationCharts已清理')
|
||
} catch (e) {
|
||
console.warn('rotationCharts dispose error in onUnmounted:', e)
|
||
}
|
||
rotationCharts = null
|
||
}
|
||
|
||
if (pitchCharts) {
|
||
try {
|
||
pitchCharts.dispose()
|
||
} catch (e) {
|
||
|
||
}
|
||
pitchCharts = null
|
||
}
|
||
|
||
// 移除页面关闭事件监听器
|
||
window.removeEventListener('beforeunload', handleBeforeUnload)
|
||
} catch (error) {
|
||
|
||
}
|
||
})
|
||
|
||
const startRecord = async () => { // 开始录屏
|
||
try {
|
||
console.log('🚀 正在开始录屏...')
|
||
startTimer()
|
||
// 验证患者信息
|
||
if (!patientInfo.value || !patientInfo.value.sessionId) {
|
||
throw new Error('缺少患者信息,无法开始录屏')
|
||
}
|
||
let screen_location = contenGridRef.value.getBoundingClientRect()
|
||
let femtobolt_location = wholeBodyRef.value.getBoundingClientRect()
|
||
|
||
|
||
|
||
|
||
let camera1_location = camera1Ref.value?.getBoundingClientRect()
|
||
let camera2_location = camera2Ref.value?.getBoundingClientRect()
|
||
let titile_height = 24
|
||
// 调用后端API开始录屏
|
||
const response = await fetch(`${BACKEND_URL}/api/detection/${patientInfo.value.sessionId}/start_record`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
patient_id: patientId.value,
|
||
// 可以添加其他录屏参数
|
||
creator_id: creatorId.value,
|
||
screen_location:[Math.round(screen_location.x), Math.round(screen_location.y) + titile_height, Math.round(screen_location.width), Math.round(screen_location.height)],
|
||
camera1_location:[
|
||
Math.round(camera1_location.x), Math.round(camera1_location.y)+ titile_height,
|
||
Math.round(camera1_location.width), Math.round(camera1_location.height)
|
||
],
|
||
camera2_location:[
|
||
Math.round(camera2_location.x), Math.round(camera2_location.y)+ titile_height,
|
||
Math.round(camera2_location.width), Math.round(camera2_location.height)
|
||
],
|
||
femtobolt_location:[Math.round(femtobolt_location.x), Math.round(femtobolt_location.y) + titile_height, Math.round(femtobolt_location.width), Math.round(femtobolt_location.height)],
|
||
|
||
})
|
||
})
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||
}
|
||
|
||
const result = await response.json()
|
||
|
||
if (result.success) {
|
||
// 保存会话ID和检测开始时间
|
||
patientInfo.value.detectionStartTime = Date.now()
|
||
console.log('✅ 录屏会话创建成功,会话ID:', patientInfo.value.sessionId)
|
||
isRecording.value = true
|
||
// ElMessage.success('录屏已开始')
|
||
} else {
|
||
throw new Error(result.message || '开始录屏失败')
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error(`开始录屏失败: ${error.message}`)
|
||
throw error
|
||
}
|
||
}
|
||
|
||
const stopRecord = async () => { // 停止录屏
|
||
try {
|
||
resetTimer()
|
||
// 计算检测持续时间
|
||
let duration = 0
|
||
if (patientInfo.value.detectionStartTime) {
|
||
duration = Math.floor((Date.now() - patientInfo.value.detectionStartTime) / 1000)
|
||
}
|
||
|
||
// 调用后端API停止检测
|
||
const response = await fetch(`${BACKEND_URL}/api/detection/${patientInfo.value.sessionId}/stop_record`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
duration: duration
|
||
})
|
||
})
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||
}
|
||
isRecording.value = false
|
||
|
||
} catch (error) {
|
||
console.error('❌ 停止检测失败:', error)
|
||
ElMessage.error(`停止检测失败: ${error.message}`)
|
||
}
|
||
}
|
||
function routerClick(){
|
||
historyDialogVisible.value = true
|
||
}
|
||
|
||
const isRestart = ref(false) // 防止连击
|
||
|
||
// 单个刷新数据
|
||
function refreshClick(type) {
|
||
// 检查是否在冷却期内
|
||
if (isRestart.value === true) {
|
||
ElMessage.warning(`请勿连续点击设备重启按钮!请等待5秒后再试`)
|
||
return
|
||
}
|
||
// 设置冷却状态
|
||
isRestart.value = true
|
||
// 5秒后重置状态
|
||
setTimeout(() => {
|
||
isRestart.value = false
|
||
}, 5000)
|
||
ElMessage.warning(`🚀 发送重启设备请求...`)
|
||
if (devicesSocket && devicesSocket.connected) {
|
||
if(type == 'camera1'){
|
||
devicesSocket.emit('restart_device', { device_type: 'camera1' })
|
||
}else if(type == 'camera2'){
|
||
devicesSocket.emit('restart_device', { device_type: 'camera2' })
|
||
}else if(type == 'femtobolt'){
|
||
devicesSocket.emit('restart_device', { device_type: 'femtobolt' })
|
||
}else if(type == 'imu'){
|
||
devicesSocket.emit('restart_device', { device_type: 'imu' })
|
||
}else if(type == 'pressure'){
|
||
devicesSocket.emit('restart_device', { device_type: 'pressure' })
|
||
}else if(type == 'remote'){
|
||
devicesSocket.emit('restart_device', { device_type: 'remote' })
|
||
}
|
||
} else {
|
||
console.warn('⚠️ Socket服务未连接,无法重启设备!')
|
||
}
|
||
}
|
||
|
||
async function closeDiagnosticMessage(e){
|
||
isDiagnosticMessage.value = false
|
||
if(e == '关闭'){
|
||
isTip.value = false
|
||
}
|
||
if (e === true || (typeof e === 'object' && e)){
|
||
try {
|
||
// 清理定时器
|
||
if (timerId.value) {
|
||
clearInterval(timerId.value)
|
||
timerId.value = null
|
||
console.log('✅ 定时器已清理')
|
||
}
|
||
|
||
// 停止录制
|
||
if (isRecording.value === true) {
|
||
stopRecord()
|
||
console.log('✅ 录制已停止')
|
||
}
|
||
|
||
const summary = typeof e === 'object' && e !== null ? {
|
||
diagnosis_info: e.diagnosis_info ?? '',
|
||
treatment_info: e.treatment_info ?? '',
|
||
remark_info: (e.remark_info ?? e.suggestion_info ?? '')
|
||
} : {
|
||
diagnosis_info: diagnosticForm.value.diagnosis_info,
|
||
treatment_info: diagnosticForm.value.treatment_info,
|
||
remark_info: diagnosticForm.value.suggestion_info
|
||
}
|
||
await stopDetection(summary)
|
||
// 断开WebSocket连接
|
||
disconnectWebSocket()
|
||
// 移除页面关闭事件监听器
|
||
window.removeEventListener('beforeunload', handleBeforeUnload)
|
||
emit('endChange',true)
|
||
routeTo('/')
|
||
} catch (error) {
|
||
console.error('❌ Detection组件卸载时出错:', error)
|
||
emit('endChange',true)
|
||
}
|
||
|
||
|
||
}
|
||
|
||
}
|
||
const isPhotoAlbum = ref(false)
|
||
function closePhotoAlbum(){
|
||
isPhotoAlbum.value = false
|
||
}
|
||
function closecreatbox(e){
|
||
if(e === true){
|
||
loadPatientInfo()
|
||
}
|
||
isCloseCreat.value = false
|
||
}
|
||
|
||
function handleEditUserInfo(){
|
||
isCloseCreat.value = true
|
||
}
|
||
function closePatientProfile(){
|
||
historyDialogVisible.value =false
|
||
}
|
||
function closeViewUserInfo(){
|
||
isViewUser.value = false
|
||
}
|
||
function viewClick(e){
|
||
isExpand.value = e
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* 全局容器 */
|
||
.detection-container-box {
|
||
/* display: flex; */
|
||
height: 100vh;
|
||
background-color: #191d28;
|
||
color: #FFFFFF;
|
||
}
|
||
|
||
.detection-container-box .el-row {
|
||
margin: 0!important;
|
||
height: calc(100%);
|
||
}
|
||
|
||
.username-line {
|
||
width: 2px;
|
||
height: 22px;
|
||
margin: 0 10px;
|
||
background-color: #fff;
|
||
}
|
||
|
||
.displaycontainer {
|
||
width: 100%;
|
||
height: 62px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
box-sizing: border-box;
|
||
padding: 0 10px;
|
||
}
|
||
|
||
.displayleft {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.icon-box{
|
||
width: 32px;
|
||
height: 32px;
|
||
background-color: #374151;
|
||
border-radius: 4px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-left: 10px;
|
||
cursor: pointer;
|
||
}
|
||
.icon-box:hover{
|
||
background-color: rgb(20 170 255 / 30%);
|
||
border: 1px solid rgb(20 170 255 / 50%);
|
||
}
|
||
.startvideo-box{
|
||
width: 130px;
|
||
display: flex;
|
||
align-items: center;
|
||
margin-left: 10px;
|
||
font-family:'Noto Sans SC Bold', 'Noto Sans SC Regular', 'Noto Sans SC';
|
||
font-weight:700;
|
||
font-style:normal;
|
||
font-size:14px;
|
||
color:#FFFFFF;
|
||
}
|
||
.conductanimated{
|
||
margin-right: 5px;
|
||
|
||
animation: fadeInOut 1s infinite;
|
||
}
|
||
@keyframes fadeInOut {
|
||
0%, 100% { opacity: 0; } /* 初始状态和结束状态为完全透明 */
|
||
50% { opacity: 1; } /* 中间状态为完全不透明 */
|
||
}
|
||
|
||
.body-posture-box{
|
||
width: 100%;
|
||
height: 100%;
|
||
background: linear-gradient(135deg, #1a1e2a 0%, #222b38 100%);
|
||
border: 1px solid #262d40;
|
||
border-radius: 4px;
|
||
}
|
||
.body-header-box{
|
||
width: 100%;
|
||
height: calc(50% - 7px) ;
|
||
/* background: linear-gradient(135deg, rgba(42, 54, 73, 1) 0%, rgba(42, 54, 73, 1) 0%, rgba(34, 43, 56, 1) 100%, rgba(34, 43, 56, 1) 100%); */
|
||
background: linear-gradient(135deg, #1a1e2a 0%, #222b38 100%);
|
||
|
||
border: 1px solid #262d40;
|
||
border-radius: 4px;
|
||
}
|
||
.body-foot-box{
|
||
margin-top: 15px;
|
||
width: 100%;
|
||
height: calc(50% - 7px) ;
|
||
/* background:linear-gradient(135deg, rgba(42, 54, 73, 1) 0%, rgba(42, 54, 73, 1) 0%, rgba(34, 43, 56, 1) 100%, rgba(34, 43, 56, 1) 100%); */
|
||
background: linear-gradient(135deg, #1a1e2a 0%, #222b38 100%);
|
||
|
||
border: 1px solid #262d40;
|
||
border-radius: 4px;
|
||
}
|
||
.body-title-display{
|
||
width: 100%;
|
||
height: 50px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
box-sizing: border-box;
|
||
padding: 0 20px;
|
||
font-family: "Noto Sans SC Bold", "Noto Sans SC Regular", "Noto Sans SC";
|
||
font-weight: 700;
|
||
font-style: normal;
|
||
font-size: 20px;
|
||
color: rgb(255, 255, 255);
|
||
}
|
||
.body-son-display{
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.connecttext{
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.body-header-bottombox{
|
||
width: 100%;
|
||
height: calc(100% - 50px);
|
||
display: flex;
|
||
justify-content: center;
|
||
box-sizing: border-box;
|
||
padding: 0 20px;
|
||
}
|
||
.body-header-bottombox-left{
|
||
width: 25%;
|
||
height: calc(100%);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-content:space-between;
|
||
}
|
||
.body-header-bottombox-lefttext{
|
||
width: 100%;
|
||
height: 33%;
|
||
white-space:nowrap;
|
||
}
|
||
.body-header-bottombox-content{
|
||
width: 40%;
|
||
height: calc(100%);
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items:center;
|
||
}
|
||
.body-header-bottombox-right{
|
||
width: 25%;
|
||
height: calc(100%);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-content:space-between;
|
||
}
|
||
.body-header-bottombox-righttext{
|
||
width: 100%;
|
||
height: 33%;
|
||
white-space:nowrap;
|
||
text-align: right;
|
||
}
|
||
.currencytext1{
|
||
min-width: 100px;
|
||
font-size: 16px;
|
||
color: #FFFFFF;
|
||
font-family: "Noto Sans SC";
|
||
font-weight: 400;
|
||
color: #D1D5DB;
|
||
}
|
||
.currencytext2{
|
||
font-weight: 700;
|
||
font-style: normal;
|
||
font-size: 24px;
|
||
color: #3BF2C6;
|
||
}
|
||
.currencytext3{
|
||
width: 60px;
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
font-size: 16px;
|
||
color: #FFFFFF;
|
||
display: inline-block;
|
||
text-align: left;
|
||
}
|
||
.currencytext4{
|
||
width: 20px;
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
font-size: 16px;
|
||
color:#D1D5DB;
|
||
display: inline-block;
|
||
text-align: left;
|
||
}
|
||
.body-footbottom-box{
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: calc(100% - 50px);
|
||
}
|
||
.body-footbottom-left{
|
||
width: 28%;
|
||
height: calc(100%);
|
||
display: flex;
|
||
align-content: center;
|
||
justify-content: center;
|
||
flex-wrap: wrap;
|
||
}
|
||
.body-footbottom-leftbox{
|
||
min-width: 215px;
|
||
width: 80%;
|
||
min-height: 60px;
|
||
height: 20%;
|
||
background: inherit;
|
||
background-color: rgba(255, 255, 255, 0.1);
|
||
border-radius: 4px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0px 20px;
|
||
}
|
||
.body-footbottom-center{
|
||
width: 40%;
|
||
height: calc(100%);
|
||
}
|
||
.body-footbottom-topbox{
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-around;
|
||
width: 100%;
|
||
height: 50px;
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
font-size: 24px;
|
||
color: #FFFFFF;
|
||
}
|
||
.xline {
|
||
position: absolute;
|
||
width: calc(100% + 20px) ;
|
||
border-top: 1px dashed red;
|
||
top: 50%;
|
||
left: -10px;
|
||
}
|
||
|
||
.yline {
|
||
position: absolute;
|
||
height: calc(100% + 20px);
|
||
border-left: 1px dashed red;
|
||
top: -10px;
|
||
left: 50%;
|
||
}
|
||
.body-footbottom-leftbottom{
|
||
width: 100%;
|
||
height: calc(100% - 60px);
|
||
/* height: 281px; */
|
||
display: flex;
|
||
justify-content: center;
|
||
flex-wrap: wrap;
|
||
align-content:space-between ;
|
||
}
|
||
|
||
.body-userinfo-box{
|
||
position: relative;
|
||
z-index: 10;
|
||
width: 100%;
|
||
height: 346px;
|
||
/* background: linear-gradient(135deg, rgba(42, 54, 73, 1) 0%, rgba(42, 54, 73, 1) 0%, rgba(34, 43, 56, 1) 100%, rgba(34, 43, 56, 1) 100%); */
|
||
background: linear-gradient(135deg, #1a1e2a 0%, #222b38 100%);
|
||
border: 1px solid #262d40;
|
||
border-radius: 4px;
|
||
}
|
||
.body-userinfo-expandbox{
|
||
height: 638px ;
|
||
}
|
||
.body-userinfo-box1{
|
||
width: 100%;
|
||
height: 534px ;
|
||
/* background: linear-gradient(135deg, rgba(42, 54, 73, 1) 0%, rgba(42, 54, 73, 1) 0%, rgba(34, 43, 56, 1) 100%, rgba(34, 43, 56, 1) 100%); */
|
||
background: linear-gradient(135deg, #1a1e2a 0%, #222b38 100%);
|
||
border: 1px solid #262d40;
|
||
border-radius: 4px;
|
||
}
|
||
.body-userinfo-box3{
|
||
width: 100%;
|
||
height: 100% ;
|
||
/* background: linear-gradient(135deg, rgba(42, 54, 73, 1) 0%, rgba(42, 54, 73, 1) 0%, rgba(34, 43, 56, 1) 100%, rgba(34, 43, 56, 1) 100%); */
|
||
background: linear-gradient(135deg, #1a1e2a 0%, #222b38 100%);
|
||
border: 1px solid #262d40;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
|
||
.body-video-box{
|
||
width: 100%;
|
||
height: calc(100% - 348px - 14px) ;
|
||
/* background: linear-gradient(135deg, rgba(42, 54, 73, 1) 0%, rgba(42, 54, 73, 1) 0%, rgba(34, 43, 56, 1) 100%, rgba(34, 43, 56, 1) 100%); */
|
||
background: linear-gradient(135deg, #1a1e2a 0%, #222b38 100%);
|
||
border: 1px solid #262d40;
|
||
border-radius: 4px;
|
||
margin-top: 15px;
|
||
}
|
||
.body-video-box1{
|
||
width: 100%;
|
||
height: calc(100% - 534px - 14px) ;
|
||
background: linear-gradient(135deg, #1a1e2a 0%, #222b38 100%);
|
||
border: 1px solid #262d40;
|
||
border-radius: 4px;
|
||
margin-top: 15px;
|
||
}
|
||
.body-userinfo-content{
|
||
width: 100%;
|
||
height: calc(100% - 50px);
|
||
padding: 15px;
|
||
}
|
||
.body-userinfo-content-top{
|
||
position: relative;
|
||
width: calc(100%);
|
||
height: 116px;
|
||
background-color: #374151;
|
||
border-radius: 8px 8px 0 0;
|
||
display: flex;
|
||
align-items: center;
|
||
box-sizing: border-box;
|
||
padding: 0 20px;
|
||
}
|
||
.body-userinfo-content-bottom{
|
||
padding:10px;
|
||
padding-top: 20px;
|
||
position: relative;
|
||
width: calc(100%);
|
||
height: calc(100% - 116px);
|
||
border-radius: 8px;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-content: start;
|
||
border-width: 0px 1px 1px;
|
||
border-right-style: solid;
|
||
border-bottom-style: solid;
|
||
border-left-style: solid;
|
||
border-right-color: rgb(55, 65, 81);
|
||
border-bottom-color: rgb(55, 65, 81);
|
||
border-left-color: rgb(55, 65, 81);
|
||
border-top-style: initial;
|
||
border-top-color: initial;
|
||
border-radius: 0px 0px 8px 8px;
|
||
box-shadow: none;
|
||
font-family: "Noto Sans SC";
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
}
|
||
|
||
|
||
.body-userinfo-content-bottom0{
|
||
padding:10px;
|
||
padding-top: 20px;
|
||
position: relative;
|
||
width: calc(100%);
|
||
height: calc(100% - 116px);
|
||
border-radius: 8px;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-content: start;
|
||
border-width: 0px 1px 1px;
|
||
border-right-style: solid;
|
||
border-bottom-style: solid;
|
||
border-left-style: solid;
|
||
border-right-color: rgb(55, 65, 81);
|
||
border-bottom-color: rgb(55, 65, 81);
|
||
border-left-color: rgb(55, 65, 81);
|
||
border-top-style: initial;
|
||
border-top-color: initial;
|
||
border-radius: 0px 0px 8px 8px;
|
||
box-shadow: none;
|
||
font-family: "Noto Sans SC";
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
}
|
||
.body-userinfo-content-bottom1{
|
||
padding:10px;
|
||
padding-top: 10px;
|
||
position: relative;
|
||
width: calc(100%);
|
||
height: calc(100% - 116px);
|
||
border-radius: 8px;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-content: start;
|
||
border-width: 0px 1px 1px;
|
||
border-right-style: solid;
|
||
border-bottom-style: solid;
|
||
border-left-style: solid;
|
||
border-right-color: rgb(55, 65, 81);
|
||
border-bottom-color: rgb(55, 65, 81);
|
||
border-left-color: rgb(55, 65, 81);
|
||
border-top-style: initial;
|
||
border-top-color: initial;
|
||
border-radius: 0px 0px 8px 8px;
|
||
box-shadow: none;
|
||
font-family: "Noto Sans SC";
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
}
|
||
|
||
.body-userinfo-content-bottom2{
|
||
padding:10px;
|
||
padding-top: 20px;
|
||
position: relative;
|
||
width: calc(100%);
|
||
height: calc(100% - 116px);
|
||
border-radius: 8px;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-content: start;
|
||
border-width: 0px 1px 1px;
|
||
border-right-style: solid;
|
||
border-bottom-style: solid;
|
||
border-left-style: solid;
|
||
border-right-color: rgb(55, 65, 81);
|
||
border-bottom-color: rgb(55, 65, 81);
|
||
border-left-color: rgb(55, 65, 81);
|
||
border-top-style: initial;
|
||
border-top-color: initial;
|
||
border-radius: 0px 0px 8px 8px;
|
||
box-shadow: none;
|
||
font-family: "Noto Sans SC";
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
}
|
||
.userinfo-disyplay1{
|
||
display: flex;
|
||
width: calc(64%);
|
||
padding-top: 20px;
|
||
}
|
||
.userinfo-disyplay2{
|
||
display: flex;
|
||
width: calc(36%);
|
||
padding-top: 20px;
|
||
}
|
||
.userinfo-disyplaypadding1{
|
||
width: calc(64%);
|
||
padding-bottom: 15px;
|
||
}
|
||
.padding10{
|
||
padding-bottom: 5px;
|
||
}
|
||
.userinfo-disyplaypadding2{
|
||
width: calc(36%);
|
||
padding-bottom: 15px;
|
||
}
|
||
.userinfo-disyplaypadding3{
|
||
width: calc(100%);
|
||
padding-bottom: 15px;
|
||
}
|
||
|
||
.userinfo-disyplaypadding4{
|
||
/* display: flex; */
|
||
width: calc(64%);
|
||
padding-bottom: 5px;
|
||
}
|
||
.userinfo-disyplaypadding5{
|
||
/* display: flex; */
|
||
width: calc(36%);
|
||
padding-bottom: 5px;
|
||
}
|
||
.userinfo-disyplaypadding6{
|
||
/* display: flex; */
|
||
width: calc(100%);
|
||
padding-bottom: 5px;
|
||
}
|
||
|
||
.useravatar-box{
|
||
width: 80px;
|
||
height: 80px;
|
||
border-radius: 50%;
|
||
background: #303947;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 35px;
|
||
}
|
||
.userinfo-text-top{
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.userinfo-text1{
|
||
font-weight: 700;
|
||
font-style: normal;
|
||
color: #FFFFFF;
|
||
font-size: 20px;
|
||
}
|
||
.userinfo-text2{
|
||
font-family: "Noto Sans SC";
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
font-size: 16px;
|
||
color: rgba(255, 255, 255, 0.6);
|
||
}
|
||
.userinfo-text3{
|
||
font-family: "Noto Sans SC Bold", "Noto Sans SC Regular", "Noto Sans SC";
|
||
font-weight: 700;
|
||
font-style: normal;
|
||
font-size: 16px;
|
||
color: rgb(255, 255, 255);
|
||
margin-top: 15px;
|
||
}
|
||
.userinfo-text4{
|
||
font-family: "Noto Sans SC";
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
font-size: 14px;
|
||
color: rgba(255, 255, 255, 0.6);
|
||
width: 60px;
|
||
}
|
||
|
||
.userinfo-text5{
|
||
font-family: "Noto Sans SC";
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
font-size: 16px;
|
||
color: rgb(255, 255, 255);
|
||
overflow: hidden;
|
||
white-space: nowrap;
|
||
text-overflow: ellipsis;
|
||
}
|
||
.userinfo-line{
|
||
width: 2px;
|
||
height: 16px;
|
||
background: rgba(255, 255, 255, 0.6);
|
||
margin: 0 8px;
|
||
}
|
||
.userinfo-edit-img{
|
||
position: absolute;
|
||
right: 15px;
|
||
top: 15px;
|
||
}
|
||
.body-video-content{
|
||
width: 100%;
|
||
height: calc(100% - 50px);
|
||
padding: 15px;
|
||
box-sizing: border-box;
|
||
display: flex;
|
||
align-content: space-between;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
}
|
||
.body-video-imgbox1{
|
||
position: relative;
|
||
width: 100%;
|
||
height: calc(50% - 7px);
|
||
}
|
||
|
||
.body-video-imgbox2{
|
||
position: relative;
|
||
width: 100%;
|
||
height: calc(50% - 8px);
|
||
}
|
||
.body-video-imgbox3{
|
||
position: relative;
|
||
width: 100%;
|
||
height: calc(100%);
|
||
}
|
||
.patientInfotop1{
|
||
font-weight: 700;
|
||
font-style: normal;
|
||
font-size: 20px;
|
||
color: #FFFFFF;
|
||
margin:0 30px;
|
||
margin-left:40px
|
||
}
|
||
.patientInfotop2{
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
font-size: 16px;
|
||
color: #FFFFFF;
|
||
}
|
||
.endbutton{
|
||
width: 72px;
|
||
height: 28px;
|
||
background-color: rgba(11, 148, 213, 1);
|
||
border:1px solid rgba(11, 148, 213, 1);
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
font-size: 14px;
|
||
color: #D7D7D7;
|
||
margin-left:20px
|
||
}
|
||
.endbutton:hover {
|
||
background-color: #14aaff;
|
||
border:1px solid #14aaff;
|
||
color: #fff;
|
||
}
|
||
.pop-up-mask{
|
||
position: fixed;
|
||
z-index: 99;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100vw;
|
||
height: 100vh;
|
||
background: rgba(0, 0, 0, 0.7);
|
||
}
|
||
|
||
.pop-up-tip-container {
|
||
width: 400px;
|
||
height:220px;
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
left: 0;
|
||
bottom: 0;
|
||
margin: auto;
|
||
border-radius: 10px;
|
||
background: #1b202c;
|
||
/* background: linear-gradient(135deg, rgba(53, 67, 90, 1) 0%, rgba(53, 67, 90, 1) 0%, rgba(62, 79, 105, 1) 99%, rgba(62, 79, 105, 1) 100%); */
|
||
}
|
||
.pop-up-camera-container{
|
||
width: 668px;
|
||
height:630px;
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
left: 0;
|
||
bottom: 0;
|
||
margin: auto;
|
||
background: linear-gradient(135deg, rgb(53, 67, 90) 0%, rgb(53, 67, 90) 0%, rgb(62, 79, 105) 99%, rgb(62, 79, 105) 100%);
|
||
border-radius: 10px;
|
||
box-shadow: rgb(17, 24, 33) 0px 0px 10px;
|
||
}
|
||
.pop-up-camera-header{
|
||
width: 100%;
|
||
height: 60px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
box-sizing: border-box;
|
||
padding: 0 40px;
|
||
font-family: 'Noto Sans SC';
|
||
font-weight: 700;
|
||
font-style: normal;
|
||
font-size: 20px;
|
||
color: #FFFFFF;
|
||
text-align: left;
|
||
border-radius:10px 10px 0 0;
|
||
}
|
||
|
||
.pop-up-tip-header{
|
||
width: 100%;
|
||
height: 60px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
box-sizing: border-box;
|
||
padding: 0 30px;
|
||
font-family: 'Noto Sans SC';
|
||
font-weight: 700;
|
||
font-style: normal;
|
||
font-size: 20px;
|
||
color: #FFFFFF;
|
||
text-align: left;
|
||
}
|
||
.tipconfirmbutton-box{
|
||
width:100%;
|
||
display:flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.tipconfirmbutton{
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
font-size: 14px;
|
||
color: rgb(255, 255, 255);
|
||
background:#0b94d5;
|
||
border:1px solid #0b94d5;
|
||
width: 80px;
|
||
height: 40px;
|
||
}
|
||
.tipconfirmbutton:hover{
|
||
background:#14aaff;
|
||
border:1px solid #14aaff;
|
||
}
|
||
.pop-up-tip-text{
|
||
width:100%;
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
font-size: 16px;
|
||
color: #FFFFFF;
|
||
text-align: center;
|
||
padding:30px 10px;
|
||
}
|
||
.pop-up-camera-body{
|
||
padding: 20px 40px;
|
||
}
|
||
.pop-up-camera-display{
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.pop-up-camera-line{
|
||
width: 4px;
|
||
height: 13px;
|
||
background-color: rgba(38, 111, 255, 1);
|
||
margin-right: 3px;
|
||
}
|
||
.pop-up-camera-title{
|
||
font-weight: 700;
|
||
font-style: normal;
|
||
font-size: 15px;
|
||
color: #FFFFFF;
|
||
}
|
||
.pop-up-camera-body :deep(.el-input__wrapper) {
|
||
background-color: #2a3649;
|
||
border-width: 1px;
|
||
border-style: solid;
|
||
border-color: #2a3649;
|
||
border-radius: 4px;
|
||
box-shadow: none;
|
||
height: 40px;
|
||
|
||
}
|
||
.pop-up-camera-body :deep(.el-input__wrapper:hover) {
|
||
border-color: rgba(38, 111, 255, 1);
|
||
|
||
|
||
}
|
||
.pop-up-camera-body :deep( .el-input__inner) {
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
font-size: 14px;
|
||
letter-spacing: normal;
|
||
color: #FFFFFF;
|
||
|
||
}
|
||
.pop-up-camera-name{
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
font-size: 14px;
|
||
color: rgba(255,255,255,0.6);
|
||
width: 60px;
|
||
text-align: right;
|
||
margin-right: 20px;
|
||
}
|
||
.pop-up-camera-radio{
|
||
width: 70px;
|
||
height: 40px;
|
||
background: inherit;
|
||
background-color: rgba(40, 40, 40, 1);
|
||
box-sizing: border-box;
|
||
border-width: 1px;
|
||
border-style: solid;
|
||
border-color: rgba(54, 54, 54, 1);
|
||
border-radius: 4px;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
.pop-up-camera-display :deep(.el-radio__label){
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
color: #FFFFFF;
|
||
font-size: 14px;
|
||
}
|
||
.pop-up-camera-display :deep(.el-radio){
|
||
width: 70px;
|
||
height: 40px;
|
||
background: inherit;
|
||
background: #2a3649;
|
||
box-sizing: border-box;
|
||
border-width: 1px;
|
||
border-style: solid;
|
||
border-color: #4a576b;
|
||
border-radius: 4px;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
margin: 0;
|
||
}
|
||
.pop-up-camera-display :deep(.el-radio.is-checked){
|
||
width: 70px;
|
||
height: 40px;
|
||
background: inherit;
|
||
background-color: #2a3649;
|
||
box-sizing: border-box;
|
||
border-width: 1px;
|
||
border-style: solid;
|
||
border-color: #14aaff;
|
||
|
||
}
|
||
.pop-up-camera-display :deep(.is-checked .el-radio__label){
|
||
color: #14aaff;
|
||
}
|
||
|
||
.pop-up-camera-display :deep(.el-radio__input.is-checked .el-radio__inner){
|
||
background: #14aaff;
|
||
border-color: #14aaff;
|
||
}
|
||
.pop-up-camera-display :deep(.el-radio__input.is-checked .el-radio__inner){
|
||
background: #14aaff;
|
||
border-color: #14aaff;
|
||
}
|
||
|
||
.pop-up-camera-display :deep(.el-radio__input.is-checked .el-radio__inner:after){
|
||
background-color: #2a3649;
|
||
}
|
||
.pop-up-camera-display :deep(.el-radio__inner){
|
||
background-color: #2a3649;
|
||
border-color: #4a576b;
|
||
}
|
||
|
||
</style>
|
||
<style>
|
||
.historyDialogVisible.el-dialog{
|
||
--el-dialog-margin-top: 0 !important;
|
||
margin-bottom: 0;
|
||
}
|
||
.historyDialogVisible .el-dialog__body{
|
||
padding: 0;
|
||
}
|
||
.pop-up-camera-container .form-actions-display{
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
padding-top: 40px;
|
||
padding-right: 10px;
|
||
}
|
||
.calibration-zero{
|
||
width: 72px;
|
||
height: 28px;
|
||
background-color: rgb(69, 91, 123);
|
||
display:flex;
|
||
align-items:center;
|
||
justify-content:center;
|
||
border-radius: 4px;
|
||
box-shadow: none;
|
||
font-family: "Noto Sans SC";
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
font-size: 14px;
|
||
color: rgb(255, 255, 255);
|
||
margin-left:10px;
|
||
cursor:pointer;
|
||
}
|
||
.calibration-zero:hover{
|
||
background-color: #14AAFF;
|
||
}
|
||
.noImageSvg-bg{
|
||
display:flex;
|
||
justify-content:center;
|
||
align-items:center;
|
||
}
|
||
|
||
.pop-up-camera-container .formreturnCancel{
|
||
width: 80px;
|
||
height: 40px;
|
||
background: rgba(89, 113, 148, 1);
|
||
border: 1px solid rgba(89, 113, 148, 1);
|
||
font-family: 'Noto Sans SC';
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
font-size: 14px;
|
||
color: rgba(255, 255, 255, 0.6);
|
||
}
|
||
.pop-up-camera-container .formreturnCancel:hover{
|
||
background: #14aaff;
|
||
color:#ffffff;
|
||
}
|
||
.pop-up-camera-container .formsaveCancel {
|
||
width: 80px;
|
||
height: 40px;
|
||
background: #0b94d5;
|
||
font-family: 'Noto Sans SC';
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
font-size: 14px;
|
||
color: #FFFFFF;
|
||
}
|
||
.pop-up-camera-container .formsaveCancel:hover{
|
||
background: #14AAFF;
|
||
color:#ffffff;
|
||
}
|
||
.big-img{
|
||
position: absolute;
|
||
left: 10px;
|
||
top: 10px;
|
||
width: 30px;
|
||
height: 30px;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
}
|
||
|
||
|
||
</style>
|
||
<style>
|
||
.pop-up-camera-display .el-select__placeholder {
|
||
color: #ffffff;
|
||
font-size: 14px;
|
||
}
|
||
.pop-up-camera-display .el-select__wrapper {
|
||
background-color: #2a3649;
|
||
border-width: 1px;
|
||
border-style: solid;
|
||
border-color: transparent;
|
||
border-radius: 4px;
|
||
box-shadow: none;
|
||
height: 40px;
|
||
}
|
||
.pop-up-camera-display .el-select__wrapper:hover{
|
||
border-color: #3d4c65;
|
||
box-shadow: 0 0 0 1px transparent inset;
|
||
}
|
||
</style> |