Files
zyt/admin/src/views/tcm/diagnosis/components/PatientCaseCard.vue
T
2026-06-02 14:20:20 +08:00

370 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<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>
</template>
<script setup lang="ts">
import { computed, h } from 'vue'
import {
isHighSystolicPressure,
isHighDiastolicPressure,
isHighFastingBloodSugar
} from '@/utils/blood-thresholds'
import { maskPhone, joinArray, makeTextOf } from '@/utils/diag-display'
import { formatDiabetesDiscoveryDisplay } from '@/utils/diabetes-discovery-display'
interface Props {
apt?: Record<string, any> | null
diag?: Record<string, any> | null
}
const props = withDefaults(defineProps<Props>(), {
apt: () => ({}),
diag: () => ({})
})
const caseTypeLabel = computed(() => {
const d: any = props.diag || {}
return d.consultation_type === 'follow_up' ? '复诊' : '复诊'
})
const textOf = makeTextOf(() => props.diag || {})
// ========== 本地函数式子组件 ==========
/**
* 局部渲染组件:左侧 label + 右侧 value,支持 span / 换行(与抽离前 reception 内联实现一致)
*/
const KVItem = (kvProps: { label: string; value: any; span?: string | number; multiline?: boolean }) => {
const val = kvProps.value
const empty =
val === undefined || val === null || val === '' || (Array.isArray(val) && val.length === 0)
return h(
'div',
{
class: ['kv-item', kvProps.multiline ? 'multiline' : '', empty ? 'empty' : ''],
style: kvProps.span ? `grid-column: span ${kvProps.span}` : ''
},
[
h('span', { class: 'kv-label' }, kvProps.label),
h('span', { class: 'kv-value' }, empty ? '—' : String(val))
]
)
}
/**
* 数值型 KV:偏高时红色 + ↑ 箭头(与抽离前 reception 内联实现一致)
*/
const MetricKVItem = (mkvProps: {
label: string
value: any
unit?: string
span?: string | number
high?: boolean
}) => {
const empty = mkvProps.value === undefined || mkvProps.value === null || mkvProps.value === ''
return h(
'div',
{
class: ['kv-item', 'metric-kv-item', empty ? 'empty' : ''],
style: mkvProps.span ? `grid-column: span ${mkvProps.span}` : ''
},
[
h('span', { class: 'kv-label' }, mkvProps.label),
h(
'span',
{ class: ['kv-value', 'metric-kv-value', mkvProps.high ? 'high' : ''] },
empty
? '—'
: [
`${mkvProps.value}${mkvProps.unit || ''}`,
mkvProps.high ? h('span', { class: 'metric-up' }, '↑') : null
]
)
]
)
}
</script>
<style lang="scss" scoped>
.card {
background: var(--rx-surface);
border: 1px solid var(--rx-line);
border-radius: 14px;
padding: 18px;
display: flex;
flex-direction: column;
gap: 14px;
box-shadow: var(--rx-shadow-sm);
transition: box-shadow 0.2s ease;
&:hover {
box-shadow: var(--rx-shadow);
}
}
.card-title {
font-size: 15px;
font-weight: 700;
color: var(--rx-ink);
display: flex;
align-items: center;
gap: 10px;
padding-left: 10px;
position: relative;
&::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 16px;
border-radius: 2px;
background: linear-gradient(180deg, #4f8cff 0%, #2563eb 100%);
}
}
.case-sub {
font-size: 12px;
font-weight: 400;
color: var(--rx-soft);
font-family: var(--rx-font-data);
}
.grid-block {
display: flex;
flex-direction: column;
gap: 10px;
padding-top: 12px;
border-top: 1px dashed var(--rx-line);
&:first-of-type {
border-top: none;
padding-top: 0;
}
}
.grid-title {
font-size: 13px;
font-weight: 600;
color: var(--rx-accent-strong);
letter-spacing: 0.02em;
display: flex;
align-items: center;
gap: 6px;
&::before {
content: '';
width: 4px;
height: 4px;
border-radius: 50%;
background: var(--rx-accent);
}
}
.kv-grid {
display: grid;
gap: 8px 16px;
&.one {
grid-template-columns: 1fr;
}
&.two {
grid-template-columns: repeat(2, 1fr);
}
&.three {
grid-template-columns: repeat(3, 1fr);
}
&.four {
grid-template-columns: repeat(4, 1fr);
}
}
:deep(.kv-item) {
display: flex;
gap: 6px;
font-size: 12.5px;
line-height: 1.55;
min-width: 0;
&.multiline {
flex-direction: column;
gap: 2px;
}
.kv-label {
color: var(--rx-soft);
flex-shrink: 0;
min-width: 58px;
}
.kv-value {
color: var(--rx-ink);
word-break: break-word;
flex: 1;
font-weight: 500;
}
&.empty .kv-value {
color: #c0c4cc;
font-weight: 400;
}
&.multiline .kv-value {
white-space: pre-wrap;
font-weight: 400;
}
}
:deep(.metric-kv-item .metric-kv-value) {
display: inline-flex;
align-items: center;
gap: 4px;
&.high {
color: #dc2626;
font-weight: 700;
}
}
:deep(.metric-up) {
font-size: 14px;
line-height: 1;
color: #dc2626;
}
.rx-note {
font-size: 13px;
color: var(--rx-muted);
line-height: 1.6;
padding: 12px 14px;
background: linear-gradient(135deg, #fafbfd 0%, #f5f7fb 100%);
border-radius: 8px;
border-left: 3px solid var(--rx-accent);
}
</style>