diff --git a/_templates/md-viewer.html b/_templates/md-viewer.html index dae6c31..3d6835d 100644 --- a/_templates/md-viewer.html +++ b/_templates/md-viewer.html @@ -122,11 +122,25 @@ if (title) { if (filePath) { document.getElementById('doc-path').textContent = filePath; /* 用绝对路径(/ 开头),避免 在不同浏览器中解析不一致 */ - fetch('/' + filePath) - .then(res => { - if (!res.ok) throw new Error('文件未找到 (' + res.status + ')'); + var fetchUrl = '/' + filePath; + + function tryFetch(url, fallbackUrl) { + return fetch(url).then(function(res) { + if (!res.ok) throw new Error('HTTP ' + res.status); return res.text(); - }) + }).catch(function(err) { + /* 主路径失败 → 尝试去掉开头的 / 重新请求 */ + if (fallbackUrl && url !== fallbackUrl) { + return fetch(fallbackUrl).then(function(res) { + if (!res.ok) throw new Error('HTTP ' + res.status + ' (fallback)'); + return res.text(); + }); + } + throw err; + }); + } + + tryFetch(fetchUrl, filePath) .then(md => { /* 检测是否为自带 HTML 结构的文件(如共性需求说明.md) */ const isRawHtml = /^\s*<(?:style|div|!doctype|html)/i.test(md); @@ -227,9 +241,9 @@ if (filePath) { } } }) - .catch(err => { - document.getElementById('content').innerHTML = '
加载失败:' + err.message + '
请确认文件路径:' + filePath + '
'; - }); + .catch(err => { + document.getElementById('content').innerHTML = '
❌ 加载失败:' + err.message + '
📁 文件路径:' + filePath + '
🔗 请求地址:' + fetchUrl + '
💡 请确认服务器已启动(双击 启动服务器.bat),且文件路径正确
'; + }); } else { document.getElementById('content').innerHTML = '
未指定文件路径
'; } diff --git a/index.html b/index.html index cdbfbf0..7923b61 100644 --- a/index.html +++ b/index.html @@ -169,19 +169,22 @@ body { background: var(--background); font-family: -apple-system, BlinkMacSystem - -
-
-
🚀
-
需要启动本地服务器
-
PRD文档预览等功能需要本地HTTP服务器
- -
等待服务器就绪...
-
-
+ +
+
+
🚀
+
需要启动本地服务器
+
PRD文档预览、Markdown查看器等功能需要本地HTTP服务器。
请双击项目根目录下的 启动服务器.bat
+ +
+ 跳过,直接浏览导航页 +
+
等待服务器就绪(检测中...)
+
+
健康CQ升级项目 · 高保真交互原型
@@ -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 = '等待服务器就绪...'; - launchActions.innerHTML = '⚡ 一键启动 📖 直接打开原型'; - - /* 轮询:每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 = '立即跳转'; + launchStatus.innerHTML = '立即跳转 →'; launchActions.innerHTML = '📖 打开原型导航'; - setTimeout(redirectToServer, 500); + setTimeout(redirectToServer, 600); + } + if (attempts >= maxAttempts) { + clearInterval(pollTimer); + launchTitle.textContent = '未检测到服务器'; + launchSub.textContent = '请确认已双击运行 启动服务器.bat,然后刷新页面或点击下方按钮'; + launchStatus.innerHTML = '⏱ 等待超时(120秒)'; + launchActions.innerHTML = ' 📖 直接打开原型'; } }); - if (attempts >= 30) { - clearInterval(pollTimer); - launchTitle.textContent = '未检测到服务器'; - launchSub.textContent = '请确认 启动服务器.bat 已运行,然后刷新页面'; - launchStatus.innerHTML = '⏱ 等待超时(60秒)'; - launchActions.innerHTML = ''; - } }, 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 `${icon}${p.label}`; }).join(''); - pageCountHtml = `${mod.pages.length} 页
页面列表
${popItems}
`; + pageCountHtml = `${mod.pages.length} 页
页面列表
${popItems}
`; } /* 底部行占位:无内容时保持高度一致 */ @@ -1084,29 +1111,37 @@ function cardHTML(mod, uid) { return `
${watermarkHtml}
${mod.name}${statusHtml}
${prdLink}${pageCountHtml}${bottomPlaceholder}
`; } -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; diff --git a/other-module/bluetooth-scale-miniprogram/device-members.html b/other-module/bluetooth-scale-miniprogram/device-members.html index ccbc502..cb1274c 100644 --- a/other-module/bluetooth-scale-miniprogram/device-members.html +++ b/other-module/bluetooth-scale-miniprogram/device-members.html @@ -137,6 +137,23 @@ } .add-member-card:hover { border-color: var(--primary); color: var(--primary); } .add-member-card .plus { font-size: 20px; font-weight: 300; } + .add-member-card--self { + background: var(--primary-light); border-color: var(--primary); color: var(--primary); + font-weight: 600; + } + .add-member-card--self:hover { background: #d6e8ff; } + + /* 空状态 */ + .member-empty { + text-align: center; padding: 50px 20px 30px; + } + .member-empty__icon { font-size: 52px; margin-bottom: 12px; opacity: 0.35; } + .member-empty__text { font-size: 14px; color: var(--text-placeholder); } + .member-empty__sub { font-size: 12px; color: #bbb; margin-top: 4px; } + + /* 底部双按钮 */ + .bottom-actions { display: flex; gap: 10px; margin-top: 4px; } + .bottom-actions .add-member-card { flex: 1; } /* Toast */ .toast { @@ -295,29 +312,28 @@