From 796047fbe84d51816f44be535501415d3c66dd9d Mon Sep 17 00:00:00 2001
From: yxh <172933527@qq.com>
Date: 星期日, 21 六月 2026 23:24:37 +0800
Subject: [PATCH] yxh

---
 big.html |  563 ++++++++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 446 insertions(+), 117 deletions(-)

diff --git a/big.html b/big.html
index 54c827d..a2fb0ab 100644
--- a/big.html
+++ b/big.html
@@ -1,9 +1,10 @@
-<!doctype html>
+锘�<!doctype html>
 <html lang="zh-CN">
 
 <head>
     <meta charset="UTF-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, target-densitydpi=device-dpi" />
+    <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>
@@ -11,7 +12,8 @@
             margin: 0;
             padding: 0;
             box-sizing: border-box;
-            /* Android 6 鍙敤涓枃瀛椾綋 */;
+            /* Android 6 鍙敤涓枃瀛椾綋 */
+            ;
             font-family: "Droid Sans Fallback", "Noto Sans CJK SC", "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;
         }
 
@@ -64,10 +66,43 @@
         }
 
         .top-header .time-info {
-            font-size: 24px;
+            font-size: 20px;
             color: #aaa;
             margin-left: auto;
             z-index: 2;
+            text-align: right;
+            line-height: 1.3;
+        }
+
+        .time-info-inner {
+            display: -webkit-box;
+            display: -webkit-flex;
+            display: flex;
+            -webkit-box-align: center;
+            -webkit-align-items: center;
+            align-items: center;
+        }
+
+        .time-left {
+            display: -webkit-box;
+            display: -webkit-flex;
+            display: flex;
+            -webkit-box-orient: vertical;
+            -webkit-flex-direction: column;
+            flex-direction: column;
+            text-align: right;
+            line-height: 1.3;
+        }
+
+        .time-right {
+            margin-left: 8px;
+        }
+
+        .top-header .time-info .time-clock {
+            font-size: 36px;
+            font-weight: bold;
+            color: #ffcc00;
+            line-height: 1.1;
         }
 
         /* 3. 涓讳綋鍐呭锛坓ap 鏇挎崲涓� margin 鍏煎鏃� Chrome锛� */
@@ -78,20 +113,35 @@
             display: -webkit-box;
             display: -webkit-flex;
             display: flex;
+            -webkit-box-orient: vertical;
+            -webkit-box-direction: normal;
+            -webkit-flex-direction: column;
+            flex-direction: column;
             padding: 15px 20px;
             overflow: hidden;
         }
 
-        .main-content > .column-box {
-            margin-left: 5px;
-            margin-right: 5px;
+        /* 鍒楄瀹瑰櫒 */
+        .columns-row {
+            -webkit-box-flex: 1;
+            -webkit-flex: 1;
+            flex: 1;
+            display: -webkit-box;
+            display: -webkit-flex;
+            display: flex;
+            overflow: hidden;
         }
 
-        .main-content > .column-box:first-child {
+        .columns-row>.column-box {
+            margin-left: 3px;
+            margin-right: 3px;
+        }
+
+        .columns-row>.column-box:first-child {
             margin-left: 0;
         }
 
-        .main-content > .column-box:last-child {
+        .columns-row>.column-box:last-child {
             margin-right: 0;
         }
 
@@ -106,7 +156,7 @@
             flex-direction: column;
             background: rgba(10, 40, 80, 0.5);
             border-radius: 0;
-            padding: 10px;
+            padding: 6px;
             -webkit-box-flex: 1;
             -webkit-flex: 1;
             flex: 1;
@@ -128,16 +178,13 @@
 
         /* 鏍囬琛� */
         .col-title-line {
-            font-size: 22px;
+            font-size: 28px;
             font-weight: bold;
             color: #4da6ff;
             text-align: center;
         }
 
         .col-title {
-            font-size: 22px;
-            font-weight: bold;
-            color: #4da6ff;
             padding-bottom: 8px;
             border-bottom: 1px solid #003366;
             margin-bottom: 10px;
@@ -145,9 +192,14 @@
         }
 
         .col-subtitle {
-            font-size: 14px;
-            color: #999;
+            font-size: 24px;
+            font-weight: bold;
+            color: #ffcc00;
             text-align: center;
+            min-height: 20px;
+            /* 鍗充娇涓虹┖涔熶繚鐣欓珮搴︼紝闃叉鏍囬鏍忛珮浣庝笉骞� */
+            line-height: 1.4;
+            word-break: keep-all;
         }
 
         /* 鎮h�呭垪琛� */
@@ -156,34 +208,41 @@
             -webkit-flex: 1;
             flex: 1;
             overflow-y: auto;
-            padding-right: 5px;
-        }
-
-        /* 绗�1鏍忥細涓�琛屼袱涓� */
-        #col-0 {
+            padding-right: 2px;
             display: -webkit-box;
             display: -webkit-flex;
             display: flex;
-            -webkit-flex-wrap: wrap;
-            flex-wrap: wrap;
             -webkit-align-content: flex-start;
             align-content: flex-start;
         }
 
-        .col-normal .patient-list {
-            display: -webkit-box;
-            display: -webkit-flex;
-            display: flex;
+        /* 涓�琛屼袱涓紙甯歌蹇冪數鍥俱�佸姩鎬佸績鐢碉級 */
+        .patient-list.two-per-row {
+            -webkit-flex-wrap: wrap;
+            flex-wrap: wrap;
+        }
+
+        .patient-list.two-per-row .patient-item {
+            width: 46%;
+            margin: 0 2%;
+        }
+
+        /* 涓�琛屼竴涓� */
+        .patient-list.one-per-row {
             -webkit-box-orient: vertical;
             -webkit-box-direction: normal;
             -webkit-flex-direction: column;
             flex-direction: column;
         }
 
+        .patient-list.one-per-row .patient-item {
+            width: 100%;
+        }
+
         /* 鎮h�呴」鐩� */
         .patient-item {
-            font-size: 22px;
-            padding: 8px 5px;
+            font-size: 28px;
+            padding: 4px 3px;
             display: -webkit-box;
             display: -webkit-flex;
             display: flex;
@@ -194,19 +253,22 @@
             line-height: 1.4;
         }
 
-        #col-0 .patient-item {
-            width: 45%;
-            font-size: 20px;
-            margin: 0 2.5%;
-        }
-
         .p-number {
             color: #ffcc00;
             font-weight: bold;
-            margin-right: 8px;
+            margin-right: 6px;
+            -webkit-flex-shrink: 0;
+            flex-shrink: 0;
         }
 
         .p-name {
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+            font-weight: bold;
+        }
+
+        .p-name-wrap {
             -webkit-box-flex: 1;
             -webkit-flex: 1;
             flex: 1;
@@ -218,8 +280,31 @@
         .p-room {
             color: #4da6ff;
             font-weight: bold;
-            font-size: 18px;
-            margin-left: 5px;
+            font-size: 22px;
+            margin-left: 4px;
+            -webkit-flex-shrink: 0;
+            flex-shrink: 0;
+        }
+
+        /* 杩囧彿鎮h�咃細鎺掑湪鍒楀唴锛岃瑙夊急鍖� */
+        .patient-item.missed {
+            color: #aa8888;
+        }
+
+        .patient-item.missed .p-number {
+            color: #cc9966;
+        }
+
+        .patient-item.missed .p-name {
+            color: #aa8888;
+        }
+
+        .patient-item.missed .p-missed-tag {
+            color: #ff6666;
+            font-size: 22px;
+            margin-left: 4px;
+            -webkit-flex-shrink: 0;
+            flex-shrink: 0;
         }
 
         /* 搴曢儴鏍� */
@@ -245,10 +330,30 @@
             text-align: center;
         }
 
+        /* 娴嬭瘯璇煶鎸夐挳 */
+        #test-voice-btn {
+            margin-left: 20px;
+            background-color: #007bff;
+            color: white;
+            border: none;
+            padding: 10px 20px;
+            border-radius: 6px;
+            font-size: 16px;
+            cursor: pointer;
+            font-weight: bold;
+            -webkit-flex-shrink: 0;
+            flex-shrink: 0;
+        }
+
+        #test-voice-btn:hover {
+            background-color: #0056b3;
+        }
+
         /* 婊氬姩鏉� */
         .patient-list::-webkit-scrollbar {
             width: 4px;
         }
+
         .patient-list::-webkit-scrollbar-thumb {
             background: #444;
             border-radius: 2px;
@@ -256,14 +361,21 @@
 
         /* 璋冭瘯闈㈡澘 */
         .debug-panel {
+            display: -webkit-box;
+            display: -webkit-flex;
+            display: flex;
+            -webkit-box-orient: vertical;
+            -webkit-box-direction: normal;
+            -webkit-flex-direction: column;
+            flex-direction: column;
             position: fixed;
             right: 15px;
             bottom: 70px;
-            width: 350px;
-            height: 200px;
-            background: rgba(0, 0, 0, 0.9);
+            width: 380px;
+            height: 220px;
+            background: rgba(0, 0, 0, 0.92);
             color: #0f0;
-            border-radius: 4px;
+            border-radius: 6px;
             font-family: monospace;
             font-size: 12px;
             z-index: 9999;
@@ -322,7 +434,7 @@
     <!-- 椤堕儴鏍� -->
     <div class="top-header">
         <img src="logo.png" alt="logo" />
-        <span class="title-text">鏈嶅姟澶у巺鎺掑垪</span>
+        <span class="title-text">蹇冪數璇婂尯澶у巺</span>
         <span class="time-info" id="headerTime"></span>
     </div>
 
@@ -331,11 +443,12 @@
 
     <!-- 搴曢儴鏍� -->
     <div class="bottom-footer">
-        <div class="footer-tip">娓╅Θ鎻愮ず锛氳鍚埌鍛煎彨鍚庡墠寰�瀵瑰簲璇婂</div>
+        <div class="footer-tip">娓╅Θ鎻愮ず锛氬惉鍒板懠鍙悗璇峰墠寰�瀵瑰簲璇婂锛岃繃鍙锋偅鑰呭埌瀵艰瘖鍙板鐞嗭紒</div>
+        <button id="test-voice-btn" onclick="testVoice()">娴嬭瘯璇煶</button>
     </div>
 
     <!-- 璋冭瘯闈㈡澘 -->
-    <div class="debug-panel" id="debugPanel">
+    <div class="debug-panel" id="debugPanel" >
         <div class="debug-header">
             <span>[璋冭瘯] 杩愯鏃ュ織</span>
             <button onclick="document.getElementById('debugBody').innerHTML=''">娓呯┖</button>
@@ -355,61 +468,125 @@
             var body = document.getElementById("debugBody");
             if (body) {
                 body.insertAdjacentHTML("beforeend", logHtml);
-                body.scrollTop = body.scrollHeight;
+                // requestAnimationFrame 纭繚 DOM 鏇存柊鍚庡啀婊氬姩
+                requestAnimationFrame(function () {
+                    body.scrollTop = body.scrollHeight;
+                });
             }
         }
 
         // ================= 搴旂敤鐘舵�� =================
+        // 鍛煎彨鍙彿娆℃暟鍙傛暟锛氬懠鍙偅鑰呭氨璇婃椂閲嶅鎾姤鐨勬鏁帮紝榛樿 2 娆�
+        var CALL_TIMES = 2;   
+        // 涓�琛屼袱涓偅鑰呯殑鍒楃储寮曪紙甯歌蹇冪數鍥俱�佸姩鎬佸績鐢碉級
+        var TWO_PER_ROW_COLS = [0, 1];
+        // 椋熼亾鐢电敓鐞嗘槸鍚︽樉绀猴紙false 闅愯棌锛宼rue 鏄剧ず锛�
+        var SHOW_COL_ESOPHAGEAL = false;
         var appState = {
-            columnTitles: ["甯歌蹇冪數鍥�", "鍔ㄦ�佸績鐢�", "骞虫澘杩愬姩蹇冪數", "椋熼亾鐢电敓鐞�", "鍔ㄨ剦纭寲鐩戞祴"],
-            columnSubTitles: ["搴婅竟蹇冪數鍥�(甯歌+棰戣氨)M / 蹇冪數鍚戦噺鍥綨", "鍔ㄦ�佽鍘婥", "", "", ""],
+            columnTitles: ["甯歌蹇冪數鍥�/蹇冪數鍚戦噺鍥�", "鍔ㄦ�佸績鐢�/琛�鍘�", "骞虫澘杩愬姩蹇冪數", "椋熼亾鐢电敓鐞�", "鍔ㄨ剦纭寲鐩戞祴"],
+            columnSubTitles: ["1鍙�-2鍙疯瘖瀹�", "2鍙疯瘖瀹�", "4鍙疯瘖瀹�", "6鍙疯瘖瀹�", "5鍙疯瘖瀹�"],
             patients: [],
-            apiBaseUrl: "http://192.168.100.110/admin-api",
+            // apiBaseUrl: "http://192.168.3.12/admin-api",
+            apiBaseUrl: "http://10.0.2.193/admin-api",
             pollTimer: null,
-            callRepeatTimes: 2,
             spokenPatients: {},
             ttsQueue: [],
-            isSpeaking: false
+            isSpeaking: false,
+        };
+
+        var patStatus = {
+            3: "宸茶繃鍙�-鎺掗槦", 5: "宸茶繃鍙�", 7: "宸茶繃鍙�-瀹夎", 10: "鎺掗槦涓�", 12: "浜插拰",
+            13: "浜插拰-瀹夎", 15: "宸插彫鍥�", 20: "鍊欒瘖涓�", 30: "灏辫瘖涓�", 33: "宸查鐢�",
+            34: "宸插彫鍥�-瀹夎", 36: "瀹夎涓�", 40: "宸插氨璇�"
         };
 
         // ================= 璇煶鎾姤 =================
+        // Android WebView GC 淇濇姢锛氬叏灞�鎸佹湁 utterance 寮曠敤锛岄槻姝㈣鍥炴敹瀵艰嚧鏃犲0
+        var _gCurrentUtterance = null;
+
         function processTtsQueue() {
             if (appState.isSpeaking || appState.ttsQueue.length === 0) return;
             appState.isSpeaking = true;
             var text = appState.ttsQueue.shift();
             logDebug("[鎾姤] " + text);
 
-            var utterance = new SpeechSynthesisUtterance(text);
-            // Android 6 榛樿璇�熷彲鑳藉緢蹇紝閫傚綋璋冩參
-            utterance.rate = 0.85;
-            utterance.onend = function () {
-                appState.isSpeaking = false;
-                processTtsQueue();
-            };
-            utterance.onerror = function (e) {
-                logDebug("[TTS閿欒] " + (e.error || "unknown"));
-                appState.isSpeaking = false;
-                processTtsQueue();
-            };
-
-            // 浼樺厛灏濊瘯璁惧鍘熺敓 TTS 鎺ュ彛
+            // 浼樺厛灏濊瘯璁惧鍘熺敓 TTS 鎺ュ彛 (wowjoy)
             if (typeof wowjoy !== 'undefined' && typeof wowjoy.speek === 'function') {
                 try {
                     wowjoy.speek(text);
+                    logDebug("[鎾姤] wowjoy.speek 宸茶皟鐢�");
                     setTimeout(function () { appState.isSpeaking = false; processTtsQueue(); }, 4000);
                     return;
                 } catch (e) { logDebug("[wowjoy澶辫触] " + e.message); }
             }
 
-            if (window.speechSynthesis) {
-                // Android 6 WebView 鏈夋椂闇�瑕佸厛 cancel 鍐� speak
-                window.speechSynthesis.cancel();
-                window.speechSynthesis.speak(utterance);
-            } else {
+            if (!window.speechSynthesis) {
                 logDebug("[TTS] 娴忚鍣ㄤ笉鏀寔璇煶鍚堟垚");
                 appState.isSpeaking = false;
                 processTtsQueue();
+                return;
             }
+
+            // 浠呭湪鏈夎闊虫鍦ㄦ挱鏀炬椂鎵� cancel锛岄伩鍏嶄笉蹇呰鐨勪腑鏂�
+            if (window.speechSynthesis.speaking) {
+                logDebug("[鎾姤] cancel 褰撳墠鎾斁");
+                window.speechSynthesis.cancel();
+            }
+
+            var utterance = new SpeechSynthesisUtterance(text);
+            utterance.rate = 0.85;
+            utterance.volume = 1.0;
+            // Android WebView GC 淇濇姢锛氬瓨鍏ㄥ眬寮曠敤
+            _gCurrentUtterance = utterance;
+
+            utterance.onstart = function () {
+                logDebug("[鎾姤] onstart 瑙﹀彂");
+            };
+            utterance.onend = function () {
+                logDebug("[鎾姤] onend 瑙﹀彂");
+                _gCurrentUtterance = null;
+                appState.isSpeaking = false;
+                // 寤惰繜涓�涓嬭寮曟搸瀹屽叏閲婃斁
+                setTimeout(function () { processTtsQueue(); }, 200);
+            };
+            utterance.onerror = function (e) {
+                logDebug("[TTS閿欒] " + (e.error || "unknown"));
+                _gCurrentUtterance = null;
+                appState.isSpeaking = false;
+                setTimeout(function () { processTtsQueue(); }, 200);
+            };
+
+            // 纭繚璇煶鍖呭凡鍔犺浇 (Android 涓� getVoices 鍙兘鍒濆涓虹┖)
+            var voices = window.speechSynthesis.getVoices();
+            if (voices.length > 0) {
+                utterance.voice = voices[0];
+                logDebug("[鎾姤] 浣跨敤璇煶: " + voices[0].name + " lang=" + voices[0].lang);
+            } else {
+                logDebug("[鎾姤] getVoices 涓虹┖, 绛夊緟 voiceschanged 浜嬩欢");
+                var voicesLoaded = false;
+                var onVoicesChange = function () {
+                    if (voicesLoaded) return;
+                    voicesLoaded = true;
+                    var v2 = window.speechSynthesis.getVoices();
+                    if (v2.length > 0) {
+                        utterance.voice = v2[0];
+                        logDebug("[鎾姤] 璇煶鍔犺浇瀹屾垚: " + v2[0].name);
+                    }
+                    window.speechSynthesis.speak(utterance);
+                };
+                window.speechSynthesis.addEventListener('voiceschanged', onVoicesChange);
+                // 濡傛灉 1s 鍐呮病瑙﹀彂锛岀洿鎺� speak
+                setTimeout(function () {
+                    if (!voicesLoaded) {
+                        logDebug("[鎾姤] voiceschanged 瓒呮椂, 鐩存帴 speak");
+                        window.speechSynthesis.speak(utterance);
+                    }
+                }, 1000);
+                return;
+            }
+
+            window.speechSynthesis.speak(utterance);
+            logDebug("[鎾姤] speak 宸茶皟鐢�");
         }
 
         function speak(text) {
@@ -418,47 +595,179 @@
             processTtsQueue();
         }
 
-        // ================= 娓叉煋 =================
-        function renderMainContent() {
-            var main = document.getElementById("mainContent");
-            main.innerHTML = "";
+        // ================= 璇煶娴嬭瘯 =================
+        function testVoice() {
+            logDebug("[璇煶娴嬭瘯] ========== 寮�濮� ==========");
+            var testText = "娴嬭瘯璇煶鎾姤";
 
-            for (var i = 0; i < appState.columnTitles.length; i++) {
-                var titleHtml = '<div class="col-title-line">' + appState.columnTitles[i] + '</div>';
-                if (appState.columnSubTitles[i]) {
-                    titleHtml += '<div class="col-subtitle">' + appState.columnSubTitles[i] + '</div>';
+            // 1. 妫�娴� wowjoy 鍘熺敓鎺ュ彛
+            var hasWowjoy = (typeof wowjoy !== 'undefined');
+            logDebug("[璇煶娴嬭瘯] wowjoy 瀵硅薄瀛樺湪=" + hasWowjoy);
+            if (hasWowjoy) {
+                logDebug("[璇煶娴嬭瘯] wowjoy.speek 绫诲瀷=" + (typeof wowjoy.speek));
+                if (typeof wowjoy.speek === 'function') {
+                    try {
+                        wowjoy.speek(testText);
+                        logDebug("[璇煶娴嬭瘯] wowjoy.speek 宸茶皟鐢紝娉ㄦ剰鍚澶囧0闊�");
+                    } catch (e) {
+                        logDebug("[璇煶娴嬭瘯] wowjoy.speek 寮傚父: " + e.message);
+                    }
+                }
+            }
+
+            // 2. 妫�娴� SpeechSynthesis
+            var hasSpeech = (typeof window.speechSynthesis !== 'undefined');
+            logDebug("[璇煶娴嬭瘯] speechSynthesis 瀵硅薄瀛樺湪=" + hasSpeech);
+            
+            if (hasSpeech) {
+                var syn = window.speechSynthesis;
+                logDebug("[璇煶娴嬭瘯] speaking=" + syn.speaking + " pending=" + syn.pending + " paused=" + syn.paused);
+
+                var voices = syn.getVoices();
+                logDebug("[璇煶娴嬭瘯] getVoices 杩斿洖 " + voices.length + " 涓闊冲寘");
+                for (var v = 0; v < Math.min(voices.length, 5); v++) {
+                    logDebug("[璇煶娴嬭瘯]   璇煶[" + v + "] " + voices[v].name + " lang=" + voices[v].lang + " local=" + voices[v].localService);
                 }
 
-                var colClass = (i === 0) ? 'col-wide' : 'col-normal';
-                var colHtml = '<div class="column-box ' + colClass + '">' +
-                    '<div class="col-title">' + titleHtml + '</div>' +
-                    '<div class="patient-list" id="col-' + i + '"></div>' +
-                    '</div>';
-                main.insertAdjacentHTML("beforeend", colHtml);
+                // 濡傛灉 voices 涓虹┖锛岀瓑寰呭姞杞藉悗鍐嶆挱
+                function doSpeakTest() {
+                    var v2 = syn.getVoices();
+                    var u = new SpeechSynthesisUtterance(testText);
+                    u.rate = 0.85;
+                    u.volume = 1.0;
+                    if (v2.length > 0) {
+                        u.voice = v2[0];
+                        logDebug("[璇煶娴嬭瘯] 閫夋嫨璇煶: " + v2[0].name);
+                    }
+                    // GC 淇濇姢
+                    window._testUtterance = u;
+                    u.onstart = function() { logDebug("[璇煶娴嬭瘯] onstart 瑙﹀彂 鉁�"); };
+                    u.onend = function() { logDebug("[璇煶娴嬭瘯] onend 瑙﹀彂 鉁�"); window._testUtterance = null; };
+                    u.onerror = function(e) { logDebug("[璇煶娴嬭瘯] onerror: " + (e.error || "unknown")); window._testUtterance = null; };
+                    
+                    if (syn.speaking) {
+                        logDebug("[璇煶娴嬭瘯] 鏈夎闊虫鍦ㄦ挱鏀撅紝鍏� cancel");
+                        syn.cancel();
+                        setTimeout(function () { syn.speak(u); logDebug("[璇煶娴嬭瘯] speak 宸茶皟鐢�(寤惰繜)"); }, 300);
+                    } else {
+                        syn.speak(u);
+                        logDebug("[璇煶娴嬭瘯] speak 宸茶皟鐢�");
+                    }
+                }
+
+                if (voices.length === 0) {
+                    logDebug("[璇煶娴嬭瘯] 璇煶鍒楄〃涓虹┖锛岀瓑寰� voiceschanged...");
+                    var loaded = false;
+                    var handler = function () {
+                        if (loaded) return;
+                        loaded = true;
+                        logDebug("[璇煶娴嬭瘯] voiceschanged 瑙﹀彂, 璇煶鏁�=" + syn.getVoices().length);
+                        doSpeakTest();
+                    };
+                    syn.addEventListener('voiceschanged', handler);
+                    setTimeout(function () {
+                        if (!loaded) {
+                            logDebug("[璇煶娴嬭瘯] voiceschanged 瓒呮椂(2s), 寮哄埗娴嬭瘯");
+                            doSpeakTest();
+                        }
+                    }, 2000);
+                } else {
+                    doSpeakTest();
+                }
             }
+
+            if (!hasWowjoy && !hasSpeech) {
+                logDebug("[璇煶娴嬭瘯] 鏃犱换浣� TTS 寮曟搸鍙敤锛�");
+            }
+            logDebug("[璇煶娴嬭瘯] ========== 缁撴潫 ==========");
         }
 
-        function renderPatients() {
-            for (var i = 0; i < appState.columnTitles.length; i++) {
-                var col = document.getElementById("col-" + i);
-                if (col) col.innerHTML = "";
-            }
+        // ================= 娓叉煋锛氭爣棰樻鏋讹紙浠呭垵濮嬪寲涓�娆★級=================
+        function initLayout() {
+            logDebug("[甯冨眬] initLayout 寮�濮�, 鍒楁暟=" + appState.columnTitles.length);
+            var main = document.getElementById("mainContent");
+            if (!main) { logDebug("[甯冨眬] mainContent 鍏冪礌鏈壘鍒�!"); return; }
+            var html = '<div class="columns-row">';
+            var visibleCount = 0;
 
-            for (var c = 0; c < appState.patients.length; c++) {
-                var colData = appState.patients[c];
-                if (Array.isArray(colData)) {
-                    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>' : '';
-                        var itemHtml = '<div class="patient-item">' +
-                            '<span class="p-number">' + (pat.bookSeqNum || '') + '</span>' +
-                            '<span class="p-name">' + (pat.patName || '') + '</span>' +
-                            roomHtml +
-                            '</div>';
-                        col.insertAdjacentHTML("beforeend", itemHtml);
-                    }
+            for (var i = 0; i < appState.columnTitles.length; i++) {
+                // 椋熼亾鐢电敓鐞嗭細閫氳繃 SHOW_COL_ESOPHAGEAL 鎺у埗鏄鹃殣
+                if (i === 3 && !SHOW_COL_ESOPHAGEAL) {
+                    logDebug("[甯冨眬] 鍒�" + i + " 椋熼亾鐢电敓鐞� 宸查殣钘�");
+                    continue;
+                }
+
+                var subtitle = appState.columnSubTitles[i] || "&nbsp;";
+                // 涓�琛屼袱涓殑鍒楃敤 col-wide锛屽惁鍒� col-normal
+                var isTwoPerRow = TWO_PER_ROW_COLS.indexOf(i) !== -1;
+                var colClass = isTwoPerRow ? 'col-wide' : 'col-normal';
+                var rowClass = isTwoPerRow ? 'two-per-row' : 'one-per-row';
+                logDebug("[甯冨眬] 鍒�" + i + " " + appState.columnTitles[i] + " class=" + colClass + " row=" + rowClass);
+                html += '<div class="column-box ' + colClass + '">' +
+                    '<div class="col-title">' +
+                    '<div class="col-title-line">' + appState.columnTitles[i] + '</div>' +
+                    '<div class="col-subtitle">' + subtitle + '</div>' +
+                    '</div>' +
+                    '<div class="patient-list ' + rowClass + '" id="col-' + i + '"></div>' +
+                    '</div>';
+                visibleCount++;
+            }
+            html += '</div>';
+            main.innerHTML = html;
+            logDebug("[甯冨眬] initLayout 瀹屾垚, 鍙鍒�=" + visibleCount);
+        }
+
+        // ================= 娓叉煋锛氭偅鑰呭垪琛紙diff 鏇存柊锛屽彧鏀瑰彉鍖栫殑鍒楋級=================
+        function desensitizeName(name) {
+            if (!name || name.length < 2) return name || '';
+            return name.charAt(0) + '*' + name.substring(2);
+        }
+
+        function isMissedStatus(status) {
+            var s = parseInt(status, 10);
+            return s === 3 || s === 5 || s === 7;
+        }
+
+        function buildColumnHtml(colData) {
+            if (!Array.isArray(colData) || colData.length === 0) return "";
+            var h = "";
+            for (var p = 0; p < colData.length; p++) {
+                var pat = colData[p];
+                var missedClass = isMissedStatus(pat.status) ? ' missed' : '';
+                var roomHtml = (!isMissedStatus(pat.status) && pat.roomName) ? '<span class="p-room">' + pat.roomName + '</span>' : '';
+                h += '<div class="patient-item' + missedClass + '">' +
+                    '<span class="p-number">' + (pat.seqNum || '') + '</span>' +
+                    '<span class="p-name-wrap"><span class="p-name">' + desensitizeName(pat.patName) + '</span>' + roomHtml +
+                    (missedClass ? '<span class="p-missed-tag">杩囧彿</span>' : '') + '</span>' +
+                    '</div>';
+            }
+            return h;
+        }
+        function isSameColumn(a, b) {
+            if (!Array.isArray(a) || !Array.isArray(b)) return a === b;
+            if (a.length !== b.length) return false;
+            for (var i = 0; i < a.length; i++) {
+                if (a[i].patId !== b[i].patId) return false;
+                if (a[i].roomName !== b[i].roomName) return false;
+                if (parseInt(a[i].status, 10) !== parseInt(b[i].status, 10)) return false;
+            }
+            return true;
+        }
+
+        function updatePatients(dataList) {
+            for (var i = 0; i < appState.columnTitles.length; i++) {
+                var newData = (dataList && dataList[i]) ? dataList[i] : [];
+                var oldData = appState.patients[i];
+
+                // diff锛氭暟鎹湭鍙樺寲鍒欒烦杩囪鍒� DOM 鎿嶄綔
+                if (isSameColumn(oldData, newData)) continue;
+
+                var col = document.getElementById("col-" + i);
+                if (col) {
+                    col.innerHTML = buildColumnHtml(newData);
+                    logDebug("[鏇存柊] 鍒�" + i + " " + appState.columnTitles[i] + " 鍒锋柊 " + newData.length + " 浜�");
+                } else {
+                    logDebug("[鏇存柊] 鍒�" + i + " DOM涓嶅瓨鍦�(鍙兘宸查殣钘�)");
                 }
             }
         }
@@ -485,10 +794,10 @@
                             }
                             if (!oldPat || !oldPat.roomName) {
                                 var repeatText = "";
-                                for (var r = 0; r < appState.callRepeatTimes; r++) {
-                                    repeatText += "璇� " + newPat.bookSeqNum + " 鍙� " + newPat.patName + " 鍒� " + newPat.roomName + " 灏辫瘖銆�";
-                                }
-                                speak(repeatText);
+                                repeatText += "璇� " + newPat.seqNum + " 鍙� " + newPat.patName + " 鍒� " + newPat.roomName + " 灏辫瘖銆�";
+                                for (var r = 0; r < CALL_TIMES; r++) {
+                                    speak(repeatText);
+                                }                                
                                 appState.spokenPatients[newPat.patId] = true;
                                 logDebug("[鎺掗槦] " + newPat.patName + " -> " + newPat.roomName);
                             }
@@ -501,22 +810,28 @@
         // ================= 鏁版嵁鑾峰彇 =================
         function fetchQueueData() {
             var url = appState.apiBaseUrl + "/ecg/screen/big-screen-data";
+            logDebug("[璇锋眰] GET " + url);
             $.ajax({
                 url: url,
                 type: "GET",
                 dataType: "json",
                 timeout: 5000,
                 success: function (res) {
+                    logDebug("[鍝嶅簲] code=" + (res ? res.code : "null"));
                     var dataList = [];
                     if (res && res.code === 0 && res.data) {
                         for (var i = 0; i < appState.columnTitles.length; i++) {
                             var key = i.toString();
                             dataList[i] = (res.data[key] && Array.isArray(res.data[key])) ? res.data[key] : [];
+                            logDebug("[鏁版嵁] 鍒�" + i + " " + appState.columnTitles[i] + " 鎮h�呮暟=" + dataList[i].length);
                         }
+                    } else {
+                        logDebug("[鍝嶅簲] 寮傚父 code=" + (res ? res.code : "null") + " msg=" + (res ? res.msg : ""));
                     }
+                    // 鍏� diff 鏇存柊 DOM锛屽啀鎾姤锛屾渶鍚庝繚瀛樻暟鎹敤浜庝笅娆″姣�
+                    updatePatients(dataList);
                     checkAndSpeakNewRooms(appState.patients, dataList);
                     appState.patients = dataList;
-                    renderPatients();
 
                     // Android 6 WebView 閫氬父涓嶆敮鎸� performance.memory锛屽畨鍏ㄥ畧鍗�
                     try {
@@ -524,43 +839,57 @@
                             var usedMB = (window.performance.memory.usedJSHeapSize / 1048576).toFixed(2);
                             logDebug("[鍐呭瓨] " + usedMB + " MB");
                         }
-                    } catch (e) {}
+                    } catch (e) { }
                 },
                 error: function (err) {
-                    logDebug("[璇锋眰澶辫触] " + (err.statusText || "缃戠粶閿欒"));
+                    logDebug("[璇锋眰澶辫触] status=" + err.status + " " + (err.statusText || "缃戠粶閿欒") + " url=" + url);
                 }
             });
         }
 
         // ================= 鍒濆鍖� =================
         function onReady() {
-            renderMainContent();
+            logDebug("[鍒濆鍖朷 onReady 寮�濮�");
+            logDebug("[鐜] speechSynthesis=" + (typeof window.speechSynthesis !== 'undefined') + " wowjoy=" + (typeof wowjoy !== 'undefined'));
+            try {
+                initLayout();
+                logDebug("[鍒濆鍖朷 initLayout 瀹屾垚");
+            } catch (e) { logDebug("[鍒濆鍖朷 initLayout 寮傚父: " + e.message); }
             updateHeaderTime();
             setInterval(updateHeaderTime, 1000);
-            fetchQueueData();
+            try {
+                fetchQueueData();
+                logDebug("[鍒濆鍖朷 棣栨 fetchQueueData 宸插彂璧�");
+            } catch (e) { logDebug("[鍒濆鍖朷 fetchQueueData 寮傚父: " + e.message); }
             appState.pollTimer = setInterval(fetchQueueData, 5000);
-            logDebug("[绯荤粺] 鍚姩瀹屾垚 - Android 6.0.1");
+            logDebug("[绯荤粺] 鍚姩瀹屾垚 - Android 6.0.1 杞闂撮殧5s");
         }
 
         function updateHeaderTime() {
             var now = new Date();
             var weekDays = ["鏄熸湡鏃�", "鏄熸湡涓�", "鏄熸湡浜�", "鏄熸湡涓�", "鏄熸湡鍥�", "鏄熸湡浜�", "鏄熸湡鍏�"];
             function padZero(num) { return num < 10 ? '0' + num : '' + num; }
-            var dateStr = now.getFullYear() + "骞�" + padZero(now.getMonth() + 1) + "鏈�" + padZero(now.getDate()) + "鏃� " + weekDays[now.getDay()];
+            var dateStr = now.getFullYear() + "骞�" + padZero(now.getMonth() + 1) + "鏈�" + padZero(now.getDate()) + "鏃�";
+            var weekStr = weekDays[now.getDay()];
             var timeStr = padZero(now.getHours()) + ":" + padZero(now.getMinutes());
             var el = document.getElementById("headerTime");
-            if (el) el.textContent = dateStr + " " + timeStr;
+            // 鏄熸湡鍜屾棩鏈熷悇鍗犱竴琛屽湪宸︼紝鏃堕棿澶у瓧鍦ㄥ彸璺ㄤ袱琛�
+            if (el) el.innerHTML = '<div class="time-info-inner"><div class="time-left"><div>' + weekStr + '</div><div>' + dateStr + '</div></div><div class="time-right"><span class="time-clock">' + timeStr + '</span></div></div>';
         }
 
         // 鍏煎 DOM ready锛圓ndroid 6 鏌愪簺 WebView 鍙兘娌℃湁 $锛�
+        logDebug("[鍏ュ彛] readyState=" + document.readyState + " jQuery=" + (typeof $ !== 'undefined'));
         if (typeof $ !== 'undefined') {
+            logDebug("[鍏ュ彛] 浣跨敤 jQuery.ready");
             $(document).ready(onReady);
         } else if (document.readyState === 'complete' || document.readyState === 'interactive') {
+            logDebug("[鍏ュ彛] DOM宸插氨缁�, setTimeout瑙﹀彂");
             setTimeout(onReady, 1);
         } else {
+            logDebug("[鍏ュ彛] 鐩戝惉 DOMContentLoaded");
             document.addEventListener('DOMContentLoaded', onReady);
         }
     </script>
 </body>
 
-</html>
+</html>
\ No newline at end of file

--
Gitblit v1.9.3