Compare commits
11 Commits
0b26cbe375
...
cc97ee2b6c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc97ee2b6c | ||
|
|
78e50bc858 | ||
|
|
564979ed2a | ||
|
|
cf431d51af | ||
|
|
9f7daaf3a6 | ||
|
|
ba995906e1 | ||
|
|
09d46c4e92 | ||
|
|
0911393a44 | ||
|
|
fca6f2602a | ||
|
|
90989a43a6 | ||
|
|
06512a2134 |
@@ -7,7 +7,7 @@
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<title>兜爸心选 · 孕育陪伴</title>
|
||||
<!-- V:26101013 缓存版本标记:改 css/js 时同步 bump 该值(与 js/app.js 的 VER 同源),静态资源按 mtime 走 ETag 自动刷新 -->
|
||||
<!-- V:26101024 缓存版本标记:改 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) -->
|
||||
|
||||
92
js/app.js
92
js/app.js
@@ -3,7 +3,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
/* 页面版本号(YYMMDD+序号):显示在「关于兜爸心选」页底部;bump 时同步 head 注释与 ?v= */
|
||||
var VER = '26101013';
|
||||
var VER = '26101024';
|
||||
const $ = (s, c) => (c || document).querySelector(s);
|
||||
const $$ = (s, c) => Array.from((c || document).querySelectorAll(s));
|
||||
|
||||
@@ -376,21 +376,79 @@
|
||||
$('#sgLeft').textContent = (left >= 7 ? '还有 ' + Math.ceil(left / 7) + ' 周' : '还有 ' + left + ' 天') + ',大约 ' + mdTxt(gaToDate(g.wk * 7)) + ' 开放';
|
||||
openModal('modalStageGate');
|
||||
}
|
||||
/* 宫格 / 首页工具卡上的小角标「16周+」:没到门槛的灰一点,到了自动去掉 */
|
||||
/* 记一下宫格上的小标签「孕16周开放」(图标右上角):没到门槛的灰一点,到了自动去掉;首页工具卡改成直接换替补(swapHomeCards) */
|
||||
function markStageLocks() {
|
||||
$$('#appsGrid .app').forEach(b => {
|
||||
const tiles = $$('#appsGrid .app');
|
||||
tiles.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;
|
||||
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);
|
||||
/* 2026-10-11 用户:没开放的排到后面,不放最前面。能用的保持后台排序(data-ord),没开放的整体挪到末尾;
|
||||
孕周到了解锁,就按 data-ord 回到原位。只挪 DOM 顺序,点击走事件委托不受影响。 */
|
||||
const grid = $('#appsGrid');
|
||||
/* 有标签时行距拉开(标签探出图标上沿 9px,原 14px 行距会贴着上一行的名字) */
|
||||
if (grid) grid.classList.toggle('has-locked', tiles.some(t => t.classList.contains('is-locked')));
|
||||
if (grid && tiles.length) {
|
||||
tiles.slice().sort((a, b) => (a.classList.contains('is-locked') - b.classList.contains('is-locked')) || (+a.dataset.ord - +b.dataset.ord))
|
||||
.forEach(t => grid.appendChild(t));
|
||||
}
|
||||
swapHomeCards();
|
||||
}
|
||||
/* 首页工具卡按孕周自动换(2026-10-11 用户:「孕育工具卡片替换成其他几个能让用户用的,否则首页体验特别差,
|
||||
系统根据用户填写的孕周自动调用」)。
|
||||
· 数胎动 / 胎儿估重 / 数宫缩 三个坑位,孕周没到门槛就换成替补,到了自动换回原工具。
|
||||
· 替补按 SWAP_POOL 顺序依次填:能不能吃 → 补水补剂 → 血压 → 血糖 → 待产清单(孕早期最用得上的排前面),
|
||||
同一个替补不会出现两次;体重、孕检日历本来就在首页,不进池子。
|
||||
· 只换卡里的内容,坑位的尺寸和底色不动(五张卡的高度账、配色都是排好的,见 CSS .bento)。
|
||||
· 原内容存在 data 里,换回时原样放回,再用上一次卡片接口的数据补一遍(数胎动次数等)。 */
|
||||
const SWAP_SLOTS = [['kick', 'taidong'], ['fweight', 'taierguzhong'], ['curve', 'gongsuopinlv']];
|
||||
const SWAP_ART = { /* 白色线稿,和其余卡片插画同一套笔触 */
|
||||
yunqinengbuchi: '<path d="M4 12h16a8 8 0 01-16 0z"/><path d="M9 8.5c0-1.5 1-1.8 1-3.2M13 8.5c0-1.5 1-1.8 1-3.2"/>',
|
||||
bushui: '<path d="M7 4h10l-1.3 15a2 2 0 01-2 1.8h-3.4a2 2 0 01-2-1.8z"/><path d="M7.6 10h8.8"/>',
|
||||
yun: '<path d="M12 19.5s-7.5-4.6-7.5-10A4.2 4.2 0 0112 7a4.2 4.2 0 017.5 2.5c0 5.4-7.5 10-7.5 10z"/><path d="M6 12h3l1.5-2.5 2 4 1.5-1.5H18"/>',
|
||||
yunqixuetang: '<path d="M12 3.5s-5.5 6.2-5.5 10.2a5.5 5.5 0 0011 0C17.5 9.7 12 3.5 12 3.5z"/><path d="M9.8 14.2a2.3 2.3 0 002.2 2.1"/>',
|
||||
daichanqingdan: '<path d="M5.5 9h13l-1 11h-11z"/><path d="M9 9V7a3 3 0 016 0v2"/>',
|
||||
};
|
||||
const SWAP_POOL = [
|
||||
{ id: 'yunqinengbuchi', name: '能不能吃', sub: '馋了先查一下', btn: '去查查' },
|
||||
{ id: 'bushui', name: '补水补剂', sub: '叶酸吃了吗', btn: '去记录' },
|
||||
{ id: 'yun', name: '孕期血压', sub: '量一量更安心', btn: '去记录' },
|
||||
{ id: 'yunqixuetang', name: '孕期血糖', sub: '餐后记一笔', btn: '去记录' },
|
||||
{ id: 'daichanqingdan', name: '待产清单', sub: '提前备起来', btn: '去看看' },
|
||||
];
|
||||
let lastCards = null; /* 上一次 tool-cards 的结果:换回原卡后重放 */
|
||||
function swapArt(id) {
|
||||
return '<svg class="mc-art sw-art" viewBox="2 2 20 20" aria-hidden="true" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' + (SWAP_ART[id] || '') + '</svg>';
|
||||
}
|
||||
function swapHomeCards() {
|
||||
const used = new Set();
|
||||
let restored = false;
|
||||
SWAP_SLOTS.forEach(([slot, id]) => {
|
||||
const el = $('.mini-card[data-mini="' + slot + '"]');
|
||||
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;
|
||||
const g = STAGE_GATE.find(x => x.id === id);
|
||||
if (!stageLocked(g)) {
|
||||
if (el.dataset.swap) { el.innerHTML = el._orig; delete el.dataset.swap; el.classList.remove('is-swap'); el.removeAttribute('aria-label'); restored = true; }
|
||||
return;
|
||||
}
|
||||
const sub = SWAP_POOL.find(x => !used.has(x.id) && APP_BY_ID[x.id] && APP_BY_ID[x.id].url) ||
|
||||
SWAP_POOL.find(x => !used.has(x.id));
|
||||
if (!sub) return;
|
||||
used.add(sub.id);
|
||||
if (el.dataset.swap === sub.id) return;
|
||||
if (!el.dataset.swap) el._orig = el.innerHTML;
|
||||
el.dataset.swap = sub.id;
|
||||
el.classList.add('is-swap');
|
||||
el.classList.remove('is-warn');
|
||||
el.setAttribute('aria-label', sub.name + ',' + sub.sub);
|
||||
el.innerHTML = slot === 'kick'
|
||||
? '<div class="mc-head"><div class="mc-title">' + sub.name + '</div><div class="mc-sub">' + sub.sub + '</div></div>' +
|
||||
'<span class="mc-btn">' + sub.btn + '</span>' + swapArt(sub.id)
|
||||
: '<div class="mc-title sm">' + sub.name + '</div><div class="mc-sub">' + sub.sub + '</div>' + swapArt(sub.id);
|
||||
});
|
||||
if (restored && lastCards) applyRemoteCards(lastCards);
|
||||
}
|
||||
|
||||
/* 跳转到其它 H5 应用。identifier 可选(来自 App 表),用于 last_app 上报的服务端在架校验;
|
||||
@@ -407,14 +465,14 @@
|
||||
function renderAppGrid(apps) {
|
||||
const grid = $('#appsGrid');
|
||||
if (!grid) return;
|
||||
grid.innerHTML = apps.map(a => {
|
||||
grid.innerHTML = apps.map((a, i) => {
|
||||
const ico = a.logo || a.icon || '';
|
||||
const name = a.name || a.identifier || '应用';
|
||||
/* 有 logo:白底圆角让图盖满;无 logo:colorTheme/调色板 + 首字彩色块。
|
||||
⚠️ colorTheme 目前全是 'blue' 颜色名 —— 有图时别拿它当底色,否则清一色蓝。 */
|
||||
const bg = ico ? 'background:#fff' : 'background:' + (a.colorTheme || appTileColor(name));
|
||||
const img = ico ? '<img class="a-ico" src="' + ico + '" alt="" loading="lazy">' : '<span class="a-ico a-ico-letter" style="background:' + bg + '">' + name.slice(0, 1) + '</span>';
|
||||
return '<button class="app ripple" data-url="' + encodeURIComponent(a.url) + '" data-id="' + encodeURIComponent(a.identifier || '') + '">' + img + '<span class="a-n">' + name + '</span></button>';
|
||||
return '<button class="app ripple" data-ord="' + i + '" data-url="' + encodeURIComponent(a.url) + '" data-id="' + encodeURIComponent(a.identifier || '') + '">' + img + '<span class="a-n">' + name + '</span></button>';
|
||||
}).join('');
|
||||
/* 磁贴点击(事件委托,innerHTML 重建后不用重绑) */
|
||||
grid.onclick = function (e) {
|
||||
@@ -736,9 +794,10 @@
|
||||
const APP_BY_ID = {};
|
||||
$$('[data-mini]').forEach(b => b.addEventListener('click', () => {
|
||||
const map = { kick: '数胎动', weight: '孕期体重', cal: '孕检日历', curve: '数宫缩', fweight: '胎儿估重' };
|
||||
const app = APP_BY_ID[MINI_APP_MAP[b.dataset.mini]];
|
||||
const sw = b.dataset.swap; /* 孕周没到、坑位换成了替补(见 swapHomeCards) */
|
||||
const app = APP_BY_ID[sw || MINI_APP_MAP[b.dataset.mini]];
|
||||
if (app && app.url) { jumpApp(app.url, app.identifier); return; }
|
||||
toast('「' + map[b.dataset.mini] + '」即将上线');
|
||||
toast('「' + (sw ? (SWAP_POOL.find(x => x.id === sw) || {}).name : map[b.dataset.mini]) + '」即将上线');
|
||||
}));
|
||||
|
||||
/* 记一下弹层只保留「全部孕期工具」,不再提供输入框 */
|
||||
@@ -3420,8 +3479,13 @@
|
||||
const cardsOwned = { kick: false, cal: false }; // 卡片接口已接管哪些格(本地源据此让位)
|
||||
function applyRemoteCards(r) {
|
||||
if (!r || r.ok === false || !(r.cards || []).length) return; // 降级:保留现有值
|
||||
lastCards = r;
|
||||
const bySlot = {};
|
||||
r.cards.forEach(c => { if (c && c.slot) bySlot[c.slot] = c; });
|
||||
/* 换成替补的坑位(孕周没到)不吃原工具的数据,免得把「馋了先查一下」改成「今天 0 次」 */
|
||||
r.cards.forEach(c => {
|
||||
const el = c && c.slot ? $('.mini-card[data-mini="' + c.slot + '"]') : null;
|
||||
if (c && c.slot && !(el && el.dataset.swap)) bySlot[c.slot] = c;
|
||||
});
|
||||
const cardOf = slot => $('.mini-card[data-mini="' + slot + '"]');
|
||||
const mark = (slot, c) => { const el = cardOf(slot); if (el) el.classList.toggle('is-warn', !!c.warn); };
|
||||
const setText = (sel, v) => { const el = $(sel); if (el) el.textContent = v || ''; };
|
||||
|
||||
16
mama-box.css
16
mama-box.css
@@ -4752,13 +4752,19 @@ a.skip:focus{left:16px;top:16px}
|
||||
.apps-grid .app.is-done::after{top:2px}
|
||||
|
||||
/* ================= 孕周开放门槛(2026-10-11)=================
|
||||
没到孕周的工具:宫格和首页工具卡上挂一枚「16周+」浅色角标,图标略淡;点了弹 #modalStageGate。
|
||||
没到孕周的工具:记一下宫格里图标右上角挂一枚「孕16周开放」小标签,配色同站内通用标签(品牌橘字 + 10% 品牌橘底,叠在米底上为实色 #FDE7E1,免得透出图标)(大半在图标外,只压住右上角一点),图标略淡;点了弹 #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:-2px;left:calc(50% + 12px);
|
||||
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}
|
||||
.apps-grid .app.is-locked::after{content:attr(data-lock);position:absolute;top:-9px;right:calc(50% - 44px);
|
||||
padding:0 6px;border-radius:8px;background:#FDE7E1;color:var(--brand);font-size:10px;font-weight:700;line-height:16px;
|
||||
box-shadow:0 0 0 2px #FAF5F1;white-space:nowrap;pointer-events:none}
|
||||
/* 宫格按钮带 .ripple(overflow:hidden 裁水波纹),锁住的这几格放开,否则探出去的标签会被切掉一半。
|
||||
标签往上探出 11px(只压住图标右上角 5px),第一排上面得留出这点空 */
|
||||
.apps-grid .app.is-locked{overflow:visible}
|
||||
/* 有「孕N周开放」标签时行距从 14 拉到 24:标签离上一行名字留出约 15px,不挤 */
|
||||
#appsGrid.has-locked{row-gap:24px}
|
||||
#appsGrid{padding-top:13px}
|
||||
.mini-card.is-locked::after{content:attr(data-lock);position:absolute;top:9px;right:9px;
|
||||
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}
|
||||
@@ -4776,4 +4782,6 @@ a.skip:focus{left:16px;top:16px}
|
||||
.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}
|
||||
/* 首页工具卡自动换成替补(swapHomeCards):尺寸、底色沿用坑位原样,只换内容;插画是白色线稿 svg */
|
||||
.mini-card .sw-art{height:auto;aspect-ratio:1;opacity:.95}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user