修改了诊断页面和档案页面相关功能
This commit is contained in:
parent
d058f15488
commit
a7f48305be
@ -29,7 +29,7 @@ backend = directshow
|
||||
|
||||
[CAMERA2]
|
||||
enabled = True
|
||||
device_index = 2
|
||||
device_index = 3
|
||||
width = 1280
|
||||
height = 720
|
||||
fps = 30
|
||||
@ -50,12 +50,12 @@ synchronized_images_only = False
|
||||
|
||||
[DEVICES]
|
||||
imu_enabled = True
|
||||
imu_device_type = mock
|
||||
imu_device_type = ble
|
||||
imu_port = COM9
|
||||
imu_mac_address = ef:3c:1a:0a:fe:02
|
||||
imu_baudrate = 9600
|
||||
pressure_enabled = True
|
||||
pressure_device_type = mock
|
||||
pressure_device_type = real
|
||||
pressure_use_mock = False
|
||||
pressure_port = COM5
|
||||
pressure_baudrate = 115200
|
||||
|
||||
@ -58,85 +58,82 @@ class LicenseManager:
|
||||
"""生成机器硬件指纹"""
|
||||
if self._machine_id:
|
||||
return self._machine_id
|
||||
|
||||
try:
|
||||
# 收集硬件信息
|
||||
hardware_info = []
|
||||
|
||||
# CPU信息
|
||||
core_info = []
|
||||
aux_info = []
|
||||
try:
|
||||
if platform.system() == "Windows":
|
||||
result = subprocess.run(['wmic', 'cpu', 'get', 'ProcessorId', '/value'],
|
||||
capture_output=True, text=True, timeout=10)
|
||||
result = subprocess.run(['wmic', 'cpu', 'get', 'ProcessorId', '/value'], capture_output=True, text=True, timeout=10)
|
||||
for line in result.stdout.split('\n'):
|
||||
if 'ProcessorId=' in line:
|
||||
cpu_id = line.split('=')[1].strip()
|
||||
if cpu_id:
|
||||
hardware_info.append(f"CPU:{cpu_id}")
|
||||
core_info.append(f"CPU:{cpu_id}")
|
||||
break
|
||||
else:
|
||||
# Linux/Mac 可以使用其他方法获取CPU信息
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.warning(f"获取CPU信息失败: {e}")
|
||||
|
||||
# 主板信息
|
||||
try:
|
||||
if platform.system() == "Windows":
|
||||
result = subprocess.run(['wmic', 'baseboard', 'get', 'SerialNumber', '/value'],
|
||||
capture_output=True, text=True, timeout=10)
|
||||
result = subprocess.run(['wmic', 'baseboard', 'get', 'SerialNumber', '/value'], capture_output=True, text=True, timeout=10)
|
||||
for line in result.stdout.split('\n'):
|
||||
if 'SerialNumber=' in line:
|
||||
board_serial = line.split('=')[1].strip()
|
||||
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
|
||||
except Exception as e:
|
||||
logger.warning(f"获取主板信息失败: {e}")
|
||||
|
||||
# 磁盘信息
|
||||
try:
|
||||
if platform.system() == "Windows":
|
||||
result = subprocess.run(['wmic', 'diskdrive', 'get', 'SerialNumber', '/value'],
|
||||
capture_output=True, text=True, timeout=10)
|
||||
# 获取磁盘信息并过滤掉USB/可移动介质,收集所有内部磁盘序列号
|
||||
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'):
|
||||
if 'SerialNumber=' in line:
|
||||
disk_serial = line.split('=')[1].strip()
|
||||
if disk_serial:
|
||||
hardware_info.append(f"DISK:{disk_serial}")
|
||||
break
|
||||
line = line.strip()
|
||||
if not line:
|
||||
# 结束一个块
|
||||
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}")
|
||||
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:
|
||||
logger.warning(f"获取磁盘信息失败: {e}")
|
||||
|
||||
# MAC地址
|
||||
try:
|
||||
import uuid
|
||||
mac = ':'.join(['{:02x}'.format((uuid.getnode() >> elements) & 0xff)
|
||||
for elements in range(0,2*6,2)][::-1])
|
||||
hardware_info.append(f"MAC:{mac}")
|
||||
mac = ':'.join(['{:02x}'.format((uuid.getnode() >> elements) & 0xff) for elements in range(0, 2 * 6, 2)][::-1])
|
||||
aux_info.append(f"MAC:{mac}")
|
||||
except Exception as e:
|
||||
logger.warning(f"获取MAC地址失败: {e}")
|
||||
|
||||
# 系统信息作为补充
|
||||
hardware_info.append(f"OS:{platform.system()}")
|
||||
hardware_info.append(f"MACHINE:{platform.machine()}")
|
||||
|
||||
# 如果没有获取到足够的硬件信息,使用系统信息作为fallback
|
||||
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))
|
||||
aux_info.append(f"OS:{platform.system()}")
|
||||
aux_info.append(f"MACHINE:{platform.machine()}")
|
||||
if len(core_info) < 1:
|
||||
core_info.append(f"NODE:{platform.node()}")
|
||||
core_info.append(f"PROCESSOR:{platform.processor()}")
|
||||
combined_info = "|".join(sorted(core_info))
|
||||
machine_id = hashlib.sha256(combined_info.encode('utf-8')).hexdigest()[:16].upper()
|
||||
|
||||
self._machine_id = f"W10-{machine_id}"
|
||||
logger.info(f"生成机器指纹: {self._machine_id}")
|
||||
return self._machine_id
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"生成机器指纹失败: {e}")
|
||||
# 使用fallback方案
|
||||
fallback_info = f"{platform.system()}-{platform.node()}-{platform.machine()}"
|
||||
fallback_id = hashlib.md5(fallback_info.encode('utf-8')).hexdigest()[:12].upper()
|
||||
self._machine_id = f"FB-{fallback_id}"
|
||||
|
||||
@ -220,15 +220,6 @@ class DataValidator:
|
||||
errors.append('姓名长度应在2-50个字符之间')
|
||||
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'):
|
||||
|
||||
@ -13,7 +13,7 @@ api.interceptors.request.use(
|
||||
if (window.electronAPI) {
|
||||
config.baseURL = window.electronAPI.getBackendUrl()
|
||||
} else {
|
||||
config.baseURL = 'http://192.168.1.62:5000'
|
||||
config.baseURL = 'http://localhost:5000'
|
||||
}
|
||||
|
||||
// 为需要发送数据的请求设置Content-Type(避免覆盖FormData)
|
||||
@ -189,21 +189,11 @@ export const patientAPI = {
|
||||
return api.post('/api/patients', data)
|
||||
},
|
||||
|
||||
// 创建患者(别名方法)
|
||||
create(data) {
|
||||
return this.createPatient(data)
|
||||
},
|
||||
|
||||
// 更新患者
|
||||
updatePatient(id, data) {
|
||||
return api.put(`/api/patients/${id}`, data)
|
||||
},
|
||||
|
||||
// 更新患者(别名方法)
|
||||
update(id, data) {
|
||||
return this.updatePatient(id, data)
|
||||
},
|
||||
|
||||
// 删除患者
|
||||
deletePatient(id) {
|
||||
return api.delete(`/api/patients/${id}`)
|
||||
@ -675,7 +665,7 @@ export const getBackendUrl = () => {
|
||||
if (window.electronAPI) {
|
||||
return window.electronAPI.getBackendUrl()
|
||||
} else {
|
||||
return 'http://192.168.1.62:5000'
|
||||
return 'http://localhost:5000'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
<div class="patient-section">
|
||||
<div class="section-header">
|
||||
<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>
|
||||
@ -172,7 +172,7 @@
|
||||
</div>
|
||||
<div class="patient-detail-display">
|
||||
<div class="patient-detailinfo-leftbox">
|
||||
<div class="patient-detailinfo-key">证件号</div>
|
||||
<div class="patient-detailinfo-key">身份证号</div>
|
||||
<div class="patient-detailinfo-value">
|
||||
<span v-if="selectedPatient && selectedPatient.idcode">
|
||||
{{ selectedPatient.idcode }}
|
||||
@ -195,13 +195,13 @@
|
||||
<div v-if="selectedPatient.name != null && selectedPatient.name != ''"
|
||||
class="primary-view-profile"
|
||||
@click="viewPatientProfile">
|
||||
查看档案
|
||||
查看患者档案
|
||||
</div>
|
||||
<div v-if="selectedPatient.name != null && selectedPatient.name != ''"
|
||||
class="primary-view-profile"
|
||||
style="color:#fff;background: rgb(11, 148, 213);"
|
||||
@click="startDetection">
|
||||
开始检测
|
||||
开始体态检测
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -382,7 +382,11 @@ const getStatusText = (lastDetection) => {
|
||||
}
|
||||
|
||||
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 == '新建') {
|
||||
loadPatients()
|
||||
}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
|
||||
loadPatients()
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -366,7 +366,7 @@
|
||||
<div>提示</div>
|
||||
<img src="@/assets/new/u264.svg" alt="" style="cursor: pointer;" @click="handleCancel">
|
||||
</div>
|
||||
<div class="pop-up-tip-text">本次操作未保存有效数据,不予记录。</div>
|
||||
<div class="pop-up-tip-text">本次检测未截图或录像操作,不予存档记录!</div>
|
||||
<div class="tipconfirmbutton-box">
|
||||
<el-button type="primary" class="tipconfirmbutton" @click="closeTipClick">确定</el-button>
|
||||
</div>
|
||||
@ -521,8 +521,23 @@ function handleCancel(){
|
||||
function handleCameraCancel(){
|
||||
cameraDialogVisible.value = false
|
||||
}
|
||||
function closeTipClick(){
|
||||
emit('endChange',false)
|
||||
async function closeTipClick(){
|
||||
try {
|
||||
if (timerId.value) {
|
||||
clearInterval(timerId.value)
|
||||
timerId.value = null
|
||||
}
|
||||
if (isRecording.value === true) {
|
||||
stopRecord()
|
||||
}
|
||||
await stopDetection({})
|
||||
disconnectWebSocket()
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload)
|
||||
emit('endChange',false)
|
||||
routeTo('/')
|
||||
} catch (error) {
|
||||
emit('endChange',false)
|
||||
}
|
||||
}
|
||||
|
||||
const isDiagnosticMessage = ref(false)
|
||||
@ -1711,19 +1726,15 @@ async function startDetection() {
|
||||
}
|
||||
|
||||
// 停止检测
|
||||
async function stopDetection() {
|
||||
async function stopDetection(summary = {}) {
|
||||
try {
|
||||
// 计算检测持续时间
|
||||
let duration = 0
|
||||
// 调用后端API停止检测
|
||||
const payload = summary || {}
|
||||
const response = await fetch(`${BACKEND_URL}/api/detection/${patientInfo.value.sessionId}/stop`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
duration: duration
|
||||
})
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
if (!response.ok) {
|
||||
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
|
||||
if(e== true){
|
||||
if (e === true || (typeof e === 'object' && e)){
|
||||
try {
|
||||
// 清理定时器
|
||||
if (timerId.value) {
|
||||
@ -2146,13 +2157,22 @@ function closeDiagnosticMessage(e){
|
||||
console.log('✅ 录制已停止')
|
||||
}
|
||||
|
||||
// 停止检测
|
||||
stopDetection()
|
||||
const summary = typeof e === 'object' && e !== null ? {
|
||||
diagnosis_info: e.diagnosis_info ?? '',
|
||||
treatment_info: e.treatment_info ?? '',
|
||||
remark_info: (e.remark_info ?? e.suggestion_info ?? '')
|
||||
} : {
|
||||
diagnosis_info: diagnosticForm.value.diagnosis_info,
|
||||
treatment_info: diagnosticForm.value.treatment_info,
|
||||
remark_info: diagnosticForm.value.suggestion_info
|
||||
}
|
||||
await stopDetection(summary)
|
||||
// 断开WebSocket连接
|
||||
disconnectWebSocket()
|
||||
// 移除页面关闭事件监听器
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload)
|
||||
emit('endChange',true)
|
||||
routeTo('/')
|
||||
} catch (error) {
|
||||
console.error('❌ Detection组件卸载时出错:', error)
|
||||
emit('endChange',true)
|
||||
|
||||
@ -53,7 +53,11 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const diagnosticForm =ref({})
|
||||
const diagnosticForm = ref({
|
||||
diagnosis_info: '',
|
||||
treatment_info: '',
|
||||
suggestion_info: ''
|
||||
})
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
@ -76,9 +80,9 @@ async function handleDiagnosticInfo(status) {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
diagnosis_info: diagnosticForm.diagnosis_info,
|
||||
treatment_info: diagnosticForm.treatment_info,
|
||||
suggestion_info: diagnosticForm.suggestion_info,
|
||||
diagnosis_info: diagnosticForm.value.diagnosis_info,
|
||||
treatment_info: diagnosticForm.value.treatment_info,
|
||||
suggestion_info: diagnosticForm.value.suggestion_info,
|
||||
status: status,
|
||||
session_id: props.selectedPatient.sessionId,
|
||||
})
|
||||
@ -94,13 +98,17 @@ async function handleDiagnosticInfo(status) {
|
||||
message: status + '诊断信息成功',
|
||||
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 {
|
||||
throw new Error(result.message || '诊断信息失败')
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error({
|
||||
message:'诊断信息失败',
|
||||
message: error?.message || '诊断信息失败',
|
||||
duration: 5000
|
||||
})
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
<el-col :span="12">
|
||||
<el-form-item label="出生日期" prop="birth_date" required>
|
||||
<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-col>
|
||||
<el-col :span="12">
|
||||
@ -89,7 +89,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<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-form-item>
|
||||
</el-col>
|
||||
@ -148,12 +148,12 @@ const patientForm = reactive({
|
||||
// 加载状态
|
||||
const saveLoading = ref(false)
|
||||
const saveAndDetectLoading = ref(false)
|
||||
const patienttitle = ref("新建档案")
|
||||
const patienttitle = ref("新建患者档案")
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
// 从认证状态管理中加载用户信息
|
||||
if (props.patienttype == 'edit') {
|
||||
patienttitle.value = '编辑个人信息'
|
||||
patienttitle.value = '编辑患者档案'
|
||||
let tempInfo = props.selectedPatient
|
||||
tempInfo.age = calculateAgeres(tempInfo.birth_date )
|
||||
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 = {
|
||||
name: [
|
||||
{ required: true, message: '请输入患者姓名', trigger: 'blur' },
|
||||
{ min: 2, max: 20, message: '姓名长度在 2 到 20 个字符', trigger: 'blur' }
|
||||
{ min: 2, max: 50, message: '姓名长度应在2-50个字符之间', trigger: 'blur' }
|
||||
],
|
||||
gender: [
|
||||
{ required: true, message: '请选择性别', trigger: 'change' }
|
||||
],
|
||||
birthDate: [
|
||||
birth_date: [
|
||||
{ required: true, message: '请选择出生日期', trigger: 'change' }
|
||||
],
|
||||
height: [
|
||||
{ required: true, message: '请输入身高', trigger: 'blur' },
|
||||
{ pattern: /^\d+(\.\d+)?$/, message: '请输入有效的身高', trigger: 'blur' }
|
||||
{ validator: validateHeight, trigger: 'blur' }
|
||||
],
|
||||
weight: [
|
||||
{ required: true, message: '请输入体重', trigger: 'blur' },
|
||||
{ pattern: /^\d+(\.\d+)?$/, message: '请输入有效的体重', trigger: 'blur' }
|
||||
{ validator: validateWeight, trigger: 'blur' }
|
||||
],
|
||||
shoe_size: [
|
||||
{ validator: validateShoeSize, trigger: 'blur' }
|
||||
],
|
||||
phone: [
|
||||
{ required: true, message: '请输入联系电话', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
|
||||
{ validator: validatePhone, trigger: 'blur' }
|
||||
]
|
||||
,
|
||||
email: [
|
||||
{ validator: validateEmail, trigger: 'blur' }
|
||||
]
|
||||
,
|
||||
idcode: [
|
||||
{ validator: validateIdcode, trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
@ -225,6 +285,28 @@ const handleCancel = async () => {
|
||||
const validateForm = async () => {
|
||||
try {
|
||||
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
|
||||
} catch (error) {
|
||||
ElMessage.error('请完善必填信息')
|
||||
@ -251,17 +333,17 @@ const savePatient = async () => {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await patientAPI.create(patientData)
|
||||
const response = await patientAPI.createPatient(patientData)
|
||||
if (response.success) {
|
||||
emit('closecreatbox','新建',response.data)
|
||||
return response.data
|
||||
|
||||
} else {
|
||||
throw new Error(response.message || '保存失败')
|
||||
const msg = response.error || response.message || '保存失败'
|
||||
throw new Error(msg)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存患者信息失败:', 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 updatePatient = async () => {
|
||||
@ -287,10 +369,12 @@ const updatePatient = async () => {
|
||||
emit('closecreatbox','编辑',patientData)
|
||||
return response.data
|
||||
} else {
|
||||
throw new Error(response.message || '修改失败')
|
||||
const msg = response.error || response.message || '修改失败'
|
||||
throw new Error(msg)
|
||||
}
|
||||
} 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 () => {
|
||||
@ -298,18 +382,17 @@ const handleSave = async () => {
|
||||
|
||||
saveLoading.value = true
|
||||
try {
|
||||
if(patientForm.id == null){
|
||||
if(patientForm.id == null||patientForm.id == ''){
|
||||
await savePatient()
|
||||
}else{
|
||||
await updatePatient()
|
||||
}
|
||||
|
||||
|
||||
|
||||
ElMessage.success('患者档案保存成功')
|
||||
|
||||
} catch (error) {
|
||||
ElMessage.error('保存失败:' + error.message)
|
||||
ElMessage.error('保存失败:' + (error && error.message ? error.message : '请检查填写信息'))
|
||||
} finally {
|
||||
saveLoading.value = false
|
||||
}
|
||||
@ -444,6 +527,7 @@ const handleSave = async () => {
|
||||
font-weight: 400 !important;
|
||||
font-style: normal !important;
|
||||
color: #FFFFFF !important;
|
||||
line-height: 40px !important;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__content) {
|
||||
|
||||
@ -53,8 +53,8 @@
|
||||
<div class="patientprofile-userinfo-box">
|
||||
<div class="patientprofile-userinfo-text1">职业</div>
|
||||
<div class="patientprofile-userinfo-text2">{{ selectedPatient.occupation }}</div>
|
||||
<div class="patientprofile-userinfo-text3">证件号</div>
|
||||
<div class="patientprofile-userinfo-text2">{{ selectedPatient.workplace }}</div>
|
||||
<div class="patientprofile-userinfo-text3">身份证号</div>
|
||||
<div class="patientprofile-userinfo-text2">{{ selectedPatient.idcode }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="patientprofile-container-leftbottombox">
|
||||
@ -95,9 +95,9 @@
|
||||
@cell-click="selectRecord" @selection-change="handleSelectionChange"
|
||||
highlight-current-row>
|
||||
<el-table-column type="selection" width="30" />
|
||||
<el-table-column prop="start_time" label="就诊时间" width="160" align="center" />
|
||||
<el-table-column prop="creator_name" label="测试医生" min-width="80" align="center" />
|
||||
<el-table-column prop="report" label="报告" width="60">
|
||||
<el-table-column prop="start_time" label="就诊时间" width="180" align="center" />
|
||||
<el-table-column prop="creator_name" label="测试医生" min-width="60" align="center" />
|
||||
<el-table-column prop="report" label="报告" width="60" align="center">
|
||||
<template #default="scope">
|
||||
<div
|
||||
v-if="scope.row.detection_report == null"
|
||||
@ -314,7 +314,11 @@ import GenerateReport from '@/views/GenerateReport.vue'
|
||||
import ImageDetailsCompare from '@/views/ImageDetailsCompare.vue'
|
||||
|
||||
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()
|
||||
@ -337,7 +341,12 @@ const delType = ref('')
|
||||
const detectionId = ref('') // 列表检测ID
|
||||
const isGenerateReport = ref(false) // 是否生成报告
|
||||
const archiveType =ref(false)
|
||||
const profileInfo = ref({}) // 患者信息
|
||||
const profileInfo = ref({
|
||||
id: '',
|
||||
diagnosis_info: '',
|
||||
treatment_info: '',
|
||||
suggestion_info: ''
|
||||
})
|
||||
const selectedRecord = ref({})
|
||||
const recordData =ref([])
|
||||
const isImageDetailsCompare = ref(false) // 是否显示对比图片详情
|
||||
@ -391,10 +400,16 @@ const handleSubmit = (item) => {
|
||||
}
|
||||
};
|
||||
const sessionsId = ref('')
|
||||
function selectRecord(data){ // 列表点击单选
|
||||
function selectRecord(data){
|
||||
selectedRecord.value = data
|
||||
sessionsById(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) => { // 列表多选
|
||||
@ -414,6 +429,18 @@ const sessionsInit = async () => {
|
||||
// element.list = [{}]
|
||||
// });
|
||||
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) {
|
||||
ElMessage.error('获取失败')
|
||||
@ -432,6 +459,21 @@ const sessionsById = async (session_id) => {
|
||||
// 导出报告逻辑
|
||||
const response = await historyAPI.sessionsById(session_id)
|
||||
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) => {
|
||||
element.order ='D-'+ (getNo(index + 1))
|
||||
});
|
||||
@ -575,7 +617,7 @@ function handleVideoUpward(){ // 视频上一页
|
||||
|
||||
|
||||
|
||||
async function handleDiagnosticInfo(status) { // 保存诊断信息
|
||||
async function handleDiagnosticInfo(status) {
|
||||
try {
|
||||
// 检查是否有活跃的会话ID
|
||||
if (!profileInfo.value.id) {
|
||||
@ -592,7 +634,7 @@ async function handleDiagnosticInfo(status) { // 保存诊断信息
|
||||
treatment_info:profileInfo.value.treatment_info,
|
||||
suggestion_info:profileInfo.value.suggestion_info,
|
||||
status:status,
|
||||
id:profileInfo.value.id,
|
||||
session_id: profileInfo.value.id,
|
||||
})
|
||||
})
|
||||
if (!response.ok) {
|
||||
@ -601,21 +643,20 @@ async function handleDiagnosticInfo(status) { // 保存诊断信息
|
||||
|
||||
const result = await response.json()
|
||||
if (result.success) {
|
||||
profileInfo.value[profileInfo.value.index].diagnosis_info = profileInfo.value.diagnosis_info
|
||||
profileInfo.value[profileInfo.value.index].treatment_info = profileInfo.value.treatment_info
|
||||
profileInfo.value[profileInfo.value.index].suggestion_info = profileInfo.value.suggestion_info
|
||||
profileInfo.value[profileInfo.value.index].status = status
|
||||
selectedRecord.value.diagnosis_info = profileInfo.value.diagnosis_info
|
||||
selectedRecord.value.treatment_info = profileInfo.value.treatment_info
|
||||
selectedRecord.value.suggestion_info = profileInfo.value.suggestion_info
|
||||
selectedRecord.value.status = status
|
||||
ElMessage.success({
|
||||
message: '诊断信息成功',
|
||||
duration: 5000
|
||||
})
|
||||
dialogVisible.value =false
|
||||
} else {
|
||||
throw new Error(result.message || '诊断信息失败')
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error({
|
||||
message: errorMessage,
|
||||
message: error?.message || '诊断信息失败',
|
||||
duration: 5000
|
||||
})
|
||||
} finally {
|
||||
@ -1088,7 +1129,7 @@ historyAPI.VideoDelById(ids).then((response)=>{
|
||||
}
|
||||
.patientprofile-imgbgbox{
|
||||
width: calc(100% - 140px);
|
||||
height: 100%;
|
||||
height: 85%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
@ -661,7 +661,11 @@ const getRecordStatusType = (status) => {
|
||||
}
|
||||
|
||||
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) => {
|
||||
@ -1050,8 +1054,8 @@ onMounted(() => {
|
||||
|
||||
.content-right-top-text2 {
|
||||
width: 62px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
background: rgba(230, 162, 60, 0.1);
|
||||
color: #E6A23C;
|
||||
text-align: center;
|
||||
@ -1060,8 +1064,8 @@ onMounted(() => {
|
||||
}
|
||||
.content-right-top-text3 {
|
||||
width: 62px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
background: rgba(103, 194, 58, 0.1);
|
||||
color: rgb(103, 194, 58);
|
||||
text-align: center;
|
||||
|
||||
@ -114,15 +114,6 @@
|
||||
</div>
|
||||
<div class="ViewUserInfo-detail-display">
|
||||
<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-value">
|
||||
<span v-if="patientInfo && patientInfo.occupation">
|
||||
@ -130,6 +121,16 @@
|
||||
</span>
|
||||
<span v-else>—</span>
|
||||
</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>
|
||||
@ -172,7 +173,11 @@ const calculateAge = (birthDate) => {
|
||||
return age
|
||||
}
|
||||
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>
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user