首页工具区改为育儿首页式图标宫格(加血压/血糖),孕期数据加胎动/宫缩/估重并改毛玻璃 v26103105

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
deploy
2026-10-31 12:03:44 +00:00
parent 13bcc27cdf
commit 96b5a87d23
2 changed files with 182 additions and 139 deletions

View File

@@ -7,7 +7,7 @@
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<title>兜爸心选 · 孕育陪伴</title>
<!-- V:26103104 缓存版本标记:改 css/js 时同步 bump 该值(与 js/app.js 的 VER 同源),静态资源按 mtime 走 ETag 自动刷新 -->
<!-- V:26103105 缓存版本标记:改 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) -->
@@ -204,144 +204,161 @@
<button class="dock-all" data-sheet="open">所有工具 ›</button>
</div>
<p class="dock-note"><svg viewBox="0 0 24 24" width="11" height="11" aria-hidden="true"><path d="M11 2.6l1.9 5.2 5.2 1.9-5.2 1.9L11 16.8l-1.9-5.2L3.9 9.7l5.2-1.9L11 2.6z" fill="currentColor"/><path d="M18.4 14.6l.8 2.1 2.2.8-2.2.8-.8 2.1-.8-2.1-2.2-.8 2.2-.8.8-2.1z" fill="currentColor" opacity=".6"/></svg>妈妈每记录一次,宝宝就多一段成长记忆</p>
<!-- 2026-09-30 五张卡改版:白色粗标题 + 各工具 LOGO 里的图形(取自点进去那个工具页左上角的 LOGO,
只留里面的图形),做成白色单色半透明「水印」斜压在卡片右下角,只作装饰;assets/img/*.svg 矢量。卡底色跟点进去的工具页同色系、稍深一档。
只换皮:排布、高度账、data-mini 跳转和各 id 的数据回填一律没动。样式见 mama-box.css「参考图改版」。 -->
<!-- 五张工具卡 + 一个出口。宫格本身这一版没动:
不等宽三列 + 竖向堆叠本来就是这一屏最不传统的部分,
而且两列高度是硬凑齐的(见 style.css 里 .mini-card.curve 上面那段),
动它得整列重算,收益也不在这儿。 -->
<div class="bento">
<div class="bento-col">
<button class="mini-card kick ripple" data-mini="kick">
<div class="mc-head">
<div class="mc-title">数胎动</div>
<!-- ⚠️ 默认值必须是中性占位,**不能写演示数字**:访客态(无 openid)与
records 接口失败/超时这两种情况下没人会改它,写死 3 就是假数据。
真实值由 __applyRecords() 写入(未记录的新用户是 0)。
(2026-09-22 新用户实测:工具卡上的演示占位值一律归中性。) -->
<div class="mc-sub">今天 <b id="kickTodayHome">--</b> 次</div>
<!-- 第二行(上游 sub,如「2 段 · 最近 13:00」)——只有 kick 与 weight 两张卡
有富余竖向空间;fweight/cal/curve 是固定高度且列很窄,放不下第二行。 -->
<div class="mc-extra" id="kickExtra"></div>
</div>
<span class="mc-btn">记录</span>
<img class="mc-art" src="assets/img/6add0c9e5856.svg" alt="" aria-hidden="true">
</button>
<!-- 第五个工具。原来是「胎心记录」,本批换成「胎儿估重」——
后者是真正部署好的站点(beian1.douba.top),点了能用;
而胎心记录在这张卡上一直只是个弹 toast 的占位入口。
⚠️ 胎心记录**没有被删掉**:它还在「记一下」弹层里(快捷区 q3 +
全部工具宫格),档案页那两条「来自「胎心记录」」的历史记录也照旧。
这里换掉的只是首页宫格的一个坑位。
⚠️ 类名、结构、三个尺寸全部照搬原来那张卡:外层仍是 .mini-card
(.heart 只是改名成 .fweight,两条 CSS 跟着改),内部仍是
「名字 + 状态两行叠着 + 右下角插画」。所以左列 158 + 10 + 64 = 232
这笔账一个数都没动,右列(88 + 10 + 80 + 10 + 44)不用跟着补。
图标换成了内联 SVG 的表盘,理由见 patch 脚本里那段注释。 -->
<button class="mini-card fweight ripple" data-mini="fweight">
<div class="mc-title sm">胎儿估重</div>
<div class="mc-sub">去测算</div>
<img class="mc-art" src="assets/img/3c4cf0da356c.svg" alt="" aria-hidden="true">
</button>
</div>
<button class="mini-card weight ripple" data-mini="weight">
<img class="mc-art big" src="assets/img/fc0ab3d2e21a.svg" alt="" aria-hidden="true">
<div class="mc-title">孕期体重</div>
<!-- ⚠️ 这里原来是写死的演示值「58.2 kg」,任何用户都会看到(2026-09-22 演示数据泄漏)。
现在由 `applyRemoteCards()` 按 box_app 封装的卡片接口写入:
有值时写 `value` + 小字 `<small>unit</small>`,没有值时保持中性占位 `--`
(**不写 0**,0 是个看着像真的假值)。接口拿不到就保留这里的默认值。 -->
<div class="mc-big" id="weightVal">--</div>
<div class="mc-extra" id="weightExtra"></div>
<!-- 2026-10-31 用户定:工具区改成育儿首页那种「图标宫格」——4 列 × 2 行,每格 彩色圆角图标 + 名字 + 状态胶囊。
7 个孕期工具 + 「全部工具」出口。data-mini 与各 id(#kickTodayHome / #weightVal / #calNext)照旧,
卡片接口回填、孕周没到换替补(swapHomeCards)、点击跳转都沿用原逻辑,只换皮。
图标就是原来五张卡上的白色 LOGO 图形,铺在各工具同色系的渐变底上。样式在下面 <style>(不进 mama-box.css)。 -->
<div class="tg-grid">
<button class="tg kick ripple" data-mini="kick">
<i class="tg-ic"><img src="assets/img/6add0c9e5856.svg" alt="" aria-hidden="true"></i>
<span class="tg-n">数胎动</span>
<!-- 默认中性占位,不写演示数字(真实值由卡片接口写入) -->
<span class="tg-s">今天 <b id="kickTodayHome">--</b> 次</span>
</button>
<button class="tg weight ripple" data-mini="weight">
<i class="tg-ic"><img src="assets/img/fc0ab3d2e21a.svg" alt="" aria-hidden="true"></i>
<span class="tg-n">孕期体重</span>
<span class="tg-s" id="weightVal">--</span>
</button>
<button class="tg cal ripple" data-mini="cal">
<i class="tg-ic"><img src="assets/img/a693b92f3239.svg" alt="" aria-hidden="true"></i>
<span class="tg-n">孕检日历</span>
<span class="tg-s" id="calNext">—</span>
</button>
<button class="tg fweight ripple" data-mini="fweight">
<i class="tg-ic"><img src="assets/img/3c4cf0da356c.svg" alt="" aria-hidden="true"></i>
<span class="tg-n">胎儿估重</span>
<span class="tg-s">去测算</span>
</button>
<button class="tg curve ripple" data-mini="curve">
<i class="tg-ic"><img src="assets/img/ad7487f2e445.svg" alt="" aria-hidden="true"></i>
<span class="tg-n">数宫缩</span>
<span class="tg-s" data-wk="cn">—</span>
</button>
<button class="tg bp ripple" data-mini="bp">
<i class="tg-ic"><svg viewBox="2 2 20 20" aria-hidden="true" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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"/></svg></i>
<span class="tg-n">孕期血压</span>
<span class="tg-s">量一量</span>
</button>
<button class="tg glu ripple" data-mini="glu">
<i class="tg-ic"><svg viewBox="2 2 20 20" aria-hidden="true" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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"/></svg></i>
<span class="tg-n">孕期血糖</span>
<span class="tg-s">记一笔</span>
</button>
<button class="tg all ripple" data-sheet="open">
<i class="tg-ic"><svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="#fff" stroke-width="2.2"><rect x="4" y="4" width="6.5" height="6.5" rx="2"/><rect x="13.5" y="4" width="6.5" height="6.5" rx="2"/><rect x="4" y="13.5" width="6.5" height="6.5" rx="2"/><rect x="13.5" y="13.5" width="6.5" height="6.5" rx="2"/></svg></i>
<span class="tg-n">全部工具</span>
<span class="tg-s">更多</span>
</button>
<div class="bento-col">
<button class="mini-card cal ripple" data-mini="cal">
<div class="mc-title sm">孕检日历</div>
<div class="mc-sub" id="calNext">—</div>
<img class="mc-art" src="assets/img/a693b92f3239.svg" alt="" aria-hidden="true">
</button>
<!-- 生长曲线原来是 flex:1,把这一列剩下的高度全吃了(约 134px),
而它的内容和上面的孕检日历一模一样多。压到 80px,
省下的 54px 给下面那张「更多工具」。
⚠️ 本批(第九批)把这张卡从「生长曲线」换成「数宫缩」:
文案改这一行;落点改 app.js 的 MINI_APP_MAP.curve
(taiershengzhang → gongsuopinlv,指向已上架的「宫缩频率计算」)。
⚠️「生长曲线」工具本身**没被删**:App 表里 taiershengzhang /
taierquxian 都还在,仍会出现在「记一下」的全部工具列表里;
这里换掉的只是首页这一个坑位。
图标(2026-09-30)已换成秒表萌物,见下面五张卡的改版注释。
⚠️ 副标题仍是 data-wk="cn" 自动显示孕周,本批未要求改文案。 -->
<button class="mini-card curve ripple" data-mini="curve">
<div class="mc-title sm">数宫缩</div>
<div class="mc-sub" data-wk="cn">—</div>
<img class="mc-art" src="assets/img/ad7487f2e445.svg" alt="" aria-hidden="true">
</button>
<!-- 通往「记一下」弹层里那十几个工具。底部那颗 FAB 开的也是它。
故意不做成糖果色卡:它不是第六个工具,是个出口,
灰米底 + 一行小字,和上面五张分得开。 -->
<button class="mini-card more ripple" data-sheet="open">
<span class="mc-more-t">更多工具</span><i class="chev">›</i>
</button>
</div>
</div>
<!-- 2026-10-31 用户定:工具宫格下面加「我的孕期数据」——体重 / 血压 / 血糖最近一次的数值,
妈妈在对应工具里填完,回到首页自动刷新(app.js loadVitals:各工具自己的云端存储,只读、只认非示例记录)。
<!-- 2026-10-31 用户定:「我的孕期数据」——胎动 / 体重 / 血压 / 血糖常驻,数宫缩、胎儿估重有记录才出现;
妈妈在哪个工具里记过,哪格就有数,并排到前面(app.js loadVitals / vtLayout)。毛玻璃格 + 彩色光斑底。
有身份 + 孕期才显示;产后 / 访客整块隐藏。点哪格就进哪个工具。 -->
<div class="vt hide" id="vitals">
<div class="vt-h"><b>我的孕期数据</b><span>在工具里记一笔,这里自动更新</span></div>
<div class="vt-h"><b>我的孕期数据</b><span>用过的工具,数据自动出现在这里</span></div>
<div class="vt-row">
<button type="button" class="vt-it vt-k ripple" data-vt="kick">
<span class="vt-top"><i class="vt-ic"><svg viewBox="0 0 24 24" aria-hidden="true"><ellipse cx="13" cy="15" rx="4.6" ry="5.8" transform="rotate(10 13 15)" fill="currentColor" opacity=".25" stroke="currentColor" stroke-width="1.8"/><circle cx="6.8" cy="8.2" r="1.7" fill="currentColor"/><circle cx="10.4" cy="6.2" r="1.5" fill="currentColor"/><circle cx="14" cy="5.6" r="1.35" fill="currentColor"/><circle cx="17.2" cy="6.6" r="1.2" fill="currentColor"/></svg></i><span class="vt-l">胎动</span><small class="vt-u">次</small></span>
<span class="vt-v"><b id="vtK">--</b></span>
<span class="vt-f"><span class="vt-s" id="vtKs"></span><span class="vt-d" id="vtKd"></span></span>
</button>
<button type="button" class="vt-it vt-w ripple" data-vt="weight">
<span class="vt-top"><i class="vt-ic"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="4" width="17" height="16" rx="5" fill="currentColor" opacity=".18"/><rect x="3.5" y="4" width="17" height="16" rx="5" fill="none" stroke="currentColor" stroke-width="2"/><path d="M8.5 10.5a3.5 3.5 0 017 0" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><path d="M12 10.5l1.6-1.8" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg></i><span class="vt-l">体重<small>kg</small></span></span>
<span class="vt-top"><i class="vt-ic"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="4" width="17" height="16" rx="5" fill="currentColor" opacity=".25"/><rect x="3.5" y="4" width="17" height="16" rx="5" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M8.5 10.5a3.5 3.5 0 017 0" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M12 10.5l1.6-1.8" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg></i><span class="vt-l">体重</span><small class="vt-u">kg</small></span>
<span class="vt-v"><b id="vtW">--</b></span>
<span class="vt-s" id="vtWs">去记一笔</span>
<span class="vt-d" id="vtWd"></span>
<span class="vt-f"><span class="vt-s" id="vtWs"></span><span class="vt-d" id="vtWd"></span></span>
</button>
<button type="button" class="vt-it vt-p ripple" data-vt="bp">
<span class="vt-top"><i class="vt-ic"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 20.5s-7.5-4.6-7.5-10.2A4.3 4.3 0 0112 7.6a4.3 4.3 0 017.5 2.7c0 5.6-7.5 10.2-7.5 10.2z" fill="currentColor" opacity=".18"/><path d="M12 20.5s-7.5-4.6-7.5-10.2A4.3 4.3 0 0112 7.6a4.3 4.3 0 017.5 2.7c0 5.6-7.5 10.2-7.5 10.2z" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/><path d="M6.5 12.5h3l1.3-2.3 2 4.3 1.3-2h3.4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg></i><span class="vt-l">血压<small>mmHg</small></span></span>
<span class="vt-top"><i class="vt-ic"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 20.5s-7.5-4.6-7.5-10.2A4.3 4.3 0 0112 7.6a4.3 4.3 0 017.5 2.7c0 5.6-7.5 10.2-7.5 10.2z" fill="currentColor" opacity=".25" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M6.5 12.5h3l1.3-2.3 2 4.3 1.3-2h3.4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg></i><span class="vt-l">血压</span><small class="vt-u">mmHg</small></span>
<span class="vt-v"><b id="vtP">--</b></span>
<span class="vt-s" id="vtPs">去记一笔</span>
<span class="vt-d" id="vtPd"></span>
<span class="vt-f"><span class="vt-s" id="vtPs"></span><span class="vt-d" id="vtPd"></span></span>
</button>
<button type="button" class="vt-it vt-g ripple" data-vt="glu">
<span class="vt-top"><i class="vt-ic"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3.5s6 6.6 6 10.9a6 6 0 01-12 0C6 10.1 12 3.5 12 3.5z" fill="currentColor" opacity=".18"/><path d="M12 3.5s6 6.6 6 10.9a6 6 0 01-12 0C6 10.1 12 3.5 12 3.5z" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/><path d="M9.3 14.6a2.8 2.8 0 002.7 2.6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg></i><span class="vt-l">血糖<small>mmol/L</small></span></span>
<span class="vt-top"><i class="vt-ic"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3.5s6 6.6 6 10.9a6 6 0 01-12 0C6 10.1 12 3.5 12 3.5z" fill="currentColor" opacity=".25" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M9.3 14.6a2.8 2.8 0 002.7 2.6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg></i><span class="vt-l">血糖</span><small class="vt-u">mmol/L</small></span>
<span class="vt-v"><b id="vtG">--</b></span>
<span class="vt-s" id="vtGs">去记一笔</span>
<span class="vt-d" id="vtGd"></span>
<span class="vt-f"><span class="vt-s" id="vtGs"></span><span class="vt-d" id="vtGd"></span></span>
</button>
<button type="button" class="vt-it vt-c ripple hide" data-vt="curve">
<span class="vt-top"><i class="vt-ic"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="13" r="7.5" fill="currentColor" opacity=".25" stroke="currentColor" stroke-width="1.8"/><path d="M12 9v4l2.6 1.6M10 3h4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg></i><span class="vt-l">宫缩间隔</span><small class="vt-u"></small></span>
<span class="vt-v"><b id="vtC">--</b></span>
<span class="vt-f"><span class="vt-s" id="vtCs"></span><span class="vt-d" id="vtCd"></span></span>
</button>
<button type="button" class="vt-it vt-e ripple hide" data-vt="fweight">
<span class="vt-top"><i class="vt-ic"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="8" fill="currentColor" opacity=".25" stroke="currentColor" stroke-width="1.8"/><path d="M12 12l3.6-3.6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><circle cx="12" cy="12" r="1.6" fill="currentColor"/></svg></i><span class="vt-l">胎儿估重</span><small class="vt-u"></small></span>
<span class="vt-v"><b id="vtE">--</b></span>
<span class="vt-f"><span class="vt-s" id="vtEs"></span><span class="vt-d" id="vtEd"></span></span>
</button>
</div>
</div>
<style>
.vt{margin-top:12px;padding:12px;border-radius:20px;background:linear-gradient(180deg,#FFF3EE,#FFFDFB);border:1px solid #F7E0D7}
.vt.hide{display:none}
.vt-h{display:flex;align-items:baseline;gap:8px;margin:0 2px 10px}
/* —— 工具宫格(仿育儿首页) —— */
.tg-grid{display:grid;grid-template-columns:repeat(4,1fr);row-gap:16px;column-gap:4px;margin-top:14px}
.tg{position:relative;display:flex;flex-direction:column;align-items:center;gap:5px;min-width:0;padding:2px 0;border:0;background:none;font:inherit;color:inherit;overflow:visible}
.tg-ic{width:48px;height:48px;border-radius:15px;display:grid;place-items:center;box-shadow:0 6px 14px var(--tg-sh,rgba(230,110,80,.25)),inset 0 1px 0 rgba(255,255,255,.35);background:var(--tg-bg)}
.tg-ic img,.tg-ic svg{width:27px;height:27px;object-fit:contain}
.tg.weight .tg-ic img{width:30px;height:30px}
.tg-n{font-size:13px;font-weight:600;color:#3B2A2A;white-space:nowrap}
.tg-s{max-width:100%;padding:1px 8px;border-radius:99px;font-size:11px;line-height:17px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;background:var(--tg-pl);color:var(--tg-tx)}
.tg-s b{font-weight:800}
.tg-s small{margin-left:1px;font-size:9.5px}
.tg.kick{--tg-bg:linear-gradient(150deg,#F7A072,#E9673F);--tg-pl:#FFEADF;--tg-tx:#D4552F;--tg-sh:rgba(233,103,63,.28)}
.tg.weight{--tg-bg:linear-gradient(150deg,#F79A92,#E5606A);--tg-pl:#FFE6E6;--tg-tx:#D24B57;--tg-sh:rgba(229,96,106,.28)}
.tg.cal{--tg-bg:linear-gradient(150deg,#9CC873,#68A043);--tg-pl:#E9F5DE;--tg-tx:#4E8A2C;--tg-sh:rgba(104,160,67,.28)}
.tg.fweight{--tg-bg:linear-gradient(150deg,#8FB0EA,#5B7FCC);--tg-pl:#E5EDFC;--tg-tx:#4568B8;--tg-sh:rgba(91,127,204,.28)}
.tg.curve{--tg-bg:linear-gradient(150deg,#B39BEA,#8366CC);--tg-pl:#EFE8FC;--tg-tx:#6F50BC;--tg-sh:rgba(131,102,204,.28)}
.tg.bp{--tg-bg:linear-gradient(150deg,#F58FB0,#DC5584);--tg-pl:#FDE6EE;--tg-tx:#C93F70;--tg-sh:rgba(220,85,132,.28)}
.tg.glu{--tg-bg:linear-gradient(150deg,#6FD0C2,#2FA192);--tg-pl:#DFF5F1;--tg-tx:#22887B;--tg-sh:rgba(47,161,146,.28)}
.tg.all{--tg-bg:linear-gradient(150deg,#F2C17E,#E19A4C);--tg-pl:#FBEEDC;--tg-tx:#B9772D;--tg-sh:rgba(225,154,76,.28)}
.tg.is-warn .tg-s{background:#FDE3E0;color:#D2453B}
html.night .tg-n{color:#F1E6EE}
html.night .tg-s{background:rgba(255,255,255,.1);color:#E9D6E2}
/* —— 我的孕期数据:彩色光斑底 + 毛玻璃格 —— */
.vt{position:relative;margin-top:18px;padding:14px 12px 12px;border-radius:22px;overflow:hidden;isolation:isolate;
background:radial-gradient(120px 110px at 8% 6%,rgba(255,160,130,.55),transparent 70%),
radial-gradient(150px 120px at 96% 18%,rgba(196,166,255,.5),transparent 70%),
radial-gradient(160px 120px at 20% 100%,rgba(120,214,198,.45),transparent 70%),
radial-gradient(150px 120px at 88% 96%,rgba(255,150,190,.45),transparent 70%),
linear-gradient(160deg,#FFF4EF,#FBF3FF);
border:1px solid rgba(255,255,255,.8);box-shadow:0 10px 26px rgba(214,140,120,.14)}
.vt.hide,.vt-it.hide{display:none}
.vt-h{display:flex;align-items:baseline;gap:8px;margin:0 4px 11px}
.vt-h b{font-size:15.5px;color:#3B2A2A}
.vt-h span{font-size:11.5px;color:#A88E89}
.vt-row{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
.vt-it{display:flex;flex-direction:column;align-items:flex-start;gap:3px;padding:10px 10px 10px;border-radius:16px;background:#fff;box-shadow:0 4px 12px rgba(201,120,96,.09);text-align:left;min-width:0;border:0;font:inherit;color:inherit}
.vt-top{display:flex;align-items:center;gap:5px;width:100%}
.vt-ic{flex:none;width:24px;height:24px;border-radius:8px;display:grid;place-items:center}
.vt-ic svg{width:17px;height:17px}
.vt-l{display:flex;flex-direction:column;font-size:12.5px;line-height:1.15;color:#6E5A57;font-weight:600;white-space:nowrap;min-width:0}
.vt-l small{font-size:9.5px;line-height:1.2;color:#B7A29D;font-weight:500}
.vt-h span{font-size:11.5px;color:#9C8580}
.vt-row{display:grid;grid-template-columns:repeat(2,1fr);gap:9px}
.vt-it{position:relative;display:flex;flex-direction:column;align-items:flex-start;gap:4px;padding:11px 12px 11px;border-radius:18px;min-width:0;text-align:left;font:inherit;color:inherit;overflow:hidden;
background:linear-gradient(145deg,rgba(255,255,255,.72),rgba(255,255,255,.42));
-webkit-backdrop-filter:blur(14px) saturate(160%);backdrop-filter:blur(14px) saturate(160%);
border:1px solid rgba(255,255,255,.85);box-shadow:0 6px 16px rgba(170,110,120,.10),inset 0 1px 0 rgba(255,255,255,.9)}
.vt-it::before{content:"";position:absolute;right:-26px;top:-26px;width:84px;height:84px;border-radius:50%;background:var(--vt-c);opacity:.16;filter:blur(6px);z-index:-1}
.vt-it.span2{grid-column:span 2}
.vt-top{display:flex;align-items:center;gap:6px;width:100%}
.vt-ic{flex:none;width:26px;height:26px;border-radius:9px;display:grid;place-items:center;color:var(--vt-c);background:rgba(255,255,255,.75)}
.vt-ic svg{width:18px;height:18px}
.vt-l{font-size:13px;color:#5E4B48;font-weight:700;white-space:nowrap}
.vt-u{margin-left:auto;font-size:10.5px;color:#A8938F}
.vt-v{margin-top:2px;white-space:nowrap}
.vt-v b{font-size:23px;line-height:1.15;font-weight:800;letter-spacing:-.5px;color:#3B2A2A;font-variant-numeric:tabular-nums}
.vt-it.empty .vt-v b{color:#DCCBC6}
.vt-s{font-size:11px;line-height:1.35;padding:1px 8px;border-radius:99px;background:#FFEDE6;color:#D9573A;white-space:nowrap}
.vt-v b{font-size:25px;line-height:1.15;font-weight:800;letter-spacing:-.5px;color:#3B2A2A;font-variant-numeric:tabular-nums}
.vt-it.empty .vt-v b{color:rgba(120,90,90,.25)}
.vt-f{display:flex;align-items:center;gap:6px;width:100%;min-width:0}
.vt-s{flex:none;font-size:11px;line-height:1.4;padding:1px 8px;border-radius:99px;background:rgba(255,255,255,.75);color:var(--vt-c);font-weight:700;white-space:nowrap}
.vt-s:empty{display:none}
.vt-it.empty .vt-s{background:none;padding:1px 0;color:#E0775C;font-weight:600}
.vt-s.ok{background:#E6F5EC;color:#2F8A5E}
.vt-s.hi{background:#FDE7E5;color:#D2453B}
.vt-s.lo{background:#FFF1DC;color:#C77A1E}
.vt-d{font-size:10.5px;color:#B09C98;white-space:nowrap;max-width:100%;overflow:hidden;text-overflow:ellipsis}
.vt-it.empty .vt-s{background:none;padding:1px 0;color:var(--vt-c)}
.vt-s.ok{background:rgba(47,138,94,.12);color:#2F8A5E}
.vt-s.hi{background:rgba(210,69,59,.12);color:#D2453B}
.vt-s.lo{background:rgba(199,122,30,.14);color:#C77A1E}
.vt-d{font-size:10.5px;color:#9C8885;white-space:nowrap;min-width:0;overflow:hidden;text-overflow:ellipsis}
.vt-d:empty{display:none}
.vt-w .vt-ic{background:#FFE9E1;color:#F4593C}
.vt-p .vt-ic{background:#F1EAFB;color:#8A63C9}
.vt-g .vt-ic{background:#E4F1FC;color:#2F86C9}
html.night .vt{background:#2C2433;border-color:#3D3346}
html.night .vt-it{background:#372E40;box-shadow:none}
.vt-k{--vt-c:#E9673F}
.vt-w{--vt-c:#E5606A}
.vt-p{--vt-c:#C94F8C}
.vt-g{--vt-c:#22968A}
.vt-c{--vt-c:#7A5BC8}
.vt-e{--vt-c:#4E74C4}
html.night .vt{background:radial-gradient(130px 110px at 8% 6%,rgba(255,140,110,.22),transparent 70%),radial-gradient(150px 120px at 96% 18%,rgba(170,140,255,.22),transparent 70%),radial-gradient(160px 120px at 20% 100%,rgba(90,190,175,.2),transparent 70%),#2A2231;border-color:rgba(255,255,255,.08);box-shadow:none}
html.night .vt-it{background:rgba(255,255,255,.07);border-color:rgba(255,255,255,.1);box-shadow:none}
html.night .vt-ic,html.night .vt-s{background:rgba(255,255,255,.1)}
html.night .vt-h b,html.night .vt-v b{color:#F3E8F0}
html.night .vt-l{color:#CDBBC8}
html.night .vt-it.empty .vt-v b{color:rgba(255,255,255,.2)}
</style>
<!-- 此刻(2026-10-01 需求文档 v1.0 第二节;同日两次改版)。数据 GET /api/mp/public/mama-box/pulse,渲染在 app.js pulseCard()。
⚠️ 模拟数据是产品负责人 2026-10-01 拍板的例外(AGENTS.md §4.12),只限这一条。
@@ -2126,7 +2143,7 @@
<!-- 2026-10-05 首页头图:宝宝时不时动一动小手小脚(独立文件,删掉这一行即下线) -->
<script src="js/baby-limbs.js?v=26101201"></script>
<script src="js/coach.js?v=26101211"></script>
<script src="js/birth-flow.js?v=26103104"></script>
<script src="js/birth-flow.js?v=26103105"></script>
<!-- 禁止下拉回弹(2026-10-11 统一规则,与孕期体重同一段):竖向拖动时从手指下往上找还能朝该方向滚的容器,找不到才拦;横滑、双指、可滚列表不受影响 -->
<script>

View File

@@ -3,7 +3,7 @@
(function () {
'use strict';
/* 页面版本号(YYMMDD+序号):显示在「关于兜爸心选」页底部;bump 时同步 head 注释与 ?v= */
var VER = '26103104';
var VER = '26103105';
const $ = (s, c) => (c || document).querySelector(s);
const $$ = (s, c) => Array.from((c || document).querySelectorAll(s));
@@ -414,19 +414,18 @@
const SWAP_POOL = [
{ id: 'yunqinengbuchi', name: '能不能吃', sub: '馋了先查一下', btn: '去查查' },
{ id: 'bushui', name: '补水补剂', sub: '叶酸吃了吗', btn: '去记录' },
{ id: 'yun', name: '孕期血压', sub: '量一量更安心', btn: '去记录' },
{ id: 'yunqixuetang', name: '孕期血糖', sub: '餐后记一笔', btn: '去记录' },
/* 2026-10-31:血压、血糖已在宫格里常驻,不再进替补池 */
{ 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>';
return '<svg class="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 + '"]');
const el = $('.tg[data-mini="' + slot + '"]');
if (!el) return;
const g = STAGE_GATE.find(x => x.id === id);
if (!stageLocked(g)) {
@@ -443,10 +442,8 @@
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);
/* 2026-10-31 宫格改版:替补也是「图标 + 名字 + 胶囊」,底色沿用坑位的颜色 */
el.innerHTML = '<i class="tg-ic">' + swapArt(sub.id) + '</i><span class="tg-n">' + sub.name + '</span><span class="tg-s">' + sub.btn + '</span>';
});
if (restored && lastCards) applyRemoteCards(lastCards);
}
@@ -790,11 +787,13 @@
weight: 'yunqitizhong', // 孕期体重管理 (1012)
cal: 'chanjianrili', // 孕检日历 (1009)
curve: 'gongsuopinlv', // 数宫缩(原「生长曲线」,第九批换坑位)
fweight:'taierguzhong' // 胎儿估重 (1037)
fweight:'taierguzhong', // 胎儿估重 (1037)
bp: 'yun', // 孕期血压(2026-10-31 宫格改版加入)
glu: 'yunqixuetang' // 孕期血糖(同上)
};
const APP_BY_ID = {};
$$('[data-mini]').forEach(b => b.addEventListener('click', () => {
const map = { kick: '数胎动', weight: '孕期体重', cal: '孕检日历', curve: '数宫缩', fweight: '胎儿估重' };
const map = { kick: '数胎动', weight: '孕期体重', cal: '孕检日历', curve: '数宫缩', fweight: '胎儿估重', bp: '孕期血压', glu: '孕期血糖' };
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; }
@@ -3683,13 +3682,14 @@
function applyRemoteCards(r) {
if (!r || r.ok === false || !(r.cards || []).length) return; // 降级:保留现有值
lastCards = r;
vtCards(r);
const bySlot = {};
/* 换成替补的坑位(孕周没到)不吃原工具的数据,免得把「馋了先查一下」改成「今天 0 次」 */
r.cards.forEach(c => {
const el = c && c.slot ? $('.mini-card[data-mini="' + c.slot + '"]') : null;
const el = c && c.slot ? $('.tg[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 cardOf = slot => $('.tg[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 || ''; };
@@ -3704,7 +3704,7 @@
mark('kick', k);
}
const f = bySlot.fweight;
if (f) { setText('.mini-card.fweight .mc-sub', f.text); mark('fweight', f); }
if (f) { setText('.tg.fweight .tg-s', f.text); mark('fweight', f); }
const w = bySlot.weight;
if (w) {
@@ -3735,7 +3735,7 @@
const cv = bySlot.curve;
if (cv) {
const el = $('.mini-card.curve .mc-sub');
const el = $('.tg.curve .tg-s');
if (el) {
/* 有数据时上游 text 是「最近间隔 4分12秒」,那列只有 ≈73px 放不下(会省略掉最关键的数字)→
改用紧凑的 value+unit(「4分12秒」);空态 text 本来就是「孕 7 周」这种短句,直接用。 */
@@ -4413,7 +4413,7 @@
if (!sec) return;
let seen = false, last = null, feed = [], fi = 0, ticking = null, peerMode = false, loadedWk = null, shownN = 0;
const esc = s => String(s == null ? '' : s).replace(/[&<>"]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' })[c]);
const tapMini = k => { const b = $('.mini-card[data-mini="' + k + '"]'); if (b) b.click(); };
const tapMini = k => { const b = $('.tg[data-mini="' + k + '"]'); if (b) b.click(); };
const TODAY_GO = {
kick: () => tapMini('kick'),
weight: () => tapMini('weight'),
@@ -5190,19 +5190,47 @@
const n = Math.round((a - b) / DAY_MS);
return n === 0 ? '今天' : n === 1 ? '昨天' : (b.getMonth() + 1) + '月' + b.getDate() + '日';
}
const VT_ID = { kick: 'vtK', weight: 'vtW', bp: 'vtP', glu: 'vtG', curve: 'vtC', fweight: 'vtE' };
const VT_EXTRA = { curve: 1, fweight: 1 }; /* 这两格有记录才出现 */
function vtSet(key, val, pill, cls, when) {
const it = $('.vt-it[data-vt="' + key + '"]'); if (!it) return;
const id = { weight: 'vtW', bp: 'vtP', glu: 'vtG' }[key];
const id = VT_ID[key];
it.classList.toggle('empty', !val);
it.dataset.has = val ? '1' : '';
$('#' + id).textContent = val || '--';
const s = $('#' + id + 's'); s.textContent = val ? (pill || '') : '去记一笔 ›'; s.className = 'vt-s' + (val && cls ? ' ' + cls : '');
const s = $('#' + id + 's'); s.textContent = val ? (pill || '') : (key === 'kick' ? '去数一数 ›' : '去记一笔 ›'); s.className = 'vt-s' + (val && cls ? ' ' + cls : '');
$('#' + id + 'd').textContent = val ? (when || '') : '';
vtLayout();
}
/* 记过的格排前面;数宫缩 / 胎儿估重没记录就不出现;孕周没到的数胎动没记录也先不出现;单数时最后一格占满一行 */
function vtLayout() {
const its = $$('.vt-it[data-vt]');
const kg = STAGE_GATE.find(x => x.id === 'taidong');
its.forEach(it => {
const k = it.dataset.vt, has = !!it.dataset.has;
it.classList.toggle('hide', !has && (VT_EXTRA[k] || (k === 'kick' && stageLocked(kg))));
it.style.order = has ? 0 : 1;
it.classList.remove('span2');
});
const vis = its.filter(it => !it.classList.contains('hide')).sort((a, b) => (+a.style.order - +b.style.order) || (its.indexOf(a) - its.indexOf(b)));
if (vis.length % 2) vis[vis.length - 1].classList.add('span2');
}
/* 胎动 / 数宫缩 / 胎儿估重 来自卡片接口(tool-cards),applyRemoteCards 收到就喂进来 */
function vtCards(r) {
const by = {}; ((r && r.cards) || []).forEach(c => { if (c && c.slot) by[c.slot] = c; });
const k = by.kick;
if (k) vtSet('kick', !k.empty && +k.value > 0 ? String(k.value) : '', '今天', '', String(k.sub || '').replace(/^.*·\s*/, ''));
const c = by.curve;
if (c) vtSet('curve', !c.empty && c.value ? c.value + (c.unit || '') : '', '最近一次', '', c.sub || '');
const f = by.fweight;
if (f) vtSet('fweight', !f.empty && (f.value || f.text) ? (f.value ? f.value + (f.unit || '') : f.text) : '', '', '', f.sub || '');
}
function loadVitals() {
const box = $('#vitals'); if (!box) return;
if (!hashParam('openid') || state.postpartum) { box.classList.add('hide'); return; }
box.classList.remove('hide');
_vtAt = Date.now();
vtLayout();
vtGet('yunqi-tizhong', '/state').then(j => {
const pw = vtVal(j && j.items, 'pw_data_v1');
const ws = ((pw && pw.weights) || []).filter(w => w && !w.demo && +w.weight > 0).sort((a, b) => String(a.date).localeCompare(String(b.date)) || (a.updatedAt || 0) - (b.updatedAt || 0));
@@ -5233,11 +5261,9 @@
}
$$('.vt-it[data-vt]').forEach(b => b.addEventListener('click', () => {
const k = b.dataset.vt;
if (k === 'weight') { const c = $('.mini-card[data-mini="weight"]'); if (c) { c.click(); return; } }
const id = { weight: 'yunqitizhong', bp: 'yun', glu: 'yunqixuetang' }[k];
const app = APP_BY_ID[id];
const app = APP_BY_ID[MINI_APP_MAP[k]];
if (app && app.url) { jumpApp(app.url, app.identifier); return; }
const path = { weight: '/yunqi-tizhong/', bp: '/yunqi-xueya/', glu: '/yunqi-xuetang/' }[k];
const path = { kick: '/taidong/', weight: '/yunqi-tizhong/', bp: '/yunqi-xueya/', glu: '/yunqi-xuetang/', curve: '/gongsuo-pinlv/', fweight: '/taier-guzhong/' }[k];
haptic(); location.href = withIdentity(location.origin + path);
}));
document.addEventListener('visibilitychange', function () {