feat: 更新蓝牙体重秤小程序原型页面及后台管理页面

This commit is contained in:
liuyunqin
2026-07-17 17:28:24 +08:00
parent 6c6e92219a
commit 8a96ead2c8
7 changed files with 674 additions and 235 deletions
+111 -76
View File
@@ -169,19 +169,22 @@ body { background: var(--background); font-family: -apple-system, BlinkMacSystem
</style>
</head>
<body>
<!-- 本地服务器启动遮罩 -->
<div class="launch-overlay" id="launch-overlay">
<div class="launch-card">
<div class="launch-card__icon" id="launch-icon">🚀</div>
<div class="launch-card__title" id="launch-title">需要启动本地服务器</div>
<div class="launch-card__sub" id="launch-sub">PRD文档预览等功能需要本地HTTP服务器</div>
<div class="launch-card__actions" id="launch-actions">
<a class="launch-card__btn" href="启动服务器.bat">一键启动</a>
<a class="launch-card__btn launch-card__btn--outline" href="http://localhost:8080/index.html">📖 直接打开原型</a>
</div>
<div class="launch-card__status" id="launch-status"><span class="launch-card__spinner"></span>等待服务器就绪...</div>
</div>
</div>
<!-- 本地服务器启动遮罩 -->
<div class="launch-overlay" id="launch-overlay">
<div class="launch-card">
<div class="launch-card__icon" id="launch-icon">🚀</div>
<div class="launch-card__title" id="launch-title">需要启动本地服务器</div>
<div class="launch-card__sub" id="launch-sub">PRD文档预览、Markdown查看器等功能需要本地HTTP服务器<br>请双击项目根目录下的 <code style="background:#f0f2f8;padding:2px 8px;border-radius:4px;">启动服务器.bat</code></div>
<div class="launch-card__actions" id="launch-actions">
<a class="launch-card__btn" href="启动服务器.bat" download>下载启动脚本</a>
<a class="launch-card__btn launch-card__btn--outline" href="http://localhost:8080/index.html">📖 直接打开原型</a>
</div>
<div style="margin-top:12px;text-align:center;">
<a href="javascript:void(0)" id="launch-dismiss-btn" style="font-size:13px;color:var(--text-placeholder);text-decoration:none;border-bottom:1px dashed var(--text-placeholder);padding-bottom:1px;">跳过,直接浏览导航页</a>
</div>
<div class="launch-card__status" id="launch-status"><span class="launch-card__spinner"></span>等待服务器就绪(检测中...</div>
</div>
</div>
<div class="header">
<div class="header__inner">
<div class="header__title">健康CQ升级项目 · 高保真交互原型</div>
@@ -196,75 +199,99 @@ body { background: var(--background); font-family: -apple-system, BlinkMacSystem
/* ===== file:// 协议检测 & 服务器自动启动 ===== */
(function() {
if (location.protocol !== 'file:') return; /* 非 file:// 协议,无需处理 */
var overlay = document.getElementById('launch-overlay');
var launchIcon = document.getElementById('launch-icon');
var launchTitle = document.getElementById('launch-title');
var launchSub = document.getElementById('launch-sub');
var launchStatus = document.getElementById('launch-status');
var launchActions = document.getElementById('launch-actions');
/* 步骤1: 先静默检测服务器是否已在运行(不显示遮罩) */
var dismissed = false; /* 用户是否已点击"跳过" */
/* 服务器检测:只用 fetch(比 iframe 更可靠,不会因跨域判断误报) */
function checkServer(callback) {
var iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = 'http://localhost:8080/index.html';
var done = false;
var t = setTimeout(function() {
if (!done) { done = true; callback(false); }
}, 2000);
iframe.onload = function() {
if (!done) { done = true; clearTimeout(t); callback(true); }
};
iframe.onerror = function() {
if (!done) { done = true; clearTimeout(t); callback(false); }
};
document.body.appendChild(iframe);
try {
var controller = new AbortController();
var timeout = setTimeout(function() {
controller.abort();
callback(false);
}, 3000);
fetch('http://localhost:8080/index.html?' + Date.now(), {
mode: 'no-cors',
cache: 'no-store',
signal: controller.signal
}).then(function() {
/* fetch 成功 → 服务器可达(响应了 HTTP,即使是 404 也算可达) */
clearTimeout(timeout);
callback(true);
}).catch(function(err) {
/* fetch 失败 → 服务器不可达(连接拒绝/DNS 失败等) */
clearTimeout(timeout);
callback(false);
});
} catch (e) {
/* AbortController / fetch 不可用(极旧浏览器)→ 保守:认为服务器不在线 */
callback(false);
}
}
/* 步骤2: 服务器已运行 → 无感跳转 */
/* 服务器已运行 → 无感跳转 */
function redirectToServer() {
location.replace('http://localhost:8080/index.html');
}
/* 步骤3: 显示遮罩 + 轮询 */
/* 显示遮罩 + 轮询 */
function showOverlay() {
overlay.classList.add('launch-overlay--show');
launchIcon.textContent = '🚀';
launchIcon.className = 'launch-card__icon';
launchTitle.textContent = '需要启动本地服务器';
launchSub.textContent = 'PRD文档预览等功能需要本地HTTP服务器,请双击项目根目录下的 启动服务器.bat';
launchStatus.innerHTML = '<span class="launch-card__spinner"></span>等待服务器就绪...';
launchActions.innerHTML = '<a class="launch-card__btn" href="启动服务器.bat">⚡ 一键启动</a> <a class="launch-card__btn launch-card__btn--outline" href="http://localhost:8080/index.html">📖 直接打开原型</a>';
/* 轮询:每2秒检测一次,最多30次(60秒) */
/* 轮询:每2秒检测一次,最多60次(120秒) */
var attempts = 0;
var maxAttempts = 60;
var pollTimer = setInterval(function() {
if (dismissed) { clearInterval(pollTimer); return; }
attempts++;
checkServer(function(ok) {
if (dismissed) return;
if (ok) {
clearInterval(pollTimer);
launchIcon.textContent = '✅';
launchIcon.className = 'launch-card__icon launch-card__icon--ok';
launchTitle.textContent = '服务器已就绪';
launchSub.textContent = '即将自动跳转...';
launchStatus.innerHTML = '<a href="http://localhost:8080/index.html" style="color:var(--primary)">立即跳转</a>';
launchStatus.innerHTML = '<a href="http://localhost:8080/index.html" style="color:var(--primary);font-weight:600;">立即跳转</a>';
launchActions.innerHTML = '<a class="launch-card__btn launch-card__btn--outline" href="http://localhost:8080/index.html">📖 打开原型导航</a>';
setTimeout(redirectToServer, 500);
setTimeout(redirectToServer, 600);
}
if (attempts >= maxAttempts) {
clearInterval(pollTimer);
launchTitle.textContent = '未检测到服务器';
launchSub.textContent = '请确认已双击运行 启动服务器.bat,然后刷新页面或点击下方按钮';
launchStatus.innerHTML = '⏱ 等待超时(120秒)';
launchActions.innerHTML = '<button class="launch-card__btn" onclick="location.reload()">🔄 刷新重试</button> <a class="launch-card__btn launch-card__btn--outline" href="http://localhost:8080/index.html">📖 直接打开原型</a>';
}
});
if (attempts >= 30) {
clearInterval(pollTimer);
launchTitle.textContent = '未检测到服务器';
launchSub.textContent = '请确认 启动服务器.bat 已运行,然后刷新页面';
launchStatus.innerHTML = '⏱ 等待超时(60秒)';
launchActions.innerHTML = '<button class="launch-card__btn" onclick="location.reload()">🔄 刷新重试</button>';
}
}, 2000);
}
/* 绑定"跳过"按钮 */
function bindDismissBtn() {
var btn = document.getElementById('launch-dismiss-btn');
if (btn) {
btn.addEventListener('click', function(e) {
e.preventDefault();
dismissed = true;
overlay.classList.remove('launch-overlay--show');
});
}
}
/* 初始绑定 */
bindDismissBtn();
/* === 主流程:先静默检测 === */
checkServer(function(running) {
if (dismissed) return;
if (running) {
/* 服务器已在运行 → 直接跳转,用户全程无感 */
redirectToServer();
@@ -1076,7 +1103,7 @@ function cardHTML(mod, uid) {
const icon = p.dialog ? ICONS.dialog : ICONS.page;
return `<a class="page-popover__item${dcls}" href="${mod.dir}/${p.file}" target="_blank">${icon}${p.label}</a>`;
}).join('');
pageCountHtml = `<span class="module-card__page-count" data-pop="${uid}" onclick="event.stopPropagation();togglePop('${uid}')">${mod.pages.length} 页</span><div class="page-popover" id="pop-${uid}" onclick="event.stopPropagation()"><div class="page-popover__title">页面列表</div>${popItems}</div>`;
pageCountHtml = `<span class="module-card__page-count" data-pop="${uid}" onclick="togglePop('${uid}',event)">${mod.pages.length} 页</span><div class="page-popover" id="pop-${uid}"><div class="page-popover__title">页面列表</div>${popItems}</div>`;
}
/* 底部行占位:无内容时保持高度一致 */
@@ -1084,29 +1111,37 @@ function cardHTML(mod, uid) {
return `<div class="${cls}" ${onclick}>${watermarkHtml}<div class="module-card__header"><span class="module-card__name">${mod.name}</span>${statusHtml}</div><div class="module-card__bottom">${prdLink}${pageCountHtml}${bottomPlaceholder}</div></div>`;
}
function togglePop(uid) {
const el = document.getElementById('pop-' + uid);
if (openPopover && openPopover !== el) {
openPopover.classList.remove('page-popover--visible');
openPopover.closest('.module-card').classList.remove('module-card--pop-open');
}
el.classList.toggle('page-popover--visible');
const card = el.closest('.module-card');
if (el.classList.contains('page-popover--visible')) {
card.classList.add('module-card--pop-open');
openPopover = el;
} else {
card.classList.remove('module-card--pop-open');
openPopover = null;
}
}
document.addEventListener('click', () => {
if (openPopover) {
openPopover.closest('.module-card').classList.remove('module-card--pop-open');
openPopover.classList.remove('page-popover--visible');
openPopover = null;
}
});
function togglePop(uid, e) {
if (e) { e.stopPropagation(); e.preventDefault(); }
const el = document.getElementById('pop-' + uid);
if (!el) return;
if (openPopover && openPopover !== el) {
openPopover.classList.remove('page-popover--visible');
var prevCard = openPopover.closest('.module-card');
if (prevCard) prevCard.classList.remove('module-card--pop-open');
}
el.classList.toggle('page-popover--visible');
const card = el.closest('.module-card');
if (el.classList.contains('page-popover--visible')) {
if (card) card.classList.add('module-card--pop-open');
openPopover = el;
} else {
if (card) card.classList.remove('module-card--pop-open');
openPopover = null;
}
}
/* 点击页面任意空白处关闭 popover(不依赖 stopPropagation */
document.addEventListener('click', function(e) {
if (!openPopover) return;
/* 点击在 popover 内部 或 页数徽章上 → 不关闭 */
if (e.target.closest('.page-popover')) return;
if (e.target.closest('.module-card__page-count')) return;
/* 其他情况 → 关闭 */
var card = openPopover.closest('.module-card');
if (card) card.classList.remove('module-card--pop-open');
openPopover.classList.remove('page-popover--visible');
openPopover = null;
});
/* 生成 Tab 内容 */
let uidCounter = 0;