| | |
| | | footer 底部功能按钮(可选;固定在页面底部) |
| | | --> |
| | | <script setup lang="ts"> |
| | | import { computed, onBeforeUnmount, onMounted, ref } from 'vue' |
| | | import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue' |
| | | import BedSwitchSelect from '@/views/ecg/doctor/components/BedSwitchSelect.vue' |
| | | import { useRoomStore } from '@/store/modules/room' |
| | | |
| | |
| | | /* ==================== 主/围观(只读模式) ==================== */ |
| | | |
| | | const roomStore = useRoomStore() |
| | | |
| | | /* ==================== 显示密度(分辨率适配) ==================== */ |
| | | |
| | | /** |
| | | * 【为什么是"密度"而不是"整体缩放"】 |
| | | * 曾考虑用 `document.body.style.zoom = 0.9` 做整体缩放,**实测不可行**: |
| | | * ① CSS zoom **不会改变媒体查询**(zoom:1.25 时 innerWidth 仍是 1342、 |
| | | * `(min-width:1200px)` 仍命中)⇒ 栅格不会随之降级,"缩小"反而让字段更挤; |
| | | * ② CSS zoom 会让 Element Plus 的下拉/弹窗定位错位 —— 它们靠 |
| | | * getBoundingClientRect + translate 计算位置,rect 已含缩放倍率, |
| | | * 写回的 px 又在缩放坐标系里,等于被放大两次。 |
| | | * |
| | | * 所以改为**只压缩间距**(纯 CSS,零定位风险):表单行距、表格单元格内边距、 |
| | | * 底栏内边距。矮屏能多显示几行记录,视觉上等效于"缩放",但不破坏任何弹层。 |
| | | * |
| | | * 真·整体缩放请用**浏览器自带缩放**(Ctrl + / Ctrl - / Ctrl 0): |
| | | * 栅格已做响应式,浏览器缩放会改变 innerWidth,从而自动在 4/2/1 列之间切换。 |
| | | */ |
| | | const DENSITY_KEY = 'ecg.doctor.density' |
| | | type Density = 'normal' | 'compact' |
| | | const density = ref<Density>('normal') |
| | | const isCompact = computed(() => density.value === 'compact') |
| | | |
| | | const loadDensity = () => { |
| | | try { |
| | | const v = localStorage.getItem(DENSITY_KEY) |
| | | if (v === 'compact' || v === 'normal') density.value = v |
| | | } catch { |
| | | /* 隐私模式/禁用存储时忽略 */ |
| | | } |
| | | } |
| | | // 记住选择(用 watch 而不是 el-select 的 @change:避免 EP 回调参数类型与联合类型不匹配) |
| | | watch(density, (v) => { |
| | | try { |
| | | localStorage.setItem(DENSITY_KEY, v) |
| | | } catch { |
| | | /* 隐私模式/禁用存储时忽略 */ |
| | | } |
| | | }) |
| | | // 在 setup 阶段就读取,避免首帧从"标准"跳到"紧凑"的闪动 |
| | | loadDensity() |
| | | |
| | | /** |
| | | * 是否显示顶部提示条。 |
| | |
| | | } |
| | | ) |
| | | |
| | | /** 默认宽度(px):由 asideWidth 解析而来,用作初始值与"双击复位"值 */ |
| | | const defaultAsidePx = (() => { |
| | | /** |
| | | * 「默认」右栏宽度(px):**按窗口宽度自适应**。 |
| | | * |
| | | * 背景:原来固定 400px。医生电脑分辨率差异大(1920 / 1366 / 1024,且常带 125% 缩放), |
| | | * 窄屏上右栏吃掉 400px 后,左侧患者信息(4 列栅格,每列要 100px 标签)只剩几百像素, |
| | | * 字段被压成一条缝 —— 现场表现为"部分内容看不到"。 |
| | | * |
| | | * 规则:`min(配置值, 窗口宽 × 28%)`,且不低于 minAsideWidth。 |
| | | * 1920 → 400(与原来一致,宽屏无变化) |
| | | * 1600 → 400 |
| | | * 1366 → 382 |
| | | * 1280 → 358 |
| | | * 1024 → 287 |
| | | */ |
| | | const calcDefaultAsidePx = () => { |
| | | const n = parseInt(props.asideWidth, 10) |
| | | return Number.isFinite(n) && n > 0 ? n : 400 |
| | | })() |
| | | const base = Number.isFinite(n) && n > 0 ? n : 400 |
| | | const vw = typeof window !== 'undefined' ? window.innerWidth : 1920 |
| | | return Math.max(props.minAsideWidth, Math.min(base, Math.round(vw * 0.28))) |
| | | } |
| | | |
| | | /** |
| | | * 医生是否**手动**调过宽度(拖过分界线 / 用键盘调过 / 有记忆值)。 |
| | | * 手动调过 → 尊重他的选择,窗口变化时只做"夹紧"; |
| | | * 没调过 → 跟随窗口自适应(换到小屏自动收窄)。 |
| | | */ |
| | | const asideUserSet = ref(false) |
| | | |
| | | /** |
| | | * 【关键】不依赖 CSS `height: 100%` 的解析,改为**实测父容器可用高度**。 |
| | |
| | | /** 主体行容器:拖动时用它算"右侧栏最多能有多宽" */ |
| | | const bodyRef = ref<HTMLElement>() |
| | | |
| | | /** 右侧栏当前宽度(px) */ |
| | | const asidePx = ref<number>(defaultAsidePx) |
| | | /** 右侧栏当前宽度(px):初始值也走自适应,窄屏不会一上来就占掉 400px */ |
| | | const asidePx = ref<number>(calcDefaultAsidePx()) |
| | | |
| | | /** 是否正在拖动(用于换光标、禁选中) */ |
| | | const dragging = ref(false) |
| | |
| | | |
| | | /** 记住拖动结果(隐私模式/禁用存储时静默忽略) */ |
| | | const saveAsidePx = () => { |
| | | asideUserSet.value = true |
| | | if (!props.storageKey) return |
| | | try { |
| | | localStorage.setItem(props.storageKey, String(asidePx.value)) |
| | |
| | | if (!props.storageKey) return |
| | | try { |
| | | const v = parseInt(localStorage.getItem(props.storageKey) || '', 10) |
| | | if (Number.isFinite(v) && v > 0) asidePx.value = clampAsidePx(v) |
| | | if (Number.isFinite(v) && v > 0) { |
| | | asidePx.value = clampAsidePx(v) |
| | | asideUserSet.value = true // 有记忆值 = 医生手动调过,后续不再跟随窗口自适应 |
| | | } |
| | | } catch { |
| | | /* ignore */ |
| | | } |
| | | } |
| | | |
| | | /** 双击分界线:恢复默认宽度并清除记忆 */ |
| | | /** 双击分界线:恢复默认宽度(重新按当前窗口自适应)并清除记忆 */ |
| | | const resetAsideWidth = () => { |
| | | asidePx.value = clampAsidePx(defaultAsidePx) |
| | | asideUserSet.value = false |
| | | asidePx.value = clampAsidePx(calcDefaultAsidePx()) |
| | | if (props.storageKey) { |
| | | try { |
| | | localStorage.removeItem(props.storageKey) |
| | |
| | | |
| | | // 宽度变化(含窗口缩放)时重新夹一次右栏宽度, |
| | | // 否则拖宽后把窗口缩小,右侧栏可能把左侧内容挤没。 |
| | | asidePx.value = clampAsidePx(asidePx.value) |
| | | // |
| | | // 【分辨率自适应】没手动调过的(首次使用 / 双击复位过)→ 跟随窗口重新算默认宽度, |
| | | // 这样同一位医生换到小屏笔记本上自动收窄;手动调过的只做夹紧,尊重他的选择。 |
| | | asidePx.value = asideUserSet.value |
| | | ? clampAsidePx(asidePx.value) |
| | | : clampAsidePx(calcDefaultAsidePx()) |
| | | } |
| | | |
| | | /** 用 rAF 合并连续触发,避免 ResizeObserver 循环告警 */ |
| | |
| | | <div |
| | | ref="rootRef" |
| | | class="doctor-page" |
| | | :class="{ 'is-resizing': dragging }" |
| | | :class="{ 'is-resizing': dragging, 'is-compact': isCompact }" |
| | | :style="{ height: measuredHeight }" |
| | | > |
| | | <!-- |
| | |
| | | <div class="doctor-page__header-left"> |
| | | <slot name="header"></slot> |
| | | </div> |
| | | <BedSwitchSelect :enabled="bedSwitchEnabled" :op-types="bedSwitchOpTypes" /> |
| | | <div class="doctor-page__header-tools"> |
| | | <!-- |
| | | 【分辨率适配】显示密度:紧凑模式压缩行高/内边距,矮屏(768 高) |
| | | 能多显示几行记录。选择记在 localStorage,换页保持。 |
| | | --> |
| | | <el-select |
| | | v-model="density" |
| | | size="small" |
| | | class="doctor-page__density" |
| | | title="显示密度:紧凑模式适合小屏幕/已放大缩放的电脑" |
| | | > |
| | | <el-option label="标准显示" value="normal" /> |
| | | <el-option label="紧凑显示" value="compact" /> |
| | | </el-select> |
| | | <BedSwitchSelect :enabled="bedSwitchEnabled" :op-types="bedSwitchOpTypes" /> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 围观模式(只读)/ 未选工位 提示条:让医生一眼知道"现在只能看,不能操作" --> |
| | |
| | | flex: 1; |
| | | min-width: 0; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | /* 右上角工具区:显示密度 + 工位切换(都固定靠右,不参与压缩) */ |
| | | .doctor-page__header-tools { |
| | | display: flex; |
| | | flex: none; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .doctor-page__density { |
| | | width: 104px; |
| | | } |
| | | |
| | | /* ========================================================================== |
| | |
| | | margin-left: 0; |
| | | } |
| | | } |
| | | |
| | | /* ========================================================================== |
| | | 【分辨率适配】紧凑显示模式 |
| | | -------------------------------------------------------------------------- |
| | | 只压缩「间距」,不改字号 —— 默认(标准)**完全不受影响**,所有规则都挂在 |
| | | `.is-compact` 下,切回标准即恢复原样。 |
| | | |
| | | 针对的现场问题:768 高的笔记本(再叠加 125% 缩放)上,患者信息 + 已完成记录 |
| | | + 底部按钮加起来超过可用高度,记录列表只剩一两行。压缩后能把高度还给内容。 |
| | | ========================================================================== */ |
| | | .doctor-page.is-compact { |
| | | /* 患者信息 18 项用的是 EP 默认 18px 行距,是最大的高度浪费点 */ |
| | | :deep(.el-form-item) { |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | /* 记录表格:单元格上下内边距 8px → 3px,行高收到 18px */ |
| | | :deep(.el-table .cell) { |
| | | padding: 0 8px; |
| | | line-height: 18px; |
| | | } |
| | | |
| | | :deep(.el-table td.el-table__cell), |
| | | :deep(.el-table th.el-table__cell) { |
| | | padding: 3px 0; |
| | | } |
| | | |
| | | .doctor-page__header { |
| | | height: 36px; |
| | | } |
| | | |
| | | .doctor-page__readonly-tip { |
| | | padding: 2px 12px; |
| | | line-height: 18px; |
| | | } |
| | | |
| | | .doctor-page__footer { |
| | | padding: 2px 0 2px 8px; |
| | | } |
| | | } |
| | | </style> |