| | |
| | | |
| | | <head> |
| | | <meta charset="UTF-8" /> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, target-densitydpi=device-dpi" /> |
| | | <meta http-equiv="X-UA-Compatible" content="IE=edge" /> |
| | | <title>大厅</title> |
| | | <style> |
| | |
| | | margin: 0; |
| | | padding: 0; |
| | | box-sizing: border-box; |
| | | font-family: "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif; |
| | | /* Android 6 可用中文字体 */; |
| | | font-family: "Droid Sans Fallback", "Noto Sans CJK SC", "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif; |
| | | } |
| | | |
| | | /* 1. 全局深色背景 */ |
| | | body { |
| | | background: #001f3f; |
| | | /* 深蓝色背景,适合大屏 */ |
| | | color: #fff; |
| | | height: 100vh; |
| | | overflow: hidden; |
| | | display: -webkit-box; |
| | | display: -webkit-flex; |
| | | display: flex; |
| | | -webkit-box-orient: vertical; |
| | | -webkit-box-direction: normal; |
| | | -webkit-flex-direction: column; |
| | | flex-direction: column; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | /* 2. 顶部栏 - 简化边框,深色主题 */ |
| | | /* 2. 顶部栏 */ |
| | | .top-header { |
| | | display: -webkit-box; |
| | | display: -webkit-flex; |
| | | display: flex; |
| | | -webkit-box-align: center; |
| | | -webkit-align-items: center; |
| | | align-items: center; |
| | | padding: 15px 30px; |
| | | background: rgba(0, 30, 60, 0.9); |
| | | /* 深色半透明 */ |
| | | white-space: nowrap; |
| | | position: relative; |
| | | border-bottom: 1px solid #003366; |
| | |
| | | .top-header .title-text { |
| | | position: absolute; |
| | | left: 50%; |
| | | -webkit-transform: translateX(-50%); |
| | | transform: translateX(-50%); |
| | | font-size: 32px; |
| | | font-weight: bold; |
| | |
| | | z-index: 2; |
| | | } |
| | | |
| | | /* 3. 主体内容 */ |
| | | /* 3. 主体内容(gap 替换为 margin 兼容旧 Chrome) */ |
| | | .main-content { |
| | | -webkit-box-flex: 1; |
| | | -webkit-flex: 1; |
| | | flex: 1; |
| | | display: -webkit-box; |
| | | display: -webkit-flex; |
| | | display: flex; |
| | | padding: 15px 20px; |
| | | overflow: hidden; |
| | | gap: 10px; |
| | | } |
| | | |
| | | /* 列容器 - 移除阴影和圆角,更简洁 */ |
| | | .main-content > .column-box { |
| | | margin-left: 5px; |
| | | margin-right: 5px; |
| | | } |
| | | |
| | | .main-content > .column-box:first-child { |
| | | margin-left: 0; |
| | | } |
| | | |
| | | .main-content > .column-box:last-child { |
| | | margin-right: 0; |
| | | } |
| | | |
| | | /* 列容器 */ |
| | | .column-box { |
| | | display: -webkit-box; |
| | | display: -webkit-flex; |
| | | display: flex; |
| | | -webkit-box-orient: vertical; |
| | | -webkit-box-direction: normal; |
| | | -webkit-flex-direction: column; |
| | | flex-direction: column; |
| | | background: rgba(10, 40, 80, 0.5); |
| | | /* 深色半透明背景 */ |
| | | border-radius: 0; |
| | | padding: 10px; |
| | | -webkit-box-flex: 1; |
| | | -webkit-flex: 1; |
| | | flex: 1; |
| | | min-width: 0; |
| | | border: 1px solid #003366; |
| | | } |
| | | |
| | | .column-box.col-wide { |
| | | -webkit-box-flex: 2.5; |
| | | -webkit-flex: 2.5; |
| | | flex: 2.5; |
| | | } |
| | | |
| | | .column-box.col-normal { |
| | | -webkit-box-flex: 1; |
| | | -webkit-flex: 1; |
| | | flex: 1; |
| | | } |
| | | |
| | | /* 标题样式 - 简化下划线 */ |
| | | /* 标题行 */ |
| | | .col-title-line { |
| | | font-size: 22px; |
| | | font-weight: bold; |
| | | color: #4da6ff; |
| | | text-align: center; |
| | | } |
| | | |
| | | .col-title { |
| | | font-size: 22px; |
| | | font-weight: bold; |
| | |
| | | |
| | | /* 患者列表 */ |
| | | .patient-list { |
| | | -webkit-box-flex: 1; |
| | | -webkit-flex: 1; |
| | | flex: 1; |
| | | overflow-y: auto; |
| | | padding-right: 5px; |
| | | } |
| | | |
| | | /* 【关键修改】第1栏强制一行2个 */ |
| | | /* 第1栏:一行两个 */ |
| | | #col-0 { |
| | | display: -webkit-box; |
| | | display: -webkit-flex; |
| | | display: flex; |
| | | -webkit-flex-wrap: wrap; |
| | | flex-wrap: wrap; |
| | | -webkit-align-content: flex-start; |
| | | align-content: flex-start; |
| | | } |
| | | |
| | | /* 第2-5栏保持单列 */ |
| | | .col-normal .patient-list { |
| | | display: -webkit-box; |
| | | display: -webkit-flex; |
| | | display: flex; |
| | | -webkit-box-orient: vertical; |
| | | -webkit-box-direction: normal; |
| | | -webkit-flex-direction: column; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | /* 4. 患者项目 - 极简模式 */ |
| | | /* 患者项目 */ |
| | | .patient-item { |
| | | font-size: 22px; |
| | | padding: 8px 5px; |
| | | display: -webkit-box; |
| | | display: -webkit-flex; |
| | | display: flex; |
| | | -webkit-box-align: center; |
| | | -webkit-align-items: center; |
| | | align-items: center; |
| | | color: #fff; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | /* 【优化修改】第1栏:增加列间距,改善拥挤感 */ |
| | | #col-0 .patient-item { |
| | | width: 45%; |
| | | /* 1. 缩减宽度,为间隔留出空间 */ |
| | | font-size: 20px; |
| | | margin: 0 2.5%; |
| | | /* 2. 添加左右外边距,两列之间总间隔为5% */ |
| | | } |
| | | |
| | | .p-number { |
| | |
| | | } |
| | | |
| | | .p-name { |
| | | -webkit-box-flex: 1; |
| | | -webkit-flex: 1; |
| | | flex: 1; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | |
| | | margin-left: 5px; |
| | | } |
| | | |
| | | /* 5. 底部栏 - 居中显示 */ |
| | | /* 底部栏 */ |
| | | .bottom-footer { |
| | | display: -webkit-box; |
| | | display: -webkit-flex; |
| | | display: flex; |
| | | -webkit-box-pack: center; |
| | | -webkit-justify-content: center; |
| | | justify-content: center; |
| | | /* 居中 */ |
| | | -webkit-box-align: center; |
| | | -webkit-align-items: center; |
| | | align-items: center; |
| | | padding: 10px 30px; |
| | | background: rgba(0, 30, 60, 0.9); |
| | |
| | | text-align: center; |
| | | } |
| | | |
| | | /* 滚动条美化 - 细一点 */ |
| | | /* 滚动条 */ |
| | | .patient-list::-webkit-scrollbar { |
| | | width: 4px; |
| | | } |
| | | |
| | | .patient-list::-webkit-scrollbar-thumb { |
| | | background: #444; |
| | | border-radius: 2px; |
| | | } |
| | | |
| | | /* 6. 调试框样式 */ |
| | | /* 调试面板 */ |
| | | .debug-panel { |
| | | position: fixed; |
| | | right: 15px; |
| | |
| | | } |
| | | |
| | | .debug-header { |
| | | display: -webkit-box; |
| | | display: -webkit-flex; |
| | | display: flex; |
| | | -webkit-box-pack: justify; |
| | | -webkit-justify-content: space-between; |
| | | justify-content: space-between; |
| | | -webkit-box-align: center; |
| | | -webkit-align-items: center; |
| | | align-items: center; |
| | | padding: 5px; |
| | | background: #333; |
| | |
| | | } |
| | | |
| | | .debug-body { |
| | | -webkit-box-flex: 1; |
| | | -webkit-flex: 1; |
| | | flex: 1; |
| | | padding: 5px; |
| | | overflow-y: auto; |
| | |
| | | |
| | | <!-- 底部栏 --> |
| | | <div class="bottom-footer"> |
| | | <!-- 温馨提示语居中 --> |
| | | <div class="footer-tip">温馨提示:请听到呼叫后前往对应诊室</div> |
| | | </div> |
| | | |
| | | <!-- 调试面板 --> |
| | | <div class="debug-panel" id="debugPanel"> |
| | | <div class="debug-header"> |
| | | <span>🛠️ 运行日志</span> |
| | | <span>[调试] 运行日志</span> |
| | | <button onclick="document.getElementById('debugBody').innerHTML=''">清空</button> |
| | | </div> |
| | | <div class="debug-body" id="debugBody"></div> |
| | |
| | | |
| | | <script src="./static/jquery.min.js"></script> |
| | | <script> |
| | | // ================= 调试日志函数 ================= |
| | | // ================= 调试日志 ================= |
| | | function logDebug(msg) { |
| | | var now = new Date(); |
| | | var timeStr = now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds(); |
| | | function pad(num) { return num < 10 ? '0' + num : '' + num; } |
| | | var timeStr = pad(now.getHours()) + ':' + pad(now.getMinutes()) + ':' + pad(now.getSeconds()); |
| | | var logHtml = '<div class="debug-line"><span class="debug-time">[' + timeStr + ']</span>' + msg + '</div>'; |
| | | console.log("[" + timeStr + "] " + msg); |
| | | var $body = $("#debugBody"); |
| | | $body.append(logHtml); |
| | | $body.scrollTop($body[0].scrollHeight); |
| | | var body = document.getElementById("debugBody"); |
| | | if (body) { |
| | | body.insertAdjacentHTML("beforeend", logHtml); |
| | | body.scrollTop = body.scrollHeight; |
| | | } |
| | | } |
| | | |
| | | // ================= 应用状态 ================= |
| | |
| | | columnTitles: ["常规心电图", "动态心电", "平板运动心电", "食道电生理", "动脉硬化监测"], |
| | | columnSubTitles: ["床边心电图(常规+频谱)M / 心电向量图N", "动态血压C", "", "", ""], |
| | | patients: [], |
| | | apiBaseUrl: "http://192.168.3.12/admin-api", |
| | | apiBaseUrl: "http://192.168.100.110/admin-api", |
| | | pollTimer: null, |
| | | callRepeatTimes: 2, |
| | | spokenPatients: {}, |
| | |
| | | if (appState.isSpeaking || appState.ttsQueue.length === 0) return; |
| | | appState.isSpeaking = true; |
| | | var text = appState.ttsQueue.shift(); |
| | | logDebug("🔊 开始播报: " + text); |
| | | logDebug("[播报] " + text); |
| | | |
| | | var utterance = new SpeechSynthesisUtterance(text); |
| | | // Android 6 默认语速可能很快,适当调慢 |
| | | utterance.rate = 0.85; |
| | | utterance.onend = function () { |
| | | appState.isSpeaking = false; |
| | | processTtsQueue(); |
| | | }; |
| | | utterance.onerror = function () { |
| | | utterance.onerror = function (e) { |
| | | logDebug("[TTS错误] " + (e.error || "unknown")); |
| | | appState.isSpeaking = false; |
| | | processTtsQueue(); |
| | | }; |
| | | |
| | | // 优先尝试设备原生 TTS 接口 |
| | | if (typeof wowjoy !== 'undefined' && typeof wowjoy.speek === 'function') { |
| | | try { |
| | | wowjoy.speek(text); |
| | | setTimeout(function () { appState.isSpeaking = false; processTtsQueue(); }, 4000); |
| | | return; |
| | | } catch (e) { logDebug("❌ wowjoy 调用失败: " + e.message); } |
| | | } catch (e) { logDebug("[wowjoy失败] " + e.message); } |
| | | } |
| | | |
| | | if (window.speechSynthesis) { window.speechSynthesis.speak(utterance); } |
| | | else { appState.isSpeaking = false; processTtsQueue(); } |
| | | if (window.speechSynthesis) { |
| | | // Android 6 WebView 有时需要先 cancel 再 speak |
| | | window.speechSynthesis.cancel(); |
| | | window.speechSynthesis.speak(utterance); |
| | | } else { |
| | | logDebug("[TTS] 浏览器不支持语音合成"); |
| | | appState.isSpeaking = false; |
| | | processTtsQueue(); |
| | | } |
| | | } |
| | | |
| | | function speak(text) { |
| | |
| | | processTtsQueue(); |
| | | } |
| | | |
| | | // ================= 渲染函数 ================= |
| | | // ================= 渲染 ================= |
| | | function renderMainContent() { |
| | | var $main = $("#mainContent"); |
| | | $main.empty(); |
| | | var main = document.getElementById("mainContent"); |
| | | main.innerHTML = ""; |
| | | |
| | | for (var i = 0; i < appState.columnTitles.length; i++) { |
| | | var titleHtml = '<div class="col-title-line">' + appState.columnTitles[i] + '</div>'; |
| | |
| | | } |
| | | |
| | | var colClass = (i === 0) ? 'col-wide' : 'col-normal'; |
| | | // 注意:这里不再给第1栏加 col-flex,由 CSS #col-0 强制控制 |
| | | var colHtml = '<div class="column-box ' + colClass + '">' + |
| | | '<div class="col-title">' + titleHtml + '</div>' + |
| | | '<div class="patient-list" id="col-' + i + '"></div>' + |
| | | '</div>'; |
| | | $main.append(colHtml); |
| | | main.insertAdjacentHTML("beforeend", colHtml); |
| | | } |
| | | } |
| | | |
| | | function renderPatients() { |
| | | for (var i = 0; i < appState.columnTitles.length; i++) { |
| | | $("#col-" + i).empty(); |
| | | var col = document.getElementById("col-" + i); |
| | | if (col) col.innerHTML = ""; |
| | | } |
| | | |
| | | for (var c = 0; c < appState.patients.length; c++) { |
| | | var colData = appState.patients[c]; |
| | | if (Array.isArray(colData)) { |
| | | var $col = $("#col-" + c); |
| | | var col = document.getElementById("col-" + c); |
| | | if (!col) continue; |
| | | for (var p = 0; p < colData.length; p++) { |
| | | var pat = colData[p]; |
| | | var roomHtml = pat.roomName ? '<span class="p-room">(' + pat.roomName + ')</span>' : ''; |
| | |
| | | '<span class="p-name">' + (pat.patName || '') + '</span>' + |
| | | roomHtml + |
| | | '</div>'; |
| | | $col.append(itemHtml); |
| | | col.insertAdjacentHTML("beforeend", itemHtml); |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | speak(repeatText); |
| | | appState.spokenPatients[newPat.patId] = true; |
| | | logDebug("🔔 加入播报队列: " + newPat.patName); |
| | | logDebug("[排队] " + newPat.patName + " -> " + newPat.roomName); |
| | | } |
| | | } |
| | | } |
| | |
| | | appState.patients = dataList; |
| | | renderPatients(); |
| | | |
| | | // Android 6 WebView 通常不支持 performance.memory,安全守卫 |
| | | try { |
| | | if (window.performance && window.performance.memory) { |
| | | var usedMB = (window.performance.memory.usedJSHeapSize / 1048576).toFixed(2); |
| | | logDebug("💾 内存: " + usedMB + " MB"); |
| | | logDebug("[内存] " + usedMB + " MB"); |
| | | } |
| | | } catch (e) {} |
| | | }, |
| | | error: function (err) { |
| | | logDebug("❌ 请求失败: " + err.statusText); |
| | | logDebug("[请求失败] " + (err.statusText || "网络错误")); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | // ================= 初始化 ================= |
| | | $(document).ready(function () { |
| | | function onReady() { |
| | | renderMainContent(); |
| | | updateHeaderTime(); |
| | | setInterval(updateHeaderTime, 1000); |
| | | fetchQueueData(); |
| | | appState.pollTimer = setInterval(fetchQueueData, 5000); |
| | | logDebug("✅ 系统启动"); |
| | | }); |
| | | logDebug("[系统] 启动完成 - Android 6.0.1"); |
| | | } |
| | | |
| | | function updateHeaderTime() { |
| | | var now = new Date(); |
| | |
| | | function padZero(num) { return num < 10 ? '0' + num : '' + num; } |
| | | var dateStr = now.getFullYear() + "年" + padZero(now.getMonth() + 1) + "月" + padZero(now.getDate()) + "日 " + weekDays[now.getDay()]; |
| | | var timeStr = padZero(now.getHours()) + ":" + padZero(now.getMinutes()); |
| | | $("#headerTime").text(dateStr + " " + timeStr); |
| | | var el = document.getElementById("headerTime"); |
| | | if (el) el.textContent = dateStr + " " + timeStr; |
| | | } |
| | | |
| | | // 兼容 DOM ready(Android 6 某些 WebView 可能没有 $) |
| | | if (typeof $ !== 'undefined') { |
| | | $(document).ready(onReady); |
| | | } else if (document.readyState === 'complete' || document.readyState === 'interactive') { |
| | | setTimeout(onReady, 1); |
| | | } else { |
| | | document.addEventListener('DOMContentLoaded', onReady); |
| | | } |
| | | </script> |
| | | </body> |