孕周开放门槛:数胎动 16 / 数宫缩 20 / 胎儿估重 15 周前点入口弹定制提示,宫格与工具卡挂「N周+」角标 v26101013
Co-Authored-By: AI 助手
This commit is contained in:
16
index.html
16
index.html
@@ -7,7 +7,7 @@
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<title>兜爸心选 · 孕育陪伴</title>
|
||||
<!-- V:26101012 缓存版本标记:改 css/js 时同步 bump 该值(与 js/app.js 的 VER 同源),静态资源按 mtime 走 ETag 自动刷新 -->
|
||||
<!-- V:26101013 缓存版本标记:改 css/js 时同步 bump 该值(与 js/app.js 的 VER 同源),静态资源按 mtime 走 ETag 自动刷新 -->
|
||||
<link rel="stylesheet" href="mama-box.css">
|
||||
<link rel="stylesheet" href="night.css">
|
||||
<!-- 夜晚模式:首屏绘制前就按上次的选择加 class,避免先白后黑闪一下(开关在首页右上角 #nightBtn,逻辑见 app.js) -->
|
||||
@@ -1933,6 +1933,20 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 孕周开放门槛(2026-10-11):数胎动 16 / 数宫缩 20 / 胎儿估重 15 周后开放,没到时点入口弹这个,内容由 app.js openStageGate() 按孕周现填 -->
|
||||
<section class="modal bx-prize sg-modal" id="modalStageGate" data-autofocus="off" role="dialog" aria-modal="true" aria-labelledby="sgT">
|
||||
<div class="sg-ico" id="sgIco" aria-hidden="true"></div>
|
||||
<h3 id="sgT">孕 16 周后开放</h3>
|
||||
<span class="sg-now" id="sgNow"></span>
|
||||
<p id="sgP"></p>
|
||||
<div class="sg-track" aria-hidden="true">
|
||||
<div class="sg-rail"><i id="sgBar"></i></div>
|
||||
<div class="sg-ends"><span id="sgFrom"></span><span id="sgTo"></span></div>
|
||||
</div>
|
||||
<div class="sg-left" id="sgLeft"></div>
|
||||
<div class="modal-foot"><button class="dl-btn main" data-modal="close">好的,到时再来</button></div>
|
||||
</section>
|
||||
|
||||
<!-- 2026-10-10 用户:退出登录前二次确认,写清只清授权信息、孕育数据保留 -->
|
||||
<section class="modal bx-prize lg-modal" id="modalLogout" data-autofocus="off" role="dialog" aria-modal="true" aria-labelledby="lgT">
|
||||
<h3 id="lgT">确定退出登录?</h3>
|
||||
|
||||
65
js/app.js
65
js/app.js
@@ -3,7 +3,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
/* 页面版本号(YYMMDD+序号):显示在「关于兜爸心选」页底部;bump 时同步 head 注释与 ?v= */
|
||||
var VER = '26101012';
|
||||
var VER = '26101013';
|
||||
const $ = (s, c) => (c || document).querySelector(s);
|
||||
const $$ = (s, c) => Array.from((c || document).querySelectorAll(s));
|
||||
|
||||
@@ -337,9 +337,67 @@
|
||||
if (!oid && !uid) q.push('__no_identity=1');
|
||||
return q.length ? url + (url.indexOf('?') >= 0 ? '&' : '?') + q.join('&') : url;
|
||||
}
|
||||
/* ================= 孕周开放门槛(2026-10-11 用户)=================
|
||||
「数胎动 16 周、数宫缩 20 周、胎儿估重 15 周以后对外开放;选了没到的孕周(比如 10 周),
|
||||
这三个都不开放,点的时候告诉她几周后开放、现在还用不到,给一个定制感受。」
|
||||
· 只在**已知孕周且没到门槛**时拦:访客 / 未建档 / 产后都照常放行(拿不准就别拦人)。
|
||||
· 统一拦在 jumpApp() 里 —— 记一下宫格、首页工具卡、今日待办、盲盒任务……所有入口都走它,一处管全部。
|
||||
· 按 identifier 认,认不到再按 URL 目录认(今日待办的上游 url 可能不带 identifier)。 */
|
||||
const STAGE_GATE = [
|
||||
{ id: 'taidong', slug: '/taidong/', wk: 16, name: '数胎动',
|
||||
why: nm => nm + '现在还小,胎动要到孕 16 周以后才慢慢感觉得到。到时候每天数一数,就能知道' + nm + '今天好不好。' },
|
||||
{ id: 'gongsuopinlv', slug: '/gongsuo-pinlv/', wk: 20, name: '数宫缩',
|
||||
why: () => '宫缩计时主要用在孕中晚期,孕 20 周以后记下来才更有参考意义。现在先安心养着,不用急着用它。' },
|
||||
{ id: 'taierguzhong', slug: '/taier-guzhong/', wk: 15, name: '胎儿估重',
|
||||
why: nm => '估重要用到 B 超量出来的头围、股骨长这些数据,孕 15 周以后算出来才靠谱。等下次 B 超拿到报告,就能看看' + nm + '有多重啦。' },
|
||||
];
|
||||
function stageGateOf(url, identifier) {
|
||||
if (identifier) { const g = STAGE_GATE.find(x => x.id === identifier); if (g) return g; }
|
||||
let path = '';
|
||||
try { path = new URL(url, location.href).pathname; } catch (e) { path = String(url || ''); }
|
||||
return STAGE_GATE.find(x => path.indexOf(x.slug) === 0) || null;
|
||||
}
|
||||
/* 现在孕几天;拿不准(访客、未建档、产后)返回 null → 不拦 */
|
||||
function knownGA() {
|
||||
if (state.postpartum || state.pregEmpty || state.week == null) return null;
|
||||
return state.week * 7 + (state.day || 0);
|
||||
}
|
||||
function stageLocked(g) { const ga = knownGA(); return !!g && ga != null && ga < g.wk * 7; }
|
||||
function openStageGate(g) {
|
||||
const ga = knownGA(), left = g.wk * 7 - ga, nm = state.babyName || '宝宝';
|
||||
const app = APP_BY_ID[g.id], ico = app && (app.logo || app.icon);
|
||||
$('#sgIco').innerHTML = ico ? '<img src="' + ico + '" alt="" width="40" height="40">' : '<b>' + g.name.slice(0, 1) + '</b>';
|
||||
$('#sgT').textContent = g.name + ' · 孕 ' + g.wk + ' 周后开放';
|
||||
$('#sgNow').textContent = '你现在 ' + gaTxt(ga).replace('孕', '孕 ').replace('周', ' 周');
|
||||
$('#sgP').textContent = g.why(nm);
|
||||
$('#sgFrom').textContent = gaTxt(ga);
|
||||
$('#sgTo').textContent = '孕' + g.wk + '周';
|
||||
$('#sgBar').style.width = Math.max(6, Math.min(100, ga / (g.wk * 7) * 100)).toFixed(1) + '%';
|
||||
$('#sgLeft').textContent = (left >= 7 ? '还有 ' + Math.ceil(left / 7) + ' 周' : '还有 ' + left + ' 天') + ',大约 ' + mdTxt(gaToDate(g.wk * 7)) + ' 开放';
|
||||
openModal('modalStageGate');
|
||||
}
|
||||
/* 宫格 / 首页工具卡上的小角标「16周+」:没到门槛的灰一点,到了自动去掉 */
|
||||
function markStageLocks() {
|
||||
$$('#appsGrid .app').forEach(b => {
|
||||
const g = stageGateOf(decodeURIComponent(b.dataset.url || ''), decodeURIComponent(b.dataset.id || ''));
|
||||
const on = stageLocked(g);
|
||||
b.classList.toggle('is-locked', on);
|
||||
if (on) b.dataset.lock = g.wk + '周+'; else delete b.dataset.lock;
|
||||
});
|
||||
[['kick', 'taidong'], ['curve', 'gongsuopinlv'], ['fweight', 'taierguzhong']].forEach(([slot, id]) => {
|
||||
const el = $('.mini-card[data-mini="' + slot + '"]'), g = STAGE_GATE.find(x => x.id === id);
|
||||
if (!el) return;
|
||||
const on = stageLocked(g);
|
||||
el.classList.toggle('is-locked', on);
|
||||
if (on) el.dataset.lock = g.wk + '周+'; else delete el.dataset.lock;
|
||||
});
|
||||
}
|
||||
|
||||
/* 跳转到其它 H5 应用。identifier 可选(来自 App 表),用于 last_app 上报的服务端在架校验;
|
||||
缺省时服务端按 URL 前缀反查,效果一致。 */
|
||||
function jumpApp(url, identifier) {
|
||||
const sg = stageGateOf(url, identifier);
|
||||
if (stageLocked(sg)) { openStageGate(sg); return; } // 孕周没到:弹提示,不跳(见 STAGE_GATE)
|
||||
haptic();
|
||||
const target = withIdentity(url);
|
||||
reportLastApp(url, identifier); // 净地址:恢复时身份按当前用户重新注入
|
||||
@@ -372,6 +430,7 @@
|
||||
cnt.textContent = '共 ' + apps.length + ' 个 ›';
|
||||
cnt.dataset.toast = '共 ' + apps.length + ' 个孕期工具';
|
||||
}
|
||||
markStageLocks();
|
||||
}
|
||||
function loadPublishedApps() {
|
||||
fetch('/api/mp/apps?isPublished=1&pageSize=200')
|
||||
@@ -1362,6 +1421,7 @@
|
||||
}
|
||||
function syncPregnancy() {
|
||||
Promise.resolve().then(pubPreg);
|
||||
markStageLocks(); /* 孕周变了 → 「16周+」角标跟着变 */
|
||||
/* 孕期小知识:只认真实孕周(自己判显隐,见 renderTips) */
|
||||
renderTips();
|
||||
nurseryRender();
|
||||
@@ -4978,7 +5038,7 @@
|
||||
if (title) title.style.display = (r && r.lead) ? '' : 'none';
|
||||
if (!box) return;
|
||||
box.innerHTML = '';
|
||||
((r && r.unused) || []).slice(0, 4).forEach(u => {
|
||||
((r && r.unused) || []).filter(u => !stageLocked(stageGateOf(u.url, u.identifier))).slice(0, 4).forEach(u => {
|
||||
const ident = u.identifier || '';
|
||||
const app = ident ? APP_BY_ID[ident] : null;
|
||||
/* ⚠️ 优先用上游带回的 `url`(2026-09-23 起它只推**我们已上架**的应用并把正式 url 带回来,
|
||||
@@ -5029,6 +5089,7 @@
|
||||
const done = $('#skDone');
|
||||
let nx = null;
|
||||
for (const u of unused) {
|
||||
if (stageLocked(stageGateOf(u.url, u.identifier))) continue; // 孕周没到的不当「下一件」推
|
||||
const ident = u.identifier || '', app = ident ? APP_BY_ID[ident] : null;
|
||||
const href = u.url || (app && app.url) || '', name = u.appName || (app && app.name) || '';
|
||||
if (href && name) { nx = { href, name, id: (app && app.identifier) || ident }; break; }
|
||||
|
||||
27
mama-box.css
27
mama-box.css
@@ -4750,3 +4750,30 @@ a.skip:focus{left:16px;top:16px}
|
||||
.app 带 .ripple 是 overflow:hidden,勾原来 top:-4px 伸出格子外。格子上方留 6px,勾下移同样距离,位置相对图标不变 */
|
||||
.apps-grid .app{padding-top:6px}
|
||||
.apps-grid .app.is-done::after{top:2px}
|
||||
|
||||
/* ================= 孕周开放门槛(2026-10-11)=================
|
||||
没到孕周的工具:宫格和首页工具卡上挂一枚「16周+」浅色角标,图标略淡;点了弹 #modalStageGate。
|
||||
配色只用现有的暖米 / 品牌橘,不另起颜色:角标是米底灰字,进度条是品牌橘渐变。 */
|
||||
.apps-grid .app.is-locked .a-ico{opacity:.5;filter:saturate(.6)}
|
||||
.apps-grid .app.is-locked .a-n{color:var(--muted)}
|
||||
.apps-grid .app.is-locked::after{content:attr(data-lock);position:absolute;top:-6px;left:calc(50% + 10px);
|
||||
padding:1px 5px;border-radius:7px;background:#F3E7DF;color:#9A7E70;font-size:9px;font-weight:800;line-height:14px;
|
||||
box-shadow:0 0 0 2px #FAF5F1;white-space:nowrap}
|
||||
.mini-card.is-locked::after{content:attr(data-lock);position:absolute;top:10px;right:10px;
|
||||
padding:1px 6px;border-radius:8px;background:rgba(255,255,255,.72);color:#9A7E70;font-size:10px;font-weight:800;line-height:16px}
|
||||
.mini-card.is-locked .mc-art{opacity:.55}
|
||||
.mini-card.kick.is-locked .mc-btn{background:rgba(255,255,255,.7);color:#9A7E70;box-shadow:none}
|
||||
|
||||
.sg-modal .sg-ico{width:64px;height:64px;margin:4px auto 10px;border-radius:20px;background:linear-gradient(150deg,#FFE9E1,#FFDDD3);
|
||||
display:grid;place-items:center;box-shadow:0 8px 18px rgba(244,89,60,.14)}
|
||||
.sg-modal .sg-ico img{width:40px;height:40px;object-fit:contain}
|
||||
.sg-modal .sg-ico b{font-size:24px;font-weight:900;color:var(--brand)}
|
||||
.sg-modal .sg-now{display:inline-block;margin-top:8px;padding:3px 10px;border-radius:10px;background:rgba(244,89,60,.1);color:var(--brand-dk);font-size:12px;font-weight:800}
|
||||
.sg-modal p{margin:10px 6px 0;line-height:1.75}
|
||||
.sg-track{margin:16px 8px 0}
|
||||
.sg-rail{height:8px;border-radius:4px;background:#EFE5DE;overflow:hidden}
|
||||
.sg-rail i{display:block;height:100%;border-radius:4px;background:linear-gradient(90deg,#FFB49F,var(--brand));transition:width .5s var(--ease)}
|
||||
.sg-ends{display:flex;justify-content:space-between;margin-top:6px;font-size:11.5px;font-weight:700;color:var(--muted)}
|
||||
.sg-left{margin-top:10px;font-size:13px;font-weight:700;color:var(--ink-2)}
|
||||
.sg-modal .modal-foot{margin-top:18px}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user