医院叫号大屏自适应HTML5模板源码实战
简介这套基于HTML5的医院叫号大屏模板源码面向门诊大厅、候诊区等公共场所的信息发布场景用于实时播报与展示当前叫号、候检队列、各科室排号进度。页面同时支持滚动提示与弹框提醒功能覆盖叫号场景的完整展示逻辑且具备自适应布局适配常见大屏分辨率代码结构独立无论信息科直接部署或是前端开发者在此基础上二次改造上手门槛都比较低。压缩包共22个文件大小仅315KB包含2个HTML入口页面、2套CSS样式、2组JavaScript交互脚本和12张PNG图片素材另有说明文档与DB数据文件页面结构、交互逻辑与视觉资源划分清晰方便替换图片、调整配色或扩展科室模块。当前已有260人学习使用。打开index.html即可直接预览大屏效果也可参考代码将规则接入实际叫号数据模板自带多种风格素材适合作为医院信息化展示、毕业设计或商业项目快速落地的起点。1. 医院叫号大屏的自适应不只是把页面“等比例缩一下”医院大厅里那块叫号屏实际运行环境往往比开发机上复杂得多55 英寸横屏、32 英寸竖屏、拼接墙上的 4K 画面可能同时跑着同一套 HTML5 源码。如果把“自适应”理解成按宽高比缩放字体那只是第一层真正要解决的是不同屏体下信息排布不溢出、长时间运行不残影、数据断流时有明确提示。这是笔者在交付医院信息显示屏时最常被问到的问题也是这类“自适应医院叫号大屏模板源码”最容易在验收阶段翻车的地方。下面从一个可落地的 HTML5 模板出发把布局、数据接入、渲染帧控制和部署校验讲清楚。2. 自适应叫号大屏的布局引擎信息分区与 CSS Grid 容错设计2.1 一块叫号屏上必然出现的信息分区与优先级叫号屏是功能屏不是品牌广告屏。患者要在几秒内完成“找自己的号码、找诊室、判断是否过号”这三个动作所以屏幕内容至少要切分成五个区块当前呼号、当前诊室或窗口号、候诊队列、时间日期、底部公告。其中当前呼号和诊室号必须放在视觉焦点区字号最大候诊队列放在次焦点的侧边区域公告在底部滚动展示。自适应不能先从 CSS 写起要先把这五个区块的优先级定下来。二级医院门诊的叫号屏通常把“当前呼号”放在左中诊室号紧随其后而检验科或药房窗口取药列表的权重反而更高呼号区可以缩小。模板里一般会预留两套布局方案layout-mode: clinic表示门诊模式layout-mode: window表示窗口模式。这种做法比单纯依赖媒体查询更可靠因为不同科室不是屏幕尺寸不同而是信息重心不同。模板落地时我一般会把屏幕分为“头部信息区、主呼号区、候诊列表区、公告区”四个容器再通过 CSS Grid 把不同模式映射成不同的网格位置。网格方案确定后横竖屏切换就只需要切换一行grid-template-areas。2.2 用 CSS Grid 编排自适应叫号大屏的弹性网格并配合 clamp() 收住边距CSS Grid 是目前做自适应大屏布局最顺手的方案。下面是一段可直接复用的栅格配置.layout { display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); grid-template-rows: auto minmax(0, 1fr) auto; gap: clamp(10px, 1.5vw, 24px); padding: clamp(12px, 2vw, 32px); height: 100vh; } .head { grid-column: 1 / -1; } .queue { grid-row: span 2; } .calling { overflow: hidden; } .notice { padding-top: 0.2em; border-top: 1px solid rgba(255, 255, 255, 0.15); }这里的几个参数是叫号屏自适应的关键。grid-template-columns使用minmax(0, 3fr)而不是裸的3fr 2fr是为了防止候诊列表里超长姓名把列撑破。fr单位在内容超出时会自动扩张导致另一列被挤得看不到加minmax(0, ...)后列的弹性范围被限制在网格剩余空间内。clamp(10px, 1.5vw, 24px)同时约束了间距和内边距小屏不挤、大屏不失衡。height: 100vh直接锁高度避免滚动条破坏大屏观感。在 21:9 比较宽的带鱼屏上这个网格会显得中央呼号区太宽可以引入媒体查询media (min-aspect-ratio: 8 / 3) { .layout { grid-template-columns: minmax(0, 2fr) minmax(0, 3fr) minmax(0, 2fr); } }把候诊列表挪到最右侧呼号保持在黄金分割位置。竖屏广告机则把grid-template-columns改成单列候诊列表移到呼号下方。2.3 候诊表格自适应宽度table-layout 与 overflow 的双保险设定候诊列表是最容易破坏布局的地方。姓名长度、科室简称、排队序号混在一行里有的屏幕会在第 18 个字符处出现横向滚动条。给表格设置响应式宽度是最经济的做法.queue-table { width: 100%; table-layout: fixed; } .queue-table td { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }table-layout: fixed让列宽不再由内容决定而是由表格宽度和首行单元格宽度决定。加上text-overflow: ellipsis后超长姓名的部分会显示为省略号鼠标悬停也无法在大屏上看到完整内容所以实现时还应该在td上保留title属性内容为完整姓名方便管理人员在调试模式下查看。不同屏体下表格的行数和字号可以参考以下参数。行列数不是越多越好候诊列表显示 6 到 8 行最为清晰超出部分交给滚动动画。屏宽比常见屏体候诊列表建议列数呼号字号参考16:955 英寸横挂3 列clamp(80px, 13vmin, 180px)21:958 英寸带鱼屏4 列clamp(70px, 12vmin, 170px)9:1632 英寸竖屏2 列clamp(56px, 9vmin, 120px)竖屏场景下表格列数减少是因为可用宽度有限两列已经能容纳“序号 姓名”第三列“状态”适合用背景色块代替文字。3. 源码组织与渲染逻辑用一份 JSON 撬动整块大屏3.1 模板目录的最小结构五类文件各管一件事叫号屏模板的源码组织不需要引入 Vue 或 React。它本质上是单页应用用原生 JavaScript 反而更容易在老旧安卓盒子上跑稳。一套最精简的目录结构如下hospital-board/ ├── index.html ├── config.json ├── css/ │ ├── base.css # 主题色、字体、reset │ └── layout.css # 自适应栅格与媒体查询 ├── js/ │ ├── config.js # 读取 config.json 的入口 │ ├── api.js # WebSocket 与 fetch 封装 │ └── render.js # DOM 渲染与动画入口 └── assets/ └── audio/ └── ding.mp3 # 呼号提示音这类模板不依赖 node_modules直接把文件夹拷到内网任意静态服务器就能运行适合做源码建站式的快速交付。config.json放的是屏幕级配置比如screenId、科室名称、是否启用语音播报config.js负责在页面加载时异步读取它。这样现场实施人员只需要改 JSON不需要碰 JavaScript 代码。index.html内只需要保留根节点和静态资源引用!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 link relstylesheet hrefcss/base.css link relstylesheet hrefcss/layout.css /head body main idboard classlayout>{ screenId: entrance_3f, target: tick, payload: { room: 消化内科3诊室, current: A028, queue: [A029, A030, A031, A032], overdue: [A021, A025], notice: 过号患者请前往导诊台登记 }, ts: 1700000000 }screenId用于标识数据目标是哪块屏target表示消息类型当前主要是tick即叫号事件payload.room是诊室名称payload.current是当前呼号的号码queue是候诊数组overdue是过号数组notice是底部公告。把公告也放进同样的 payload是因为很多医院希望临时通知能直接推到指定屏上而不是让实施人员跑到现场改页面。字段解析建议按“先整体、后局部”的方式做。整体是指ts时间戳是否比本地已有数据新如果新消息的ts小于等于当前值直接丢弃这能避免后端重复推送导致页面抖动。以下是一个能直接用的接收函数骨架function applyMessage(raw) { let msg; try { msg JSON.parse(raw); } catch (err) { return; } if (msg.ts state.updatedAt) return; state.room msg.payload?.room || state.room; state.current msg.payload?.current || state.current; state.queue msg.payload?.queue || state.queue; state.overdue msg.payload?.overdue || state.overdue; state.notice msg.payload?.notice || state.notice; state.updatedAt msg.ts; }state.updatedAt是整份状态的版本号也是后面“自适应频率控制”的基础。3.3 一次 WebSocket 消息如何变成屏幕上的呼号动画数据接收到之后不直接改 DOM而是先合并进状态对象再挂一个渲染任务。这样做的原因是大屏可能在一秒内连续收到多次推送如果每次都触发 DOM 操作低性能设备会出现明显的卡顿。模板里常见的做法如下let renderQueued false; function scheduleRender() { if (renderQueued) return; renderQueued true; requestAnimationFrame(() { renderQueued false; renderDOM(state); }); } function renderDOM(s) { document.getElementById(call-num).textContent s.current; document.getElementById(room-name).textContent s.room; renderQueueList(s.queue); }requestAnimationFrame保证同一帧内的多次数据更新只触发一次 DOM 写入这就是“自适应频率控制”在 HTML5 大屏里的典型应用更新频率跟随浏览器刷新帧率而不是跟随后端推送频率。当state.current发生变化时模板会为呼号区补一个弹跳动画也就是 HTML5 动画里最常见的pop效果keyframes pop-in { 0% { transform: scale(0.8); opacity: 0.4; } 80% { transform: scale(1.04); opacity: 1; } 100% { transform: scale(1); opacity: 1; } } .pop { animation: pop-in 0.35s ease-out; }重触发动画的技巧是“先重置再添加类”const el document.getElementById(call-num); el.classList.remove(pop); void el.offsetWidth; el.classList.add(pop);offsetWidth的读取会强制浏览器进行一次样式重算确保动画能重新播放。4. 实时叫号接入、断线重连与自适应频率刷新的落地细节4.1 短轮询、SSE 与 WebSocket叫号屏实时通道的选型对比医院内网环境比较特殊老旧的呼叫系统往往只提供一个 HTTP 查询接口并没有独立的推送服务。接入方式需要根据后端能力来选传输方式实现成本典型延迟适合场景短轮询很低5~15 秒后端只开放 HTTP 查询接口SSE中秒内后端可独立部署单向推送足够WebSocket中高毫秒级有独立推送服务或存在过号即时提醒需求模板一般会封装数据源接口启动时读取config.json里的transport字段来决定走哪条通道。如果选短轮询最简单的做法是setInterval(fetchData, 5000)并保证上一次请求结束后再计时避免请求堆积。如果后端可以直接建 WebSocket推荐在消息里带上screenId让服务端精确推送而不是广播到所有屏。否则同一网段几十块屏同时收到无关消息渲染层的过滤逻辑写得再好网络带宽也会被浪费。4.2 心跳、指数退避重连与陈旧数据提示大屏长期运行Wi-Fi 或网线端口都可能出现短暂断开。WebSocket 断线时浏览器会自动触发onclose但网络处于“半开状态”时onclose可能延迟很久所以必须有应用层心跳。下面这段完整代码可以直接放进js/api.jslet ws null; let failCount 0; let staleTimeout null; function connect() { ws new WebSocket(ws://${location.host}/ws?screenId${SCREEN_ID}); ws.onopen () { failCount 0; hideStaleBanner(); }; ws.onmessage (ev) { applyMessage(ev.data); scheduleRender(); }; ws.onclose () { ws null; const delay Math.min(30000, 3000 * Math.pow(2, failCount)); setTimeout(connect, delay); }; ws.onerror () ws.close(); } setInterval(() { if (ws ws.readyState WebSocket.OPEN) { ws.send(JSON.stringify({ type: ping, ts: Date.now() })); } if (Date.now() - state.updatedAt 10000) { showStaleBanner(true); } }, 5000);failCount控制重连间隔第一次失败 3 秒后重试第二次 6 秒依次翻倍最多 30 秒。这样在网络抖动时恢复快长时间断网时也不会频繁握手。陈旧数据提示则是根据state.updatedAt判断超过 10 秒没收到新数据就显示“数据连接中断”而不是让屏上留着旧号码造成叫号延误。这里的 10 秒和 5 秒要按实际推送频率调如果该系统每分钟只推送一次叫号消息阈值应放大到 70 秒。4.3 用自适应频率控制把 HTML5 动画的帧消耗压下去大屏设备性能差异极大从 i5 工控机到几百元的安卓盒子都有。同一条呼号动画在高端设备上流畅在低端盒子上就可能掉帧。几个有效的降载手段集中在渲染函数里第一所有列表更新用增量渲染不要重建整张表格。候诊队列更新时先对比新旧数组只对新插入的节点调用insertAdjacentHTML其余节点只改文本。第二动画属性只使用transform和opacity不要用top、left或height做动画。CSS 动画触发重排时大屏上数千个像素点的代价是明显的。第三多加一个简单比较在刷新前过滤重复数据function shouldUpdate(prev, next) { return prev.current ! next.current || prev.room ! next.room || prev.queue.join(,) ! next.queue.join(,); }queue.join(,)在队列长度只有几十条时开销可忽略却能挡住一半以上重复推送。装在这些细节上低端盒子的 CPU 占用能下降 30% 以上。5. 收尾的最后一公里防烧屏、kiosk 部署和同屏校验5.1 交付前的稳定性检查表叫号屏交付给医院后实施人员往往不在现场所以安装前必须把运行环境参数固定下来。常用的检查项如下检查项推荐设置说明屏保与休眠系统层面关闭任何电源休眠都会中断 WebSocket浏览器启动全屏模式或 kiosk 模式Chrome 可用--kiosk参数分辨率缩放4K 屏设 200% 或 300%避免vmin计算出现偏差开机自启写脚本加入启动项掉电恢复后能自动拉起浏览器夜间刷新凌晨 3 点强制 reload清掉长期驻留的页面内存夜间刷新可以通过一段setInterval判断时间实现注意在模板里加开关。如果院方不允许页面定时刷新就要把数据异常自动重启的逻辑做得更保守一些。5.2 用状态自检页与内屏切换保证 7×24 可观测最后推荐一个轻量做法在模板里增加debug.html它不渲染叫号界面只显示连接状态和数据版本号。现场排查时维护人员访问http://屏的IP/debug.html?screenIdentrance_3f就能立刻看到这块屏有没有收到数据。script const screenId location.search.split(screenId)[1]; fetch(/api/status?screenId${screenId}) .then(r r.json()) .then(d { document.body.textContent (Date.now() - d.ts 10000) ? 在线${d.screenId}最后更新 ${d.ts} : 失联${d.screenId}; }); /script自检页只需要说明“在线还是失联”不用做复杂报表。把自检逻辑与主模板分离是为了让维护入口不受叫号页面的动画和脚本影响即便主页面渲染崩溃自检页依然能工作。剩下的部署细节比如把自检页加入收藏夹、把 IP 绑定到屏身标签上都是现场实施时顺手做的事但能省掉后续大量沟通成本。本文还有配套的精品资源点击获取