| <!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"> | 
|   <title>金华人民医院叫号系统</title> | 
|   <style> | 
|     * { | 
|       margin: 0; | 
|       padding: 0; | 
|       box-sizing: border-box; | 
|       font-family: 'Helvetica Neue', Arial, sans-serif; | 
|       -webkit-tap-highlight-color: transparent; | 
|     } | 
|   | 
|     body { | 
|       background: linear-gradient(135deg, #e6f0f8, #d9e4f0); | 
|       color: #333; | 
|       line-height: 1.5; | 
|       overflow: hidden; | 
|       height: 100vh; | 
|       touch-action: manipulation; | 
|     } | 
|   | 
|     #app { | 
|       height: 100%; | 
|       display: flex; | 
|       flex-direction: column; | 
|       max-width: 100%; | 
|       overflow: hidden; | 
|       padding: 10px; | 
|     } | 
|   | 
|     .search-bar { | 
|       background: rgba(255, 255, 255, 0.8); | 
|       border-radius: 16px; | 
|       padding: 12px 15px; | 
|       margin-bottom: 10px; | 
|       box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); | 
|       display: flex; | 
|       align-items: center; | 
|     } | 
|   | 
|     .search-input { | 
|       flex: 1; | 
|       border: none; | 
|       background: rgba(240, 244, 249, 0.7); | 
|       border-radius: 12px; | 
|       padding: 8px 12px; | 
|       font-size: 0.9rem; | 
|       outline: none; | 
|       color: #4a5568; | 
|     } | 
|   | 
|     .search-btn { | 
|       margin-left: 8px; | 
|       background: #5b8cff; | 
|       color: white; | 
|       border: none; | 
|       border-radius: 12px; | 
|       padding: 8px 15px; | 
|       font-size: 0.9rem; | 
|       cursor: pointer; | 
|       transition: all 0.3s; | 
|     } | 
|   | 
|     .search-btn:hover { | 
|       background: #3a7bff; | 
|     } | 
|   | 
|     .header { | 
|       background: rgba(255, 255, 255, 0.8); | 
|       border-radius: 16px; | 
|       padding: 12px 15px; | 
|       margin-bottom: 10px; | 
|       box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | 
|       display: flex; | 
|       flex-direction: column; | 
|       align-items: center; | 
|     } | 
|   | 
|     .clinic-title { | 
|       font-size: 1.4rem; | 
|       font-weight: bold; | 
|       text-align: center; | 
|       margin-bottom: 5px; | 
|       color: #4a7dff; | 
|       text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); | 
|     } | 
|   | 
|     .clinic-info { | 
|       display: flex; | 
|       justify-content: space-between; | 
|       width: 100%; | 
|       font-size: 0.9rem; | 
|       color: #555; | 
|     } | 
|   | 
|     .room-name { | 
|       background: rgba(91, 140, 255, 0.1); | 
|       padding: 4px 10px; | 
|       border-radius: 20px; | 
|       min-width: 120px; | 
|       text-align: center; | 
|       color: #4a7dff; | 
|     } | 
|   | 
|     .screen-type { | 
|       background: rgba(91, 140, 255, 0.1); | 
|       padding: 4px 10px; | 
|       border-radius: 20px; | 
|       color: #4a7dff; | 
|     } | 
|   | 
|     .main-content { | 
|       flex: 1; | 
|       display: flex; | 
|       flex-direction: column; | 
|       gap: 12px; | 
|       overflow: hidden; | 
|     } | 
|   | 
|     .panel { | 
|       background: rgba(255, 255, 255, 0.95); | 
|       border-radius: 14px; | 
|       padding: 12px; | 
|       box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); | 
|       display: flex; | 
|       flex-direction: column; | 
|       overflow: hidden; | 
|       flex: 1; | 
|       border: 1px solid rgba(0, 0, 0, 0.05); | 
|     } | 
|   | 
|     .panel-header { | 
|       background: linear-gradient(90deg, #a8c4ff, #c0d3ff); | 
|       color: #2c3e50; | 
|       padding: 8px 12px; | 
|       border-radius: 8px; | 
|       margin-bottom: 10px; | 
|       font-size: 1rem; | 
|       font-weight: bold; | 
|       text-align: center; | 
|     } | 
|   | 
|     .patient-list { | 
|       flex: 1; | 
|       overflow-y: auto; | 
|       -webkit-overflow-scrolling: touch; | 
|     } | 
|   | 
|     .patient-item { | 
|       display: flex; | 
|       align-items: center; | 
|       padding: 10px 8px; | 
|       border-bottom: 1px solid #eee; | 
|       transition: all 0.3s; | 
|     } | 
|   | 
|     .patient-item:last-child { | 
|       border-bottom: none; | 
|     } | 
|   | 
|     .patient-item.warning { | 
|       background-color: #fdf6ec; | 
|     } | 
|   | 
|     .patient-item.in-progress { | 
|       background-color: #f0f9eb; | 
|     } | 
|   | 
|     .patient-item.completed { | 
|       background-color: #f4f4f5; | 
|     } | 
|   | 
|     .patient-info { | 
|       display: flex; | 
|       flex: 1; | 
|       min-width: 0; | 
|     } | 
|   | 
|     .patient-number { | 
|       width: 80px; | 
|       font-weight: bold; | 
|       color: #5b8cff; | 
|       font-size: 0.95rem; | 
|       overflow: hidden; | 
|       text-overflow: ellipsis; | 
|     } | 
|   | 
|     .patient-name { | 
|       width: 100px; | 
|       font-size: 0.95rem; | 
|       overflow: hidden; | 
|       text-overflow: ellipsis; | 
|     } | 
|   | 
|     .patient-check-type { | 
|       flex: 1; | 
|       font-size: 0.95rem; | 
|       overflow: hidden; | 
|       text-overflow: ellipsis; | 
|     } | 
|   | 
|     .patient-status { | 
|       width: 70px; | 
|       font-size: 0.8rem; | 
|       font-weight: bold; | 
|       text-align: center; | 
|       padding: 3px 8px; | 
|       border-radius: 10px; | 
|     } | 
|   | 
|     .status-waiting { | 
|       background-color: #fdf6ec; | 
|       color: #e6a23c; | 
|     } | 
|   | 
|     .status-in-progress { | 
|       background-color: #f0f9eb; | 
|       color: #67c23a; | 
|     } | 
|   | 
|     .status-completed { | 
|       background-color: #f4f4f5; | 
|       color: #909399; | 
|     } | 
|   | 
|     .patient-bed { | 
|       width: 60px; | 
|       font-size: 0.85rem; | 
|       text-align: right; | 
|       color: #666; | 
|     } | 
|   | 
|     .footer { | 
|       background: rgba(255, 255, 255, 0.8); | 
|       border-radius: 16px; | 
|       padding: 12px 15px; | 
|       margin-top: 10px; | 
|       box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1); | 
|       border: 1px solid rgba(0, 0, 0, 0.05); | 
|     } | 
|   | 
|     .announcement { | 
|       background: rgba(91, 140, 255, 0.1); | 
|       padding: 8px 15px; | 
|       border-radius: 20px; | 
|       font-size: 0.9rem; | 
|       text-align: center; | 
|       margin-bottom: 12px; | 
|       min-height: 20px; | 
|       color: #4a7dff; | 
|     } | 
|   | 
|   | 
|   | 
|     .controls { | 
|       display: flex; | 
|       justify-content: space-between; | 
|       gap: 8px; | 
|     } | 
|   | 
|     .control-btn { | 
|       padding: 8px 5px; | 
|       background: rgba(91, 140, 255, 0.1); | 
|       flex: 1; | 
|       /* 平均分配宽度 */ | 
|       min-width: 0; | 
|       /* 防止内容溢出 */ | 
|       border: none; | 
|       border-radius: 12px; | 
|       color: #4a7dff; | 
|       font-size: 0.85rem; | 
|       cursor: pointer; | 
|       transition: all 0.3s; | 
|       display: flex; | 
|       flex-direction: column; | 
|       align-items: center; | 
|       justify-content: center; | 
|     } | 
|   | 
|     .control-btn i { | 
|       font-size: 1.2rem; | 
|       margin-bottom: 3px; | 
|     } | 
|   | 
|     .control-btn:hover { | 
|       background: rgba(91, 140, 255, 0.2); | 
|     } | 
|   | 
|     .control-btn:active { | 
|       transform: scale(0.95); | 
|     } | 
|   | 
|     .empty-state { | 
|       text-align: center; | 
|       color: #999; | 
|       padding: 20px; | 
|       font-size: 0.9rem; | 
|     } | 
|   | 
|     /* 滚动条样式 */ | 
|     .patient-list::-webkit-scrollbar { | 
|       width: 5px; | 
|     } | 
|   | 
|     .patient-list::-webkit-scrollbar-track { | 
|       background: #f1f1f1; | 
|       border-radius: 4px; | 
|     } | 
|   | 
|     .patient-list::-webkit-scrollbar-thumb { | 
|       background: #c0c4cc; | 
|       border-radius: 4px; | 
|     } | 
|   | 
|     .patient-list::-webkit-scrollbar-thumb:hover { | 
|       background: #909399; | 
|     } | 
|   | 
|     /* 动画效果 */ | 
|     @keyframes pulse { | 
|       0% { | 
|         transform: scale(1); | 
|       } | 
|   | 
|       50% { | 
|         transform: scale(1.05); | 
|       } | 
|   | 
|       100% { | 
|         transform: scale(1); | 
|       } | 
|     } | 
|   | 
|     .pulse { | 
|       animation: pulse 2s infinite; | 
|     } | 
|   | 
|     /* 响应式调整 */ | 
|     @media (max-width: 480px) { | 
|       .header { | 
|         padding: 10px 12px; | 
|       } | 
|   | 
|       .clinic-title { | 
|         font-size: 1.2rem; | 
|       } | 
|   | 
|       .clinic-info { | 
|         font-size: 0.8rem; | 
|       } | 
|   | 
|       .panel { | 
|         padding: 10px; | 
|       } | 
|   | 
|       .panel-header { | 
|         font-size: 0.9rem; | 
|         padding: 6px 10px; | 
|       } | 
|   | 
|       .patient-item { | 
|         padding: 8px 6px; | 
|       } | 
|   | 
|       .patient-number, | 
|       .patient-name { | 
|         width: 60px; | 
|         font-size: 0.9rem; | 
|       } | 
|   | 
|       .patient-status { | 
|         width: 60px; | 
|         font-size: 0.7rem; | 
|       } | 
|   | 
|       .patient-bed { | 
|         width: 50px; | 
|       } | 
|     } | 
|   | 
|     @media (max-height: 600px) { | 
|       .header { | 
|         padding: 8px 10px; | 
|       } | 
|   | 
|       .panel { | 
|         padding: 8px; | 
|       } | 
|   | 
|       .patient-item { | 
|         padding: 6px 4px; | 
|       } | 
|     } | 
|   </style> | 
| </head> | 
|   | 
| <body> | 
|   <div id="app"> | 
|     <div class="search-bar"> | 
|       <input class="search-input" type="text" placeholder="请输入房间号查询" id="searchRoomInput"> | 
|       <button class="search-btn" id="searchRoomBtn">查询</button> | 
|     </div> | 
|   | 
|     <div class="header"> | 
|       <div class="clinic-title">心电图诊间叫号系统</div> | 
|       <div class="clinic-info"> | 
|         <div class="room-name" id="roomName">诊间加载中...</div> | 
|         <div class="screen-type" id="screenType">模式:加载中...</div> | 
|       </div> | 
|     </div> | 
|   | 
|     <div class="main-content"> | 
|       <div class="panel"> | 
|         <div class="panel-header">检查队列</div> | 
|         <div class="patient-list" id="checkPatientList"> | 
|           <div class="empty-state"> | 
|             暂无等待检查的患者 | 
|           </div> | 
|         </div> | 
|       </div> | 
|     </div> | 
|   | 
|     <div class="footer"> | 
|       <div class="announcement" id="announcementText"> | 
|         系统运行中... | 
|       </div> | 
|       <div class="controls"> | 
|         <button class="control-btn pulse" id="callBtn"> | 
|           <i>📢</i> | 
|           <span>叫号</span> | 
|         </button> | 
|         <button class="flex-1 control-btn" id="testSpeakBtn"> | 
|           <i>🔊</i> | 
|           <span>测试播音</span> | 
|         </button> | 
|         <button class="flex-1 control-btn" id="changeRoomBtn"> | 
|           <i>🔄</i> | 
|           <span>切换诊间</span> | 
|         </button> | 
|       </div> | 
|     </div> | 
|   </div> | 
|   | 
|   <!-- 使用本地 jQuery 文件 --> | 
|   <script src="./static/jquery.min.js"></script> | 
|   <script> | 
|     // 应用状态 | 
|     var appState = { | 
|       roomProfile: { | 
|         roomName: '心电图诊室 01', | 
|         callingScreenType: 40 | 
|       }, | 
|       checkRelatedPatientList: [], | 
|       curSpeakPat: null, | 
|       roomId: 1, | 
|       timer: null, | 
|       speechSynthesis: window.speechSynthesis || null, | 
|       apiBaseUrl: 'http://10.0.2.193/admin-api'  | 
|       // apiBaseUrl: 'http://localhost:48080/admin-api'  | 
|     }; | 
|   | 
|     // 页面加载完成后初始化 | 
|     // 页面加载完成后初始化 | 
|     document.addEventListener('DOMContentLoaded', function () { | 
|       // 初始化事件监听 | 
|       document.getElementById('searchRoomBtn').addEventListener('click', searchRoom); | 
|       document.getElementById('callBtn').addEventListener('click', initiateSpeak); | 
|       document.getElementById('testSpeakBtn').addEventListener('click', function () { | 
|         speak('欢迎使用诊间叫号系统'); | 
|       }); | 
|       document.getElementById('changeRoomBtn').addEventListener('click', changeRoom); | 
|   | 
|       // 初始化数据 | 
|       getRoomByIp(); | 
|       startScrolling(); | 
|   | 
|       // 初始化语音合成 | 
|       if (appState.speechSynthesis) { | 
|         appState.speechSynthesis.onend = onSpeachEndEvent; | 
|       } | 
|     }); | 
|   | 
|     // 搜索房间 | 
|     function searchRoom() { | 
|       var searchInput = document.getElementById('searchRoomInput').value.trim(); | 
|       if (!searchInput) { | 
|         updateAnnouncement('请输入有效的房间号'); | 
|         return; | 
|       } | 
|   | 
|       updateAnnouncement('正在查询 ' + searchInput + ' 房间信息...'); | 
|   | 
|       var xhr = new XMLHttpRequest(); | 
|       xhr.open('GET', appState.apiBaseUrl + '/clinic/room/get-room-by-ip?roomId=' + encodeURIComponent(searchInput), true); | 
|       xhr.onreadystatechange = function () { | 
|         if (xhr.readyState === 4) { | 
|           if (xhr.status === 200) { | 
|             try { | 
|               var response = JSON.parse(xhr.responseText); | 
|               appState.roomProfile = response.data || response; | 
|               updateRoomInfo(); | 
|               updateAnnouncement('已加载 ' + appState.roomProfile.roomName + ' 信息'); | 
|               getList(); // 获取该房间的患者列表 | 
|             } catch (e) { | 
|               updateAnnouncement('解析响应数据失败'); | 
|               console.error('解析响应失败:', e); | 
|             } | 
|           } else { | 
|             updateAnnouncement('查询房间失败: ' + xhr.status); | 
|             console.error('查询房间失败:', xhr.status); | 
|           } | 
|         } | 
|       }; | 
|       xhr.send(); | 
|     } | 
|   | 
|     function getRoomByIp() { | 
|       var searchInput = document.getElementById('searchRoomInput').value.trim(); | 
|   | 
|       var xhr = new XMLHttpRequest(); | 
|       xhr.open('GET', appState.apiBaseUrl + '/ecg/screen/room-screen-data?roomId=' + encodeURIComponent(searchInput), true); | 
|       xhr.onreadystatechange = function () { | 
|         if (xhr.readyState === 4) { | 
|           if (xhr.status === 200) { | 
|             try { | 
|               var response = JSON.parse(xhr.responseText); | 
|               appState.roomProfile = response.data || response; | 
|               updateRoomInfo(); | 
|   | 
|               // 检查是否有需要叫号的患者 | 
|               if (response.data && response.data.called === 0) { | 
|                 appState.curSpeakPat = response.data; | 
|                 speak('请' + response.data.patName + '到' + appState.roomProfile.roomName + '装机'); | 
|               } | 
|             } catch (e) { | 
|               console.error('解析响应失败:', e); | 
|               // 使用模拟数据作为后备 | 
|               appState.roomProfile = { | 
|                 roomName: '心电图诊室 ' + (appState.roomId + 1), | 
|                 callingScreenType: [40, 10, 30][appState.roomId % 3] | 
|               }; | 
|               updateRoomInfo(); | 
|             } | 
|           } else { | 
|             console.error('获取房间信息失败:', xhr.status); | 
|             // 使用模拟数据作为后备 | 
|             appState.roomProfile = { | 
|               roomName: '心电图诊室 ' + (appState.roomId + 1), | 
|               callingScreenType: [40, 10, 30][appState.roomId % 3] | 
|             }; | 
|             updateRoomInfo(); | 
|           } | 
|         } | 
|       }; | 
|       xhr.send(); | 
|     } | 
|   | 
|     // 更新房间信息显示 | 
|     function updateRoomInfo() { | 
|       $('#roomName').text(appState.roomProfile.roomName || '诊间加载中...'); | 
|   | 
|       var screenTypeText = '未知模式'; | 
|       switch (appState.roomProfile.callingScreenType) { | 
|         case 10: | 
|         case 20: | 
|           screenTypeText = '仅检查队列'; | 
|           break; | 
|         case 30: | 
|           screenTypeText = '仅装机队列'; | 
|           break; | 
|         case 40: | 
|         case 50: | 
|           screenTypeText = '双队列模式'; | 
|           break; | 
|       } | 
|   | 
|       $('#screenType').text('模式:' + screenTypeText); | 
|     } | 
|   | 
|     // 获取患者列表 | 
|     function getList() { | 
|       var searchInput = document.getElementById('searchRoomInput').value.trim(); | 
|   | 
|       var xhr = new XMLHttpRequest(); | 
|       xhr.open('GET', appState.apiBaseUrl + '/ecg/screen/room-screen-data?roomId=' + encodeURIComponent(searchInput), true); | 
|       xhr.onreadystatechange = function () { | 
|         if (xhr.readyState === 4) { | 
|           if (xhr.status === 200) { | 
|             try { | 
|               var response = JSON.parse(xhr.responseText); | 
|               if (response.data) { | 
|                 appState.checkRelatedPatientList = response.data[1] || []; | 
|               } | 
|               updatePatientList(); | 
|             } catch (e) { | 
|               console.error('解析患者列表失败:', e); | 
|             } | 
|           } else { | 
|             console.error('获取患者列表失败:', xhr.status); | 
|           } | 
|         } | 
|       }; | 
|       xhr.send(); | 
|     } | 
|   | 
|     // 开始定时刷新 | 
|     function startScrolling() { | 
|       getList(); | 
|       appState.timer = setInterval(getList, 5000); | 
|     } | 
|   | 
|     // 更新患者列表显示 | 
|     function updatePatientList() { | 
|       var $list = $('#checkPatientList'); | 
|       $list.empty(); | 
|   | 
|       if (appState.checkRelatedPatientList.length === 0) { | 
|         $list.append('<div class="empty-state">暂无等待检查的患者</div>'); | 
|         return; | 
|       } | 
|   | 
|       for (var i = 0; i < appState.checkRelatedPatientList.length; i++) { | 
|         var patient = appState.checkRelatedPatientList[i]; | 
|         var statusClass = getStatusClass(patient.status); | 
|   | 
|         var $item = $('<div class="patient-item ' + statusClass + '">' + | 
|           '<div class="patient-info">' + | 
|           '<div class="patient-number">' + getSeqPrefix(patient) + patient.bedNo + '</div>' + | 
|           '<div class="patient-name">' + nameDesensitize(patient.patName) + '</div>' + | 
|           '<div class="patient-check-type">' + getCheckTypeName(patient.bookCheckType) + '</div>' + | 
|           '<div class="patient-status status-' + statusClass + '">' + | 
|           queueStatusConvert(patient.status) + | 
|           '</div>' + | 
|           '</div>' + | 
|           '</div>'); | 
|   | 
|         $list.append($item); | 
|       } | 
|     } | 
|   | 
|     // 更新公告信息 | 
|     function updateAnnouncement(text) { | 
|       $('#announcementText').text(text); | 
|     } | 
|   | 
|     // 工具函数 | 
|     function nameDesensitize(patName) { | 
|       if (!patName) return ''; | 
|       if (patName.length === 2) { | 
|         return patName.substring(0, 1) + '*'; | 
|       } else if (patName.length === 3) { | 
|         return patName.substring(0, 1) + '*' + patName.substring(2, 3); | 
|       } else if (patName.length > 3) { | 
|         return patName.substring(0, 1) + '*' + '*' + patName.substring(3, patName.length); | 
|       } | 
|       return patName; | 
|     } | 
|   | 
|     function getStatusClass(status) { | 
|       if (status === 10 || status === 40 || status === 33 || status === 20 || status === 10) return 'waiting'; | 
|       if (status === 30) return 'in-progress'; | 
|       if (status === 7 || status === 3 || status === 5) return 'completed'; | 
|       return ''; | 
|     } | 
|   | 
|     function queueStatusConvert(status) { | 
|       var statusMap = { | 
|         3: '已过号-排队', | 
|         5: '已过号', | 
|         7: '已过号-安装', | 
|         10: '排队中', | 
|         12: '亲和', | 
|         13: '亲和-安装', | 
|         15: '已召回', | 
|         20: '候诊中', | 
|         30: '就诊中', | 
|         33: '已领用', | 
|         34: '已召回-安装', | 
|         36: '安装中', | 
|         40: '已就诊' | 
|       }; | 
|       return statusMap[status] || '未知状态'; | 
|     } | 
|   | 
|     function getCheckTypeName(type) { | 
|       var types = { | 
|         1: '常规心电图', | 
|         2: '动态心电图', | 
|         3: '运动试验', | 
|         4: '心电监护' | 
|       }; | 
|       return types[type] || '未知检查'; | 
|     } | 
|   | 
|     function getSeqPrefix(patient) { | 
|       var types = { | 
|         1: 'A001', | 
|         2: 'A002', | 
|         3: 'A003', | 
|         4: 'A004' | 
|       }; | 
|       return types[patient.bookCheckType] || ''; | 
|     } | 
|   | 
|     // 叫号功能 | 
|     function initiateSpeak() { | 
|       // 找出等待中的患者 | 
|       var waitingPatients = []; | 
|       for (var i = 0; i < appState.checkRelatedPatientList.length; i++) { | 
|         if (appState.checkRelatedPatientList[i].status === 5 || appState.checkRelatedPatientList[i].called === 0) { | 
|           waitingPatients.push(appState.checkRelatedPatientList[i]); | 
|         } | 
|       } | 
|   | 
|       if (waitingPatients.length === 0) { | 
|         updateAnnouncement('当前没有等待装机的患者'); | 
|         return; | 
|       } | 
|   | 
|       var patient = waitingPatients[0]; | 
|   | 
|       // 调用API标记为已叫号 | 
|       var xhr = new XMLHttpRequest(); | 
|       xhr.open('POST', appState.apiBaseUrl + '/ecg/screen/mark-patient-called', true); | 
|       xhr.setRequestHeader('Content-Type', 'application/json'); | 
|       xhr.onreadystatechange = function () { | 
|         if (xhr.readyState === 4) { | 
|           if (xhr.status === 200) { | 
|             appState.curSpeakPat = { | 
|               patName: patient.patName, | 
|               roomName: appState.roomProfile.roomName | 
|             }; | 
|             speak('请' + patient.patName + '到' + appState.roomProfile.roomName + '装机'); | 
|           } else { | 
|             updateAnnouncement('叫号失败: ' + xhr.status); | 
|             console.error('叫号失败:', xhr.status); | 
|           } | 
|         } | 
|       }; | 
|       xhr.send(JSON.stringify({ patientId: patient.id })); | 
|     } | 
|   | 
|     // 语音播报 | 
|     function speak(msg) { | 
|       updateAnnouncement('正在呼叫: ' + msg); | 
|   | 
|       if (!appState.speechSynthesis) { | 
|         console.warn("当前浏览器不支持语音合成"); | 
|         return; | 
|       } | 
|   | 
|       // 取消当前正在进行的语音 | 
|       appState.speechSynthesis.cancel(); | 
|   | 
|       var speech = new SpeechSynthesisUtterance(); | 
|       speech.text = msg + "。。。" + msg + "。。。" + msg; | 
|       speech.pitch = 1; | 
|       speech.rate = 0.9; | 
|       speech.volume = 1; | 
|       speech.lang = 'zh-CN'; | 
|   | 
|       appState.speechSynthesis.speak(speech); | 
|     } | 
|   | 
|     // 语音结束事件 | 
|     function onSpeachEndEvent(event) { | 
|       appState.curSpeakPat = null; | 
|       updateAnnouncement('系统运行中...'); | 
|     } | 
|   | 
|     // 切换房间 | 
|     function changeRoom() { | 
|       appState.roomId = (appState.roomId + 1) % 3; | 
|       getRoomByIp(); | 
|       updateAnnouncement('正在切换诊间...'); | 
|     } | 
|   </script> | 
| </body> | 
|   | 
| </html> |