diff --git a/frontend/src/renderer/src/views/Dashboard.vue b/frontend/src/renderer/src/views/Dashboard.vue index 354823de..c08ab4f9 100644 --- a/frontend/src/renderer/src/views/Dashboard.vue +++ b/frontend/src/renderer/src/views/Dashboard.vue @@ -24,13 +24,15 @@
-
患者列表
+
+
患者列表
+
- @@ -49,7 +51,9 @@
-
基础信息
+
+
基础信息
+
@@ -133,6 +137,112 @@
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
@@ -155,7 +265,83 @@ const userInfo = reactive({ username: '医生', avatar: '' }) - +const tableRef = ref(null) +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([ + { label: '学生', value: '学生' } +]) +const residenceOptions = ref([ + { label: '北京', value: '北京' } +]) +const nationalityOptions = ref([ + { label: '汉族', value: '汉族' } +]) +// 表单验证规则 +const formRules = { + name: [ + { required: true, message: '请输入患者姓名', trigger: 'blur' }, + { min: 2, max: 20, message: '姓名长度在 2 到 20 个字符', trigger: 'blur' } + ], + gender: [ + { required: true, message: '请选择性别', trigger: 'change' } + ], + birthDate: [ + { required: true, message: '请选择出生日期', trigger: 'change' } + ], + height: [ + { required: true, message: '请输入身高', trigger: 'blur' }, + { pattern: /^\d+(\.\d+)?$/, message: '请输入有效的身高', trigger: 'blur' } + ], + weight: [ + { required: true, message: '请输入体重', trigger: 'blur' }, + { pattern: /^\d+(\.\d+)?$/, message: '请输入有效的体重', trigger: 'blur' } + ], + phone: [ + { required: true, message: '请输入联系电话', trigger: 'blur' }, + { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' } + ] +} +const validateForm = async () => { + try { + await patientFormRef.value.validate() + return true + } catch (error) { + ElMessage.error('请完善必填信息') + return false + } +} +const calculatedAge = ref('') +// 实现年龄计算方法 calculateAgeres +const calculateAgeres = (date) => { + if (!date) return '0' + const today = new Date() + const birthDate = new Date(date) + let age = today.getFullYear() - birthDate.getFullYear() + const monthDiff = today.getMonth() - birthDate.getMonth() + if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { + age-- + } + calculatedAge.value = age +} // 计算属性 const filteredPatients = computed(() => { if (!searchKeyword.value) { @@ -181,11 +367,71 @@ const selectPatient = (patient) => { const editPatient = () => { - router.push(`/patient/edit/${selectedPatient.value.id}`) + // router.push(`/patient/edit/${selectedPatient.value.id}`) + // 修改选中患者信息 + patientForm.value = JSON.parse(JSON.stringify(selectedPatient.value)) + if (patientForm.value.birth_date) { + calculatedAge.value = calculateAgeres(patientForm.value.birth_date) + } + dialogVisible.value = true +} +const handleSave = async () => { + if (!(await validateForm())) return + saveLoading.value = true + try { + await savePatient() + ElMessage.success('修改成功') + dialogVisible.value = false + saveLoading.value = false + await loadPatients() + if (patients.value.length > 0 && selectedPatient.value.id) { + for (var i = 0; i < patients.value.length; i++) { + if (patients.value[i].id === selectedPatient.value.id) { + selectedPatient.value = patients.value[i] + break + } + } + } + setTimeout(() => { + tableRef.value.setCurrentRow(selectedPatient.value) + }, 300) + } 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 viewPatientProfile = () => { - router.push(`/patient/profile/${selectedPatient.value.id}`) + router.push(`/patient/${selectedPatient.value.id}`) } const startDetection = () => { @@ -310,7 +556,10 @@ const loadPatients = async () => { ] } } - +const handleClose = () => { + patientFormRef.value?.resetFields() + dialogVisible.value = false +} // 生命周期 onMounted(() => { // 从认证状态管理中加载用户信息 @@ -330,7 +579,7 @@ function delClick(id) { cancelButtonText: '取消', type: 'warning', }) - .then(async () => { + .then(async () => { try { const response = await patientAPI.deletePatient(id); if (response.success) { @@ -603,7 +852,7 @@ function delClick(id) { align-items: center; margin-bottom: 20px; padding-bottom: 15px; - border-bottom: 1px solid #e4e7ed; + border-bottom: 1px solid #434343; } .detail-header h3 { @@ -689,13 +938,22 @@ function delClick(id) { display: flex; align-items: center; justify-content: center; - border: 2px solid #ffffff; - border-image: linear-gradient(to right, rgb(245, 173, 7), rgb(160, 5, 216)) 1; + /* border: 2px solid #ffffff; */ + background: linear-gradient(to right, rgb(245, 173, 7), rgb(160, 5, 216)); border-radius: 20px; - padding: 1px 10px; font-size: 16px; font-weight: bold; color: #ffffff; + padding: 2px; +} + +.module-title-text { + display: flex; + align-items: center; + width: 116px; + background: #2C2C2C; + border-radius: 20px; + padding: 0px 25px; } :deep(.el-input__wrapper) { @@ -763,6 +1021,60 @@ function delClick(id) { color: #FFFFFF; font-family: 'Arial Normal', 'Arial', sans-serif; } + +/* 必填项标识 */ +:deep(.el-form-item.is-required .el-form-item__label::before) { + content: '*'; + color: #f56c6c; + margin-right: 4px; +} + +:deep(.el-input__wrapper) { + background-color: rgba(51, 51, 51, 1); + border-width: 1px; + border-style: solid; + border-color: rgba(127, 127, 127, 1); + border-radius: 4px; + box-shadow: none; +} + +:deep(.el-select__wrapper) { + background-color: rgba(51, 51, 51, 1); + border-width: 1px; + border-style: solid; + border-color: rgba(127, 127, 127, 1); + border-radius: 4px; + box-shadow: none; +} + +:deep(.el-form-item__label) { + font-size: 14px; + font-family: '苹方 粗体', '苹方 中等', '苹方', sans-serif; + font-weight: 700; + font-style: normal; + color: #FFFFFF; +} + +:deep(.el-col-12) { + margin-bottom: 15px; +} + +:deep(.el-input__inner) { + color: #ffffff; +} + +:deep(.el-select__placeholder) { + color: #ffffff; +} + +:deep(.el-input.is-disabled .el-input__wrapper) { + background-color: rgba(127, 127, 127, 1); + box-shadow: none; + border-width: 1px; + border-style: solid; + border-color: rgba(215, 215, 215, 1); + color: #FFFFFF; +} @@ -817,15 +1129,45 @@ function delClick(id) { } .dashboard-container.dashboard-container-home .el-input__wrapper { - box-shadow: 0 0 0 1px #787878; + box-shadow: none; } -.dashboard-container.dashboard-container-home .el-table__cell{ + +.dashboard-container.dashboard-container-home .el-table__cell { border-bottom: 1px solid #787878; } -.dashboard-container.dashboard-container-home .el-table__cell{ + +.dashboard-container.dashboard-container-home .el-table__cell { border-bottom: 1px solid #787878; } + .dashboard-container.dashboard-container-home .el-table__header th { border-color: #787878; } + +.dashboard-container.dashboard-container-home .el-dialog { + background-color: rgba(85, 85, 85, 0.9); +} + +.dashboard-container.dashboard-container-home .el-dialog__title { + color: #ffffff; +} + +.dashboard-container.dashboard-container-home .el-dialog__headerbtn .el-dialog__close { + font-size: 25px; +} + +.dashboard-container.dashboard-container-home .el-dialog__footer { + text-align: center; +} + +.dashboard-container.dashboard-container-home .dialog-footer .el-button { + background: linear-gradient(to right, #F135A6, #A005D8); + border: none; + color: #ffffff; + height: 55px; + width: 190px; + border-radius: 50px; + font-size: 20px; + font-family: 'Arial Normal', 'Arial', sans-serif; +} \ 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 cf5c79ac..79d9898e 100644 --- a/frontend/src/renderer/src/views/Detection.vue +++ b/frontend/src/renderer/src/views/Detection.vue @@ -1,37 +1,34 @@ @@ -340,9 +379,11 @@ import { ElMessage } from 'element-plus' import { useRouter, useRoute } from 'vue-router' import { io } from 'socket.io-client' import html2canvas from 'html2canvas' +import Header from '@/views/Header.vue' +import { useAuthStore } from '../stores/index.js' +const authStore = useAuthStore() const router = useRouter() const route = useRoute() - const isStart = ref(false) const isConnected = ref(false) const rtspImgSrc = ref('') @@ -378,10 +419,14 @@ let socket = null let frameCount = 0 // 后端服务器地址配置 -const BACKEND_URL = 'http://localhost:5000' - - +const BACKEND_URL = 'http://192.168.1.173:5000' +const formattedTime = ref(0) +const dialogVisible = ref(false) +const handleClose = () => { + dialogVisible.value = false +} +const diagnosticForm = ref({}) // 模拟历史数据 const historyData = ref([ { id: 3, rotLeft: '-55.2°', rotRight: '54.2°', tiltLeft: '-17.7°', tiltRight: '18.2°', pitchDown: '-20.2°', pitchUp: '10.5°' }, @@ -568,7 +613,6 @@ function handleStartStopRecording() { // 检测数据采集功能 async function handleDataCollection() { if (dataCollectionLoading.value) return - try { dataCollectionLoading.value = true @@ -587,8 +631,11 @@ async function handleDataCollection() { 'Content-Type': 'application/json' }, body: JSON.stringify({ - patient_id: patientInfo.value.id, - timestamp: Date.now() + // patient_id: patientInfo.value.id, + // timestamp: Date.now() + head_pose:{}, + body_pose:{}, + foot_data:{} }) }) @@ -934,7 +981,6 @@ function stopRecording() { saveRecording() } } - if (recordingStream) { recordingStream.getTracks().forEach(track => track.stop()) recordingStream = null @@ -955,7 +1001,7 @@ async function saveRecording() { if (recordedChunks.length === 0) { throw new Error('没有录制数据') } - + console.log() // 验证必需的患者信息 if (!patientInfo.value.id || !patientInfo.value.name || !patientInfo.value.sessionId) { throw new Error(`缺少必需的患者信息: ID=${patientInfo.value.id}, 姓名=${patientInfo.value.name}, 会话ID=${patientInfo.value.sessionId}`) @@ -1006,7 +1052,7 @@ async function saveRecording() { message: `录像保存成功!文件路径: ${result.filepath}`, duration: 5000 }) - + dialogVisible.value = false // 更新会话的视频路径 if (patientInfo.value.sessionId) { try { @@ -1015,15 +1061,15 @@ async function saveRecording() { console.error('更新会话视频路径失败:', error) } } - // 清空录制数据,避免重复保存 recordedChunks.length = 0 console.log('🧹 录像数据已清空') // 录像保存完成后,清空会话ID,正式结束会话 - patientInfo.value.sessionId = null + // patientInfo.value.sessionId = null console.log('✅ 会话正式结束,会话ID已清空') } else { + dialogVisible.value = false throw new Error(result.message || '保存失败') } @@ -1033,7 +1079,7 @@ async function saveRecording() { message: `保存录像失败: ${error.message}`, duration: 5000 }) - + dialogVisible.value = false // 即使保存失败,也要清空会话ID,避免状态混乱 patientInfo.value.sessionId = null console.log('⚠️ 录像保存失败,但会话已结束,会话ID已清空') @@ -1059,6 +1105,7 @@ async function saveRecording() { // 处理开始/停止按钮点击 async function handleStartStop() { + patientInfo.value.sessionId = null if (!isConnected.value) { ElMessage.warning('WebSocket未连接,无法操作') return @@ -1072,17 +1119,14 @@ async function handleStartStop() { await startDetection() } } - // 开始检测 async function startDetection() { try { console.log('🚀 正在开始检测...') - // 验证患者信息 if (!patientInfo.value || !patientInfo.value.id) { throw new Error('缺少患者信息,无法开始检测') } - // 调用后端API开始检测 const response = await fetch(`${BACKEND_URL}/api/detection/start`, { method: 'POST', @@ -1090,31 +1134,31 @@ async function startDetection() { 'Content-Type': 'application/json' }, body: JSON.stringify({ - patientId: patientInfo.value.id, + patient_id: patientInfo.value.id, // 可以添加其他检测参数 - settings: JSON.stringify({ - frequency: 30, // 采样频率 - // 其他设置参数 - }) + creator_id:creatorId.value, + // settings: JSON.stringify({ + // frequency: 30, // 采样频率 + // // 其他设置参数 + // }) }) }) - 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.data.session_id + patientInfo.value.sessionId = result.session_id patientInfo.value.detectionStartTime = Date.now() console.log('✅ 检测会话创建成功,会话ID:', patientInfo.value.sessionId) isStart.value = true - + startRecording() ElMessage.success('检测已开始') } else { throw new Error(result.message || '开始检测失败') @@ -1161,16 +1205,18 @@ async function stopDetection() { const result = await response.json() if (result.success) { console.log('✅ 检测会话已停止') + //停止弹出诊断信息 + dialogVisible.value = true if (result.duration) { console.log(`⏱️ 检测持续时间: ${result.duration}秒`) } } } } - + dialogVisible.value = true // 清除检测开始时间和会话ID patientInfo.value.detectionStartTime = null - patientInfo.value.sessionId = null + // patientInfo.value.sessionId = null ElMessage.success('检测已停止,视频继续播放') @@ -1233,7 +1279,7 @@ const handleBeforeUnload = () => { socket.disconnect() } } - +const creatorId = ref('') onMounted(() => { // 加载患者信息 loadPatientInfo() @@ -1243,6 +1289,10 @@ onMounted(() => { // 监听页面关闭或刷新事件 window.addEventListener('beforeunload', handleBeforeUnload) + if (authStore.currentUser) { + console.log(authStore.currentUser) + creatorId.value = authStore.currentUser.id + } }) onUnmounted(() => { @@ -1274,9 +1324,9 @@ onUnmounted(() => { \ No newline at end of file diff --git a/frontend/src/renderer/src/views/PatientCreate.vue b/frontend/src/renderer/src/views/PatientCreate.vue index 137b7e57..3d526fdc 100644 --- a/frontend/src/renderer/src/views/PatientCreate.vue +++ b/frontend/src/renderer/src/views/PatientCreate.vue @@ -17,8 +17,11 @@
- -
基本信息
+
+
+ 基本信息 +
+
@@ -63,10 +66,7 @@ - - - + @@ -334,6 +334,7 @@ const handleSaveAndDetect = async () => { font-size: 18px; width: calc(100% - 500px); display: flex; + cursor: pointer; } .nav-container-info { @@ -391,16 +392,25 @@ const handleSaveAndDetect = async () => { display: flex; align-items: center; justify-content: center; - border: 2px solid #ffffff; - border-image: linear-gradient(to right, rgb(245, 173, 7), rgb(160, 5, 216)) 1; + /* border: 2px solid #ffffff; */ + background: linear-gradient(to right, rgb(245, 173, 7), rgb(160, 5, 216)); border-radius: 20px; - padding: 1px 10px; font-size: 16px; font-weight: bold; color: #ffffff; + padding: 2px; margin-bottom: 20px; } +.section-title-text { + display: flex; + align-items: center; + width: 116px; + background: #2C2C2C; + border-radius: 20px; + padding: 0px 25px; +} + .footer-actions { height: 142px; background: #000000; @@ -412,8 +422,9 @@ const handleSaveAndDetect = async () => { padding: 0 20px; box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.1); } -:deep(.footer-actions .el-button){ - background: linear-gradient(to right,#F135A6,#A005D8,#A005D8); + +:deep(.footer-actions .el-button) { + background: linear-gradient(to right, #F135A6, #A005D8); border: none; color: #ffffff; height: 60px; diff --git a/frontend/src/renderer/src/views/PatientProfile.vue b/frontend/src/renderer/src/views/PatientProfile.vue index 6dec322f..a0a35bb6 100644 --- a/frontend/src/renderer/src/views/PatientProfile.vue +++ b/frontend/src/renderer/src/views/PatientProfile.vue @@ -1,25 +1,25 @@