From b3f13468a912ddd04a3383905bb90458a0178690 Mon Sep 17 00:00:00 2001 From: karlcarlo Date: Sun, 11 Oct 2026 14:59:27 +0000 Subject: [PATCH] =?UTF-8?q?=E3=80=8C=E6=88=91=E7=9A=84=E8=B5=84=E6=96=99?= =?UTF-8?q?=E3=80=8D=E6=94=B9=E3=80=8C=E5=AD=95=E6=9C=9F=E6=A1=A3=E6=A1=88?= =?UTF-8?q?=E3=80=8D=EF=BC=9A=E7=85=A7=E6=95=B0=E8=83=8E=E5=8A=A8=E6=94=B9?= =?UTF-8?q?=E6=88=90=E4=B8=80=E8=A1=8C=E4=B8=80=E9=A1=B9=E7=99=BD=E5=8D=A1?= =?UTF-8?q?=EF=BC=8C=E5=A4=B4=E5=83=8F=E6=94=BE=E7=AC=AC=E4=B8=80=E8=A1=8C?= =?UTF-8?q?=EF=BC=9B=E5=AD=95=E5=91=A8=20/=20=E9=A2=84=E4=BA=A7=E6=9C=9F?= =?UTF-8?q?=E7=82=B9=E5=BC=80=E6=BB=9A=E8=BD=AE=E9=80=89=E6=8B=A9=20v26101?= =?UTF-8?q?025?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: AI 助手 --- index.html | 172 +++++++++++++++++---------------------------------- js/app.js | 128 +++++++++++++++++++++++++++++++++++--- mama-box.css | 52 ++++++++++++++++ 3 files changed, 230 insertions(+), 122 deletions(-) diff --git a/index.html b/index.html index 277dd77..810fd06 100644 --- a/index.html +++ b/index.html @@ -7,7 +7,7 @@ 兜爸心选 · 孕育陪伴 - + @@ -1957,134 +1957,76 @@ + + + +
+
diff --git a/js/app.js b/js/app.js index c91108f..bba55ae 100644 --- a/js/app.js +++ b/js/app.js @@ -3,7 +3,7 @@ (function () { 'use strict'; /* 页面版本号(YYMMDD+序号):显示在「关于兜爸心选」页底部;bump 时同步 head 注释与 ?v= */ - var VER = '26101024'; + var VER = '26101025'; const $ = (s, c) => (c || document).querySelector(s); const $$ = (s, c) => Array.from((c || document).querySelectorAll(s)); @@ -1302,7 +1302,15 @@ function syncDuePh() { const due = parseDay(pgDue.value); const t = $('#pgDueTxt'); - if (t) { t.textContent = due ? fmtDay(due) : '点这里选预产期'; t.parentNode.classList.toggle('is-set', !!due); } + /* 孕期档案两行(2026-10-11 改版):右侧值都从 #pgDue 现算 */ + if (t) { t.textContent = due ? due.getFullYear() + ' 年 ' + (due.getMonth() + 1) + ' 月 ' + due.getDate() + ' 日' : '去填写'; t.classList.toggle('is-empty', !due); } + const wt = $('#pgWeekTxt'); + if (wt) { + const lf = due ? Math.round((midnight(due) - midnight(new Date())) / DAY_MS) : null; + const g = due ? gaFromDue(due) : 0; + wt.textContent = !due ? '去填写' : lf < 0 ? '宝宝已出生' : '孕 ' + Math.floor(g / 7) + ' 周' + (g % 7 ? ' +' + (g % 7) + ' 天' : ''); + wt.classList.toggle('is-empty', !due); + } const sum = $('#pgSum'); if (!sum) return; sum.classList.toggle('is-set', !!due); @@ -1382,11 +1390,112 @@ })); /* 性别三选一 */ + /* 性别:男孩 / 女孩二选一,再点一下已选的就取消(= 还不知道,保存成 unknown) */ $$('#pgSex .pick').forEach(b => b.addEventListener('click', () => { - $$('#pgSex .pick').forEach(x => x.classList.toggle('is-active', x === b)); + const on = !b.classList.contains('is-active'); + $$('#pgSex .pick').forEach(x => x.classList.toggle('is-active', on && x === b)); haptic(); })); + /* ---------- 孕期档案 · 滚轮选择(2026-10-11,照数胎动的孕期档案) ---------- + week:孕 0–42 周 × 0–6 天,下面实时显示对应预产期;due:年 / 月 / 日,下面显示对应孕周。 + 确定后写 #pgWeek/#pgDay 或 #pgDue 并 dispatch change —— 换算、行内文字都走原来那两个监听,不另算一套。 */ + const PK_H = 40; /* 每行高度,CSS .pk-col i 同值 */ + const pkSheet = $('#pkSheet'), pkScrim = $('#pkScrim'), pkWheels = $('#pkWheels'); + let pkMode = 'week'; + function pkCol(key, items, sel) { + return '
' + items.map((t, i) => '' + t + '').join('') + '
'; + } + function pkIdx(col) { return Math.max(0, Math.min(col.children.length - 1, Math.round(col.scrollTop / PK_H))); } + function pkSet(col, i) { + col.scrollTop = i * PK_H; + $$('i', col).forEach((x, k) => x.classList.toggle('on', k === i)); + } + function pkGet(k) { const c = $('.pk-col[data-k="' + k + '"]', pkWheels); return c ? pkIdx(c) : 0; } + const pkYears = () => { const y = new Date().getFullYear(); return [y - 1, y, y + 1]; }; + function pkDayCol(keep) { + const ys = pkYears(), y = ys[pkGet('y')], m = pkGet('m'); + const n = new Date(y, m + 1, 0).getDate(); + const old = $('.pk-col[data-k="d"]', pkWheels); + const tmp = document.createElement('div'); + tmp.innerHTML = pkCol('d', Array.from({ length: n }, (_, i) => (i + 1) + ' 日')); + const col = tmp.firstChild; + old.replaceWith(col); + pkBind(col); + pkSet(col, Math.min(keep, n - 1)); + } + function pkResult() { + if (pkMode === 'week') { + const due = dueFromGA(pkGet('w') * 7 + pkGet('dd')); + $('#pkResL').textContent = '预产期'; + $('#pkResV').textContent = toISO(due); + } else { + const ys = pkYears(), due = new Date(ys[pkGet('y')], pkGet('m'), pkGet('d') + 1); + const left = Math.round((midnight(due) - midnight(new Date())) / DAY_MS), g = gaFromDue(due); + $('#pkResL').textContent = '对应孕周'; + $('#pkResV').textContent = left < 0 ? '已过预产期' : left > TERM ? '还没怀上哦' : '孕 ' + Math.floor(g / 7) + ' 周' + (g % 7 ? ' +' + (g % 7) + ' 天' : ''); + } + } + function pkBind(col) { + let tm = 0; + col.addEventListener('scroll', () => { + clearTimeout(tm); + tm = setTimeout(() => { + const i = pkIdx(col); + $$('i', col).forEach((x, k) => x.classList.toggle('on', k === i)); + if (col.dataset.k === 'y' || col.dataset.k === 'm') pkDayCol(pkGet('d')); + pkResult(); + }, 90); + }, { passive: true }); + col.addEventListener('click', e => { const it = e.target.closest('i'); if (it) col.scrollTo({ top: +it.dataset.i * PK_H, behavior: 'smooth' }); }); + } + function openPicker(mode) { + pkMode = mode; + const cur = parseDay(pgDue.value); + if (mode === 'week') { + $('#pkT').textContent = '你现在怀孕多久了?'; + $('#pkSub').textContent = '按孕周推算预产期'; + $('#pkAlt').textContent = '知道预产期?直接选日期'; + pkWheels.innerHTML = pkCol('w', Array.from({ length: 43 }, (_, i) => '孕 ' + i + ' 周')) + pkCol('dd', Array.from({ length: 7 }, (_, i) => i + ' 天')); + const g = cur ? gaFromDue(cur) : 12 * 7; + $$('.pk-col', pkWheels).forEach(pkBind); + pkSet($('.pk-col[data-k="w"]', pkWheels), Math.floor(g / 7)); + pkSet($('.pk-col[data-k="dd"]', pkWheels), g % 7); + } else { + $('#pkT').textContent = '预产期是哪一天?'; + $('#pkSub').textContent = '以产检单 / B 超单上写的为准'; + $('#pkAlt').textContent = '不知道预产期?按孕周填'; + const d = cur || dueFromGA(12 * 7), ys = pkYears(); + pkWheels.innerHTML = pkCol('y', ys.map(y => y + ' 年')) + pkCol('m', Array.from({ length: 12 }, (_, i) => (i + 1) + ' 月')) + pkCol('d', ['1 日']); + $$('.pk-col', pkWheels).forEach(pkBind); + pkSet($('.pk-col[data-k="y"]', pkWheels), Math.max(0, ys.indexOf(d.getFullYear()))); + pkSet($('.pk-col[data-k="m"]', pkWheels), d.getMonth()); + pkDayCol(d.getDate() - 1); + } + pkWheels.className = 'pk-wheels n' + $$('.pk-col', pkWheels).length; + pkResult(); + pkScrim.classList.add('show'); + pkSheet.classList.add('show'); + haptic(); + } + function closePicker() { pkScrim.classList.remove('show'); pkSheet.classList.remove('show'); } + $('#pfWeekRow').addEventListener('click', () => openPicker('week')); + $('#pfDueRow').addEventListener('click', () => openPicker('due')); + $('#pkAlt').addEventListener('click', () => openPicker(pkMode === 'week' ? 'due' : 'week')); + pkScrim.addEventListener('click', closePicker); + $('#pkOk').addEventListener('click', () => { + if (pkMode === 'week') { + pgWeek.value = pkGet('w'); pgDay.value = pkGet('dd'); + pgWeek.dispatchEvent(new Event('change')); + } else { + const ys = pkYears(); + pgDue.value = toISO(new Date(ys[pkGet('y')], pkGet('m'), pkGet('d') + 1)); + pgDue.dispatchEvent(new Event('change')); + } + closePicker(); + haptic(); + }); + /* 环的目标值改了要顺手清掉上一轮动画的终值,否则 animateRings 不会重跑 */ function setRing(el, p) { if (!el) return; @@ -1575,7 +1684,7 @@ $('#arTitleName').textContent = nm; $('#arLine1').textContent = '完善孕期信息后开启'; $('#arDays').textContent = '--'; - $('#arLine2').textContent = '先去「我的资料」填预产期'; + $('#arLine2').textContent = '先去「孕期档案」填预产期'; $('#arPct').textContent = '0%'; setArBar(0); $('#genWeekTag').textContent = '--'; @@ -1761,7 +1870,13 @@ /* 点大圆 → 转交给隐藏的 file input。openModal 里会 focus 第一个输入框, 这里 avFile 排在 pfName 前面,所以 openModal 用的选择器只取 .modal-ta/input 可能命中 file input——用 :not([type=file]) 排除掉,见 openModal */ - avBig.addEventListener('click', () => avFile.click()); + avBig.addEventListener('click', e => { e.stopPropagation(); avFile.click(); }); + /* 孕期档案里头像是一整行,点行上任何地方都换头像 */ + const pfMeRow = $('#pfMe'); + if (pfMeRow) { + pfMeRow.addEventListener('click', e => { if (e.target !== avFile) avFile.click(); }); + pfMeRow.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); avFile.click(); } }); + } avFile.addEventListener('change', () => { const f = avFile.files && avFile.files[0]; @@ -1786,8 +1901,7 @@ /* 预产期是一切的源头:孕周、剩余天数、信里的日期全从它算。没有它没什么可存的。 */ if (!parseDay(pgDue.value)) { toast('先填一下预产期,孕周会自动算出来'); - setPgMode('due'); - pgDue.focus(); + openPicker('week'); return; } const name = $('#pfName').value.trim() || '妈妈'; diff --git a/mama-box.css b/mama-box.css index adf50f1..f1dd8fb 100644 --- a/mama-box.css +++ b/mama-box.css @@ -4785,3 +4785,55 @@ a.skip:focus{left:16px;top:16px} /* 首页工具卡自动换成替补(swapHomeCards):尺寸、底色沿用坑位原样,只换内容;插画是白色线稿 svg */ .mini-card .sw-art{height:auto;aspect-ratio:1;opacity:.95} +/* ================= 孕期档案弹窗(2026-10-11 改版,原「我的资料」)================= + 照数胎动的孕期档案:居中标题 + 一张白卡一行一项,右侧值橘色;点孕周 / 预产期再弹滚轮 #pkSheet。 */ +.pf-modal{padding-top:10px} +.pf-grip{width:38px;height:4px;border-radius:2px;background:#E2D6CE;margin:0 auto 12px} +.pf-title{text-align:center;font-size:18px;font-weight:900;letter-spacing:-.2px} +.pf-modal .pf-lead{text-align:center;font-size:12px;color:var(--muted);margin-top:4px} +.pf-list{margin-top:14px;border-radius:20px;background:#fff;box-shadow:0 4px 14px rgba(160,110,90,.08);overflow:hidden} +.pf-row{display:flex;align-items:center;gap:10px;width:100%;min-height:62px;padding:12px 14px 12px 16px;text-align:left;position:relative;cursor:pointer} +.pf-row+.pf-row::before{content:"";position:absolute;left:16px;right:0;top:0;height:1px;background:#F2EAE4} +.pf-row:active{background:#FBF6F2} +.pf-row .pr-l{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px} +.pf-row .pr-l b{font-size:15px;font-weight:800;color:var(--ink)} +.pf-row .pr-l em{font-style:normal;font-size:11.5px;color:var(--muted)} +.pf-row .pr-v{flex:none;font-size:15px;font-weight:800;color:var(--ink)} +.pf-row .pr-v.hot{color:var(--brand)} +.pf-row .pr-v.is-empty{color:#B9ABA2;font-weight:700} +.pf-row .chev{flex:none;margin-left:-4px} +.pf-row .pr-in{flex:0 1 52%;min-width:0;border:0;background:none;text-align:right;font:inherit;font-size:15px;font-weight:800;color:var(--ink);padding:6px 0} +.pf-row .pr-in::placeholder{color:#B9ABA2;font-weight:700} +.pf-row .pr-in:focus{outline:none} +.pf-row-av .av-big{width:46px;height:46px;margin:0;flex:none} +.pf-row-av .av-big>svg{width:26px;height:26px} +.pf-seg{flex:none;display:flex;gap:4px;padding:3px;border-radius:12px;background:#F3ECE6} +.pf-seg .pick{flex:none;padding:6px 16px;border-radius:9px;background:none;font-size:13.5px;font-weight:700;color:var(--ink-2);box-shadow:none} +.pf-seg .pick.is-active{background:#fff;color:var(--brand);box-shadow:0 2px 6px rgba(160,110,90,.14)} +.pf-modal .pf-unauth{margin-top:14px} +.pf-note{margin-top:12px;text-align:center;font-size:11.5px;color:var(--muted)} +.pf-modal .pf-foot{margin-top:12px} +.pf-modal .pf-logout{background:none;color:var(--muted);font-size:13px;font-weight:600;margin:0 auto;padding:4px 12px calc(10px + env(safe-area-inset-bottom));width:auto} + +.pk-scrim{position:absolute;inset:0;z-index:130;background:rgba(50,30,22,.32);opacity:0;visibility:hidden;transition:opacity .25s,visibility .25s} +.pk-scrim.show{opacity:1;visibility:visible} +.pk-sheet{position:absolute;left:0;right:0;bottom:0;z-index:131;background:#FAF5F1;border-radius:28px 28px 0 0; + padding:24px 22px calc(14px + env(safe-area-inset-bottom));text-align:center; + transform:translateY(102%);transition:transform .36s var(--ease)} +.pk-sheet.show{transform:none;box-shadow:0 -10px 40px rgba(90,50,35,.22)} +.pk-sheet h3{font-size:18px;font-weight:900} +.pk-sub{margin-top:4px;font-size:12px;color:var(--muted)} +.pk-wheels{position:relative;display:flex;height:200px;margin:16px 0 0} +.pk-wheels::before{content:"";position:absolute;left:0;right:0;top:80px;height:40px;border-radius:12px;background:#fff;box-shadow:0 2px 10px rgba(160,110,90,.08);z-index:0} +.pk-wheels::after{content:"";position:absolute;inset:0;pointer-events:none;z-index:2; + background:linear-gradient(#FAF5F1,rgba(250,245,241,0) 34%,rgba(250,245,241,0) 66%,#FAF5F1)} +.pk-col{position:relative;z-index:1;flex:1;height:200px;overflow-y:auto;scroll-snap-type:y mandatory;overscroll-behavior:contain; + padding:80px 0;scrollbar-width:none;-webkit-overflow-scrolling:touch} +.pk-col::-webkit-scrollbar{display:none} +.pk-col i{display:block;height:40px;line-height:40px;font-style:normal;font-size:15px;color:var(--ink-2);scroll-snap-align:center;transition:color .15s,font-size .15s} +.pk-col i.on{font-size:17px;font-weight:800;color:var(--brand)} +.pk-res{margin-top:14px;font-size:13px;color:var(--muted)} +.pk-res b{margin-left:8px;font-size:16px;font-weight:900;color:var(--brand)} +.pk-ok{display:block;width:100%;margin-top:16px} +.pk-alt{margin-top:12px;font-size:12.5px;color:var(--muted);text-decoration:underline;text-underline-offset:3px} +