diff --git a/frontend/src/renderer/src/assets/new/archive.svg b/frontend/src/renderer/src/assets/new/archive.svg new file mode 100644 index 00000000..75d3f08f --- /dev/null +++ b/frontend/src/renderer/src/assets/new/archive.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/renderer/src/assets/new/conduct.png b/frontend/src/renderer/src/assets/new/conduct.png new file mode 100644 index 00000000..d705efe8 Binary files /dev/null and b/frontend/src/renderer/src/assets/new/conduct.png differ diff --git a/frontend/src/renderer/src/assets/new/conduct.svg b/frontend/src/renderer/src/assets/new/conduct.svg new file mode 100644 index 00000000..657eae97 --- /dev/null +++ b/frontend/src/renderer/src/assets/new/conduct.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/frontend/src/renderer/src/assets/new/endvideo.svg b/frontend/src/renderer/src/assets/new/endvideo.svg new file mode 100644 index 00000000..1285fc14 --- /dev/null +++ b/frontend/src/renderer/src/assets/new/endvideo.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/renderer/src/assets/new/jietu.svg b/frontend/src/renderer/src/assets/new/jietu.svg new file mode 100644 index 00000000..3ebb379c --- /dev/null +++ b/frontend/src/renderer/src/assets/new/jietu.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/renderer/src/assets/new/newbg.jpg b/frontend/src/renderer/src/assets/new/newbg.jpg new file mode 100644 index 00000000..b429dd2c Binary files /dev/null and b/frontend/src/renderer/src/assets/new/newbg.jpg differ diff --git a/frontend/src/renderer/src/assets/new/refresh.svg b/frontend/src/renderer/src/assets/new/refresh.svg new file mode 100644 index 00000000..37c462b7 --- /dev/null +++ b/frontend/src/renderer/src/assets/new/refresh.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/renderer/src/assets/new/settings.svg b/frontend/src/renderer/src/assets/new/settings.svg new file mode 100644 index 00000000..1ef71f99 --- /dev/null +++ b/frontend/src/renderer/src/assets/new/settings.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/renderer/src/assets/new/startvideo.svg b/frontend/src/renderer/src/assets/new/startvideo.svg new file mode 100644 index 00000000..703f1d35 --- /dev/null +++ b/frontend/src/renderer/src/assets/new/startvideo.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/renderer/src/assets/new/testheader.png b/frontend/src/renderer/src/assets/new/testheader.png new file mode 100644 index 00000000..5ee897b4 Binary files /dev/null and b/frontend/src/renderer/src/assets/new/testheader.png differ diff --git a/frontend/src/renderer/src/assets/new/title1.svg b/frontend/src/renderer/src/assets/new/title1.svg new file mode 100644 index 00000000..f87b6a4b --- /dev/null +++ b/frontend/src/renderer/src/assets/new/title1.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/renderer/src/assets/new/title2.svg b/frontend/src/renderer/src/assets/new/title2.svg new file mode 100644 index 00000000..9229295a --- /dev/null +++ b/frontend/src/renderer/src/assets/new/title2.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/renderer/src/assets/new/title3.svg b/frontend/src/renderer/src/assets/new/title3.svg new file mode 100644 index 00000000..65d2c9ec --- /dev/null +++ b/frontend/src/renderer/src/assets/new/title3.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/renderer/src/assets/new/title4.svg b/frontend/src/renderer/src/assets/new/title4.svg new file mode 100644 index 00000000..f211968c --- /dev/null +++ b/frontend/src/renderer/src/assets/new/title4.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/renderer/src/assets/new/title5.svg b/frontend/src/renderer/src/assets/new/title5.svg new file mode 100644 index 00000000..36d53724 --- /dev/null +++ b/frontend/src/renderer/src/assets/new/title5.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/renderer/src/assets/new/u10.svg b/frontend/src/renderer/src/assets/new/u10.svg new file mode 100644 index 00000000..60e866b4 --- /dev/null +++ b/frontend/src/renderer/src/assets/new/u10.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/renderer/src/assets/new/u13.png b/frontend/src/renderer/src/assets/new/u13.png new file mode 100644 index 00000000..dc301d37 Binary files /dev/null and b/frontend/src/renderer/src/assets/new/u13.png differ diff --git a/frontend/src/renderer/src/assets/new/u16.svg b/frontend/src/renderer/src/assets/new/u16.svg new file mode 100644 index 00000000..a3c3e1d0 --- /dev/null +++ b/frontend/src/renderer/src/assets/new/u16.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/renderer/src/assets/new/u249.svg b/frontend/src/renderer/src/assets/new/u249.svg new file mode 100644 index 00000000..4161523a --- /dev/null +++ b/frontend/src/renderer/src/assets/new/u249.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/renderer/src/assets/new/u253.svg b/frontend/src/renderer/src/assets/new/u253.svg new file mode 100644 index 00000000..1f260d35 --- /dev/null +++ b/frontend/src/renderer/src/assets/new/u253.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/renderer/src/assets/new/u257.svg b/frontend/src/renderer/src/assets/new/u257.svg new file mode 100644 index 00000000..596a771c --- /dev/null +++ b/frontend/src/renderer/src/assets/new/u257.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/renderer/src/assets/new/u264.svg b/frontend/src/renderer/src/assets/new/u264.svg new file mode 100644 index 00000000..f357134a --- /dev/null +++ b/frontend/src/renderer/src/assets/new/u264.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/renderer/src/assets/new/useravatar.svg b/frontend/src/renderer/src/assets/new/useravatar.svg new file mode 100644 index 00000000..b262db65 --- /dev/null +++ b/frontend/src/renderer/src/assets/new/useravatar.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/renderer/src/assets/new/useredit.svg b/frontend/src/renderer/src/assets/new/useredit.svg new file mode 100644 index 00000000..dcaf053c --- /dev/null +++ b/frontend/src/renderer/src/assets/new/useredit.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/renderer/src/assets/new/userinfo.svg b/frontend/src/renderer/src/assets/new/userinfo.svg new file mode 100644 index 00000000..695d2e1b --- /dev/null +++ b/frontend/src/renderer/src/assets/new/userinfo.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/renderer/src/services/api.js b/frontend/src/renderer/src/services/api.js index c175d682..0d8ba21e 100644 --- a/frontend/src/renderer/src/services/api.js +++ b/frontend/src/renderer/src/services/api.js @@ -13,7 +13,7 @@ api.interceptors.request.use( if (window.electronAPI) { config.baseURL = window.electronAPI.getBackendUrl() } else { - config.baseURL = 'http://localhost:5000' + config.baseURL = 'http://192.168.1.60:5000' } // 为需要发送数据的请求设置Content-Type(避免覆盖FormData) @@ -660,7 +660,7 @@ export const getBackendUrl = () => { if (window.electronAPI) { return window.electronAPI.getBackendUrl() } else { - return 'http://localhost:5000' + return 'http://192.168.1.60:5000' } } diff --git a/frontend/src/renderer/src/style.css b/frontend/src/renderer/src/style.css index 5347ea2a..06c14be0 100644 --- a/frontend/src/renderer/src/style.css +++ b/frontend/src/renderer/src/style.css @@ -7,7 +7,7 @@ } body { - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + font-family: 'Noto Sans SC', sans-serif; background-color: #f5f5f5; color: #333; line-height: 1.6; diff --git a/frontend/src/renderer/src/views/Dashboard.vue b/frontend/src/renderer/src/views/Dashboard.vue index 137a9aeb..9adfa164 100644 --- a/frontend/src/renderer/src/views/Dashboard.vue +++ b/frontend/src/renderer/src/views/Dashboard.vue @@ -1,256 +1,221 @@ @@ -261,10 +226,15 @@ import { ElMessage, ElMessageBox } from 'element-plus' import api, { patientAPI } from '../services/api.js' import { useAuthStore } from '../stores/index.js' import Header from '@/views/Header.vue' +import PatientCreate from '@/views/PatientCreate.vue' +import Detection from '@/views/Detection.vue' + import { color } from 'echarts' const router = useRouter() const authStore = useAuthStore() - +const isDetection = ref(true) // 显示检查页面 +const patienttype = ref('add') +const patienttotal = ref(0) // 响应式数据 const activeNav = ref('detection') const searchKeyword = ref('') @@ -361,18 +331,20 @@ const calculateAgeres = (date) => { } // 计算属性 const filteredPatients = computed(() => { - if (!searchKeyword.value) { - return patients.value - } + // if (!searchKeyword.value) { + // return patients.value + // } - return patients.value.filter(patient => - patient.name.toLowerCase().includes(searchKeyword.value.toLowerCase()) - ) + // return patients.value.filter(patient => + // patient.name.toLowerCase().includes(searchKeyword.value.toLowerCase()) + // ) }) // 方法 const handleSearch = () => { // 搜索逻辑已在计算属性中处理 + loadPatients() + } // const selectPatient = (patient) => { @@ -457,7 +429,8 @@ const startDetection = () => { ElMessage.warning('请先选择患者') return } - router.push(`/detection/${selectedPatient.value.id}`) + isDetection.value = true + // router.push(`/detection/${selectedPatient.value.id}`) } const createNewPatient = async () => { @@ -465,7 +438,9 @@ const createNewPatient = async () => { const response = await api.get('/api/license/info') const isValid = response && response.success && response.data && response.data.valid if (isValid) { - router.push('/patient/create') + // router.push('/patient/create') + patienttype.value = 'add' + isCloseCreat.value =true return } const msg ='['+ (response && response.data && response.data.message)+'],软件使用授权不正确,您不能创建新患者!' @@ -546,16 +521,24 @@ const calculateAge = (birthDate) => { } return age } - +const size =ref(15) +const page =ref(1) +const search = ref("") const loadPatients = async () => { try { - const response = await patientAPI.getPatients() + const response = await patientAPI.getPatients({ + page:page.value, + size:size.value, + search:search.value + }) if (response.success) { // 如果返回的是分页数据对象,提取patients数组 if (response.data && Array.isArray(response.data.patients)) { patients.value = response.data.patients + patienttotal.value =response.data.total } else if (Array.isArray(response.data)) { patients.value = response.data + patienttotal.value =response.total } else { patients.value = [] } @@ -563,32 +546,7 @@ const loadPatients = async () => { } catch (error) { console.error('加载患者列表失败:', error) // 模拟数据 - patients.value = [ - { - id: 1, - name: '张三', - gender: '男', - age: 45, - updated_at: '2023-05-01 14:00:00', - num: 4 - }, - { - id: 2, - name: '李四', - gender: '女', - updated_at: '2023-05-01 14:00:00', - num: 4 - }, - { - id: 3, - name: '王五', - gender: '男', - age: 52, - updated_at: '2023-05-01 14:00:00', - num: 4 - - } - ] + patients.value = [] } } const handleClose = () => { @@ -639,7 +597,24 @@ function delClick(id) { .catch(() => { }); } - +const isCloseCreat = ref(false) +function closecreatbox(e){ + if(e == true){ + loadPatients() + } + isCloseCreat.value = false +} +function endChange(){ + isDetection.value = false +} +function handleCurrentChange (val) { + page.value = val + loadPatients() +} +function editClick(){ + patienttype.value = 'edit' + isCloseCreat.value = true +} diff --git a/frontend/src/renderer/src/views/Detection - 副本.vue b/frontend/src/renderer/src/views/Detection - 副本.vue new file mode 100644 index 00000000..8229f853 --- /dev/null +++ b/frontend/src/renderer/src/views/Detection - 副本.vue @@ -0,0 +1,3001 @@ + + + + + + + \ No newline at end of file diff --git a/frontend/src/renderer/src/views/Detection.vue b/frontend/src/renderer/src/views/Detection.vue index f087f07f..cbbd4d3b 100644 --- a/frontend/src/renderer/src/views/Detection.vue +++ b/frontend/src/renderer/src/views/Detection.vue @@ -1,587 +1,292 @@ @@ -595,233 +300,58 @@ import { useAuthStore } from '../stores/index.js' import * as echarts from 'echarts' import { getBackendUrl, patientAPI } from '../services/api.js' import noImageSvg from '@/assets/no-image.svg' -import HistoryDashboard from '@/views/PatientProfile.vue' -const isBig =ref(false) const authStore = useAuthStore() -const router = useRouter() -const route = useRoute() -const isRecording = ref(false) -const isConnected = ref(false) +const emit = defineEmits([ 'endChange']); +const props = defineProps({ + selectedPatient: { + required: false, + type: Object, + default: null + } +}) + +// 四个设备的独立连接状态 +const cameraStatus = ref('未连接') // 相机设备状态 +const femtoboltStatus = ref('未连接') // 深度相机(FemtoBolt)设备状态 +const imuStatus = ref('未连接') // IMU设备状态 +const pressureStatus = ref('未连接') // 压力传感器设备状态 +const footImgSrc = ref('') // 足底压力传感器图片 + + +const patientInfo =ref({}) +const seconds = ref(0); +const isRunning = ref(false); +const timerId = ref(null); +const blinkState = ref(false); + + const rtspImgSrc = ref('') -const camera1ImgSrc = ref('') -const camera2ImgSrc = ref('') +const camera1ImgSrc = ref('') // 相机视频流1 +const camera2ImgSrc = ref('')// 相机视频流2 + const depthCameraImgSrc = ref('') // 深度相机视频流 const screenshotLoading = ref(false) const cameraDialogVisible =ref(false) // 设置相机参数弹框 const contenGridRef =ref(null) // 实时检查整体box const wholeBodyRef = ref(null) // 身体姿态ref const videoImgRef =ref(null) // 视频流图片ref -const camera1Ref = ref(null) -const camera2Ref = ref(null) -const historyDialogVisible = ref(false) -// 录像相关变量 -let mediaRecorder = null -let recordedChunks = [] -let recordingStream = null -// 患者信息(从页面获取或通过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 - -// 后端服务器地址配置 -const BACKEND_URL = getBackendUrl() -const resDialogVisible = ref(false) -const reshandleClose = () => { - resDialogVisible.value = false -} -const cameraHandleClose = () => { - cameraDialogVisible.value = false -} -const dialogVisible = ref(false) -const handleClose = () => { - dialogVisible.value = 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({ // 相机参数 - camera:{ - device_index: '', // 序号 - }, - femtobolt:{ - algorithm_type: '', // 算法类型 - depth_mode: '', // 相机模式 - depth_range_min: '', // 距离范围最小值 - depth_range_max: '', // 距离范围最大值 - }, - imu:{ - port: '', // IMU串口号 +// 处理开始/停止按钮点击 +async function handleStartStop() { + if (isRecording.value) { + // 停止录制视频 + await stopRecord() + } else { + // 开始录制视频 + await startRecord() } -}) -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 cameraStatus = ref('未连接') // 相机设备状态 -const femtoboltStatus = ref('未连接') // 深度相机(FemtoBolt)设备状态 -const imuStatus = ref('未连接') // IMU设备状态 -const pressureStatus = 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 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; @@ -860,115 +390,210 @@ const resetTimer = () => { 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 +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(response.message || '修改失败') + throw new Error(result.message || '开始录屏失败') } } catch (error) { + ElMessage.error(`开始录屏失败: ${error.message}`) 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 = { // 相机参数 - camera:{ - device_index: '', // 序号 - }, - femtobolt:{ - algorithm_type: '', // 算法类型 - depth_mode: '', // 相机模式 - depth_range_min: '', // 距离范围最小值 - depth_range_max: '', // 距离范围最大值 - }, - imu:{ - port: '', // IMU串口号 + +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}`) } - // 加载相机参数信息 - 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-- +const isStartVideo = ref(false) +function startVideoClick() { + startRecord() + isStartVideo.value = true +} +function stopVideoClick() { + stopRecord() + isStartVideo.value = false +} +function endClick() { + emit('endChange',false) + ElMessage.success('结束监测') +} +const patientId = ref("") // 患者ID +const loadPatientInfo = async () => { // 加载患者信息 + try { + debugger + // 从路由参数获取患者ID + // patientId.value = props.selectedPatient.id + patientId.value = '202511150005' + 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('加载患者信息失败,请检查网络连接') } - return age } -// 返回按钮逻辑 -const handleBack = () => { - console.log('返回上一页') -} -const editPatient = () => { - // 修改患者信息 - patientForm.value = JSON.parse(JSON.stringify(patientInfo.value)) - if (patientForm.value.birth_date) { - calculatedAge.value = calculateAge(patientForm.value.birth_date) +onMounted(() => { + if (authStore.currentUser) { + creatorId.value = authStore.currentUser.id + } + + // 加载患者信息 + loadPatientInfo() + // 启动检测 + // startDetection() + // 页面加载时自动连接WebSocket + // connectWebSocket() + + // 监听页面关闭或刷新事件 + window.addEventListener('beforeunload', handleBeforeUnload) + +}) +// 开始检测 +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: route.params.id, + // 可以添加其他检测参数 + 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() { + try { + // 计算检测持续时间 + let duration = 0 + // 调用后端API停止检测 + const response = await fetch(`${BACKEND_URL}/api/detection/${patientInfo.value.sessionId}/stop`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + duration: duration + }) + }) + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`) + } + } catch (error) { + console.error('❌ 停止检测失败:', error) + ElMessage.error(`停止检测失败: ${error.message}`) } - dialogVisible.value = true } -const tempInfo = ref({}) // WebSocket连接函数 function connectWebSocket() { try { @@ -1029,7 +654,7 @@ function connectWebSocket() { console.log('✅ 主WebSocket连接成功!Socket ID:', socket.id) isConnected.value = true //绘制头部仪表盘 - initchart() + // initchart() }) socket.on('connect_error', (error) => { @@ -1175,672 +800,7 @@ function connectWebSocket() { 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: 'camera' }) - 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 - } - - // 重置所有设备状态 - cameraStatus.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) { - const url = 'data:image/jpeg;base64,' + base64Image - if (String(deviceId).toLowerCase() === 'camera2') { - camera2ImgSrc.value = url - } else { - camera1ImgSrc.value = url - // 旧变量保留(避免其它位置引用出错) - rtspImgSrc.value = url - } - } 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, - Math.abs(headPose.rotation) - ) - } else if (headPose.rotation > 0) { - // 右旋(正值) - headPoseMaxValues.value.rotationRightMax = Math.max( - headPoseMaxValues.value.rotationRightMax, - headPose.rotation - ) - } - - // 更新倾斜角最值 - if (headPose.tilt < 0) { - // 左倾(负值),取绝对值的最大值 - headPoseMaxValues.value.tiltLeftMax = Math.max( - headPoseMaxValues.value.tiltLeftMax, - Math.abs(headPose.tilt) - ) - } else if (headPose.tilt > 0) { - // 右倾(正值) - headPoseMaxValues.value.tiltRightMax = Math.max( - headPoseMaxValues.value.tiltRightMax, - headPose.tilt - ) - } - - // 更新俯仰角最值 - if (headPose.pitch < 0) { - // 下俯(负值),取绝对值的最大值 - headPoseMaxValues.value.pitchDownMax = Math.max( - headPoseMaxValues.value.pitchDownMax, - Math.abs(headPose.pitch) - ) - } else if (headPose.pitch > 0) { - // 上仰(正值) - headPoseMaxValues.value.pitchUpMax = Math.max( - headPoseMaxValues.value.pitchUpMax, - 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 handleDiagnosticInfo(status) { - try { - // 检查是否有活跃的会话ID - if (!patientInfo.value.sessionId) { - throw new Error('缺少会话Id') - } - // 调用后端API采集检测数据 - const response = await fetch(`${BACKEND_URL}/api/detection/${patientInfo.value.sessionId}/save-info`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - diagnosis_info: diagnosticForm.diagnosis_info, - treatment_info: diagnosticForm.treatment_info, - suggestion_info: diagnosticForm.suggestion_info, - status: status, - session_id: patientInfo.value.sessionId, - }) - }) - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`) - } - - const result = await response.json() - if (result.success) { - // 显示成功消息 - ElMessage.success({ - message: status + '诊断信息成功', - duration: 5000 - }) - - } else { - throw new Error(result.message || '诊断信息失败') - } - } catch (error) { - ElMessage.error({ - message: errorMessage, - duration: 5000 - }) - - } finally { - - } -} - -// 保存检测数据 -async function saveDetectionData() { - console.log(tempInfo.value) - if (screenshotLoading.value) return - - try { - screenshotLoading.value = true - // 显示保存进度 - ElMessage.info('正在保存检测截图数据...') - - // 检查是否有活跃的会话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 foot_image="" - if(tempInfo.value.camera_frame != null - && tempInfo.value.camera_frame.image != null ){ - foot_image=base64 + tempInfo.value.camera_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, - - head_pose:head_pose, - body_pose:null, - body_image: body_image, - - foot_data:foot_data, - foot_image:foot_image, - foot_data_image:pressure_image, - screen_image:null - - }) - - // 显示成功消息和文件路径 - ElMessage.success({ - message: `检测数据保存成功!`, - duration: 5000 - }) - - - } 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}/collect` - , { - 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: route.params.id, - // 可以添加其他检测参数 - 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() { - try { - // 计算检测持续时间 - let duration = 0 - // 调用后端API停止检测 - const response = await fetch(`${BACKEND_URL}/api/detection/${patientInfo.value.sessionId}/stop`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - duration: duration - }) - }) - 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 - patientId.value = route.params.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 creatorId = ref('') // 处理页面关闭或刷新事件 const handleBeforeUnload = (event) => { console.log('页面即将关闭,正在清理资源...') @@ -1853,88 +813,29 @@ const handleBeforeUnload = (event) => { 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); - } - } - }); - }); + + + +// 深度相机帧显示函数 +function displayDepthCameraFrame(base64Image) { + if (base64Image && base64Image.length > 0) { + depthCameraImgSrc.value = 'data:image/jpeg;base64,' + base64Image + } else { + console.warn('⚠️ 收到空的深度相机帧数据') + } } -const calibrationClick = async () => { +// 头部姿态最值跟踪数据 +const headPoseMaxValues = ref({ + rotationLeftMax: 0, // 旋转-左旋最大值 + rotationRightMax: 0, // 旋转-右旋最大值 + tiltLeftMax: 0, // 倾斜-左倾最大值 + tiltRightMax: 0, // 倾斜-右倾最大值 + pitchUpMax: 0, // 俯仰-上仰最大值 + pitchDownMax: 0 // 俯仰-下俯最大值 +}) + + +const calibrationClick = async () => { // 校准按钮点击事件 const response = await fetch(`${BACKEND_URL}/api/devices/calibrate/imu`, { method: 'POST', headers: { @@ -1953,228 +854,23 @@ const calibrationClick = async () => { } } - -const cameraSubmit = async () => { - 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) - } - } -} - -// 加载相机参数信息 -const getDevicesInit = async () => { +// 清零最值并开始跟踪 +function clearAndStartTracking() { 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}`) + // 重置所有最值为0 + headPoseMaxValues.value = { + rotationLeftMax: 0, + rotationRightMax: 0, + tiltLeftMax: 0, + tiltRightMax: 0, + pitchUpMax: 0, + pitchDownMax: 0 } } catch (error) { - console.error('加载相机参数失败:', error) - ElMessage.warning('加载相机参数失败,请检查网络连接') + ElMessage.error('清零失败') } } -onMounted(() => { - - if (authStore.currentUser) { - console.log(authStore.currentUser) - creatorId.value = authStore.currentUser.id - } - - // 加载患者信息 - loadPatientInfo() - // 启动检测 - startDetection() - // 页面加载时自动连接WebSocket - connectWebSocket() - - // 监听页面关闭或刷新事件 - window.addEventListener('beforeunload', handleBeforeUnload) - -}) - -onUnmounted(() => { - console.log('🔄 Detection组件正在卸载,开始清理资源...') - - try { - // 清理定时器 - if (timerId.value) { - clearInterval(timerId.value) - timerId.value = null - console.log('✅ 定时器已清理') - } - - // 停止录制 - if (isRecording.value === true) { - stopRecord() - console.log('✅ 录制已停止') - } - - // 停止检测 - stopDetection() - console.log('✅ 检测已停止') - - // 断开WebSocket连接 - disconnectWebSocket() - console.log('✅ WebSocket连接已断开') - - // 清理图表资源 - 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() - console.log('✅ pitchCharts已清理') - } catch (e) { - console.warn('pitchCharts dispose error in onUnmounted:', e) - } - pitchCharts = null - } - - // 移除页面关闭事件监听器 - window.removeEventListener('beforeunload', handleBeforeUnload) - console.log('✅ beforeunload事件监听器已移除') - - console.log('🎉 Detection组件资源清理完成') - - } catch (error) { - console.error('❌ Detection组件卸载时出错:', 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) { // 检查是否在冷却期内 @@ -2203,792 +899,397 @@ function refreshClick(type) { console.warn('⚠️ Socket服务未连接,无法重启设备!') } } + +// 处理压力传感器足部压力数据 +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) + } +} +function displayCameraFrameById(deviceId, base64Image) { // 显示相机帧 + if (base64Image && base64Image.length > 0) { + const url = 'data:image/jpeg;base64,' + base64Image + if (String(deviceId).toLowerCase() === 'camera2') { + camera2ImgSrc.value = url + } else { + camera1ImgSrc.value = url + // 旧变量保留(避免其它位置引用出错) + rtspImgSrc.value = url + } + } else { + console.warn('⚠️ 收到空的视频帧数据') + } +} + - - \ No newline at end of file diff --git a/frontend/src/renderer/src/views/Header.vue b/frontend/src/renderer/src/views/Header.vue index e67329c4..bd9803a1 100644 --- a/frontend/src/renderer/src/views/Header.vue +++ b/frontend/src/renderer/src/views/Header.vue @@ -14,16 +14,13 @@
登录时间:{{ time }}
- +
- Avatar - + Avatar {{ userInfo.name }} - +
+ 退出 +
@@ -86,18 +83,19 @@ const handleUserCommand = (command) => { - switch (command) { - case 'profile': - viewInfoClick() - // 个人信息 - break - case 'settings': - // 系统设置 - break - case 'logout': - handleLogout() - break - } + handleLogout() + // switch (command) { + // case 'profile': + // viewInfoClick() + // // 个人信息 + // break + // case 'settings': + // // 系统设置 + // break + // case 'logout': + // handleLogout() + // break + // } } const dialogVisible =ref(false) function viewInfoClick(){ @@ -214,7 +212,7 @@ } .header { - height: 50px; + height: 60px; background: #323232; border-bottom: none; display: flex; @@ -240,17 +238,18 @@ width: 26px; height: 26px; border-radius: 4px; - background: #0099ff; + background: rgba(38, 111, 255, 1); text-align: center; padding-top: 3px; } .system-title { margin: 0; - font-family: 微软雅黑, sans-serif; - font-weight: 400; + font-family: 'Noto Sans SC'; + font-weight: 700; font-style: normal; font-size: 22px; + color: #FFFFFF; color: rgb(255, 255, 255); display: flex; align-items: center; @@ -287,15 +286,15 @@ font-size: 12px; margin-left: 8px; } - .badge-invalid { background-color: #8b0000; color: #fff; } - .badge-trial { background-color: #ff8c00; color: #fff; } - .badge-valid { background-color: #2e8b57; color: #fff; } + .badge-invalid { background-color: rgba(67, 67, 67, 1); color: #949494; } + .badge-trial { background-color: rgba(67, 67, 67, 1); color: #949494; } + .badge-valid { background-color: rgba(38, 111, 255, 1); color: #fff; } .activate-btn { margin-left: 8px; padding: 2px 10px; font-size: 12px; - border: 1px solid #409EFF; - background-color: #409EFF; + border: 1px solid rgba(38, 111, 255, 1); + background-color: rgba(38, 111, 255, 1); color: #fff; border-radius: 12px; cursor: pointer; @@ -316,4 +315,15 @@ box-shadow: 0 0 0 1px transparent inset; border-color: transparent !important; } +.user-line{ + width: 1px; + height: 15px; + margin: 0 10px; + background-color: #949494; +} +.user-return:hover{ + cursor: pointer; + color: rgb(0, 140, 255); + text-decoration: underline; +} \ No newline at end of file diff --git a/frontend/src/renderer/src/views/Login.vue b/frontend/src/renderer/src/views/Login.vue index f1229753..6a90148a 100644 --- a/frontend/src/renderer/src/views/Login.vue +++ b/frontend/src/renderer/src/views/Login.vue @@ -1,67 +1,69 @@