修改了诊断页面和档案页面相关功能

This commit is contained in:
root 2025-12-07 20:07:22 +08:00
parent d058f15488
commit a7f48305be
11 changed files with 301 additions and 154 deletions

View File

@ -29,7 +29,7 @@ backend = directshow
[CAMERA2] [CAMERA2]
enabled = True enabled = True
device_index = 2 device_index = 3
width = 1280 width = 1280
height = 720 height = 720
fps = 30 fps = 30
@ -50,12 +50,12 @@ synchronized_images_only = False
[DEVICES] [DEVICES]
imu_enabled = True imu_enabled = True
imu_device_type = mock imu_device_type = ble
imu_port = COM9 imu_port = COM9
imu_mac_address = ef:3c:1a:0a:fe:02 imu_mac_address = ef:3c:1a:0a:fe:02
imu_baudrate = 9600 imu_baudrate = 9600
pressure_enabled = True pressure_enabled = True
pressure_device_type = mock pressure_device_type = real
pressure_use_mock = False pressure_use_mock = False
pressure_port = COM5 pressure_port = COM5
pressure_baudrate = 115200 pressure_baudrate = 115200

View File

@ -58,85 +58,82 @@ class LicenseManager:
"""生成机器硬件指纹""" """生成机器硬件指纹"""
if self._machine_id: if self._machine_id:
return self._machine_id return self._machine_id
try: try:
# 收集硬件信息 core_info = []
hardware_info = [] aux_info = []
# CPU信息
try: try:
if platform.system() == "Windows": if platform.system() == "Windows":
result = subprocess.run(['wmic', 'cpu', 'get', 'ProcessorId', '/value'], result = subprocess.run(['wmic', 'cpu', 'get', 'ProcessorId', '/value'], capture_output=True, text=True, timeout=10)
capture_output=True, text=True, timeout=10)
for line in result.stdout.split('\n'): for line in result.stdout.split('\n'):
if 'ProcessorId=' in line: if 'ProcessorId=' in line:
cpu_id = line.split('=')[1].strip() cpu_id = line.split('=')[1].strip()
if cpu_id: if cpu_id:
hardware_info.append(f"CPU:{cpu_id}") core_info.append(f"CPU:{cpu_id}")
break break
else:
# Linux/Mac 可以使用其他方法获取CPU信息
pass
except Exception as e: except Exception as e:
logger.warning(f"获取CPU信息失败: {e}") logger.warning(f"获取CPU信息失败: {e}")
# 主板信息
try: try:
if platform.system() == "Windows": if platform.system() == "Windows":
result = subprocess.run(['wmic', 'baseboard', 'get', 'SerialNumber', '/value'], result = subprocess.run(['wmic', 'baseboard', 'get', 'SerialNumber', '/value'], capture_output=True, text=True, timeout=10)
capture_output=True, text=True, timeout=10)
for line in result.stdout.split('\n'): for line in result.stdout.split('\n'):
if 'SerialNumber=' in line: if 'SerialNumber=' in line:
board_serial = line.split('=')[1].strip() board_serial = line.split('=')[1].strip()
if board_serial and board_serial != "To be filled by O.E.M.": if board_serial and board_serial != "To be filled by O.E.M.":
hardware_info.append(f"BOARD:{board_serial}") core_info.append(f"BOARD:{board_serial}")
break break
except Exception as e: except Exception as e:
logger.warning(f"获取主板信息失败: {e}") logger.warning(f"获取主板信息失败: {e}")
# 磁盘信息
try: try:
if platform.system() == "Windows": if platform.system() == "Windows":
result = subprocess.run(['wmic', 'diskdrive', 'get', 'SerialNumber', '/value'], # 获取磁盘信息并过滤掉USB/可移动介质,收集所有内部磁盘序列号
capture_output=True, text=True, timeout=10) result = subprocess.run(
['wmic', 'path', 'Win32_DiskDrive', 'get', 'SerialNumber,InterfaceType,PNPDeviceID,MediaType', '/value'],
capture_output=True, text=True, timeout=10
)
block = {}
for line in result.stdout.split('\n'): for line in result.stdout.split('\n'):
if 'SerialNumber=' in line: line = line.strip()
disk_serial = line.split('=')[1].strip() if not line:
if disk_serial: # 结束一个块
hardware_info.append(f"DISK:{disk_serial}") serial = (block.get('SerialNumber') or '').strip()
break iface = (block.get('InterfaceType') or '').strip().upper()
pnp = (block.get('PNPDeviceID') or '').strip().upper()
media = (block.get('MediaType') or '').strip().upper()
if serial and iface != 'USB' and not pnp.startswith('USBSTOR') and 'REMOVABLE' not in media:
core_info.append(f"DISK:{serial}")
block = {}
continue
if '=' in line:
k, v = line.split('=', 1)
block[k] = v
# 处理最后一个块
if block:
serial = (block.get('SerialNumber') or '').strip()
iface = (block.get('InterfaceType') or '').strip().upper()
pnp = (block.get('PNPDeviceID') or '').strip().upper()
media = (block.get('MediaType') or '').strip().upper()
if serial and iface != 'USB' and not pnp.startswith('USBSTOR') and 'REMOVABLE' not in media:
core_info.append(f"DISK:{serial}")
except Exception as e: except Exception as e:
logger.warning(f"获取磁盘信息失败: {e}") logger.warning(f"获取磁盘信息失败: {e}")
# MAC地址
try: try:
import uuid import uuid
mac = ':'.join(['{:02x}'.format((uuid.getnode() >> elements) & 0xff) mac = ':'.join(['{:02x}'.format((uuid.getnode() >> elements) & 0xff) for elements in range(0, 2 * 6, 2)][::-1])
for elements in range(0,2*6,2)][::-1]) aux_info.append(f"MAC:{mac}")
hardware_info.append(f"MAC:{mac}")
except Exception as e: except Exception as e:
logger.warning(f"获取MAC地址失败: {e}") logger.warning(f"获取MAC地址失败: {e}")
aux_info.append(f"OS:{platform.system()}")
# 系统信息作为补充 aux_info.append(f"MACHINE:{platform.machine()}")
hardware_info.append(f"OS:{platform.system()}") if len(core_info) < 1:
hardware_info.append(f"MACHINE:{platform.machine()}") core_info.append(f"NODE:{platform.node()}")
core_info.append(f"PROCESSOR:{platform.processor()}")
# 如果没有获取到足够的硬件信息使用系统信息作为fallback combined_info = "|".join(sorted(core_info))
if len(hardware_info) < 2:
hardware_info.append(f"NODE:{platform.node()}")
hardware_info.append(f"PROCESSOR:{platform.processor()}")
# 生成指纹哈希
combined_info = "|".join(sorted(hardware_info))
machine_id = hashlib.sha256(combined_info.encode('utf-8')).hexdigest()[:16].upper() machine_id = hashlib.sha256(combined_info.encode('utf-8')).hexdigest()[:16].upper()
self._machine_id = f"W10-{machine_id}" self._machine_id = f"W10-{machine_id}"
logger.info(f"生成机器指纹: {self._machine_id}") logger.info(f"生成机器指纹: {self._machine_id}")
return self._machine_id return self._machine_id
except Exception as e: except Exception as e:
logger.error(f"生成机器指纹失败: {e}") logger.error(f"生成机器指纹失败: {e}")
# 使用fallback方案
fallback_info = f"{platform.system()}-{platform.node()}-{platform.machine()}" fallback_info = f"{platform.system()}-{platform.node()}-{platform.machine()}"
fallback_id = hashlib.md5(fallback_info.encode('utf-8')).hexdigest()[:12].upper() fallback_id = hashlib.md5(fallback_info.encode('utf-8')).hexdigest()[:12].upper()
self._machine_id = f"FB-{fallback_id}" self._machine_id = f"FB-{fallback_id}"
@ -429,4 +426,4 @@ class LicenseManager:
except Exception as e: except Exception as e:
logger.error(f"生成激活请求失败: {e}") logger.error(f"生成激活请求失败: {e}")
raise raise

View File

@ -218,17 +218,8 @@ class DataValidator:
name = data['name'].strip() name = data['name'].strip()
if len(name) < 2 or len(name) > 50: if len(name) < 2 or len(name) > 50:
errors.append('姓名长度应在2-50个字符之间') errors.append('姓名长度应在2-50个字符之间')
data['name'] = name data['name'] = name
# 性别验证
if data.get('gender'):
# 支持中文和英文性别值
gender_map = {'': 'male', '': 'female', 'male': 'male', 'female': 'female'}
gender_value = data['gender'].strip()
if gender_value in gender_map:
data['gender'] = gender_map[gender_value]
else:
errors.append('性别值无效应为男、女、male、female')
# 出生日期验证 # 出生日期验证
if data.get('birth_date'): if data.get('birth_date'):

View File

@ -13,7 +13,7 @@ api.interceptors.request.use(
if (window.electronAPI) { if (window.electronAPI) {
config.baseURL = window.electronAPI.getBackendUrl() config.baseURL = window.electronAPI.getBackendUrl()
} else { } else {
config.baseURL = 'http://192.168.1.62:5000' config.baseURL = 'http://localhost:5000'
} }
// 为需要发送数据的请求设置Content-Type避免覆盖FormData // 为需要发送数据的请求设置Content-Type避免覆盖FormData
@ -187,22 +187,12 @@ export const patientAPI = {
// 创建患者 // 创建患者
createPatient(data) { createPatient(data) {
return api.post('/api/patients', data) return api.post('/api/patients', data)
}, },
// 创建患者(别名方法)
create(data) {
return this.createPatient(data)
},
// 更新患者 // 更新患者
updatePatient(id, data) { updatePatient(id, data) {
return api.put(`/api/patients/${id}`, data) return api.put(`/api/patients/${id}`, data)
}, },
// 更新患者(别名方法)
update(id, data) {
return this.updatePatient(id, data)
},
// 删除患者 // 删除患者
deletePatient(id) { deletePatient(id) {
@ -675,7 +665,7 @@ export const getBackendUrl = () => {
if (window.electronAPI) { if (window.electronAPI) {
return window.electronAPI.getBackendUrl() return window.electronAPI.getBackendUrl()
} else { } else {
return 'http://192.168.1.62:5000' return 'http://localhost:5000'
} }
} }

View File

@ -10,7 +10,7 @@
<div class="patient-section"> <div class="patient-section">
<div class="section-header"> <div class="section-header">
<div class="search-box"> <div class="search-box">
<el-input v-model="search" placeholder="搜索用户信息" clearable class="search-input"/> <el-input v-model="search" placeholder="搜索患者姓名" clearable class="search-input"/>
<div class="primary-search-buttons" @click="handleSearch"> <div class="primary-search-buttons" @click="handleSearch">
搜索 搜索
</div> </div>
@ -172,7 +172,7 @@
</div> </div>
<div class="patient-detail-display"> <div class="patient-detail-display">
<div class="patient-detailinfo-leftbox"> <div class="patient-detailinfo-leftbox">
<div class="patient-detailinfo-key"></div> <div class="patient-detailinfo-key">身份证号</div>
<div class="patient-detailinfo-value"> <div class="patient-detailinfo-value">
<span v-if="selectedPatient && selectedPatient.idcode"> <span v-if="selectedPatient && selectedPatient.idcode">
{{ selectedPatient.idcode }} {{ selectedPatient.idcode }}
@ -195,13 +195,13 @@
<div v-if="selectedPatient.name != null && selectedPatient.name != ''" <div v-if="selectedPatient.name != null && selectedPatient.name != ''"
class="primary-view-profile" class="primary-view-profile"
@click="viewPatientProfile"> @click="viewPatientProfile">
查看档案 查看患者档案
</div> </div>
<div v-if="selectedPatient.name != null && selectedPatient.name != ''" <div v-if="selectedPatient.name != null && selectedPatient.name != ''"
class="primary-view-profile" class="primary-view-profile"
style="color:#fff;background: rgb(11, 148, 213);" style="color:#fff;background: rgb(11, 148, 213);"
@click="startDetection"> @click="startDetection">
开始检测 开始体态检测
</div> </div>
</div> </div>
@ -382,7 +382,11 @@ const getStatusText = (lastDetection) => {
} }
const formatDate = (date) => { const formatDate = (date) => {
return new Date(date).toLocaleDateString('zh-CN') const d = new Date(date)
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${y}-${m}-${day}`
} }
// //
@ -476,8 +480,11 @@ function closecreatbox(e,e2){
}else if(e == '新建') { }else if(e == '新建') {
loadPatients() loadPatients()
}else if(e == '编辑') { }else if(e == '编辑') {
// patients.value[rowIndex.value] = e2 if (rowIndex.value >= 0 && patients.value[rowIndex.value]) {
patients.value[rowIndex.value] = { ...patients.value[rowIndex.value], ...e2 }
}
selectedPatient.value = e2 selectedPatient.value = e2
loadPatients()
} }

View File

@ -366,7 +366,7 @@
<div>提示</div> <div>提示</div>
<img src="@/assets/new/u264.svg" alt="" style="cursor: pointer;" @click="handleCancel"> <img src="@/assets/new/u264.svg" alt="" style="cursor: pointer;" @click="handleCancel">
</div> </div>
<div class="pop-up-tip-text">本次操作未保存有效数据不予记录</div> <div class="pop-up-tip-text">本次检测未截图或录像操作不予存档记录</div>
<div class="tipconfirmbutton-box"> <div class="tipconfirmbutton-box">
<el-button type="primary" class="tipconfirmbutton" @click="closeTipClick">确定</el-button> <el-button type="primary" class="tipconfirmbutton" @click="closeTipClick">确定</el-button>
</div> </div>
@ -521,8 +521,23 @@ function handleCancel(){
function handleCameraCancel(){ function handleCameraCancel(){
cameraDialogVisible.value = false cameraDialogVisible.value = false
} }
function closeTipClick(){ async function closeTipClick(){
emit('endChange',false) 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 isDiagnosticMessage = ref(false)
@ -1711,19 +1726,15 @@ async function startDetection() {
} }
// //
async function stopDetection() { async function stopDetection(summary = {}) {
try { try {
// const payload = summary || {}
let duration = 0
// API
const response = await fetch(`${BACKEND_URL}/api/detection/${patientInfo.value.sessionId}/stop`, { const response = await fetch(`${BACKEND_URL}/api/detection/${patientInfo.value.sessionId}/stop`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ body: JSON.stringify(payload)
duration: duration
})
}) })
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`) throw new Error(`HTTP ${response.status}: ${response.statusText}`)
@ -2129,9 +2140,9 @@ function refreshClick(type) {
} }
} }
function closeDiagnosticMessage(e){ async function closeDiagnosticMessage(e){
isDiagnosticMessage.value = false isDiagnosticMessage.value = false
if(e== true){ if (e === true || (typeof e === 'object' && e)){
try { try {
// //
if (timerId.value) { if (timerId.value) {
@ -2146,13 +2157,22 @@ function closeDiagnosticMessage(e){
console.log('✅ 录制已停止') console.log('✅ 录制已停止')
} }
// const summary = typeof e === 'object' && e !== null ? {
stopDetection() 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 // WebSocket
disconnectWebSocket() disconnectWebSocket()
// //
window.removeEventListener('beforeunload', handleBeforeUnload) window.removeEventListener('beforeunload', handleBeforeUnload)
emit('endChange',true) emit('endChange',true)
routeTo('/')
} catch (error) { } catch (error) {
console.error('❌ Detection组件卸载时出错:', error) console.error('❌ Detection组件卸载时出错:', error)
emit('endChange',true) emit('endChange',true)

View File

@ -53,7 +53,11 @@ const props = defineProps({
}, },
}) })
const diagnosticForm =ref({}) const diagnosticForm = ref({
diagnosis_info: '',
treatment_info: '',
suggestion_info: ''
})
// //
onMounted(() => { onMounted(() => {
@ -76,9 +80,9 @@ async function handleDiagnosticInfo(status) {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ body: JSON.stringify({
diagnosis_info: diagnosticForm.diagnosis_info, diagnosis_info: diagnosticForm.value.diagnosis_info,
treatment_info: diagnosticForm.treatment_info, treatment_info: diagnosticForm.value.treatment_info,
suggestion_info: diagnosticForm.suggestion_info, suggestion_info: diagnosticForm.value.suggestion_info,
status: status, status: status,
session_id: props.selectedPatient.sessionId, session_id: props.selectedPatient.sessionId,
}) })
@ -94,13 +98,17 @@ async function handleDiagnosticInfo(status) {
message: status + '诊断信息成功', message: status + '诊断信息成功',
duration: 5000 duration: 5000
}) })
emit('closeDiagnosticMessage',true) emit('closeDiagnosticMessage', {
diagnosis_info: diagnosticForm.value.diagnosis_info,
treatment_info: diagnosticForm.value.treatment_info,
remark_info: diagnosticForm.value.suggestion_info
})
} else { } else {
throw new Error(result.message || '诊断信息失败') throw new Error(result.message || '诊断信息失败')
} }
} catch (error) { } catch (error) {
ElMessage.error({ ElMessage.error({
message:'诊断信息失败', message: error?.message || '诊断信息失败',
duration: 5000 duration: 5000
}) })

View File

@ -30,7 +30,7 @@
<el-col :span="12"> <el-col :span="12">
<el-form-item label="出生日期" prop="birth_date" required> <el-form-item label="出生日期" prop="birth_date" required>
<el-date-picker v-model="patientForm.birth_date" type="date" placeholder="请选择" style="width: 100%" <el-date-picker v-model="patientForm.birth_date" type="date" placeholder="请选择" style="width: 100%"
@change="calculateAgeres" /> format="YYYY-MM-DD" value-format="YYYY-MM-DD" @change="calculateAgeres" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
@ -89,7 +89,7 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="号" prop="idcode"> <el-form-item label="身份证号" prop="idcode">
<el-input v-model="patientForm.idcode" placeholder="请输入" clearable /> <el-input v-model="patientForm.idcode" placeholder="请输入" clearable />
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -148,12 +148,12 @@ const patientForm = reactive({
// //
const saveLoading = ref(false) const saveLoading = ref(false)
const saveAndDetectLoading = ref(false) const saveAndDetectLoading = ref(false)
const patienttitle = ref("新建档案") const patienttitle = ref("新建患者档案")
// //
onMounted(() => { onMounted(() => {
// //
if (props.patienttype == 'edit') { if (props.patienttype == 'edit') {
patienttitle.value = '编辑个人信息' patienttitle.value = '编辑患者档案'
let tempInfo = props.selectedPatient let tempInfo = props.selectedPatient
tempInfo.age = calculateAgeres(tempInfo.birth_date ) tempInfo.age = calculateAgeres(tempInfo.birth_date )
Object.assign(patientForm, tempInfo) Object.assign(patientForm, tempInfo)
@ -180,28 +180,88 @@ const nationalityOptions = ref(["汉族", "满族", "蒙古族", "回族", "藏
"俄罗斯族", "鄂温克族", "德昂族", "保安族", "裕固族", "京族", "塔塔尔族", "独龙族", "俄罗斯族", "鄂温克族", "德昂族", "保安族", "裕固族", "京族", "塔塔尔族", "独龙族",
"鄂伦春族", "赫哲族", "门巴族", "珞巴族", "基诺族"]) "鄂伦春族", "赫哲族", "门巴族", "珞巴族", "基诺族"])
// //
const validateHeight = (rule, value, callback) => {
if (value === '' || value === null || value === undefined) return callback(new Error('请输入身高'))
const n = parseFloat(value)
if (isNaN(n)) return callback(new Error('身高格式无效'))
if (n < 50 || n > 250) return callback(new Error('身高应在50-250cm之间'))
callback()
}
const validateWeight = (rule, value, callback) => {
if (value === '' || value === null || value === undefined) return callback(new Error('请输入体重'))
const n = parseFloat(value)
if (isNaN(n)) return callback(new Error('体重格式无效'))
if (n < 10 || n > 300) return callback(new Error('体重应在10-300kg之间'))
callback()
}
const validateShoeSize = (rule, value, callback) => {
if (value === '' || value === null || value === undefined) return callback()
const n = parseFloat(value)
if (isNaN(n)) return callback(new Error('鞋码格式无效'))
if (n < 10 || n > 80) return callback(new Error('鞋码应在10-80之间'))
callback()
}
const validatePhone = (rule, value, callback) => {
const v = (value || '').trim()
if (!v) return callback(new Error('请输入联系电话'))
const digits = v.replace(/[-\s]/g, '')
if (!/^\d+$/.test(digits)) return callback(new Error('电话号码格式无效'))
callback()
}
const validateEmail = (rule, value, callback) => {
const v = (value || '').trim()
if (!v) return callback()
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!re.test(v)) return callback(new Error('电子邮箱格式无效'))
callback()
}
const validateIdcode = (rule, value, callback) => {
const v = (value || '').trim()
if (!v) return callback()
const re = /^\d{17}[\dXx]$/
if (!re.test(v)) return callback(new Error('身份证号格式无效'))
const weights = [7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2]
const checkMap = ['1','0','X','9','8','7','6','5','4','3','2']
let sum = 0
for (let i = 0; i < 17; i++) {
sum += parseInt(v[i], 10) * weights[i]
}
const mod = sum % 11
const code = checkMap[mod]
const last = v[17].toUpperCase()
if (last !== code) return callback(new Error('身份证号校验失败'))
callback()
}
const formRules = { const formRules = {
name: [ name: [
{ required: true, message: '请输入患者姓名', trigger: 'blur' }, { required: true, message: '请输入患者姓名', trigger: 'blur' },
{ min: 2, max: 20, message: '姓名长度在 2 到 20 个字符', trigger: 'blur' } { min: 2, max: 50, message: '姓名长度应在2-50个字符之间', trigger: 'blur' }
], ],
gender: [ gender: [
{ required: true, message: '请选择性别', trigger: 'change' } { required: true, message: '请选择性别', trigger: 'change' }
], ],
birthDate: [ birth_date: [
{ required: true, message: '请选择出生日期', trigger: 'change' } { required: true, message: '请选择出生日期', trigger: 'change' }
], ],
height: [ height: [
{ required: true, message: '请输入身高', trigger: 'blur' }, { validator: validateHeight, trigger: 'blur' }
{ pattern: /^\d+(\.\d+)?$/, message: '请输入有效的身高', trigger: 'blur' }
], ],
weight: [ weight: [
{ required: true, message: '请输入体重', trigger: 'blur' }, { validator: validateWeight, trigger: 'blur' }
{ pattern: /^\d+(\.\d+)?$/, message: '请输入有效的体重', trigger: 'blur' } ],
shoe_size: [
{ validator: validateShoeSize, trigger: 'blur' }
], ],
phone: [ phone: [
{ required: true, message: '请输入联系电话', trigger: 'blur' }, { validator: validatePhone, trigger: 'blur' }
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' } ]
,
email: [
{ validator: validateEmail, trigger: 'blur' }
]
,
idcode: [
{ validator: validateIdcode, trigger: 'blur' }
] ]
} }
@ -225,6 +285,28 @@ const handleCancel = async () => {
const validateForm = async () => { const validateForm = async () => {
try { try {
await patientFormRef.value.validate() await patientFormRef.value.validate()
const errors = []
const n = patientForm.name
if (!n || n.trim().length < 2 || n.trim().length > 50) errors.push('姓名长度应在2-50个字符之间')
const g = patientForm.gender
if (!g || (g !== '男' && g !== '女')) errors.push('性别值无效,应为:男、女')
const bd = patientForm.birth_date
if (!bd) errors.push('请选择出生日期')
if (bd) {
try {
const d = new Date(bd)
const today = new Date()
const lower = new Date('1900-01-01')
if (d > today) errors.push('出生日期不能是未来时间')
if (d < lower) errors.push('出生日期过早')
} catch {
errors.push('出生日期格式无效')
}
}
if (errors.length) {
ElMessage.error(errors.join('; '))
return false
}
return true return true
} catch (error) { } catch (error) {
ElMessage.error('请完善必填信息') ElMessage.error('请完善必填信息')
@ -251,17 +333,17 @@ const savePatient = async () => {
} }
try { try {
const response = await patientAPI.create(patientData) const response = await patientAPI.createPatient(patientData)
if (response.success) { if (response.success) {
emit('closecreatbox','新建',response.data) emit('closecreatbox','新建',response.data)
return response.data return response.data
} else { } else {
throw new Error(response.message || '保存失败') const msg = response.error || response.message || '保存失败'
throw new Error(msg)
} }
} catch (error) { } catch (error) {
console.error('保存患者信息失败:', error) const msg = (error && error.response && error.response.data && (error.response.data.error || error.response.data.message)) || error.message || '保存失败'
throw error throw new Error(msg)
} }
} }
const updatePatient = async () => { const updatePatient = async () => {
@ -287,10 +369,12 @@ const updatePatient = async () => {
emit('closecreatbox','编辑',patientData) emit('closecreatbox','编辑',patientData)
return response.data return response.data
} else { } else {
throw new Error(response.message || '修改失败') const msg = response.error || response.message || '修改失败'
throw new Error(msg)
} }
} catch (error) { } catch (error) {
throw error const msg = (error && error.response && error.response.data && (error.response.data.error || error.response.data.message)) || error.message || '修改失败'
throw new Error(msg)
} }
} }
const handleSave = async () => { const handleSave = async () => {
@ -298,18 +382,17 @@ const handleSave = async () => {
saveLoading.value = true saveLoading.value = true
try { try {
if(patientForm.id == null){ if(patientForm.id == null||patientForm.id == ''){
await savePatient() await savePatient()
}else{ }else{
await updatePatient() await updatePatient()
} }
ElMessage.success('患者档案保存成功') ElMessage.success('患者档案保存成功')
} catch (error) { } catch (error) {
ElMessage.error('保存失败:' + error.message) ElMessage.error('保存失败:' + (error && error.message ? error.message : '请检查填写信息'))
} finally { } finally {
saveLoading.value = false saveLoading.value = false
} }
@ -444,6 +527,7 @@ const handleSave = async () => {
font-weight: 400 !important; font-weight: 400 !important;
font-style: normal !important; font-style: normal !important;
color: #FFFFFF !important; color: #FFFFFF !important;
line-height: 40px !important;
} }
:deep(.el-form-item__content) { :deep(.el-form-item__content) {

View File

@ -53,8 +53,8 @@
<div class="patientprofile-userinfo-box"> <div class="patientprofile-userinfo-box">
<div class="patientprofile-userinfo-text1">职业</div> <div class="patientprofile-userinfo-text1">职业</div>
<div class="patientprofile-userinfo-text2">{{ selectedPatient.occupation }}</div> <div class="patientprofile-userinfo-text2">{{ selectedPatient.occupation }}</div>
<div class="patientprofile-userinfo-text3"></div> <div class="patientprofile-userinfo-text3">身份证号</div>
<div class="patientprofile-userinfo-text2">{{ selectedPatient.workplace }}</div> <div class="patientprofile-userinfo-text2">{{ selectedPatient.idcode }}</div>
</div> </div>
</div> </div>
<div class="patientprofile-container-leftbottombox"> <div class="patientprofile-container-leftbottombox">
@ -95,9 +95,9 @@
@cell-click="selectRecord" @selection-change="handleSelectionChange" @cell-click="selectRecord" @selection-change="handleSelectionChange"
highlight-current-row> highlight-current-row>
<el-table-column type="selection" width="30" /> <el-table-column type="selection" width="30" />
<el-table-column prop="start_time" label="就诊时间" width="160" align="center" /> <el-table-column prop="start_time" label="就诊时间" width="180" align="center" />
<el-table-column prop="creator_name" label="测试医生" min-width="80" align="center" /> <el-table-column prop="creator_name" label="测试医生" min-width="60" align="center" />
<el-table-column prop="report" label="报告" width="60"> <el-table-column prop="report" label="报告" width="60" align="center">
<template #default="scope"> <template #default="scope">
<div <div
v-if="scope.row.detection_report == null" v-if="scope.row.detection_report == null"
@ -314,7 +314,11 @@ import GenerateReport from '@/views/GenerateReport.vue'
import ImageDetailsCompare from '@/views/ImageDetailsCompare.vue' import ImageDetailsCompare from '@/views/ImageDetailsCompare.vue'
const formatDate = (date) => { const formatDate = (date) => {
return new Date(date).toLocaleDateString('zh-CN') const d = new Date(date)
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${y}-${m}-${day}`
} }
// //
const BACKEND_URL = getBackendUrl() const BACKEND_URL = getBackendUrl()
@ -337,7 +341,12 @@ const delType = ref('')
const detectionId = ref('') // ID const detectionId = ref('') // ID
const isGenerateReport = ref(false) // const isGenerateReport = ref(false) //
const archiveType =ref(false) const archiveType =ref(false)
const profileInfo = ref({}) // const profileInfo = ref({
id: '',
diagnosis_info: '',
treatment_info: '',
suggestion_info: ''
})
const selectedRecord = ref({}) const selectedRecord = ref({})
const recordData =ref([]) const recordData =ref([])
const isImageDetailsCompare = ref(false) // const isImageDetailsCompare = ref(false) //
@ -391,10 +400,16 @@ const handleSubmit = (item) => {
} }
}; };
const sessionsId = ref('') const sessionsId = ref('')
function selectRecord(data){ // function selectRecord(data){
selectedRecord.value = data selectedRecord.value = data
sessionsById(data.id) sessionsById(data.id)
sessionsId.value = data.id sessionsId.value = data.id
profileInfo.value = {
id: data.id,
diagnosis_info: data.diagnosis_info || '',
treatment_info: data.treatment_info || '',
suggestion_info: data.suggestion_info || ''
}
} }
const handleSelectionChange = (val) => { // const handleSelectionChange = (val) => { //
@ -414,6 +429,18 @@ const sessionsInit = async () => {
// element.list = [{}] // element.list = [{}]
// }); // });
recordData.value = response.data.sessions recordData.value = response.data.sessions
if (recordData.value && recordData.value.length > 0) {
const first = recordData.value[0]
selectedRecord.value = first
sessionsId.value = first.id
profileInfo.value = {
id: first.id,
diagnosis_info: first.diagnosis_info || '',
treatment_info: first.treatment_info || '',
suggestion_info: first.suggestion_info || ''
}
sessionsById(first.id)
}
} }
} catch (error) { } catch (error) {
ElMessage.error('获取失败') ElMessage.error('获取失败')
@ -432,6 +459,21 @@ const sessionsById = async (session_id) => {
// //
const response = await historyAPI.sessionsById(session_id) const response = await historyAPI.sessionsById(session_id)
if (response.success) { if (response.success) {
//
const sessionInfo = response.data || {}
profileInfo.value = {
id: session_id,
diagnosis_info: sessionInfo.diagnosis_info || '',
treatment_info: sessionInfo.treatment_info || '',
suggestion_info: sessionInfo.suggestion_info || ''
}
selectedRecord.value = {
...(selectedRecord.value || {}),
id: session_id,
diagnosis_info: profileInfo.value.diagnosis_info,
treatment_info: profileInfo.value.treatment_info,
suggestion_info: profileInfo.value.suggestion_info
}
response.data.data.forEach((element,index) => { response.data.data.forEach((element,index) => {
element.order ='D-'+ (getNo(index + 1)) element.order ='D-'+ (getNo(index + 1))
}); });
@ -575,7 +617,7 @@ function handleVideoUpward(){ // 视频上一页
async function handleDiagnosticInfo(status) { // async function handleDiagnosticInfo(status) {
try { try {
// ID // ID
if (!profileInfo.value.id) { if (!profileInfo.value.id) {
@ -592,7 +634,7 @@ async function handleDiagnosticInfo(status) { // 保存诊断信息
treatment_info:profileInfo.value.treatment_info, treatment_info:profileInfo.value.treatment_info,
suggestion_info:profileInfo.value.suggestion_info, suggestion_info:profileInfo.value.suggestion_info,
status:status, status:status,
id:profileInfo.value.id, session_id: profileInfo.value.id,
}) })
}) })
if (!response.ok) { if (!response.ok) {
@ -601,21 +643,20 @@ async function handleDiagnosticInfo(status) { // 保存诊断信息
const result = await response.json() const result = await response.json()
if (result.success) { if (result.success) {
profileInfo.value[profileInfo.value.index].diagnosis_info = profileInfo.value.diagnosis_info selectedRecord.value.diagnosis_info = profileInfo.value.diagnosis_info
profileInfo.value[profileInfo.value.index].treatment_info = profileInfo.value.treatment_info selectedRecord.value.treatment_info = profileInfo.value.treatment_info
profileInfo.value[profileInfo.value.index].suggestion_info = profileInfo.value.suggestion_info selectedRecord.value.suggestion_info = profileInfo.value.suggestion_info
profileInfo.value[profileInfo.value.index].status = status selectedRecord.value.status = status
ElMessage.success({ ElMessage.success({
message: '诊断信息成功', message: '诊断信息成功',
duration: 5000 duration: 5000
}) })
dialogVisible.value =false
} else { } else {
throw new Error(result.message || '诊断信息失败') throw new Error(result.message || '诊断信息失败')
} }
} catch (error) { } catch (error) {
ElMessage.error({ ElMessage.error({
message: errorMessage, message: error?.message || '诊断信息失败',
duration: 5000 duration: 5000
}) })
} finally { } finally {
@ -1088,7 +1129,7 @@ historyAPI.VideoDelById(ids).then((response)=>{
} }
.patientprofile-imgbgbox{ .patientprofile-imgbgbox{
width: calc(100% - 140px); width: calc(100% - 140px);
height: 100%; height: 85%;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;

View File

@ -661,7 +661,11 @@ const getRecordStatusType = (status) => {
} }
const formatDate = (date) => { const formatDate = (date) => {
return new Date(date).toLocaleString('zh-CN') const d = new Date(date)
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${y}-${m}-${day}`
} }
const formatTime = (timestamp) => { const formatTime = (timestamp) => {
@ -1050,8 +1054,8 @@ onMounted(() => {
.content-right-top-text2 { .content-right-top-text2 {
width: 62px; width: 62px;
height: 32px; height: 40px;
line-height: 32px; line-height: 40px;
background: rgba(230, 162, 60, 0.1); background: rgba(230, 162, 60, 0.1);
color: #E6A23C; color: #E6A23C;
text-align: center; text-align: center;
@ -1060,8 +1064,8 @@ onMounted(() => {
} }
.content-right-top-text3 { .content-right-top-text3 {
width: 62px; width: 62px;
height: 32px; height: 40px;
line-height: 32px; line-height: 40px;
background: rgba(103, 194, 58, 0.1); background: rgba(103, 194, 58, 0.1);
color: rgb(103, 194, 58); color: rgb(103, 194, 58);
text-align: center; text-align: center;

View File

@ -114,15 +114,6 @@
</div> </div>
<div class="ViewUserInfo-detail-display"> <div class="ViewUserInfo-detail-display">
<div class="ViewUserInfo-detailinfo-leftbox"> <div class="ViewUserInfo-detailinfo-leftbox">
<div class="ViewUserInfo-detailinfo-key">证件号</div>
<div class="ViewUserInfo-detailinfo-value">
<span v-if="patientInfo && patientInfo.idcode">
{{ patientInfo.idcode }}
</span>
<span v-else></span>
</div>
</div>
<div class="ViewUserInfo-detailinfo-rightbox">
<div class="ViewUserInfo-detailinfo-key">职业</div> <div class="ViewUserInfo-detailinfo-key">职业</div>
<div class="ViewUserInfo-detailinfo-value"> <div class="ViewUserInfo-detailinfo-value">
<span v-if="patientInfo && patientInfo.occupation"> <span v-if="patientInfo && patientInfo.occupation">
@ -130,6 +121,16 @@
</span> </span>
<span v-else></span> <span v-else></span>
</div> </div>
</div>
<div class="ViewUserInfo-detailinfo-rightbox">
<div class="ViewUserInfo-detailinfo-key">身份证号</div>
<div class="ViewUserInfo-detailinfo-value">
<span v-if="patientInfo && patientInfo.idcode">
{{ patientInfo.idcode }}
</span>
<span v-else></span>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -172,7 +173,11 @@ const calculateAge = (birthDate) => {
return age return age
} }
const formatDate = (date) => { const formatDate = (date) => {
return new Date(date).toLocaleDateString('zh-CN') const d = new Date(date)
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${y}-${m}-${day}`
} }
</script> </script>