chenyc
2024-01-15 191100aca6b5afac1d3c76afeea56b3063b81071
src/views/home/index.vue
@@ -1,6 +1,8 @@
<template>
  <div class="homeclass">
    <!-- {{dialogVisible}} -->
    <div class="page" v-if="!dialogVisible">
        <el-row style="height: 11.3%;">
        </el-row>
        <el-row style="height: 50%;margin-bottom: 2%;" :gutter="20">
@@ -143,25 +145,26 @@
                  机位/床位号
                </div>
              </div>
              <div style="height: 75%;">
              <div style="height: 81%;background-color: #FFFFFF;">
                <div class="textjuzhong" style="background: rgba(243, 247, 243, 0.3); font-size: 6rem; font-weight: 800; color: #DFB144;">
                  {{ patientInfo.deviceNo }}
                </div>
              </div>
          </el-col>
          <el-col :span="17">
            <div style="height: 25%;">
            <!-- <div style="height: 25%;">
                <div class="textjuzhong" style="background: #DFB144; font-size: 2rem; color: #FFFFFF;">
                  治疗信息
                </div>
              </div>
              <div style="height: 75%;">
                <div style="background: rgba(243, 247, 243, 0.3); height: 100%; width: 100%; font-size: 2.5rem; line-height: 4.5rem; font-weight: 800; color: #FFFFFF; text-align: center;padding-top: 1rem;">
                  <span style="font-size: 4rem; color:#DFB144 ; " >干体重  {{patientInfo.pureWeight}}kg</span>
              </div> -->
              <div style="height: 100%;background-color: #FFFFFF;">
                <div ref="myChartRef" style="background: rgba(243, 247, 243, 0.3); height: 100%; width: 100%; font-size: 2.5rem; line-height: 4.5rem; font-weight: 800; color: #FFFFFF; text-align: center;padding-top: 1rem;">
                  <!-- <div    ref="myChartRef" style="height: 300px; width: 100%;"></div> -->
                  <!-- <span style="font-size: 4rem; color:#DFB144 ; " >干体重  {{patientInfo.pureWeight}}kg</span>
                  <br>
                  <span > 透析器:{{patientInfo.txq}}</span>
                  <span > 治疗模式:{{patientInfo.medMethod}}</span>
                  <span > 灌流器:{{patientInfo.glq}}</span>
                  <span > 灌流器:{{patientInfo.glq}}</span> -->
                </div>
              </div>
          </el-col>
@@ -185,7 +188,10 @@
          </el-row>
        </div>
        <div class="rWrapp">
          <video id="myVideo" preload="preload" autoplay loop muted />
          <video ref="video" id="myVideo"  preload="preload" autoplay loop muted   />
          <canvas ref="canvas" width="{this.width}" height="{this.height}" />
          <div style="text-align: center; font-size: 2rem; color: bisque;margin-top: 1rem;">请将脸部对准摄像头</div>
        </div>
        <div v-if="imgSrc" class="img_bg_camera" style="margin-top: 200px;">
@@ -193,7 +199,7 @@
          <img :src="imgSrc"  class="tx_img" />
        </div>
        <div style="margin-top: 0px; z-index: -1;position:absolute;" >
          <canvas ref="myCanvas" id="myCanvas" class="myCanvas" width="200" height="200" ></canvas>
        </div>
        <div class="footer" style="text-align: right;">
          <div class="fanhuikey" v-if="!dialogVisible" @click="fuxuan">
@@ -207,82 +213,50 @@
  </div>
</template>
<script setup lang="ts">
import * as echarts from 'echarts';
import { detectSingleFace, nets, matchDimensions, resizeResults, draw, SsdMobilenetv1Options, Box } from 'face-api.js'
import * as faceapi from 'face-api.js'
import { userInfoStore } from '@/stores/userInfo'
import { sockteStore } from '@/stores/sockteInfo'
import { ipcRenderer } from 'electron'
import { patientInfoStore } from '@/stores/patient'
import { updatePatient } from '@/api/user/index'
import { updatePatient,listWeightHistory,listBPHistory } from '@/api/user/index'
import os from "os"
import Speech from 'speak-tts'
import { reactive,computed, toRefs, onMounted, watch, ref } from "vue"
import { reactive,computed, toRefs, onMounted, watch, ref, getCurrentInstance } from "vue"
import { sendPationCodeApi } from '../../samples/httpApi'
import { formatDate,jgTime } from '@/utils/formatTime'
import { formatDate,jgTime,isworkTime } from '@/utils/formatTime'
import { confingInfoStore } from '@/stores/StoresConfing'
import {base64toFile} from '@/samples/faceApi'
import{initPort} from '@/samples/portApi'
import{initPort as oumulongHbp9030 } from '@/samples/deviceApi/oumulong-HBP-9030'
import{initPort as  TM2655VP} from '@/samples/deviceApi/TM2655VP'
import{initPort as zhiRongT605 } from '@/samples/deviceApi/zhiRongT605'
import{initPort as zhiRongHehui } from '@/samples/deviceApi/zhiRongHehui'
import{initPort as taiHengM523 } from '@/samples/deviceApi/taiHengM523'
import{initPort as XK3190A12 } from '@/samples/deviceApi/XK3190A12'
import {initPort as mbp7000} from '@/samples/deviceApi/mbp7000'
import {initPort as M503} from '@/samples/deviceApi/M503'
import {initPort as seca102} from '@/samples/deviceApi/seca102'
// 读取体重文件
import{toDataTz,todatatzs } from '@/samples/deviceApi/seca101'
import { ElLoading, ElMessage } from 'element-plus'
import { el } from 'element-plus/es/locale'
import { Console } from 'console'
let trackerTask: any = null;
import{todatatzs } from '@/samples/deviceApi/seca101'
import {ElMessage } from 'element-plus'
const { proxy } = getCurrentInstance() as any;
// 人脸检测对象
const options = new SsdMobilenetv1Options({
  // 最小置信阈值
  // 默认值:0.5
  minConfidence: 0.5
})
const canvas = ref('canvas') // 图像画布
const video = ref('video') // 视频元素
const stream = ref(null) // 当前流
const getUserMediaFail = ref(false) // 获取用户媒体失败
let lsDateTime:any=new Date()
// 标识用的画布
const myCanvas = ref<HTMLCanvasElement | null>(null);
let imgSrc:'';
const msg = ref<string>("没识别到人脸...");
// 实例人脸检查器                    ObjectTracker
const myTracker: any = new tracking.ObjectTracker("face");
myTracker.setInitialScale(4);
myTracker.setStepSize(2);
myTracker.setEdgesDensity(0.1);
// 监听人脸检查器
myTracker.on("track", (event: tracking.TrackEvent) => {
  const context = myCanvas.value?.getContext("2d") as CanvasRenderingContext2D;
  if (myCanvas.value) {
    context.clearRect(0, 0, myCanvas.value.width, myCanvas.value.height);
  }
  if (event.data.length === 0) {
    // console.log('没有监测到---')
    msg.value = "没识别到人脸...";
  } else if(event.data.length >= 1){
    trackerTask.stop();
    msg.value = "检测到人脸";
    console.log('监测到人脸2样')
    console.log(event.data)
    const myCanvas = document.getElementById("myCanvas");//
    const thisContext = myCanvas?.getContext("2d");
    const myVideo = document.querySelector("#myVideo") as HTMLVideoElement;
    thisContext.drawImage(myVideo, 0,0, 250, 200);
    imgSrc = myCanvas?.toDataURL('image/png');
    const X= jgTime(lsDateTime,new Date())
    // 转文件
    // 识别框显示才能到传阿里云识别
    if(dialogVisible.value&&X>configData.value.face_push){
      lsDateTime=new Date()
      console.log(X,'上传阿里识别间隔')
      base64toFile(imgSrc)
    }
    setTimeout(() => {
      // console.log(configData.value.face_push+'秒跑一次人脸识别')
      trackerTask.run();
    }, configData.value.face_push*1000);
    // @ts-ignore
    if (typeof window.stream === "object") {
      myVideo.srcObject = null;
      // @ts-ignore
      window.stream.getTracks().forEach((track) => track.stop());
    }
  }
});
let timer: any = 0
let timer:any=new Date()
const date=ref('')
const timeShidaun=ref('')
// 语音播报
@@ -421,9 +395,147 @@
    dialogVisible.value = true
  }else{
    console.log('关闭人脸识别')
    dialogVisible.value = false
    dialogVisible.value = true
  }
}
const huatu=(series: { name: string; type: string; stack: string; data: any; }[],xAxis: any[],legendData:any[],textTitle:string)=>{
  const myChart = echarts.init(proxy.$refs.myChartRef);
      const option = {
        title: {
          text: textTitle,//'体重趋势图',
          borderRadius:5,
          backgroundColor:"#409EFF",
          left:20,
          textStyle:{
            color:'#ffffff',
            fontSize:18
          }
        },
        tooltip: {
          trigger: 'axis',
        },
        legend: {
          data:legendData //['透前体重', '透后体重', '干体重']
        },
        grid: {
          left: '3%',
          right: '4%',
          bottom: '3%',
          containLabel: true
        },
        toolbox: {
          show: true,
          feature: {
            // restore: {}
          }
        },
        xAxis: {
          type: 'category',
          boundaryGap: false,
          data: xAxis
        },
        yAxis: {
          type: 'value',
          axisLabel: {
            formatter: '{value} kg'
          },
          min:30,
          minInterval:1,
        },
        series:  series
      };
      myChart.setOption(option);
}
// 体重趋势图
const intiTubiao=()=>{
  console.log('----------3333')
  const xAxisData: any[]=[]
  const series=[
    {
      name: '透前体重',
      type: 'line',
      color:'#E6A23C',
      smooth: true,
      data: []
    },
    {
      name: '透后体重',
      color:'#409EFF',
      type: 'line',
      smooth: true,
      data: <any>[]
    },
    {
      name: '干体重',
      type: 'line',
      color:'red',
      smooth: true,
      data: []
    },
  ]
  console.log(patientInfo.value)
  listWeightHistory(`patientCode=${patientInfo.value.code}`).then(res=>{
    console.log(res.data,'-----------------------')
    if(res.data.length>0){
      res.data.forEach((e:any)=>{
        xAxisData.push(e.透析日期.substring(5,10))
        series[0].data.push(e.透前称重)
        series[1].data.push(e.透后称重)
        series[2].data.push(e.干体重)
      })
      const legendData=['透前体重', '透后体重', '干体重']
      huatu(series,xAxisData,legendData,'体重趋势图')
    }
  })
}
// 血压趋势图
const intiTubiaoXY=()=>{
  const xAxisData: any[]=[]
  const series=[
    {
      name: '透前伸缩压',
      type: 'line',
      color:'#E6A23C',
      smooth: true,
      data: []
    },
    {
      name: '透前舒张压',
      color:'#409EFF',
      type: 'line',
      smooth: true,
      data: <any>[]
    },
    {
      name: '透后伸缩压',
      type: 'line',
      color:'#606266',
      smooth: true,
      data: []
    },{
      name: '透后舒张压',
      type: 'line',
      color:'#E6A23C',
      smooth: true,
      data: []
    }
  ]
  listBPHistory(`patientCode=${patientInfo.value.code}`).then(res=>{
    console.log(res.data,'--------sss---')
    if(res.data.length>0){
      res.data.forEach((e:any)=>{
        xAxisData.push(e.透析日期.substring(5,10))
        series[0].data.push(e.透前伸缩压)
        series[1].data.push(e.透前舒张压)
        series[2].data.push(e.透后伸缩压)
        series[3].data.push(e.透后舒张压)
      })
      const legendData=['透前伸缩压', '透前舒张压', '透后伸缩压','透后舒张压']
      huatu(series,xAxisData,legendData,'血压趋势图')
    }
  })
}
// 监控患者信息变化
watch(
@@ -443,6 +555,12 @@
      dialogVisible.value = false
      speech.value?.speak({ text: str }).then(() => {
      })
      if(configData.value.deviceType==='体重秤'){
        setTimeout(()=>{intiTubiao()},1000)
      }else{
        setTimeout(()=>{intiTubiaoXY()},1000)
      }
      sockteStore().setweightSockte({
        type: "体重秤",
@@ -466,7 +584,7 @@
    // 没有找到患者
    else if (patientInfo.value.name === '') {
      ipcRenderer.invoke('logger', '接收到的患者为空')
      // console.log('接收到的患者为空')
      console.log('接收到的患者为空')
      if (isUseFaceRecogService.value) {
        dialogVisible.value = true
      }
@@ -474,6 +592,7 @@
    }
    // 没有排班
    else if (patientInfo.value.isScheduled === 0) {
      setTimeout(()=>{intiTubiao()},1000)
      settime()
      ipcRenderer.invoke('logger', `患者没有排班:${patientInfo.value.name}`)
      // console.log( `患者没有排班:${patientInfo.value.name}`)
@@ -504,6 +623,8 @@
watch(
  () => weightInfo.value.resultTime,
  async () => {
    const X= jgTime(timer,new Date())
    console.log(X,'收到体重',configData.value.BobaoJg)
    // 体重不能0
    if (weightInfo.value.result !== "0"&&patientInfo.value.code!=='') {
        ipcRenderer.invoke('logger', `体重变化了:${weightInfo.value.result}`)
@@ -537,9 +658,10 @@
        }else if(Number(patientInfo.value.pureWeight) !== 0&&patientInfo.value.isAfterMed===1 && patientInfo.value.preWeight>10){
          aimTSL.value = (Number(patientInfo.value.preWeight)- Number(patientInfo.value.pureWeight) - Number(patientInfo.value.clothesWeight)).toFixed(2)
        }
    }else if(patientInfo.value.code===''&&weightInfo.value.result !== "0"){
    }// 要过4秒才能重复播报这个消息
    else if(patientInfo.value.code===''&&weightInfo.value.result !== "0"&&X>configData.value.BobaoJg){
      timer=new Date()
      speech.value?.speak({ text: "没有识别的患者,请先验证患者" }).then(() => {
        // console.log("播报完成...")
      })
    }
  }
@@ -553,9 +675,9 @@
      console.log(`血压发生变化了:${weightInfo.value.result}`)
      const list = xyjInfo.value.result.split(',')
      if (list.length === 3) {
        gao_ya.value = list[0]
        di_ya.value = list[1]
        mai_bu.value = list[2]
        gao_ya.value = Number(list[0]).toString()
        di_ya.value = Number(list[1]).toString()
        mai_bu.value = Number(list[2]).toString()
      }
      // 患者信息为空
      if (patientInfo.value.id === 0) {
@@ -706,7 +828,6 @@
  console.log(0%5)
  console.log('页面初始化', os.hostname())
  setTimeout(()=>{
    console.log('8秒后执行')
    console.log('打印设置文件')
    console.log(configData.value)
@@ -718,7 +839,10 @@
    if(configData.value.Is_xyj){
      if(configData.value.xyj_type==='TM2655'){
        TM2655VP(configData.value.xueyanjiPortPath,configData.value.xueyanjiBaudRate)
      }else{
      }else if(configData.value.xyj_type==='mbp7000'){
        mbp7000(configData.value.xueyanjiPortPath,configData.value.xueyanjiBaudRate)
      }
      else{
        oumulongHbp9030(configData.value.xueyanjiPortPath,configData.value.xueyanjiBaudRate)
      } 
    }
@@ -728,12 +852,20 @@
      if(configData.value.tzc_type==='taiHengM523'){
        taiHengM523(configData.value.tzcPortPath,configData.value.tzcBaudRate)
      }
      else if(configData.value.tzc_type==='M503'){
        M503(configData.value.tzcPortPath,configData.value.tzcBaudRate)
      }else if(configData.value.tzc_type==='seca102'){
        seca102(configData.value.tzcPortPath,configData.value.tzcBaudRate)
      }
      // seca101读取文件
      else if(configData.value.tzc_type==='seca101'){
        console.log('体重是读取文件')
      }// 耀华XK3190-A12 
      else if(configData.value.tzc_type==='XK3190-A12'){
        XK3190A12(configData.value.tzcPortPath,configData.value.tzcBaudRate)
      }else if(configData.value.tzc_type==='zhiRongHehui'){
        zhiRongHehui(configData.value.tzcPortPath,configData.value.tzcBaudRate)
      }
      else{
        zhiRongT605(configData.value.tzcPortPath,configData.value.tzcBaudRate)
@@ -744,7 +876,30 @@
    console.log('人脸识别',isUseFaceRecogService.value)
    if (isUseFaceRecogService.value) {
      console.log('开启人脸识别')
      trackerTask = tracking.track("#myVideo", myTracker, { camera: true });
      faceapi.env.monkeyPatch({
          Canvas: HTMLCanvasElement,
          Image: HTMLImageElement,
          ImageData: ImageData,
          Video: HTMLVideoElement,
          createCanvasElement: () => document.createElement('canvas'),
          createImageElement: () => document.createElement('img')
      })
      // 获取用户媒体流
      getUserMedia(
        (streams: null) => {
          //后续用于停止视频流
          stream.value = streams
          //显示视频
          if (video.value) {
            video.value['srcObject'] = streams
          }
        },
        (error: any) => (getUserMediaFail.value = true)
      )
      init()
      setTimeout(()=>{
        detectFace()
      },2000)
      dialogVisible.value = true
    }else{
      console.log('关闭人脸识别')
@@ -756,7 +911,6 @@
      if(clockNum.value===0){
        fuxuan()
      }
      date.value=formatDate(new Date(),'YYYY-mm-dd HH:MM')
      if(Number(date.value.substring(11,13))<12){
        timeShidaun.value='上午好!'
@@ -766,11 +920,14 @@
        timeShidaun.value='晚上好!'
      }
      // 验证人脸识别已经通过但是还没有体重数据主动获取数据
      if(patientInfo.value.name!==''&&weightInfo.value.result==='0'&&configData.value.Is_tzc){
      if(patientInfo.value.name!==''&&weightInfo.value.result==='0'&&configData.value.Is_tzc&&configData.value.tzc_type==='seca101'){
        console.log('主动获取体重')
        todatatzs(patientInfo.value.datetime)
      }
      inputRef.value.focus();
      if(inputRef.value!==null){
         inputRef.value.focus();
      }
    }, 1000)
    speech.value = new Speech();
@@ -780,8 +937,110 @@
    })
  },8000)
})
/** @name 人脸检测 */
const detectFace:any = async () => {
  //非常重要:防止卡死
  await new Promise((resolve) => requestAnimationFrame(resolve))
  //绘制取景框
  // drawViewFinder()
  if (!canvas.value || !video.value || !video.value.currentTime || video.value.paused || video.value.ended)
    return detectFace()
  // 检测图像中具有最高置信度得分的脸部
  const result = await detectSingleFace(video.value, options)
  if (!result) return detectFace()
  // 匹配尺寸
  const dims = matchDimensions(canvas.value, video.value, true)
  // 调整检测到的框的大小,以防显示的图像的大小与原始
  const resizedResult = resizeResults(result, dims)
  const box = resizedResult.box
  // 不要小头像
  if(box._height<120) return detectFace()
  //检测框是否在取景框内
  // if (!checkInViewFinder(box)) return detectFace()
  // drawViewFinder()
  //将检测结果绘制到画布(此处不用,可以直接用来绘制检测到的人脸盒子)
  draw.drawDetections(canvas.value, resizedResult.box);
  drawBox(box, '')
  // video.value.pause()
  // //截取人脸图片
  const image = await cameraShoot(
    video.value,
    resizedResult.box.topLeft,
    resizedResult.box.width,
    resizedResult.box.height
  )
  if (!image) {
    drawBox(box, '识别失败')
    video.value.play()
    return detectFace()
  }
  const X= jgTime(lsDateTime,new Date())
  console.log('----',X,dialogVisible.value)
  // console.log(image)
  if(dialogVisible.value&&X>configData.value.face_push&&isworkTime(new Date())){
    lsDateTime=new Date()
    console.log(X,'上传阿里识别间隔')
    base64toFile(image)
  }
  return detectFace()
}
// 加载算法模型 文件存储在 public 文件夹下models文件夹。// 需要文件的话联系我
const init = async () => {
  await nets.ssdMobilenetv1.loadFromUri('./models')
}
/** @name 调用摄像头 */
const getUserMedia = (success: NavigatorUserMediaSuccessCallback, error: NavigatorUserMediaErrorCallback) => {
  const constraints = {
    audio:false,
    video: {
      width: 800,
      height: 500
    }
  }
  if (navigator.mediaDevices.getUserMedia) {
    // 最新的标准API
    console.log('sssdsdsd')
    navigator.mediaDevices.getUserMedia(constraints).then(success).catch(error)
  } else if (navigator.webkitGetUserMedia) {
    // webkit核心浏览器
    navigator.webkitGetUserMedia(constraints, success, error)
  } else if (navigator.mozGetUserMedia) {
    // firfox浏览器
    navigator.mozGetUserMedia(constraints, success, error)
  } else if (navigator.getUserMedia) {
    // 旧版API
    navigator.getUserMedia(constraints, success, error)
  }
}
/** @name 截取快照 */
const cameraShoot = (video: HTMLVideoElement, startPoint: { x: number; y: number }, width: number, height: number) => {
  const canvas = document.createElement('canvas')
  canvas.width = video.videoWidth
  canvas.height = video.videoHeight
  canvas.getContext('2d')?.drawImage(video, 0, 0, canvas.width, canvas.height)
  const imgSrc = canvas?.toDataURL('image/png');
  return imgSrc
}
// 画盒子
const drawBox = (box: Box<any> | faceapi.IBoundingBox | faceapi.IRect, label: string) => {
  if (!canvas.value) return
  const context = canvas.value.getContext('2d')
  context?.clearRect(box.x, box.y, box.width, box.height)
  const drawBox = new draw.DrawBox(box, {
    label: label,
    boxColor:'#409EFF'
  })
  drawBox.draw(canvas.value)
}
// 停止
const handleStopVideo = () => {
  if (stream.value) {
    stream.value.getTracks().forEach((track: { stop: () => void; }) => {
      track.stop()
    })
  }
}
</script>
<style src="./index.css" />
<style lang="less" scoped>
@@ -794,25 +1053,26 @@
  justify-content: center;
  border-radius: 2px;
  .rWrapp {
    width: 500px;
    display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: relative;
  // transform: rotateY(180deg);
  overflow: hidden;
  canvas {
    position: absolute;
    top: 0;
    width: 800px;
    height: 500px;
    margin: auto;
    // margin-top: 30px;
    position: relative;
    .myCanvas {
      position: absolute;
      top: 0;
      left: 0;
      border-radius: 50%;
      width: 100%;
      height: 100%;
    }
    #myVideo {
      width: 100%;
      height: 100%;
      border-radius: 50%;
      object-fit: cover;
    }
    border-radius: 20%;
  }
  video {
    object-fit: fill;
    width: 800px;
    height: 500px;
    border-radius: 20px;
  }
  }
  .status {
    //margin-top: 100px;