yxh
yxh
6 天以前 09dc74e264b6262cbe41c9c8476e24705871d4a6
src/components/DoctorPageLayout/src/DoctorPageLayout.vue
@@ -38,7 +38,7 @@
    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'
@@ -47,6 +47,47 @@
/* ==================== 主/围观(只读模式) ==================== */
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()
/**
 * 是否显示顶部提示条。
@@ -94,11 +135,33 @@
  }
)
/** 默认宽度(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%` 的解析,改为**实测父容器可用高度**。
@@ -125,8 +188,8 @@
/** 主体行容器:拖动时用它算"右侧栏最多能有多宽" */
const bodyRef = ref<HTMLElement>()
/** 右侧栏当前宽度(px) */
const asidePx = ref<number>(defaultAsidePx)
/** 右侧栏当前宽度(px):初始值也走自适应,窄屏不会一上来就占掉 400px */
const asidePx = ref<number>(calcDefaultAsidePx())
/** 是否正在拖动(用于换光标、禁选中) */
const dragging = ref(false)
@@ -149,6 +212,7 @@
/** 记住拖动结果(隐私模式/禁用存储时静默忽略) */
const saveAsidePx = () => {
  asideUserSet.value = true
  if (!props.storageKey) return
  try {
    localStorage.setItem(props.storageKey, String(asidePx.value))
@@ -162,15 +226,19 @@
  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)
@@ -244,7 +312,12 @@
  // 宽度变化(含窗口缩放)时重新夹一次右栏宽度,
  // 否则拖宽后把窗口缩小,右侧栏可能把左侧内容挤没。
  asidePx.value = clampAsidePx(asidePx.value)
  //
  // 【分辨率自适应】没手动调过的(首次使用 / 双击复位过)→ 跟随窗口重新算默认宽度,
  // 这样同一位医生换到小屏笔记本上自动收窄;手动调过的只做夹紧,尊重他的选择。
  asidePx.value = asideUserSet.value
    ? clampAsidePx(asidePx.value)
    : clampAsidePx(calcDefaultAsidePx())
}
/** 用 rAF 合并连续触发,避免 ResizeObserver 循环告警 */
@@ -280,7 +353,7 @@
  <div
    ref="rootRef"
    class="doctor-page"
    :class="{ 'is-resizing': dragging }"
    :class="{ 'is-resizing': dragging, 'is-compact': isCompact }"
    :style="{ height: measuredHeight }"
  >
    <!--
@@ -292,7 +365,22 @@
      <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>
    <!-- 围观模式(只读)/ 未选工位 提示条:让医生一眼知道"现在只能看,不能操作" -->
@@ -395,6 +483,18 @@
  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;
}
/* ==========================================================================
@@ -577,4 +677,44 @@
    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>