feat: 更新蓝牙体重秤小程序原型页面及后台管理页面
This commit is contained in:
+111
-76
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user