<!--
|
患者信息展示块(需求1:18 项统一展示)
|
|
背景:
|
需求要求「流水号、排队序号、姓名、性别、出生日期、年龄、项目、病区、病床号、
|
登记时间、状态、患者电话、开单日期、距今天数、来源、门诊号、医嘱号、开单医师」
|
在**每个界面**都有。
|
|
(原始需求写的是「就诊卡号」;因 HIS 在卡号槽位传空、medical_card 全库为空,
|
该位置实际展示 outpatient_no,经确认标签改为「门诊号」——详见《部署清单》5.1。)
|
|
为什么做成组件:
|
有 10+ 个界面需要同一套字段。若各自复制粘贴,会出现
|
· 口径不一致(某个页面用 createTime 当开单日期)
|
· 改一处漏一处
|
因此统一到这里;各页面只需传数据对象。
|
|
数据兼容性:
|
不同页面的数据形态略有差异,本组件按以下优先级取值:
|
1) 后端平铺字段(QueueRespVO / AppointmentRespVO / DevRentRespVO 新增的 patWardDesc 等)
|
2) patDetails JSON 快照(旧数据可能只在这里有)
|
3) '-' 占位
|
-->
|
<script setup lang="ts">
|
import { computed } from 'vue'
|
import { DICT_TYPE } from '@/utils/dict'
|
import {
|
formatAge,
|
formatDaysFromOrder,
|
formatDateOnly,
|
formatDateTime,
|
resolveCardNo
|
} from '@/utils/patientInfo'
|
import { getCheckTypeName, getCheckTypeSeqPrefix } from '@/utils/checkTypeFormatter'
|
|
const props = defineProps<{
|
/** 患者/排队/预约/租赁 数据对象(形态兼容,见文件头说明) */
|
data: any
|
/** 检查项目字段名:队列用 bookCheckType,租赁用 checkType */
|
checkTypeField?: string
|
/** 状态字典:队列用 ecg_queue_status,预约单用 ecg_status */
|
statusDict?: string
|
/** 是否显示排队序号(预约记录等无此字段的页面可关闭) */
|
showSeqNum?: boolean
|
/**
|
* 是否显示医嘱号,默认 **true**。
|
*
|
* 历史说明:早期排查得出"HIS 报文中不存在医嘱号"的结论,故当时默认关闭,
|
* 以免用申请号冒充。**该结论已被推翻** —— HIS《交互服务通用接口文档》
|
* 6.1/6.4/6.5 明确 `observationRequest.id.item[root=2.16.156.10011.1.28]`
|
* 即「医嘱ID/医嘱号」,且实测 S0201ECG 220 条、S0202ECG 29 条均携带该值。
|
* 后端已新增 `doctorAdviceNo` 字段并在解析时采集,故此处默认显示。
|
*
|
* 注意该节点在规范中**非必须项**,历史数据(新增列之前采集的)为空 →
|
* 显示 `-`,属预期。
|
*/
|
showAdviceNo?: boolean
|
/**
|
* 【分辨率自适应】**≥1200px** 时每列的栅格宽度(默认 6 = 一行 4 个)。
|
*
|
* 窄屏会自动降级,避免医生电脑分辨率不同导致字段挤成一条缝:
|
* · ≥1200px(lg) → 用本参数(默认 6,一行 4 个)
|
* · 992~1199(md) → 12,一行 2 个
|
* · 768~991(sm) → 12,一行 2 个
|
* · <768(xs) → 24,一行 1 个
|
*/
|
span?: number
|
}>()
|
|
const d = computed(() => props.data || {})
|
const pd = computed(() => d.value.patDetails || {})
|
const colSpan = computed(() => props.span ?? 6)
|
// 医嘱号默认显示(详见 props 说明);仅在显式传 false 时隐藏
|
const showAdvice = computed(() => props.showAdviceNo !== false)
|
const checkField = computed(() => props.checkTypeField ?? 'bookCheckType')
|
|
/** 取第一个有值的候选 */
|
function pick(...vals: any[]): any {
|
for (const v of vals) {
|
if (v !== undefined && v !== null && v !== '') return v
|
}
|
return undefined
|
}
|
|
const phone = computed(() =>
|
pick(d.value.patMobile, d.value.patPhone, pd.value.mobile) ?? '-'
|
)
|
|
/**
|
* 排队号显示文案(本需求:号统一)。
|
* · 只认 `seq_num`(签到时分配、之后不变),**不再回落 bookSeqNum**(该号已停用);
|
* · 显示为「检查项目前缀 + 号」—— 号按检查项目各自编号,不同项目之间会出现相同数字,
|
* 必须靠 `check_type.seq_prefix` 区分(例:A12 / 12)。
|
*/
|
const seqNumText = computed(() => {
|
const seq = pick(d.value.seqNum)
|
if (seq === undefined) return '-'
|
return `${getCheckTypeSeqPrefix(d.value[checkField.value])}${seq}`
|
})
|
|
/** 是否加急(HIS 加急 jjbz=J 或人工插队共用 jump_flag):医生侧要显眼提示 */
|
const isJump = computed(() => Number(d.value.jumpFlag) === 1)
|
const ward = computed(() => pick(d.value.patWardDesc, pd.value.wardDesc) ?? '-')
|
// 注意:d.bedNo 在 queue 里是【诊疗床/工位号】,真正的病床号是 patBedNo / patDetails.bedNo
|
const patBed = computed(() => pick(d.value.patBedNo, pd.value.bedNo) ?? '-')
|
|
const cardNo = computed(() => resolveCardNo(d.value))
|
const source = computed(() => pick(d.value.patSrc, pd.value.source))
|
const checkType = computed(() => d.value[checkField.value])
|
</script>
|
|
<template>
|
<div class="patient-info-block">
|
<!--
|
患者标识头(醒目显示当前患者)。
|
背景:需求1 的 18 项信息块里,姓名只占其中一格、与其它字段同样字号,
|
查看时容易"看不到姓名"。这里把它单独提出来做成标题行,
|
并带上患者编号 / 排队序号 / 状态,保证"当前是谁"一眼可见。
|
各页面(叫号检查、设备领用、装机、拆机、预约签到)共用本组件,故一致生效。
|
-->
|
<div class="patient-identity">
|
<span class="pi-name">{{ d.patName || '—' }}</span>
|
<span class="pi-item">患者编号:{{ d.patId || '-' }}</span>
|
<span class="pi-item">就诊号:{{ d.episodeId || '-' }}</span>
|
<span v-if="showSeqNum" class="pi-item">
|
排队序号:{{ seqNumText }}
|
</span>
|
<span v-if="isJump" class="pi-item pi-warn">(加急)</span>
|
<span v-if="d.status !== undefined && d.status !== null && statusDict" class="pi-item">
|
状态:<dict-tag :type="statusDict" :value="d.status" />
|
</span>
|
<span class="pi-item pi-warn" v-if="d.passed === 1 || d.passed === '1'">(已过号)</span>
|
</div>
|
|
<el-row :gutter="20">
|
<el-col :xs="24" :sm="12" :md="12" :lg="colSpan">
|
<el-form-item label="姓名">
|
{{ d.patName || '-' }}
|
</el-form-item>
|
</el-col>
|
<el-col :xs="24" :sm="12" :md="12" :lg="colSpan">
|
<el-form-item label="就诊号">
|
{{ d.episodeId || '-' }}
|
</el-form-item>
|
</el-col>
|
<el-col v-if="showSeqNum" :xs="24" :sm="12" :md="12" :lg="colSpan">
|
<el-form-item label="排队序号">
|
{{ seqNumText }}
|
</el-form-item>
|
</el-col><el-col :xs="24" :sm="12" :md="12" :lg="colSpan">
|
<!-- 标签由「就诊卡号」改为「门诊号」(经确认,详见《部署清单》5.1):
|
HIS 在卡号槽位传空,medical_card 全库 159,116 行无一非空;
|
该位置实际展示 outpatient_no。cardNo.from 标注值的真实来源,
|
用于区分混在该字段里的身份证号 / 住院号。 -->
|
<el-form-item label="门诊号">
|
<span>{{ cardNo.value }}</span>
|
<el-tag v-if="cardNo.from" size="small" type="info" effect="plain" class="ml-1">
|
{{ cardNo.from }}
|
</el-tag>
|
</el-form-item>
|
</el-col> <el-col :xs="24" :sm="12" :md="12" :lg="colSpan">
|
<el-form-item label="状态">
|
<dict-tag v-if="d.status !== undefined && d.status !== null && statusDict" :type="statusDict"
|
:value="d.status" />
|
<span v-else>-</span>
|
</el-form-item>
|
</el-col>
|
<el-col :xs="24" :sm="12" :md="12" :lg="colSpan">
|
<el-form-item label="申请编号">
|
{{ d.applyNo || '-' }}
|
</el-form-item>
|
</el-col><el-col :xs="24" :sm="12" :md="12" :lg="colSpan">
|
<el-form-item label="项目">
|
{{ checkType ? getCheckTypeName(checkType) : '-' }}
|
</el-form-item>
|
</el-col>
|
|
<el-col :xs="24" :sm="12" :md="12" :lg="colSpan">
|
<el-form-item label="多耐">
|
<dict-tag :type="DICT_TYPE.ECG_TOLERANCE" :value="d.tolerance ?? 0" />
|
</el-form-item>
|
</el-col><el-col :xs="24" :sm="12" :md="12" :lg="colSpan">
|
<el-form-item label="来源">
|
<dict-tag v-if="source !== undefined" :type="DICT_TYPE.ECG_PAT_SOURCE" :value="source" />
|
<span v-else>-</span>
|
</el-form-item>
|
</el-col>
|
<el-col :xs="24" :sm="12" :md="12" :lg="colSpan">
|
<el-form-item label="性别">
|
<!-- 1 男 / 2 女;0 或缺失(HIS 未提供,实测库中有 2123 行 = 0)显示"未知"。
|
注意不能直接把 0 交给 dict-tag —— 字典里没有 0 这一项,会渲染成空白。 -->
|
<dict-tag v-if="d.patGender === 1 || d.patGender === 2 || d.patGender === '1' || d.patGender === '2'"
|
:type="DICT_TYPE.SYSTEM_USER_SEX" :value="d.patGender" />
|
<span v-else>未知</span>
|
</el-form-item>
|
</el-col>
|
<el-col :xs="24" :sm="12" :md="12" :lg="colSpan">
|
<el-form-item label="出生日期">
|
{{ formatDateOnly(d.patBirthday) || '-' }}
|
</el-form-item>
|
</el-col>
|
<el-col :xs="24" :sm="12" :md="12" :lg="colSpan">
|
<el-form-item label="年龄">
|
{{ formatAge(d.patBirthday) || '-' }}
|
</el-form-item>
|
</el-col>
|
<el-col :xs="24" :sm="12" :md="12" :lg="colSpan">
|
<el-form-item label="患者电话">
|
{{ phone }}
|
</el-form-item>
|
</el-col>
|
|
<el-col :xs="24" :sm="12" :md="12" :lg="colSpan">
|
<el-form-item label="病区">
|
{{ ward }}
|
</el-form-item>
|
</el-col>
|
<el-col :xs="24" :sm="12" :md="12" :lg="colSpan">
|
<el-form-item label="病床号">
|
{{ patBed }}
|
</el-form-item>
|
</el-col>
|
|
<el-col :xs="24" :sm="12" :md="12" :lg="colSpan">
|
<el-form-item label="登记时间">
|
{{ formatDateTime(d.registerDate) || '-' }}
|
</el-form-item>
|
</el-col><el-col :xs="24" :sm="12" :md="12" :lg="colSpan">
|
<el-form-item label="开单医师">
|
{{ d.doctor || '-' }}
|
</el-form-item>
|
</el-col>
|
<el-col :xs="24" :sm="12" :md="12" :lg="colSpan">
|
<el-form-item label="开单日期">
|
{{ formatDateTime(d.doctorTime) || '-' }}
|
</el-form-item>
|
</el-col>
|
<el-col :xs="24" :sm="12" :md="12" :lg="colSpan">
|
<el-form-item label="距今天数">
|
{{ formatDaysFromOrder(d.doctorTime) || '-' }}
|
</el-form-item>
|
</el-col>
|
<el-col v-if="showAdvice" :xs="24" :sm="12" :md="12" :lg="colSpan">
|
<el-form-item label="医嘱号">
|
{{ d.doctorAdviceNo || '-' }}
|
</el-form-item>
|
</el-col>
|
</el-row>
|
</div>
|
</template>
|
|
<style scoped lang="scss">
|
/* ==========================================================================
|
紧凑排版:本组件每个界面都要用,18 项按 4 列 = 5 行。
|
Element Plus 默认 `.el-form-item { margin-bottom: 18px }`,
|
5 行约 90px 纯间距,会把下方 el-table 挤掉(用户反馈"表单挤占表格")。
|
这里统一压到 4px,并收紧标识头。
|
⚠️ `.el-form-item` 是本组件渲染出的元素(模板直接写了 el-form-item),
|
带 scoped 属性,故不带 :deep 也能命中;同时保留 :deep 兜底。
|
========================================================================== */
|
.el-form-item,
|
:deep(.el-form-item) {
|
margin-bottom: 4px;
|
}
|
|
/* 患者标识头:让"当前是哪位患者"一眼可见 */
|
.patient-identity {
|
display: flex;
|
flex-wrap: wrap;
|
align-items: center;
|
gap: 2px 16px;
|
margin-bottom: 6px;
|
padding: 3px 10px;
|
border-left: 3px solid var(--el-color-primary);
|
background-color: var(--el-color-primary-light-9);
|
border-radius: 4px;
|
|
.pi-name {
|
font-size: 16px;
|
font-weight: 700;
|
color: var(--el-color-primary);
|
margin-right: 4px;
|
line-height: 22px;
|
}
|
|
.pi-item {
|
font-size: 13px;
|
color: var(--el-text-color-regular);
|
display: inline-flex;
|
align-items: center;
|
gap: 4px;
|
line-height: 22px;
|
}
|
|
.pi-warn {
|
color: var(--el-color-danger);
|
font-weight: 600;
|
}
|
}
|
</style>
|