| | |
| | | <!-- 常规检查 医生界面--> |
| | | <template> |
| | | <el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-loading="formLoading"> |
| | | <el-form class="patient-compact-form" ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-loading="formLoading"> |
| | | <!-- ===== 编辑区(需要录入的字段)===== --> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="6"> |
| | | <el-form-item label="患者编号" prop="patId"> |
| | | <el-input v-model="formData.patId" placeholder="请输入患者编号" @input="getRentInfoByPatId" /> |
| | | <!-- |
| | | <Qrcode v-if="formData.patId !== undefined && formData.patId !== ''" :text="formData.patId" logo="/logo.gif" width=100 /> |
| | | <Barcode v-if="displayBarCode" :value="displayBarCode" :width="100" /> |
| | | <el-button @click="copyBarcode()">复制</el-button> |
| | | --> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="患者名称" prop="patName"> |
| | | <el-input v-model="formData.patName" placeholder="请输入患者名称" /> |
| | | </el-form-item> |
| | | </el-col><el-col :span="6"> |
| | | <el-form-item label="性别"> |
| | | {{ genderText }} |
| | | </el-form-item> |
| | | </el-col> |
| | | |
| | | <el-col :span="6"> |
| | | <el-form-item label="联系电话" prop="episodeId"> |
| | | <!-- 修正:原读 `patDetails.mobile`(queue 表的 JSON 快照,只含 source/mobile/wardDesc/bedNo 四字段)。 |
| | | 而 setPatient 已把后端从 appointment 回填的**平铺字段** `patMobile` 赋进 formData, |
| | | 故应以平铺字段优先,patDetails 仅作旧数据回退。 --> |
| | | {{ formData.patMobile || formData?.patDetails?.mobile || '-' }} |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="6"> |
| | | <el-form-item label="就诊编号" prop="episodeId"> |
| | | <el-input v-model="formData.episodeId" placeholder="请输入患者就诊编号" /> |
| | | </el-form-item> |
| | | </el-col><el-col :span="6"> |
| | | <el-form-item label="出生日期"> |
| | | {{ fmtDate(formData.patBirthday) }} |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="年龄"> |
| | | {{ ageText }} |
| | | </el-form-item> |
| | | </el-col><el-col :span="6"> |
| | | <el-form-item label="开单医师"> |
| | | {{ formData.doctor }} |
| | | </el-form-item> |
| | | </el-col> |
| | | |
| | | |
| | | </el-row> |
| | | <el-row><el-col :span="6"> |
| | | <el-form-item label="申请单号" prop="applyNo"> |
| | | <el-input v-model="formData.applyNo" placeholder="请输入申请单号" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="18"> |
| | | <el-form-item label="科室病区" prop="patDetails"> |
| | | <!-- 修正:来源与病区/床号原读 `patDetails.*`(queue 表的 JSON 快照,可能滞后且常缺病区), |
| | | 改为**平铺字段优先**(后端已从 appointment 回填 patSrc/patWardDesc/patBedNo), |
| | | patDetails 仅作旧数据回退。 --> |
| | | <dict-tag v-if="formData.patSrc ?? formData.patDetails?.source" |
| | | :type="DICT_TYPE.ECG_PAT_SOURCE" |
| | | :value="formData.patSrc ?? formData.patDetails?.source" /> |
| | | {{ formData.patWardDesc || formData?.patDetails?.wardDesc || '-' }} |
| | | - {{ formData.patBedNo || formData?.patDetails?.bedNo || '-' }} |
| | | </el-form-item></el-col> |
| | | |
| | | |
| | | <el-col :span="6"> |
| | | <el-form-item label="检查项目" prop=""> |
| | | {{ formData?.checkType && getCheckTypeName(formData?.checkType) }} |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="患者编号" prop="patId"> |
| | | <el-input v-model="formData.patId" placeholder="请输入患者编号" @input="getRentInfoByPatId" /> |
| | | <Barcode v-if="displayBarCode" :value="displayBarCode" :width="100" /> |
| | | <el-button @click="copyBarcode()">复制</el-button> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="患者姓名" prop="patName"> |
| | | <el-input v-model="formData.episodeId" placeholder="请输入患者姓名" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="6"> |
| | | <el-form-item label="开单日期"> |
| | | {{ fmtDate(formData.doctorTime) }} |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="距今天数"> |
| | | {{ daysFromOrder }} |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="医嘱号"> |
| | | <!-- 修正:此处原先绑定的是 `formData.applyNo`(申请单号), |
| | | 与「医嘱号」是**两个不同的标识**: |
| | | 申请单号 apply_no = HIS root 2.16.156.10011.1.24,例 2126091700011 |
| | | 医嘱号 doctor_advice_no = HIS root 2.16.156.10011.1.28,例 112663505 |
| | | 原写法是把申请单号冒充成医嘱号,属于错误展示。 |
| | | 现改为读后端新采集的 doctorAdviceNo(该节点规范中非必须项,历史数据为空 → 显示 '-')。 --> |
| | | {{ formData.doctorAdviceNo || '-' }} |
| | | </el-form-item> |
| | | </el-col><el-col :span="6"> |
| | | <el-form-item label="登记时间"> |
| | | {{ fmtDate(formData.registerDate) }} |
| | | </el-form-item> |
| | | </el-col> |
| | | |
| | | </el-row> |
| | | <!-- 需求1:补齐本面板原先缺失的两项(排队序号、状态),使 18 项齐全 --> |
| | | <el-row> |
| | | <el-col :span="6"> |
| | | <el-form-item label="排队序号"> |
| | | {{ formData?.seqNum ?? formData?.bookSeqNum ?? '-' }} |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="状态"> |
| | | <dict-tag v-if="formData?.status !== undefined && formData?.status !== null" |
| | | :type="DICT_TYPE.ECG_QUEUE_STATUS" :value="formData.status" /> |
| | | <span v-else>-</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <!-- ===== 患者信息(需求1:18 项统一展示,**一行四列**)===== |
| | | 原先本面板把 18 项手写在多行 el-col(span=6/18 混排), |
| | | 现统一改用 PatientInfoBlock 组件,与预约签到/设备领用/装机/拆机各页**口径一致**。 --> |
| | | <PatientInfoBlock :data="formData" check-type-field="checkType" :span="6" /> |
| | | <el-row> |
| | | <el-col :span="6"> |
| | | <el-form-item label="检查时间" prop="rentTime"> |
| | | <el-date-picker v-model="formData.rentTime" type="date" value-format="x" placeholder="选择时间" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="检查项目" prop=""> |
| | | {{ formData?.checkType && getCheckTypeName(formData?.checkType) }} |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="多耐药" prop="tolerance"> |
| | | <!-- 需求6:统一走字典 ecg_tolerance(0=无 1=多耐 2=多重耐药); |
| | | NULL 按"无"显示(Q12)。原先硬编码"否/是/未知"三分支,与字典重复且文案不符。 --> |
| | | <dict-tag :type="DICT_TYPE.ECG_TOLERANCE" :value="formData.tolerance ?? 0" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | </el-col> |
| | | <el-col :span="18"> |
| | | <el-form-item label="备注" prop="remark"> |
| | | <el-input v-model="formData.remark" placeholder="请输入备注" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <el-button @click="submitForm" type="primary" :disabled="formLoading">检查完成</el-button> |
| | | <el-button @click="cancelInstall" :disabled="formLoading">放弃检查</el-button> |
| | | <el-button @click="resetForm()">重 置</el-button> |
| | | <el-button @click="yddownload()" :loading="yddownloadLoading" :disabled="yddownloadLoading">平板运动心电图知情同意书</el-button> |
| | | <el-button @click="sgdownload()" :loading="sgdownloadLoading" :disabled="sgdownloadLoading">食管心脏电生理诊疗知情同意书</el-button> |
| | | <el-button v-if="needAutoFeeConfirm(formData.checkType)" @click="feeConfirm" :disabled="formLoading">确费</el-button> |
| | | <el-button v-if="needAutoFeeConfirm(formData.checkType)" @click="feeCancel" :disabled="formLoading">取消确费</el-button> |
| | | <div class="patient-actions"> |
| | | <el-button size="small" @click="submitForm" type="primary" :disabled="formLoading">检查完成</el-button> |
| | | <el-button size="small" @click="cancelInstall" :disabled="formLoading">放弃检查</el-button> |
| | | <el-button size="small" @click="resetForm()">重 置</el-button> |
| | | <el-button size="small" @click="yddownload()" :loading="yddownloadLoading" :disabled="yddownloadLoading">平板运动心电图知情同意书</el-button> |
| | | <el-button size="small" @click="sgdownload()" :loading="sgdownloadLoading" :disabled="sgdownloadLoading">食管心脏电生理诊疗知情同意书</el-button> |
| | | <el-button size="small" v-if="needAutoFeeConfirm(formData.checkType)" @click="feeConfirm" :disabled="formLoading">确费</el-button> |
| | | <el-button size="small" v-if="needAutoFeeConfirm(formData.checkType)" @click="feeCancel" :disabled="formLoading">取消确费</el-button> |
| | | </div> |
| | | |
| | | <!-- 医生装机列表 --> |
| | | <ContentWrap> |
| | | <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @row-click="onRowClick"> |
| | | <!-- <el-table-column label="id" align="center" prop="id" />--> |
| | | <!-- |
| | | 已完成记录列表。 |
| | | 滚动方式:**中间内容区自身滚动**(.doctor-page__content 为 overflow-y:auto), |
| | | 患者信息用 sticky 固定在顶部;底部 footer 由 DoctorPageLayout 固定,无需滚动即可见。 |
| | | (曾尝试让 el-table 内部滚动以固定表头,但本项目 Element Plus 2.7.0 下 |
| | | height/maxHeight 均未生效,已回退,详见《部署清单》5.3.15。) |
| | | --> |
| | | <div class="records-host"> |
| | | <ContentWrap class="records-wrap"> |
| | | <el-table |
| | | v-loading="loading" |
| | | :data="list" |
| | | :stripe="true" |
| | | :show-overflow-tooltip="true" |
| | | @row-click="onRowClick" |
| | | > |
| | | <!-- |
| | | 需求11(修正):把「撤销」放在**已完成检查记录列表的第一列**里。 |
| | | 原先放在表格上方的独立按钮行,位置不对 —— 现改为该列的**表头插槽**, |
| | | 使其成为表格的第一列(且只出现一次,作用于最近一位已完成患者, |
| | | 语义与后端 lastFinishedPatient 一致)。 |
| | | --> |
| | | <el-table-column label="撤销" align="center" width="90px" fixed="left"> |
| | | <template #header> |
| | | <el-button |
| | | link |
| | | type="warning" |
| | | :disabled="undoDisabled" |
| | | :loading="undoLoading" |
| | | @click="$emit('event_undo_finish')" |
| | | > |
| | | 撤销 |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="检查状态" align="center" prop="lost"> |
| | | <template #default="scope"> |
| | | <span v-if="scope.row.jobType === 60">已检查</span> |
| | |
| | | </template> |
| | | </el-table-column> |
| | | --> |
| | | </el-table> |
| | | </el-table> |
| | | </ContentWrap> |
| | | <!-- 分页 --> |
| | | <Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" |
| | | @pagination="getList" /> |
| | | </ContentWrap> |
| | | <Pagination |
| | | :total="total" |
| | | v-model:page="queryParams.pageNo" |
| | | v-model:limit="queryParams.pageSize" |
| | | @pagination="getList" |
| | | /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | |
| | | import { Barcode } from '@/components/Barcode' |
| | | import { useCheckTypeStore } from '@/store/modules/checkType' |
| | | import { getCheckTypeName, needAutoFeeConfirm } from '../../../../utils/checkTypeFormatter' |
| | | import { useRoomStore } from '@/store/modules/room' |
| | | // ⚠️ 必须显式 import:`unplugin-vue-components` 的 globs 只覆盖 |
| | | // `src/components/**`(见 build/vite/index.ts 的 Components({ globs: [...] })), |
| | | // 因此 src/components/ 下的 Dialog、ContentWrap、Pagination 等可自动引入, |
| | | // 但 **src/views/** 下的本组件必须手写 import**。 |
| | | // 若漏写,Vue 会把它当成「未知标签」**静默不渲染**(只有控制台 warning), |
| | | // 而 vite build 与 ESLint 都不会报错 —— 实测因改造时误删本行 import, |
| | | // 导致「叫号检查」页面患者信息**整块消失**。 |
| | | import PatientInfoBlock from '@/views/ecg/doctor/components/PatientInfoBlock.vue' |
| | | |
| | | /** 设备装机 表单 */ |
| | | defineComponent({ name: 'RoutinePanel' }) |
| | |
| | | type: [String, Number], |
| | | required: false, |
| | | default: null |
| | | }, |
| | | /** |
| | | * 需求11:记录列表第一列的「撤销」按钮状态,由父页面控制 |
| | | * (撤销动作请求后端、需要 loading 与禁用态,逻辑保留在父页面)。 |
| | | */ |
| | | undoDisabled: { |
| | | type: Boolean, |
| | | required: false, |
| | | default: false |
| | | }, |
| | | undoLoading: { |
| | | type: Boolean, |
| | | required: false, |
| | | default: false |
| | | } |
| | | }) |
| | | |
| | | const emit = defineEmits(['event_routine']) // 定义 success 事件,用于操作成功后的回调 |
| | | const emit = defineEmits(['event_routine', 'event_undo_finish']) |
| | | |
| | | const checkTypeStore = useCheckTypeStore() |
| | | |
| | |
| | | const loading = ref(true) // 列表的加载中 |
| | | const list = ref<JobRecordVO[]>([]) // 列表的数据 |
| | | const total = ref(0) // 列表的总页数 |
| | | // 当前诊室信息(用于取诊室开展的检查项目做兜底,见 syncRoomCheckTypes) |
| | | const roomStore = useRoomStore() |
| | | |
| | | |
| | | const queryParams = reactive({ |
| | | pageNo: 1, |
| | | pageSize: 10, |
| | |
| | | jobTime: [curDayStart(), curDayEnd()], |
| | | summary: undefined, |
| | | remark: undefined, |
| | | createTime: [] |
| | | createTime: [], |
| | | // ======================================================================== |
| | | // 按**当前诊室开展的检查项目**隔离已完成记录。 |
| | | // |
| | | // 背景:`job_record` 表只有 doc_id + check_type,**没有 room_id**; |
| | | // 而 /ecg/job-record/doctor-page 是强制按登录医生查"自己的记录"。 |
| | | // 于是同一医生在多个诊室坐诊时(如 1号门诊 ct=[100,1100]、6号门诊 ct=[400] 平板运动心电), |
| | | // 叫号检查页会把**多个诊室的记录混在一起**显示 —— 正是用户反馈的现象。 |
| | | // |
| | | // 由各页面(Routine.vue / index.vue / DevInstall.vue)在 onMounted 时 |
| | | // 用当前诊室的 checkTypes 赋值,并在每次查询前同步,确保切诊室后立即生效。 |
| | | // ======================================================================== |
| | | checkTypes: undefined as number[] | undefined |
| | | }) |
| | | |
| | | /** |
| | | * 把「当前诊室开展的检查项目」同步到查询条件,实现已完成记录按诊室隔离。 |
| | | * 见 queryParams.checkTypes 的说明。 |
| | | */ |
| | | const syncRoomCheckTypes = () => { |
| | | // 优先用页面传入的诊室信息;为空时回退到 store(更可靠,见 TitlePanel 同处说明) |
| | | const fromProp = props.room?.checkTypes |
| | | const types = (Array.isArray(fromProp) && fromProp.length > 0) |
| | | ? fromProp |
| | | : roomStore.room?.checkTypes |
| | | queryParams.checkTypes = (Array.isArray(types) && types.length > 0) ? [...types] : undefined |
| | | } |
| | | /** 查询列表 */ |
| | | const getList = async () => { |
| | | // 查询前同步当前诊室开展的检查项目,保证只显示本诊室的记录 |
| | | syncRoomCheckTypes() |
| | | loading.value = true |
| | | try { |
| | | const data = await JobRecordApi.getJobRecordDoctorPage(queryParams) |
| | |
| | | if (cur !== null) getSelectedRent(cur.rentId) |
| | | } |
| | | |
| | | /** |
| | | * 点击「已完成检查记录」的某一行:只把**该条记录**的信息合入表单。 |
| | | * |
| | | * ⚠️ 原实现是 `formData.value = await DevRentApi.getDevRent(rentId)` —— **整个替换**。 |
| | | * 而实测 `GET /ecg/dev-rent/get` 的响应里,除 `patName` 外的患者字段**全是 null**: |
| | | * {"patName":"…","patGender":null,"patBirthday":null,"registerDate":null,"doctor":null, |
| | | * "patMobile":null,"patPhone":null,"patWardDesc":null,"patBedNo":null,"outpatientNo":null, |
| | | * "patIdentityId":null,"doctorAdviceNo":null,"tolerance":null} |
| | | * 原因:`dev_rent` 表**不存**这些字段,它们是由 `DevRentServiceImpl` 从 appointment |
| | | * 回填的;而 `/dev-rent/get` 这条路径**没有做回填**。 |
| | | * |
| | | * 后果:医生一点记录行,PatientInfoBlock 里除姓名外的信息**全部变成 '-'**, |
| | | * 表现为"患者信息缺失"。 |
| | | * |
| | | * 修复:**保留当前患者信息**,只用返回的记录字段覆盖(仅当返回值为非空时)。 |
| | | */ |
| | | const getSelectedRent = async (rentId) => { |
| | | formData.value = await DevRentApi.getDevRent(rentId) |
| | | const record = await DevRentApi.getDevRent(rentId) |
| | | if (!record) return |
| | | const merged = { ...formData.value } as Record<string, any> |
| | | Object.keys(record as Record<string, any>).forEach((k) => { |
| | | const v = (record as Record<string, any>)[k] |
| | | // 只覆盖"确有值"的字段,避免用 null 冲掉已回填好的患者信息 |
| | | if (v !== null && v !== undefined && v !== '') { |
| | | merged[k] = v |
| | | } |
| | | }) |
| | | // 记录里可能完全没有患者字段,此时保留原患者信息 |
| | | if (!merged.patName) merged.patName = formData.value.patName |
| | | formData.value = merged as typeof formData.value |
| | | } |
| | | |
| | | const cancelInstall = async () => { |
| | |
| | | getList() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | /* ========================================================================== |
| | | 需求:表单内容挤占了下方记录表格的空间 —— 压缩行间距。 |
| | | Element Plus 默认 `.el-form-item { margin-bottom: 18px }`; |
| | | 本页表单含 18 项患者信息(4 列 = 5 行)+ 9 项设备信息, |
| | | 仅间距就要上百 px,把 el-table 挤到看不见。 |
| | | 这里统一压到 4px。注意 :deep 是必需的(el-form-item 是本组件的子组件渲染)。 |
| | | ========================================================================== */ |
| | | .patient-compact-form :deep(.el-form-item) { |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | /* ========================================================================== |
| | | 患者信息固定在内容区顶部。 |
| | | 内容区自身不滚动(滚动条在记录表格上),因此**不需要 position:sticky** —— |
| | | 直接作为 flex 的固定项(flex:none)即可始终留在顶部。 |
| | | 背景不透明,保证与下方记录表格视觉分离。 |
| | | ========================================================================== */ |
| | | :deep(.doctor-page__content) > .patient-compact-form { |
| | | flex: none; |
| | | padding-bottom: 4px; |
| | | background-color: var(--el-bg-color); |
| | | border-bottom: 1px solid var(--el-border-color-lighter); |
| | | } |
| | | |
| | | |
| | | |
| | | /* ========================================================================== |
| | | 患者信息固定在内容区顶部。 |
| | | 内容区(.doctor-page__content)自身滚动,所以这里必须用 position:sticky |
| | | 才能让 18 项患者信息在记录列表滚动时保持可见。 |
| | | 背景不透明,避免滚动内容从表单项间隙透出。 |
| | | ========================================================================== */ |
| | | :deep(.doctor-page__content) > .patient-compact-form { |
| | | position: sticky; |
| | | top: 0; |
| | | z-index: 3; |
| | | padding-bottom: 4px; |
| | | background-color: var(--el-bg-color); |
| | | border-bottom: 1px solid var(--el-border-color-lighter); |
| | | } |
| | | |
| | | /* ========================================================================== |
| | | 患者操作功能块:位于「患者信息」与「已完成记录列表」之间,独立成块。 |
| | | 需求:各功能按钮(检查完成 / 放弃检查 / 重置 / 知情同意书 …)**排在一行**。 |
| | | 做法:flex + gap,并 **禁止换行**(flex-wrap:nowrap); |
| | | 按钮用 size="small" 以容纳全部按钮;即使宽度不足也不折行。 |
| | | 上下边框 + 底色,使它与患者信息、记录列表在视觉上分区。 |
| | | ========================================================================== */ |
| | | .patient-actions { |
| | | display: flex; |
| | | flex: none; |
| | | flex-wrap: nowrap; |
| | | gap: 8px; |
| | | align-items: center; |
| | | /* 上下留白 + 分隔线,形成独立功能块 */ |
| | | padding: 6px 4px; |
| | | margin-bottom: 6px; |
| | | background-color: var(--el-bg-color); |
| | | border-top: 1px solid var(--el-border-color-lighter); |
| | | border-bottom: 1px solid var(--el-border-color-lighter); |
| | | |
| | | /* Element Plus 给相邻 el-button 默认加了 margin-left:12px,会与 gap 叠加,清掉 */ |
| | | :deep(.el-button + .el-button) { |
| | | margin-left: 0; |
| | | } |
| | | } |
| | | </style> |