<!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 http-equiv="X-UA-Compatible" content="IE=edge" />
|
<title>大厅</title>
|
<style>
|
* {
|
margin: 0;
|
padding: 0;
|
box-sizing: border-box;
|
/* 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. 顶部栏 */
|
.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 img {
|
height: 50px;
|
margin-right: 20px;
|
z-index: 2;
|
}
|
|
.top-header .title-text {
|
position: absolute;
|
left: 50%;
|
-webkit-transform: translateX(-50%);
|
transform: translateX(-50%);
|
font-size: 32px;
|
font-weight: bold;
|
color: #fff;
|
z-index: 1;
|
}
|
|
.top-header .time-info {
|
font-size: 24px;
|
color: #aaa;
|
margin-left: auto;
|
z-index: 2;
|
}
|
|
/* 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;
|
}
|
|
.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;
|
color: #4da6ff;
|
padding-bottom: 8px;
|
border-bottom: 1px solid #003366;
|
margin-bottom: 10px;
|
text-align: center;
|
}
|
|
.col-subtitle {
|
font-size: 14px;
|
color: #999;
|
text-align: center;
|
}
|
|
/* 患者列表 */
|
.patient-list {
|
-webkit-box-flex: 1;
|
-webkit-flex: 1;
|
flex: 1;
|
overflow-y: auto;
|
padding-right: 5px;
|
}
|
|
/* 第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;
|
}
|
|
.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;
|
}
|
|
/* 患者项目 */
|
.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;
|
}
|
|
#col-0 .patient-item {
|
width: 45%;
|
font-size: 20px;
|
margin: 0 2.5%;
|
}
|
|
.p-number {
|
color: #ffcc00;
|
font-weight: bold;
|
margin-right: 8px;
|
}
|
|
.p-name {
|
-webkit-box-flex: 1;
|
-webkit-flex: 1;
|
flex: 1;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
}
|
|
.p-room {
|
color: #4da6ff;
|
font-weight: bold;
|
font-size: 18px;
|
margin-left: 5px;
|
}
|
|
/* 底部栏 */
|
.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);
|
border-top: 1px solid #003366;
|
}
|
|
.footer-tip {
|
font-size: 22px;
|
color: #ffcc00;
|
font-weight: bold;
|
text-align: center;
|
}
|
|
/* 滚动条 */
|
.patient-list::-webkit-scrollbar {
|
width: 4px;
|
}
|
.patient-list::-webkit-scrollbar-thumb {
|
background: #444;
|
border-radius: 2px;
|
}
|
|
/* 调试面板 */
|
.debug-panel {
|
position: fixed;
|
right: 15px;
|
bottom: 70px;
|
width: 350px;
|
height: 200px;
|
background: rgba(0, 0, 0, 0.9);
|
color: #0f0;
|
border-radius: 4px;
|
font-family: monospace;
|
font-size: 12px;
|
z-index: 9999;
|
overflow: hidden;
|
}
|
|
.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;
|
border-bottom: 1px solid #000;
|
}
|
|
.debug-header span {
|
font-weight: bold;
|
font-size: 13px;
|
}
|
|
.debug-header button {
|
background: #c00;
|
color: #fff;
|
border: none;
|
padding: 2px 6px;
|
font-size: 10px;
|
cursor: pointer;
|
}
|
|
.debug-body {
|
-webkit-box-flex: 1;
|
-webkit-flex: 1;
|
flex: 1;
|
padding: 5px;
|
overflow-y: auto;
|
font-size: 11px;
|
}
|
|
.debug-line {
|
margin-bottom: 2px;
|
}
|
|
.debug-time {
|
color: #888;
|
}
|
</style>
|
</head>
|
|
<body>
|
<!-- 顶部栏 -->
|
<div class="top-header">
|
<img src="logo.png" alt="logo" />
|
<span class="title-text">服务大厅排列</span>
|
<span class="time-info" id="headerTime"></span>
|
</div>
|
|
<!-- 主体内容 -->
|
<div class="main-content" id="mainContent"></div>
|
|
<!-- 底部栏 -->
|
<div class="bottom-footer">
|
<div class="footer-tip">温馨提示:请听到呼叫后前往对应诊室</div>
|
</div>
|
|
<!-- 调试面板 -->
|
<div class="debug-panel" id="debugPanel">
|
<div class="debug-header">
|
<span>[调试] 运行日志</span>
|
<button onclick="document.getElementById('debugBody').innerHTML=''">清空</button>
|
</div>
|
<div class="debug-body" id="debugBody"></div>
|
</div>
|
|
<script src="./static/jquery.min.js"></script>
|
<script>
|
// ================= 调试日志 =================
|
function logDebug(msg) {
|
var now = new Date();
|
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 = document.getElementById("debugBody");
|
if (body) {
|
body.insertAdjacentHTML("beforeend", logHtml);
|
body.scrollTop = body.scrollHeight;
|
}
|
}
|
|
// ================= 应用状态 =================
|
var appState = {
|
columnTitles: ["常规心电图", "动态心电", "平板运动心电", "食道电生理", "动脉硬化监测"],
|
columnSubTitles: ["床边心电图(常规+频谱)M / 心电向量图N", "动态血压C", "", "", ""],
|
patients: [],
|
apiBaseUrl: "http://192.168.100.110/admin-api",
|
pollTimer: null,
|
callRepeatTimes: 2,
|
spokenPatients: {},
|
ttsQueue: [],
|
isSpeaking: false
|
};
|
|
// ================= 语音播报 =================
|
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 接口
|
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); }
|
}
|
|
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) {
|
if (!text) return;
|
appState.ttsQueue.push(text);
|
processTtsQueue();
|
}
|
|
// ================= 渲染 =================
|
function renderMainContent() {
|
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>';
|
if (appState.columnSubTitles[i]) {
|
titleHtml += '<div class="col-subtitle">' + appState.columnSubTitles[i] + '</div>';
|
}
|
|
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);
|
}
|
}
|
|
function renderPatients() {
|
for (var i = 0; i < appState.columnTitles.length; i++) {
|
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 = 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);
|
}
|
}
|
}
|
}
|
|
// ================= 数据对比与播报 =================
|
function checkAndSpeakNewRooms(oldData, newData) {
|
if (!oldData || oldData.length === 0) return;
|
for (var c = 0; c < newData.length; c++) {
|
var newCol = newData[c];
|
var oldCol = oldData[c];
|
if (Array.isArray(newCol)) {
|
for (var p = 0; p < newCol.length; p++) {
|
var newPat = newCol[p];
|
if (newPat.roomName) {
|
if (appState.spokenPatients[newPat.patId]) continue;
|
var oldPat = null;
|
if (Array.isArray(oldCol)) {
|
for (var k = 0; k < oldCol.length; k++) {
|
if (oldCol[k].patId === newPat.patId) {
|
oldPat = oldCol[k];
|
break;
|
}
|
}
|
}
|
if (!oldPat || !oldPat.roomName) {
|
var repeatText = "";
|
for (var r = 0; r < appState.callRepeatTimes; r++) {
|
repeatText += "请 " + newPat.bookSeqNum + " 号 " + newPat.patName + " 到 " + newPat.roomName + " 就诊。";
|
}
|
speak(repeatText);
|
appState.spokenPatients[newPat.patId] = true;
|
logDebug("[排队] " + newPat.patName + " -> " + newPat.roomName);
|
}
|
}
|
}
|
}
|
}
|
}
|
|
// ================= 数据获取 =================
|
function fetchQueueData() {
|
var url = appState.apiBaseUrl + "/ecg/screen/big-screen-data";
|
$.ajax({
|
url: url,
|
type: "GET",
|
dataType: "json",
|
timeout: 5000,
|
success: function (res) {
|
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] : [];
|
}
|
}
|
checkAndSpeakNewRooms(appState.patients, dataList);
|
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");
|
}
|
} catch (e) {}
|
},
|
error: function (err) {
|
logDebug("[请求失败] " + (err.statusText || "网络错误"));
|
}
|
});
|
}
|
|
// ================= 初始化 =================
|
function onReady() {
|
renderMainContent();
|
updateHeaderTime();
|
setInterval(updateHeaderTime, 1000);
|
fetchQueueData();
|
appState.pollTimer = setInterval(fetchQueueData, 5000);
|
logDebug("[系统] 启动完成 - Android 6.0.1");
|
}
|
|
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 timeStr = padZero(now.getHours()) + ":" + padZero(now.getMinutes());
|
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>
|
|
</html>
|