<!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>
|
* {
|
box-sizing: border-box;
|
margin: 0;
|
padding: 0;
|
}
|
|
body {
|
font-family: "Microsoft YaHei", "Helvetica Neue", sans-serif;
|
background-color: #f4f5f7;
|
height: 100vh;
|
display: flex;
|
flex-direction: column;
|
overflow: hidden;
|
color: #333;
|
}
|
|
/* ================= 顶部 Header ================= */
|
.header {
|
background: #ffffff;
|
padding: 15px 20px;
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
|
z-index: 10;
|
text-align: center;
|
border-bottom: 1px solid #eee;
|
flex-shrink: 0;
|
}
|
|
.header-top {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
}
|
|
.logo {
|
height: 45px;
|
}
|
|
.time-box {
|
display: flex;
|
flex-direction: column;
|
line-height: 1.2;
|
text-align: right;
|
}
|
|
.week-day,
|
.full-date {
|
font-size: 16px;
|
color: #666;
|
}
|
|
.header-right {
|
display: flex;
|
align-items: center;
|
gap: 15px;
|
}
|
|
.clock {
|
font-size: 36px;
|
font-weight: bold;
|
color: #333;
|
}
|
|
.room-line {
|
margin-top: 10px;
|
font-size: 28px;
|
font-weight: bold;
|
letter-spacing: 2px;
|
color: #333;
|
}
|
|
/* ================= 主体内容区 ================= */
|
.main-container {
|
flex: 1;
|
display: flex;
|
flex-direction: column;
|
padding: 15px;
|
gap: 15px;
|
overflow: hidden;
|
}
|
|
/* 队列面板通用样式 (左右布局) */
|
.panel {
|
background: white;
|
border-radius: 12px;
|
display: flex;
|
flex-direction: row;
|
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.15);
|
overflow: hidden;
|
flex: 1;
|
border: 2px solid #e0e0e0;
|
}
|
|
.status-active {
|
border-color: #67c23a;
|
}
|
|
.status-waiting {
|
border-color: #e6a23c;
|
}
|
|
.status-missed {
|
border-color: #f56c6c;
|
}
|
|
/* 左侧状态标签 (竖排) */
|
.panel-header {
|
width: 70px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
font-size: 32px;
|
font-weight: bold;
|
color: #fff;
|
writing-mode: vertical-rl;
|
letter-spacing: 12px;
|
text-align: center;
|
flex-shrink: 0;
|
}
|
|
.status-active .panel-header {
|
background: #67c23a;
|
}
|
|
.status-waiting .panel-header {
|
background: #e6a23c;
|
}
|
|
.status-missed .panel-header {
|
background: #f56c6c;
|
}
|
|
/* 右侧列表内容区 */
|
.list-content {
|
flex: 1;
|
overflow-y: auto;
|
padding: 10px;
|
display: flex;
|
flex-wrap: wrap;
|
align-content: flex-start;
|
gap: 10px;
|
background: #fafafa;
|
}
|
|
/* 患者信息项 (固定高度,一行两个) */
|
.patient-item {
|
width: calc(50% - 5px);
|
height: 70px;
|
display: flex;
|
justify-content: flex-start;
|
align-items: center;
|
padding: 0 8px;
|
background: #ffffff;
|
border: 1px solid #eee;
|
border-radius: 8px;
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
white-space: nowrap;
|
overflow: hidden;
|
}
|
|
.p-name {
|
font-size: 36px;
|
font-weight: bold;
|
color: #303133;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
}
|
|
.p-num {
|
font-size: 30px;
|
color: #909399;
|
font-weight: bold;
|
flex-shrink: 0;
|
}
|
|
/* ================= 底部控制区 ================= */
|
.footer {
|
background: #fff;
|
padding: 15px 20px;
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.05);
|
z-index: 10;
|
flex-shrink: 0;
|
position: relative;
|
}
|
|
.footer-text {
|
font-size: 20px;
|
color: #666;
|
font-weight: bold;
|
}
|
|
#test-voice-btn {
|
display: none;
|
position: absolute;
|
right: 20px;
|
background-color: #007bff;
|
color: white;
|
border: none;
|
padding: 10px 20px;
|
border-radius: 6px;
|
font-size: 16px;
|
cursor: pointer;
|
font-weight: bold;
|
}
|
|
#test-voice-btn:hover {
|
background-color: #0056b3;
|
}
|
|
/* 调试信息区 */
|
.debug-info {
|
display: none;
|
font-size: 14px;
|
color: #999;
|
background: #f0f0f0;
|
padding: 8px 15px;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
flex-shrink: 0;
|
}
|
|
/* 滚动条美化 */
|
.list-content::-webkit-scrollbar {
|
width: 6px;
|
}
|
|
.list-content::-webkit-scrollbar-thumb {
|
background: #ddd;
|
border-radius: 3px;
|
}
|
</style>
|
</head>
|
|
<body>
|
<!-- 顶部栏 -->
|
<div class="header">
|
<div class="header-top">
|
<img src="logo.png" alt="Logo" class="logo">
|
<div class="header-right">
|
<div class="time-box">
|
<span class="week-day" id="weekDay">星期日</span>
|
<span class="full-date" id="fullDate">2024年01月01日</span>
|
</div>
|
<div class="clock" id="clock">12:00</div>
|
</div>
|
</div>
|
<div class="room-line"><span id="currentRoomId">--</span></div>
|
</div>
|
|
<!-- 主体内容 -->
|
<div class="main-container">
|
<!-- 1. 正在就诊 -->
|
<div class="panel status-active" style="max-height: calc(70px * 2 + 20px + 20px);">
|
<div class="panel-header">诊中</div>
|
<div class="list-content" id="inProgressList"></div>
|
</div>
|
|
<!-- 2. 候诊中 -->
|
<div class="panel status-waiting" style="max-height: calc(70px * 4 + 30px + 20px);">
|
<div class="panel-header">等候</div>
|
<div class="list-content" id="waitingList"></div>
|
</div>
|
|
<!-- 3. 过号 -->
|
<div class="panel status-missed" style="max-height: calc(70px * 3 + 20px + 20px);">
|
<div class="panel-header">过号</div>
|
<div class="list-content" id="missedList"></div>
|
</div>
|
</div>
|
|
<!-- 底部控制栏 -->
|
<div class="footer">
|
<span class="footer-text">温馨提示:请过号患者到分诊台处理!</span>
|
<button id="test-voice-btn">测试语音</button>
|
</div>
|
|
<!-- 调试信息 -->
|
<div class="debug-info" id="debugInfo">调试状态:等待数据...</div>
|
|
<script>
|
// ================= URL 参数读取 =================
|
function getUrlParam(name) {
|
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
var r = window.location.search.substr(1).match(reg);
|
if (r != null) return decodeURIComponent(r[2]);
|
return null;
|
}
|
|
// ================= 配置参数 =================
|
var CONFIG = {
|
// apiBaseUrl: "http://192.168.3.12/admin-api",
|
apiBaseUrl: "http://10.0.2.193/admin-api",
|
roomId: getUrlParam("roomID") || "116",
|
refreshRate: 5000
|
};
|
// 诊室编号 → 名称映射:当接口未返回 roomName 时兜底
|
var ROOM_NAME_MAP = {
|
"116": "1号诊室",
|
"117": "2号诊室",
|
"118": "3号诊室",
|
"119": "4号诊室",
|
"121": "6号诊室",
|
"123": "8号诊室",
|
"125": "分诊台"
|
};
|
var CALL_TIMES = 2; // 叫号次数
|
|
var appState = { roomName: '', lastSpokenPatient: null, serverTimeOffset: 0 };
|
function $(id) { return document.getElementById(id); }
|
|
// ================= 时间模块 =================
|
function getNow() {
|
return new Date(Date.now() + (appState.serverTimeOffset || 0));
|
}
|
function updateClock() {
|
var now = getNow();
|
$('clock').innerText = ('0' + now.getHours()).slice(-2) + ':' + ('0' + now.getMinutes()).slice(-2);
|
$('fullDate').innerText = now.getFullYear() + '年' + ('0' + (now.getMonth() + 1)).slice(-2) + '月' + ('0' + now.getDate()).slice(-2) + '日';
|
$('weekDay').innerText = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'][now.getDay()];
|
}
|
function syncServerTime(serverTimeStr) {
|
if (!serverTimeStr) return;
|
try {
|
var serverTime = new Date(serverTimeStr);
|
if (!isNaN(serverTime.getTime())) {
|
appState.serverTimeOffset = serverTime.getTime() - Date.now();
|
updateDebugInfo("时间已同步 | 偏移=" + (appState.serverTimeOffset / 1000).toFixed(1) + "s");
|
}
|
} catch (e) { updateDebugInfo("时间同步失败: " + e.message); }
|
}
|
setInterval(updateClock, 1000); updateClock();
|
|
// ================= 语音播报 =================
|
var _gUtterance = null; // GC 保护
|
function speakText(text, times) {
|
times = times || CALL_TIMES;
|
var isAndroid = /Android/i.test(navigator.userAgent);
|
if (isAndroid && window.wowjoy && typeof window.wowjoy.speek === 'function') {
|
updateDebugInfo("TTS: wowjoy模式");
|
for (var i = 0; i < times; i++) {
|
setTimeout(function () { window.wowjoy.speek(text); }, i * 1500);
|
}
|
} else if (window.speechSynthesis) {
|
updateDebugInfo("TTS: speechSynthesis模式");
|
if (window.speechSynthesis.speaking) {
|
window.speechSynthesis.cancel();
|
}
|
function doSpeak(idx) {
|
var u = new window.SpeechSynthesisUtterance(text);
|
u.lang = 'zh-CN';
|
u.rate = 1.0;
|
u.volume = 1.0;
|
_gUtterance = u;
|
u.onend = function () { _gUtterance = null; };
|
u.onerror = function (e) { _gUtterance = null; updateDebugInfo("TTS错误: " + (e.error || "unknown")); };
|
window.speechSynthesis.speak(u);
|
}
|
doSpeak(0);
|
for (var j = 1; j < times; j++) {
|
setTimeout((function (idx) { return function () { doSpeak(idx); }; })(j), j * 1500);
|
}
|
} else {
|
updateDebugInfo("TTS: 无引擎可用");
|
}
|
}
|
|
// ================= 数据请求 =================
|
function fetchData() {
|
var url = CONFIG.apiBaseUrl + "/ecg/screen/room-screen-data?roomId=" + CONFIG.roomId;
|
updateDebugInfo("请求: " + url);
|
var xhr = new XMLHttpRequest();
|
xhr.open('GET', url, true);
|
xhr.timeout = 8000;
|
xhr.onreadystatechange = function () {
|
if (xhr.readyState === 4) {
|
if (xhr.status === 200) {
|
try {
|
var response = JSON.parse(xhr.responseText);
|
processData(response);
|
updateDebugInfo("成功 | 患者: " + (response.data ? JSON.stringify(Object.keys(response.data)) : "无"));
|
} catch (e) { updateDebugInfo("处理失败: " + e.message); }
|
} else if (xhr.status === 0) {
|
updateDebugInfo("网络错误(status=0) | " + url + " | 请检查API服务/URL可达性/跨域");
|
} else {
|
updateDebugInfo("请求失败 | status=" + xhr.status + " | " + url);
|
}
|
}
|
};
|
xhr.onerror = function () {
|
updateDebugInfo("网络异常(onerror) | " + url + " | 设备可能无法访问该地址");
|
};
|
xhr.ontimeout = function () {
|
updateDebugInfo("请求超时 | " + url);
|
};
|
try {
|
xhr.send();
|
} catch (e) {
|
updateDebugInfo("send异常: " + e.message);
|
}
|
}
|
|
// ================= 核心业务逻辑处理 =================
|
function processData(res) {
|
syncServerTime(res.serverTime || (res.data && res.data.serverTime) || null);
|
var data = res.data || res;
|
|
// 1. 更新诊室名称 (从第一条数据中获取)
|
var currentRoomName = CONFIG.roomId;
|
var waitingArr = data["1"] || [];
|
var inProgressArr = data["2"] || [];
|
var missedArr = data["3"] || [];
|
|
if (waitingArr.length > 0) currentRoomName = waitingArr[0].roomName || currentRoomName;
|
else if (inProgressArr.length > 0) currentRoomName = inProgressArr[0].roomName || currentRoomName;
|
else if (missedArr.length > 0) currentRoomName = missedArr[0].roomName || currentRoomName;
|
|
// 兜底:接口未返回 roomName 时,从本地映射表取诊室名称
|
currentRoomName = ROOM_NAME_MAP[currentRoomName] || currentRoomName;
|
|
appState.roomName = currentRoomName;
|
$('currentRoomId').innerText = currentRoomName;
|
|
// 2. 根据状态码进行二次分类 (处理兜底逻辑)
|
var finalWaitingList = [];
|
var finalInProgressList = [];
|
var finalMissedList = [];
|
|
// 处理候诊队列 (包含兜底)
|
for (var i = 0; i < waitingArr.length; i++) {
|
var status = parseInt(waitingArr[i].status, 10);
|
if (status === 30) finalInProgressList.push(waitingArr[i]);
|
else if (status === 3 || status === 5 || status === 7) finalMissedList.push(waitingArr[i]);
|
else finalWaitingList.push(waitingArr[i]);
|
}
|
// 处理就诊队列
|
for (var j = 0; j < inProgressArr.length; j++) {
|
var status2 = parseInt(inProgressArr[j].status, 10);
|
if (status2 === 3 || status2 === 5 || status2 === 7) finalMissedList.push(inProgressArr[j]);
|
else if (status2 !== 30) finalWaitingList.push(inProgressArr[j]);
|
else finalInProgressList.push(inProgressArr[j]);
|
}
|
// 处理过号队列
|
for (var k = 0; k < missedArr.length; k++) {
|
var status3 = parseInt(missedArr[k].status, 10);
|
if (status3 === 30) finalInProgressList.push(missedArr[k]);
|
else if (status3 !== 3 && status3 !== 5 && status3 !== 7) finalWaitingList.push(missedArr[k]);
|
else finalMissedList.push(missedArr[k]);
|
}
|
|
// 3. 渲染列表
|
renderList('inProgressList', finalInProgressList);
|
renderList('waitingList', finalWaitingList);
|
renderList('missedList', finalMissedList);
|
|
// 4. 更新调试信息
|
updateDebugInfo("候诊:" + finalWaitingList.length + " | 就诊:" + finalInProgressList.length + " | 过号:" + finalMissedList.length);
|
|
// 5. 智能呼叫逻辑
|
if (finalInProgressList.length > 0) {
|
var currentPatient = finalInProgressList[0];
|
var patientId = currentPatient.patId || currentPatient.seqNum;
|
if (appState.lastSpokenPatient !== patientId) {
|
appState.lastSpokenPatient = patientId;
|
speakText(currentPatient.patName + ",请到" + appState.roomName + "就诊", CALL_TIMES);
|
}
|
} else {
|
appState.lastSpokenPatient = null;
|
}
|
}
|
|
// ================= 渲染列表 =================
|
function desensitizeName(name) {
|
if (!name || name.length < 2) return name || '';
|
return name.charAt(0) + '*' + name.substring(2);
|
}
|
|
function renderList(containerId, listData) {
|
var container = $(containerId);
|
if (!container) return;
|
container.innerHTML = "";
|
if (listData.length === 0) {
|
container.innerHTML = '<div style="text-align:center; color:#ccc; width:100%; height:70px; line-height:70px; font-size:18px;">暂无患者</div>';
|
return;
|
}
|
for (var i = 0; i < listData.length; i++) {
|
var item = listData[i];
|
var div = document.createElement('div');
|
div.className = 'patient-item';
|
div.innerHTML = '<span class="p-num">' + (item.seqNum || '--') + '号</span> <span class="p-name">' + (item.patName ? desensitizeName(item.patName) : '未知') + '</span>';
|
container.appendChild(div);
|
}
|
}
|
|
function updateDebugInfo(msg) {
|
var debugEl = $('debugInfo');
|
if (debugEl) debugEl.innerText = "调试信息: " + msg + " | 时间: " + new Date().toLocaleTimeString();
|
}
|
|
// ================= 初始化 =================
|
window.onload = function () {
|
updateDebugInfo("页面加载完成,即将开始轮询...");
|
fetchData();
|
setInterval(fetchData, CONFIG.refreshRate);
|
$('test-voice-btn').addEventListener('click', function () {
|
updateDebugInfo("用户点击测试语音");
|
speakText("正在测试语音呼叫", CALL_TIMES);
|
}, false);
|
};
|
</script>
|
</body>
|
|
</html>
|