1
This commit is contained in:
@@ -111,126 +111,10 @@
|
||||
|
||||
<template v-else-if="detail">
|
||||
<!-- 患者信息 -->
|
||||
<div class="card patient-card">
|
||||
<div class="card-title">患者信息</div>
|
||||
<div class="patient-hero">
|
||||
<div class="patient-name">{{ apt.patient_name || '—' }}</div>
|
||||
<div class="patient-meta">
|
||||
<div>{{ maskPhone(apt.patient_phone) }} · {{ formatGender(diag.gender) }} · {{ diag.age != null ? diag.age + '岁' : '—' }}</div>
|
||||
<div>{{ diag.height ? diag.height + 'cm' : '—' }} / {{ diag.weight ? diag.weight + 'kg' : '—' }} · {{ diag.region || '—' }}</div>
|
||||
<div>预约:{{ apt.appointment_date }} {{ apt.appointment_time }} · {{ formatPeriod(apt.period) }}</div>
|
||||
<div>医生:{{ apt.doctor_name || '—' }} 医助:{{ apt.assistant_name || '—' }}</div>
|
||||
<div>状态:{{ apt.status_desc || '—' }} · {{ apt.has_prescription ? '已开方' : '未开方' }}</div>
|
||||
<div v-if="apt.remark">备注:{{ apt.remark }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<PatientInfoCard :apt="apt" :diag="diag" />
|
||||
|
||||
<!-- 患者病例(完整、字段与 tcm/diagnosis/edit.vue 保持同步,只读展示) -->
|
||||
<div class="card case-card">
|
||||
<div class="card-title">
|
||||
患者病例
|
||||
<span class="case-sub">{{ caseTypeLabel }} · 诊断日期 {{ diag.diagnosis_date || apt.appointment_date || '—' }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<div class="grid-block">
|
||||
<div class="grid-title">基本信息</div>
|
||||
<div class="kv-grid four">
|
||||
<KVItem label="诊单ID" :value="diag.patient_id" />
|
||||
<KVItem label="姓名" :value="diag.patient_name" />
|
||||
<KVItem label="身份证号" :value="diag.id_card" />
|
||||
<KVItem label="手机号" :value="maskPhone(diag.phone || apt.patient_phone)" />
|
||||
<KVItem label="性别" :value="textOf('gender')" />
|
||||
<KVItem label="年龄" :value="diag.age != null ? diag.age + '岁' : ''" />
|
||||
<KVItem label="婚姻状态" :value="textOf('marital_status')" />
|
||||
<KVItem label="地区" :value="diag.region" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 生命体征 -->
|
||||
<div class="grid-block">
|
||||
<div class="grid-title">生命体征</div>
|
||||
<div class="kv-grid four">
|
||||
<KVItem label="身高" :value="diag.height ? diag.height + ' cm' : ''" />
|
||||
<KVItem label="体重" :value="diag.weight ? diag.weight + ' kg' : ''" />
|
||||
<MetricKVItem label="高压" :value="diag.systolic_pressure" unit=" mmHg" :high="isHighSystolicPressure(diag.systolic_pressure)" />
|
||||
<MetricKVItem label="低压" :value="diag.diastolic_pressure" unit=" mmHg" :high="isHighDiastolicPressure(diag.diastolic_pressure)" />
|
||||
<KVItem label="诊断类型" :value="textOf('diagnosis_type')" />
|
||||
<MetricKVItem label="空腹血糖" :value="diag.fasting_blood_sugar" unit=" mmol/L" :high="isHighFastingBloodSugar(diag.fasting_blood_sugar, diag.age)" />
|
||||
<KVItem label="在用药物" :value="diag.current_medications" multiline />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主诉 -->
|
||||
<div class="grid-block">
|
||||
<div class="grid-title">主诉</div>
|
||||
<div class="kv-grid four">
|
||||
<KVItem label="当地医院诊断日期" :value="diag.diagnosis_date" />
|
||||
<KVItem label="发现糖尿病病史" :value="formatDiabetesDiscoveryDisplay ? formatDiabetesDiscoveryDisplay(diag.diabetes_discovery_year) : diag.diabetes_discovery_year" />
|
||||
<KVItem label="当地就诊医院" :value="diag.local_hospital_name" />
|
||||
<KVItem label="当地医院诊断结果" :value="joinArray(diag.local_hospital_diagnosis)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 现病史(后端已翻译 *_text) -->
|
||||
<div class="grid-block">
|
||||
<div class="grid-title">现病史</div>
|
||||
<div class="kv-grid three">
|
||||
<KVItem label="口腔感觉" :value="textOf('appetite')" />
|
||||
<KVItem label="每日饮水量" :value="textOf('water_intake')" />
|
||||
<KVItem label="近月体重变化" :value="textOf('weight_change')" />
|
||||
<KVItem label="脂肪肝程度" :value="textOf('fatty_liver_degree')" />
|
||||
<KVItem label="饮食情况" :value="textOf('diet_condition')" span="2" />
|
||||
<KVItem label="肢体感觉" :value="textOf('body_feeling')" />
|
||||
<KVItem label="睡眠情况" :value="textOf('sleep_condition')" />
|
||||
<KVItem label="眼睛情况" :value="textOf('eye_condition')" />
|
||||
<KVItem label="头部感觉" :value="textOf('head_feeling')" />
|
||||
<KVItem label="出汗情况" :value="textOf('sweat_condition')" />
|
||||
<KVItem label="皮肤情况" :value="textOf('skin_condition')" />
|
||||
<KVItem label="小便情况" :value="textOf('urine_condition')" />
|
||||
<KVItem label="大便情况" :value="textOf('stool_condition')" />
|
||||
<KVItem label="腰肾情况" :value="textOf('kidney_condition')" />
|
||||
<KVItem label="其他补充" :value="diag.symptoms" span="3" multiline />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 既往史 -->
|
||||
<div class="grid-block">
|
||||
<div class="grid-title">既往史</div>
|
||||
<div class="kv-grid three">
|
||||
<KVItem label="既往病史" :value="textOf('past_history')" span="3" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 其他病史 -->
|
||||
<div class="grid-block">
|
||||
<div class="grid-title">其他病史</div>
|
||||
<div class="kv-grid three">
|
||||
<KVItem label="外伤史" :value="textOf('trauma_history')" />
|
||||
<KVItem label="手术史" :value="textOf('surgery_history')" />
|
||||
<KVItem label="过敏史" :value="textOf('allergy_history')" />
|
||||
<KVItem label="家族病史" :value="textOf('family_history')" />
|
||||
<KVItem label="妊娠哺乳史" :value="textOf('pregnancy_history')" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 病史补充 -->
|
||||
<div class="grid-block" v-if="diag.remark">
|
||||
<div class="grid-title">病史补充</div>
|
||||
<div class="kv-grid one">
|
||||
<KVItem label="" :value="diag.remark" multiline />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 处方意见 -->
|
||||
<div class="grid-block">
|
||||
<div class="grid-title">处方意见</div>
|
||||
<div class="rx-note">
|
||||
{{ apt.has_prescription ? '已开方。' : '当前未开方。' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 患者病例(与 tcm/diagnosis/readonly 共用同一组件,确保两处字段一致) -->
|
||||
<PatientCaseCard :apt="apt" :diag="diag" />
|
||||
|
||||
<!-- 医生备注 & 舌苔照片 -->
|
||||
<div class="card note-card">
|
||||
@@ -353,13 +237,23 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineAsyncComponent, onMounted, onUnmounted, h } from 'vue'
|
||||
import { defineAsyncComponent, onMounted, onUnmounted } from 'vue'
|
||||
import { receptionQueue, receptionDetail, notifyAssistant, addDoctorNote } from '@/api/patient'
|
||||
import NoteTimeline from './components/NoteTimeline.vue'
|
||||
import PatientInfoCard from '@/views/tcm/diagnosis/components/PatientInfoCard.vue'
|
||||
import PatientCaseCard from '@/views/tcm/diagnosis/components/PatientCaseCard.vue'
|
||||
import { completeAppointment } from '@/api/doctor'
|
||||
import { getCallSignature } from '@/api/tcm'
|
||||
import feedback from '@/utils/feedback'
|
||||
import { formatDiabetesDiscoveryDisplay } from '@/utils/diabetes-discovery-display'
|
||||
import { formatGender } from '@/utils/diag-display'
|
||||
import {
|
||||
isHighFastingBloodSugar,
|
||||
isHighPostprandialBloodSugar,
|
||||
isHighOtherBloodSugar,
|
||||
isHighBloodPressure,
|
||||
formatBp
|
||||
} from '@/utils/blood-thresholds'
|
||||
import {
|
||||
Phone,
|
||||
Search,
|
||||
@@ -370,55 +264,6 @@ import {
|
||||
const ChatDialog = defineAsyncComponent(() => import('@/components/chat-dialog/index.vue'))
|
||||
const DiagnosisEdit = defineAsyncComponent(() => import('@/views/tcm/diagnosis/edit.vue'))
|
||||
|
||||
/**
|
||||
* 局部渲染组件:左侧 label + 右侧 value,支持 span / 换行
|
||||
*/
|
||||
const KVItem = (props: { label: string; value: any; span?: string | number; multiline?: boolean }) => {
|
||||
const val = props.value
|
||||
const empty = val === undefined || val === null || val === '' || (Array.isArray(val) && val.length === 0)
|
||||
return h(
|
||||
'div',
|
||||
{
|
||||
class: ['kv-item', props.multiline ? 'multiline' : '', empty ? 'empty' : ''],
|
||||
style: props.span ? `grid-column: span ${props.span}` : ''
|
||||
},
|
||||
[
|
||||
h('span', { class: 'kv-label' }, props.label),
|
||||
h('span', { class: 'kv-value' }, empty ? '—' : String(val))
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
const MetricKVItem = (props: {
|
||||
label: string
|
||||
value: any
|
||||
unit?: string
|
||||
span?: string | number
|
||||
high?: boolean
|
||||
}) => {
|
||||
const empty = props.value === undefined || props.value === null || props.value === ''
|
||||
return h(
|
||||
'div',
|
||||
{
|
||||
class: ['kv-item', 'metric-kv-item', empty ? 'empty' : ''],
|
||||
style: props.span ? `grid-column: span ${props.span}` : ''
|
||||
},
|
||||
[
|
||||
h('span', { class: 'kv-label' }, props.label),
|
||||
h(
|
||||
'span',
|
||||
{ class: ['kv-value', 'metric-kv-value', props.high ? 'high' : ''] },
|
||||
empty
|
||||
? '—'
|
||||
: [
|
||||
`${props.value}${props.unit || ''}`,
|
||||
props.high ? h('span', { class: 'metric-up' }, '↑') : null
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
const POLL_MS = 5_000
|
||||
const PAGE_SIZE = 15
|
||||
|
||||
@@ -489,38 +334,12 @@ const todayMeta = computed(() => {
|
||||
})
|
||||
|
||||
|
||||
const caseTypeLabel = computed(() => {
|
||||
if (!diag.value) return '初诊'
|
||||
return diag.value.consultation_type === 'follow_up' ? '复诊' : '初诊'
|
||||
})
|
||||
|
||||
/**
|
||||
* 诊单字典字段由后端 AppointmentLogic::enrichDiagnosisLabels 预翻译,
|
||||
* 前端直接读 `<field>_text`;找不到时回退原值。
|
||||
*/
|
||||
const textOf = (field: string): string => {
|
||||
const d: any = diag.value || {}
|
||||
const t = d[field + '_text']
|
||||
if (t !== undefined && t !== null && t !== '') return String(t)
|
||||
const raw = d[field]
|
||||
if (raw === undefined || raw === null || raw === '') return ''
|
||||
return Array.isArray(raw) ? raw.join('、') : String(raw)
|
||||
}
|
||||
|
||||
const todayStr = () => {
|
||||
const d = new Date()
|
||||
const pad = (n: number) => String(n).padStart(2, '0')
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
|
||||
}
|
||||
|
||||
const maskPhone = (phone?: string) => {
|
||||
if (!phone) return '—'
|
||||
if (phone.length < 11) return phone
|
||||
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
|
||||
}
|
||||
|
||||
const formatGender = (g?: number) => (g === 1 ? '男' : g === 0 ? '女' : '—')
|
||||
|
||||
const formatGenderAge = (row: QueueRow) => {
|
||||
const parts = [formatGender(row.gender), row.age != null ? row.age + '岁' : '']
|
||||
return parts.filter((p) => p && p !== '—').join(' ')
|
||||
@@ -531,73 +350,6 @@ const formatTimeHM = (time?: string) => {
|
||||
return time.replace(/^(\d{2}:\d{2})(:\d{2})?$/, '$1')
|
||||
}
|
||||
|
||||
const formatPeriod = (period?: string) => {
|
||||
if (period === 'morning') return '上午'
|
||||
if (period === 'afternoon') return '下午'
|
||||
return '全天'
|
||||
}
|
||||
|
||||
const formatBp = (row: any) => {
|
||||
const sys = row?.systolic_pressure
|
||||
const dia = row?.diastolic_pressure
|
||||
if (sys == null && dia == null) return '—'
|
||||
return `${sys ?? '—'} / ${dia ?? '—'}`
|
||||
}
|
||||
|
||||
const toNumeric = (value: any) => {
|
||||
if (value === null || value === undefined || value === '') return null
|
||||
const num = Number(value)
|
||||
return Number.isFinite(num) ? num : null
|
||||
}
|
||||
|
||||
const getBloodSugarThresholds = (ageValue: any) => {
|
||||
const age = toNumeric(ageValue)
|
||||
if (age === null) return null
|
||||
if (age < 50) return { fasting: 7, postprandial: 9 }
|
||||
if (age >= 50) return { fasting: 8, postprandial: 10 }
|
||||
return null
|
||||
}
|
||||
|
||||
const isHighFastingBloodSugar = (value: any, ageValue: any) => {
|
||||
const num = toNumeric(value)
|
||||
const thresholds = getBloodSugarThresholds(ageValue)
|
||||
return num !== null && thresholds !== null && num >= thresholds.fasting
|
||||
}
|
||||
|
||||
const isHighPostprandialBloodSugar = (value: any, ageValue: any) => {
|
||||
const num = toNumeric(value)
|
||||
const thresholds = getBloodSugarThresholds(ageValue)
|
||||
return num !== null && thresholds !== null && num >= thresholds.postprandial
|
||||
}
|
||||
|
||||
const isHighOtherBloodSugar = (value: any, ageValue: any) => {
|
||||
const num = toNumeric(value)
|
||||
const thresholds = getBloodSugarThresholds(ageValue)
|
||||
return num !== null && thresholds !== null && num >= thresholds.postprandial
|
||||
}
|
||||
|
||||
const isHighBloodPressure = (row: any) => {
|
||||
const sys = toNumeric(row?.systolic_pressure)
|
||||
const dia = toNumeric(row?.diastolic_pressure)
|
||||
return (sys !== null && sys > 140) || (dia !== null && dia > 90)
|
||||
}
|
||||
|
||||
const isHighSystolicPressure = (value: any) => {
|
||||
const num = toNumeric(value)
|
||||
return num !== null && num > 140
|
||||
}
|
||||
|
||||
const isHighDiastolicPressure = (value: any) => {
|
||||
const num = toNumeric(value)
|
||||
return num !== null && num > 90
|
||||
}
|
||||
|
||||
const joinArray = (arr: any): string => {
|
||||
if (!arr) return ''
|
||||
if (Array.isArray(arr)) return arr.filter(Boolean).join('、')
|
||||
return String(arr)
|
||||
}
|
||||
|
||||
const fetchQueuePage = async (pageNo: number, status?: number, patientName?: string) => {
|
||||
const today = todayStr()
|
||||
const res: any = await receptionQueue({
|
||||
|
||||
Reference in New Issue
Block a user