<!--
|
医生工作台通用布局(叫号检查 / 设备领用 / 装机 三页共用)
|
|
【解决什么问题】
|
原先三个页面的功能按钮放在 `.right-bottom { position: absolute; bottom: 50px }`,
|
**脱离文档流**,因此"已完成患者列表"变长后会直接铺到按钮底下、把
|
「叫号 / 过号 / 重叫 / 暂停 / 撤销叫号」挡住;
|
且外层容器没有确定高度,中间区无法产生滚动,内容只能一路把页面撑长。
|
|
【布局契约】——纵向三段 + 右侧栏(中间有**可拖动分界线**)
|
┌────────────────────────────────────────────┐
|
│ #header 标题栏(固定高度,不滚动) │ flex: none
|
├──────────────────────────────┬─────────────┤
|
│ #default 主体(滚动区) │ #aside │ 仅中间区与右栏各自滚动
|
│ · 患者信息(由各面板内部 │ (独立滚动)│ 两者之间的竖线**可左右拖动**
|
│ 设为 sticky,钉在顶部) │ │ 以调整右栏宽度
|
│ · 已完成记录列表(滚动) │ │
|
├──────────────────────────────┴─────────────┤
|
│ #footer 功能按钮(固定底部,永不被遮挡) │ flex: none
|
└────────────────────────────────────────────┘
|
|
【关键实现点】
|
1. 根元素 `height:100%; display:flex; flex-direction:column; overflow:hidden`
|
—— 让"整页"有确定高度,中间区才可能滚动(而不是把页面撑长)。
|
2. 中间区必须有 `min-height: 0`。flex 子项默认 `min-height: auto`
|
会**拒绝收缩到内容高度以下**,从而 `overflow-y:auto` 永不生效 ——
|
这是本类布局最常见的坑,别删。
|
3. `#footer` 在**正常文档流**中(不再用 position:absolute),
|
带不透明背景与上边框,内容永远不会透过来或盖住它。
|
4. 根元素**自带不透明背景**:各面板内 sticky 的患者信息若不透明,
|
滚动内容会从它与边缘的缝隙透出,故整页统一底色。
|
5. 【本次新增】分界线可拖动(见下),宽度记在 localStorage 里,切页/刷新后保持。
|
|
【插槽】
|
header 标题栏(可选)
|
default 主体内容(滚动区)
|
aside 右侧栏(可选;独立滚动,如患者队列)
|
footer 底部功能按钮(可选;固定在页面底部)
|
-->
|
<script setup lang="ts">
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
import BedSwitchSelect from '@/views/ecg/doctor/components/BedSwitchSelect.vue'
|
import { useRoomStore } from '@/store/modules/room'
|
|
defineOptions({ name: 'DoctorPageLayout' })
|
|
/* ==================== 主/围观(只读模式) ==================== */
|
|
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()
|
|
/**
|
* 是否显示顶部提示条。
|
*
|
* 判定:**只要当前不可操作**就提示 —— 含两种情况:
|
* ① 还没选定要看哪个工位(未入座也没围观);
|
* ② 正在围观别人的工位(我入座的 ≠ 我正在看的)。
|
*/
|
const showReadonlyTip = computed(() => !roomStore.getCanOperate)
|
const readonlyTipText = computed(() =>
|
roomStore.getViewText ? `「${roomStore.getViewText}」` : '(尚未选择工位)'
|
)
|
const readonlyTipPrefix = computed(() =>
|
roomStore.getViewText ? '围观模式(只读):正在查看' : '尚未选择工位:请用右上角「工位」下拉选择要查看 / 要入座的工位'
|
)
|
|
const props = withDefaults(
|
defineProps<{
|
/** 右侧栏**初始/默认**宽度(拖动之后以实际拖动值为准;双击分界线恢复该值) */
|
asideWidth?: string
|
/** 右侧栏最小宽度(px) */
|
minAsideWidth?: number
|
/** 左侧内容区最小宽度(px):左侧是四列患者信息表单,太窄会折行 */
|
minContentWidth?: number
|
/** 记忆拖动结果的 localStorage key;传空字符串表示不记忆 */
|
storageKey?: string
|
/**
|
* 【本次需求】是否允许在标题栏切换工位。
|
* 设备领用 / 装机页传 `false`:工位固定,不提供切换(未入座者即围观者,只读)。
|
*/
|
bedSwitchEnabled?: boolean
|
/**
|
* 【本次需求】可切换工位的**作业类型**过滤(0检查 1领用 2装机)。
|
* 叫号检查页传 `[0]` → 下拉只列检查类工位;不传 = 不过滤。
|
*/
|
bedSwitchOpTypes?: number[] | null
|
}>(),
|
{
|
asideWidth: '400px',
|
minAsideWidth: 280,
|
minContentWidth: 520,
|
storageKey: 'ecg.doctor.asideWidth',
|
bedSwitchEnabled: true,
|
bedSwitchOpTypes: null
|
}
|
)
|
|
/**
|
* 「默认」右栏宽度(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)
|
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%` 的解析,改为**实测父容器可用高度**。
|
*
|
* 背景(真实故障):原先靠 `.doctor-page { height: 100% }` 撑起高度。
|
* 该写法要求**父级有确定高度**,而本组件挂在 `AppView` 的 `<section>` 下,
|
* 那条链上任何一环(`el-scrollbar__view` 的 99%、`AppView` 的 `min-height` 等)
|
* 一旦不成立,`height:100%` 就会退化成 `auto` ——
|
* 于是 `.doctor-page` **随内容长高**,flex 的 `flex:none` footer 被推到视口下方,
|
* 用户必须滚动页面才能看到 footer。这正是用户反馈的现象。
|
*
|
* 解决:用 `ResizeObserver` 量出**父元素的实际内容高度**,以内联 style 直接给根元素
|
* 设定像素高度。这样无论外层是 flex、block 还是百分比链,
|
* 我们的三段式布局都拿到一个**确定的**高度,footer 必然贴在容器底部、无需滚动。
|
*
|
* 若父级高度不可测(罕见),`height` 保持 undefined,
|
* 此时仍回退到 CSS 的 `height: 100%`,不会比原来更差。
|
*/
|
const rootRef = ref<HTMLElement>()
|
const measuredHeight = ref<string | undefined>(undefined)
|
|
// ==================== 右侧栏宽度(可拖动分界线) ====================
|
|
/** 主体行容器:拖动时用它算"右侧栏最多能有多宽" */
|
const bodyRef = ref<HTMLElement>()
|
|
/** 右侧栏当前宽度(px):初始值也走自适应,窄屏不会一上来就占掉 400px */
|
const asidePx = ref<number>(calcDefaultAsidePx())
|
|
/** 是否正在拖动(用于换光标、禁选中) */
|
const dragging = ref(false)
|
|
/**
|
* 把宽度夹到合法区间。
|
*
|
* 上限 = 容器宽度 − 左侧最小宽度(保证左侧患者信息/记录列表不被挤没);
|
* 下限 = minAsideWidth。窗口极窄导致两者冲突时,**优先保证下限**,
|
* 左侧靠自身的 `min-width:0 + overflow` 优雅降级。
|
*/
|
const clampAsidePx = (px: number) => {
|
const bodyPx = bodyRef.value?.clientWidth ?? 0
|
const lower = props.minAsideWidth
|
const upper = bodyPx > 0
|
? Math.max(lower, bodyPx - props.minContentWidth)
|
: Number.MAX_SAFE_INTEGER
|
return Math.round(Math.min(Math.max(px, lower), upper))
|
}
|
|
/** 记住拖动结果(隐私模式/禁用存储时静默忽略) */
|
const saveAsidePx = () => {
|
asideUserSet.value = true
|
if (!props.storageKey) return
|
try {
|
localStorage.setItem(props.storageKey, String(asidePx.value))
|
} catch {
|
/* ignore */
|
}
|
}
|
|
/** 读取上次拖动结果;必须等 bodyRef 挂载后才能 clamp */
|
const loadAsidePx = () => {
|
if (!props.storageKey) return
|
try {
|
const v = parseInt(localStorage.getItem(props.storageKey) || '', 10)
|
if (Number.isFinite(v) && v > 0) {
|
asidePx.value = clampAsidePx(v)
|
asideUserSet.value = true // 有记忆值 = 医生手动调过,后续不再跟随窗口自适应
|
}
|
} catch {
|
/* ignore */
|
}
|
}
|
|
/** 双击分界线:恢复默认宽度(重新按当前窗口自适应)并清除记忆 */
|
const resetAsideWidth = () => {
|
asideUserSet.value = false
|
asidePx.value = clampAsidePx(calcDefaultAsidePx())
|
if (props.storageKey) {
|
try {
|
localStorage.removeItem(props.storageKey)
|
} catch {
|
/* ignore */
|
}
|
}
|
}
|
|
let startX = 0
|
let startPx = 0
|
|
/**
|
* 用 Pointer Events + setPointerCapture:
|
* 捕获后 pointermove/up 都只在分界线上触发,**不需要**再往 document 上挂/摘监听,
|
* 也就不会出现"拖到 iframe 或窗口外松手后监听残留"的老问题。
|
*/
|
const onSplitterDown = (e: PointerEvent) => {
|
const el = e.currentTarget as HTMLElement | null
|
dragging.value = true
|
startX = e.clientX
|
startPx = asidePx.value
|
el?.setPointerCapture?.(e.pointerId)
|
e.preventDefault()
|
}
|
|
const onSplitterMove = (e: PointerEvent) => {
|
if (!dragging.value) return
|
// 分界线向右拖(Δx>0)→ 右侧栏变窄,故用减法
|
asidePx.value = clampAsidePx(startPx - (e.clientX - startX))
|
}
|
|
const onSplitterUp = (e: PointerEvent) => {
|
if (!dragging.value) return
|
dragging.value = false
|
const el = e.currentTarget as HTMLElement | null
|
el?.releasePointerCapture?.(e.pointerId)
|
saveAsidePx()
|
}
|
|
/** 键盘微调(←/→ 每次 20px),无鼠标也能调整 */
|
const onSplitterKey = (e: KeyboardEvent) => {
|
const step = 20
|
if (e.key === 'ArrowLeft') {
|
asidePx.value = clampAsidePx(asidePx.value + step) // 分界线左移 → 右侧变宽
|
} else if (e.key === 'ArrowRight') {
|
asidePx.value = clampAsidePx(asidePx.value - step)
|
} else {
|
return
|
}
|
saveAsidePx()
|
e.preventDefault()
|
}
|
|
let ro: ResizeObserver | undefined
|
let rafId = 0
|
|
const measure = () => {
|
const el = rootRef.value
|
const parent = el?.parentElement
|
if (!el || !parent) return
|
|
const cs = getComputedStyle(parent)
|
const inner =
|
parent.clientHeight -
|
parseFloat(cs.paddingTop || '0') -
|
parseFloat(cs.paddingBottom || '0')
|
|
// 父级高度不可测(auto/0)时保留 CSS 回退,不设内联高度
|
measuredHeight.value = inner > 0 ? `${inner}px` : undefined
|
|
// 宽度变化(含窗口缩放)时重新夹一次右栏宽度,
|
// 否则拖宽后把窗口缩小,右侧栏可能把左侧内容挤没。
|
//
|
// 【分辨率自适应】没手动调过的(首次使用 / 双击复位过)→ 跟随窗口重新算默认宽度,
|
// 这样同一位医生换到小屏笔记本上自动收窄;手动调过的只做夹紧,尊重他的选择。
|
asidePx.value = asideUserSet.value
|
? clampAsidePx(asidePx.value)
|
: clampAsidePx(calcDefaultAsidePx())
|
}
|
|
/** 用 rAF 合并连续触发,避免 ResizeObserver 循环告警 */
|
const scheduleMeasure = () => {
|
if (rafId) cancelAnimationFrame(rafId)
|
rafId = requestAnimationFrame(() => {
|
rafId = 0
|
measure()
|
})
|
}
|
|
onMounted(() => {
|
measure()
|
loadAsidePx()
|
const parent = rootRef.value?.parentElement
|
if (parent && typeof ResizeObserver !== 'undefined') {
|
ro = new ResizeObserver(scheduleMeasure)
|
ro.observe(parent)
|
} else {
|
window.addEventListener('resize', scheduleMeasure)
|
}
|
})
|
|
onBeforeUnmount(() => {
|
if (rafId) cancelAnimationFrame(rafId)
|
ro?.disconnect()
|
ro = undefined
|
window.removeEventListener('resize', scheduleMeasure)
|
})
|
</script>
|
|
<template>
|
<div
|
ref="rootRef"
|
class="doctor-page"
|
:class="{ 'is-resizing': dragging, 'is-compact': isCompact }"
|
:style="{ height: measuredHeight }"
|
>
|
<!--
|
标题栏:固定高度,不滚动。
|
右侧固定放「工位切换 / 围观」下拉(主/围观模型的入口)——放在布局层,
|
叫号检查 / 设备领用 / 装机 三页自动都有,不用各页重复加。
|
-->
|
<div v-if="$slots.header" class="doctor-page__header">
|
<div class="doctor-page__header-left">
|
<slot name="header"></slot>
|
</div>
|
<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>
|
|
<!-- 围观模式(只读)/ 未选工位 提示条:让医生一眼知道"现在只能看,不能操作" -->
|
<div v-if="showReadonlyTip" class="doctor-page__readonly-tip">
|
<Icon icon="ep:view" class="mr-4px" />
|
{{ readonlyTipPrefix }}{{ roomStore.getViewText ? readonlyTipText : '' }}<template v-if="roomStore.getViewText && roomStore.viewOwnerName">,主人 {{ roomStore.viewOwnerName }}</template><template v-if="roomStore.getViewText">;如需操作,请在右上角「工位」里入座到该工位。</template>
|
</div>
|
|
<!-- 主体:占据剩余全部高度;内部只让"内容区"和"右栏"各自滚动 -->
|
<div ref="bodyRef" class="doctor-page__body" :class="{ 'is-readonly': !roomStore.getCanOperate }">
|
<div class="doctor-page__content">
|
<slot></slot>
|
</div>
|
|
<!--
|
可拖动分界线(本次需求):
|
· 左右拖动 = 调整右侧患者清单的宽度;
|
· 双击 = 恢复默认宽度(并清除记忆);
|
· 聚焦后 ←/→ = 每次 20px 微调;
|
· 宽度记忆在 localStorage,三个工作台页面共用同一个 key。
|
-->
|
<div
|
v-if="$slots.aside"
|
class="doctor-page__splitter"
|
:class="{ 'is-dragging': dragging }"
|
role="separator"
|
aria-orientation="vertical"
|
:aria-valuenow="asidePx"
|
:aria-valuemin="minAsideWidth"
|
tabindex="0"
|
title="拖动调整右侧宽度(双击复位)"
|
@pointerdown="onSplitterDown"
|
@pointermove="onSplitterMove"
|
@pointerup="onSplitterUp"
|
@pointercancel="onSplitterUp"
|
@dblclick="resetAsideWidth"
|
@keydown="onSplitterKey"
|
></div>
|
|
<div
|
v-if="$slots.aside"
|
class="doctor-page__aside"
|
:style="{ width: `${asidePx}px`, flex: '0 0 auto' }"
|
>
|
<slot name="aside"></slot>
|
</div>
|
</div>
|
|
<!--
|
功能按钮:正常文档流中的固定底部栏,永不被内容遮挡。
|
需求:`自动`、`停止候诊` 与 `叫号/过号/重叫/暂停/撤销叫号` **合成一组**,
|
一起**靠右**(对着右侧候诊表),不要被拆散到左边/中间。
|
-->
|
<div v-if="$slots.footer" class="doctor-page__footer" :class="{ 'is-readonly': !roomStore.getCanOperate }">
|
<div class="doctor-page__footer-right">
|
<slot name="footer"></slot>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<style scoped lang="scss">
|
.doctor-page {
|
display: flex;
|
flex-direction: column;
|
/*
|
兜底高度:仅在 `measuredHeight` 未测到时生效(见 script 中的说明)。
|
真正的确定性高度来自 ResizeObserver 实测后写入的行内 style ——
|
因为 `height: 100%` 依赖父级有确定高度,这条链在外层布局变化时会失效,
|
导致根元素随内容长高、把 footer 推到视口下方(用户反馈的现象)。
|
*/
|
height: 100%;
|
overflow: hidden;
|
/* 不透明底色:面板内 sticky 的患者信息与边缘缝隙不会透出滚动内容 */
|
background-color: var(--el-bg-color);
|
}
|
|
/* 拖动分界线过程中:全局换成左右箭头光标,并禁止选中文字(否则会拖出一片蓝色选区) */
|
.doctor-page.is-resizing {
|
cursor: col-resize;
|
user-select: none;
|
}
|
|
/* ---------- 标题栏 ---------- */
|
.doctor-page__header {
|
flex: none;
|
height: 40px;
|
padding: 0 12px;
|
overflow: hidden;
|
background-color: var(--el-color-primary-light-7);
|
/* 左侧标题(各页传入的 TitlePanel)+ 右侧「工位切换 / 围观」下拉 */
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
gap: 8px;
|
}
|
|
/* 标题区可压缩(工位下拉固定靠右) */
|
.doctor-page__header-left {
|
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;
|
}
|
|
/* ==========================================================================
|
【本次需求】工作台按钮统一尺寸 / 字号(方便鼠标击中)
|
|
原来混用 `size="small"`(24px / 12px 字)与默认(32px / 14px 字),
|
同一页里大小不一。这里做统一归一化,**不逐个改 size 属性**:
|
归一化后 `--small` 已无视觉效果,后续新增按钮也会自动一致。
|
========================================================================== */
|
.doctor-page :deep(.el-button),
|
.doctor-page :deep(.el-button--small),
|
.doctor-page :deep(.el-button--default) {
|
height: 32px;
|
padding: 0 14px;
|
font-size: 14px;
|
}
|
|
/* 表格 / 队列列表内的行内按钮:小一档,避免把表格行撑高 */
|
.doctor-page :deep(.el-table .el-button:not(.is-link)),
|
.doctor-page :deep(.queue-list .el-button:not(.is-link)) {
|
height: 28px;
|
padding: 0 10px;
|
font-size: 13px;
|
}
|
|
/* 链接式按钮(表头「撤销」、候选区「立即补人」等):只放大字号,不加高度 */
|
.doctor-page :deep(.el-button.is-link) {
|
height: auto;
|
padding: 0 4px;
|
font-size: 14px;
|
}
|
|
/* ---------- 围观模式(只读)提示条 ---------- */
|
.doctor-page__readonly-tip {
|
flex: none;
|
padding: 4px 12px;
|
font-size: 13px;
|
line-height: 20px;
|
color: var(--el-color-warning);
|
background-color: var(--el-color-warning-light-9);
|
border-bottom: 1px solid var(--el-color-warning-light-7);
|
}
|
|
/* ---------- 主体 ---------- */
|
.doctor-page__body {
|
display: flex;
|
flex: 1;
|
/* ⚠️ 关键:不加 min-height:0,flex 子项不会收缩,overflow 不生效 */
|
min-height: 0;
|
}
|
|
/*
|
围观模式(只读):内容区里的**按钮 / 开关 / 勾选框**一律不可点。
|
覆盖各面板里的「检查完成 / 放弃检查 / 确费 / 领用完成 / 装机完成 / 数据录入 / 撤销 …」。
|
说明:
|
· 弹窗(患者信息、打印预览等)是 teleport 出去的,不受这里影响,仍可正常查看;
|
· 服务端还有一道硬校验(非工位主人调用写接口会被拒绝),这里只是体验层。
|
*/
|
.doctor-page__body.is-readonly :deep(.el-button),
|
.doctor-page__body.is-readonly :deep(.el-switch),
|
.doctor-page__body.is-readonly :deep(.el-checkbox) {
|
pointer-events: none;
|
opacity: 0.5;
|
}
|
|
/*
|
内容区:**自身不滚动**(需求:滚动条要长在「已完成记录列表」上,而不是中间区整体)。
|
因此这里 flex 纵向排列 + overflow-y:auto:
|
· 患者信息(面板里的 el-form)flex:none,固定在上方;
|
· 记录列表(.records-host)flex:1;记录列表**自己滚动**
|
(各面板内 `.records-wrap :deep(.el-card__body) { max-height: calc(100% - 545px); overflow-y:auto }`
|
配合 `.el-table__header-wrapper { position: sticky }` 固定表头)。
|
|
这里的 `overflow-y: auto` 只在**空间实在不足**(患者信息 + 按钮把高度吃光)时生效,
|
作为兜底;正常情况不会出现滚动条。
|
注意必须保留 min-height:0,否则 flex 子项不会收缩,记录列表拿不到受限高度。
|
|
⚠️ 右边框已去掉:分界线改由 `.doctor-page__splitter` 提供,
|
否则「3px 右边框 + 分界线」会并排成两条线。
|
*/
|
.doctor-page__content {
|
display: flex;
|
flex: 1;
|
flex-direction: column;
|
min-width: 0;
|
min-height: 0;
|
padding: 6px 10px;
|
overflow-y: auto;
|
overflow-x: hidden;
|
border: solid var(--el-color-primary-light-7);
|
border-right: none;
|
}
|
|
/* ---------- 可拖动分界线 ---------- */
|
/*
|
命中区 6px(好点中),视觉线 1px 居中;hover / 拖动中加粗变主色。
|
注意:宽度由它自己占位,因此拖动时"内容区宽度"由 flex 自动让出,无需 JS 改左侧。
|
*/
|
.doctor-page__splitter {
|
position: relative;
|
flex: none;
|
width: 6px;
|
cursor: col-resize;
|
background-color: transparent;
|
/* 防止拖动时浏览器做原生拖拽/滚动 */
|
touch-action: none;
|
outline: none;
|
|
&::before {
|
content: '';
|
position: absolute;
|
top: 0;
|
bottom: 0;
|
left: 50%;
|
width: 1px;
|
transform: translateX(-50%);
|
background-color: var(--el-border-color);
|
transition: background-color 0.15s, width 0.15s;
|
}
|
|
&:hover::before,
|
&.is-dragging::before,
|
&:focus-visible::before {
|
width: 2px;
|
background-color: var(--el-color-primary);
|
}
|
}
|
|
/* ---------- 右侧栏 ---------- */
|
.doctor-page__aside {
|
flex: none;
|
/* 允许右栏自身收缩,配合 overflow 独立滚动 */
|
min-width: 0;
|
min-height: 0;
|
/* 右栏独立滚动,长列表不会把底部按钮顶走 */
|
overflow-y: auto;
|
border: solid var(--el-color-primary-light-7);
|
border-left: none;
|
}
|
|
/* ---------- 底部功能按钮 ---------- */
|
.doctor-page__footer {
|
display: flex;
|
flex: none;
|
/* 需求:所有控件「一行显示」且整组靠右 —— 不允许换行,否则 footer 变两行、
|
会占掉记录区高度(原先 flex-wrap:wrap 在 1366 宽下就会折行)。 */
|
flex-wrap: nowrap;
|
align-items: center;
|
/* 整组靠右:右侧不留 padding,使按钮组与右侧 #aside(候诊表)右边线对齐 */
|
justify-content: flex-end;
|
padding: 4px 0 4px 8px;
|
/* 固定底部:有背景/上边框,内容不会透过来 */
|
background-color: var(--el-bg-color);
|
border-top: 1px solid var(--el-border-color);
|
}
|
|
/*
|
围观模式(只读):底部功能按钮整组禁用 ——
|
pointer-events:none 同时挡住点击与悬停,再加透明度让"不能点"一眼可见。
|
(服务端另有硬校验:非工位主人调用写接口会被拒绝,前端禁用只是体验层。)
|
*/
|
.doctor-page__footer.is-readonly {
|
pointer-events: none;
|
opacity: 0.55;
|
}
|
|
/*
|
右侧控件组:`自动` 复选框 + 功能按钮 + `停止候诊` 开关 **合成一组**,
|
一起靠右。用 gap 统一间距(并清掉 Element Plus 给相邻 el-button 的
|
默认 margin-left:12px,否则会与 gap 叠加导致间距不均)。
|
*/
|
.doctor-page__footer-right {
|
display: flex;
|
flex: none;
|
gap: 8px;
|
align-items: center;
|
white-space: nowrap;
|
|
:deep(.el-button + .el-button) {
|
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>
|