yxh
yxh
7 天以前 09dc74e264b6262cbe41c9c8476e24705871d4a6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
<!--
  患者信息展示块(需求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>