首页兜豆模块加横滑可兑好物;我的页孕期卡与兜豆卡对调;资料弹窗改版(实时结果条、预产期三种填法、孕周步进、小名建议)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 16:12:25 +00:00
parent 96b04877de
commit da995ae540
3 changed files with 331 additions and 112 deletions

137
js/app.js
View File

@@ -3,7 +3,7 @@
(function () {
'use strict';
/* 页面版本号(YYMMDD+序号):显示在「关于兜爸心选」页底部;bump 时同步 head 注释与 ?v= */
var VER = '26100137';
var VER = '26100138';
const $ = (s, c) => (c || document).querySelector(s);
const $$ = (s, c) => Array.from((c || document).querySelectorAll(s));
@@ -890,14 +890,10 @@
return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate());
}
/* 2026-10-01 资料弹窗改版后:孕周两格不再锁(「按当前孕周」那页本来就是让人从零填的),
这里只负责没预产期时把两格清空。函数名留着,调用点不用动。 */
function lockWeekFields(locked) {
pgWeek.disabled = pgDay.disabled = locked;
/* 锁着的时候连数字也不留 —— 没填预产期就没有「当前孕周」这回事,
留个旧值在灰格子里反而像是已经填好了 */
if (locked) { pgWeek.value = ''; pgDay.value = ''; }
pgHint.textContent = locked
? '还没填预产期,孕周先锁着 —— 选好上面的预产期就会自动算出来。'
: '已按预产期自动算出,觉得不准可以自己微调。';
}
/* 缓存戳重载:只换查询串(?t=…),两处都不能丢:
① URL 已有 query 时必须用 & 拼接 —— 直接拼 '?t=' 会把第二个 ? 吃进已有参数值里
@@ -944,6 +940,8 @@
lockWeekFields(true);
}
syncDuePh();
setPgMode('due');
if (pgLmp) { pgLmp.value = ''; pgLmp.dispatchEvent(new Event('change')); }
/* 头像预览跟随卡片(有正式头像才显示;新用户空圆 + +号) */
const _me = $('#meAvatar');
if (_me && _me.classList.contains('has-img') && meAvatarImg && meAvatarImg.src) {
@@ -955,13 +953,72 @@
}
}
/* 预产期占位层:空值显示「- 年 - 月 - 日」,选了值隐藏 */
/* 预产期按钮上的文字 + 顶部结果条,都从 #pgDue 现算(唯一数据源)。
函数名沿用 syncDuePh(原来只管「- 年 - 月 - 日」占位层),调用点不用动。 */
const WK = '日一二三四五六';
function fmtDay(d) { return d.getFullYear() + '年' + (d.getMonth() + 1) + '月' + d.getDate() + '日 周' + WK[d.getDay()]; }
function syncDuePh() {
const ph = $('#pgDuePh');
if (ph) ph.style.display = pgDue.value ? 'none' : 'block';
pgDue.classList.toggle('due-empty', !pgDue.value);
const due = parseDay(pgDue.value);
const t = $('#pgDueTxt');
if (t) { t.textContent = due ? fmtDay(due) : '点这里选预产期'; t.parentNode.classList.toggle('is-set', !!due); }
const sum = $('#pgSum');
if (!sum) return;
sum.classList.toggle('is-set', !!due);
if (!due) {
$('#pgSumT').textContent = '还没填预产期';
$('#pgSumS').textContent = '选一种方式填一下,马上就好';
return;
}
const left = Math.round((midnight(due) - midnight(new Date())) / DAY_MS);
if (left < 0) {
$('#pgSumT').textContent = '宝宝已经出生啦';
$('#pgSumS').textContent = '预产期 ' + fmtDay(due);
return;
}
const ga = gaFromDue(due);
$('#pgSumT').textContent = '孕 ' + Math.floor(ga / 7) + ' 周' + (ga % 7 ? ' +' + (ga % 7) + ' 天' : '');
$('#pgSumS').textContent = (left ? '还有 ' + left + ' 天见面' : '就是今天啦') + ' · 预产期 ' + (due.getMonth() + 1) + '月' + due.getDate() + '日';
}
/* 三种填法切换(只切面板;数据都落在 #pgDue) */
function setPgMode(m) {
$$('#pgMode .seg-b').forEach(b => b.classList.toggle('is-active', b.dataset.mode === m));
$$('#modalProfile .pg-pane').forEach(p => p.classList.toggle('is-on', p.dataset.pane === m));
}
$$('#pgMode .seg-b').forEach(b => b.addEventListener('click', () => { setPgMode(b.dataset.mode); haptic(); }));
/* 孕周步进:空着时从 12 周起步(一般建档时间),天数 6 再 + 进位到下一周 */
$$('#modalProfile .stp-b').forEach(b => b.addEventListener('click', () => {
let w = parseInt(pgWeek.value, 10), d = parseInt(pgDay.value, 10);
if (isNaN(w)) w = 12;
if (isNaN(d)) d = 0;
const k = +b.dataset.d;
if (b.dataset.stp === 'w') w += k;
else {
d += k;
if (d > 6) { d = 0; w += 1; }
if (d < 0) { d = w > 0 ? 6 : 0; if (w > 0) w -= 1; }
}
pgWeek.value = Math.min(42, Math.max(0, w));
pgDay.value = d;
pgWeek.dispatchEvent(new Event('change'));
haptic();
}));
/* 末次月经第一天 + 280 天 = 预产期 */
const pgLmp = $('#pgLmp');
if (pgLmp) pgLmp.addEventListener('change', () => {
const l = parseDay(pgLmp.value);
const t = $('#pgLmpTxt');
if (t) { t.textContent = l ? fmtDay(l) : '点这里选末次月经第一天'; t.parentNode.classList.toggle('is-set', !!l); }
if (!l) return;
pgDue.value = toISO(new Date(l.getFullYear(), l.getMonth(), l.getDate() + 280));
pgDue.dispatchEvent(new Event('change'));
});
/* 小名建议:点一下填进去 */
$$('#pgNameChips .nm-c').forEach(b => b.addEventListener('click', () => { pgName.value = b.textContent; haptic(); }));
/* 预产期一变,孕周 / 额外天数自动算出来 */
pgDue.addEventListener('change', () => {
syncDuePh();
@@ -979,6 +1036,7 @@
const d = Math.min(6, Math.max(0, parseInt(pgDay.value, 10) || 0));
pgWeek.value = w; pgDay.value = d;
pgDue.value = toISO(dueFromGA(w * 7 + d));
syncDuePh();
}));
/* 性别三选一 */
@@ -1373,7 +1431,8 @@
$('#pfSave').addEventListener('click', () => {
/* 预产期是一切的源头:孕周、剩余天数、信里的日期全从它算。没有它没什么可存的。 */
if (!parseDay(pgDue.value)) {
toast('先选一下预产期,孕周会自动算出来');
toast('先填一下预产期,孕周会自动算出来');
setPgMode('due');
pgDue.focus();
return;
}
@@ -4172,27 +4231,49 @@
function setExchSub(t) {
$$('#meExchSub, #homeExchSub').forEach(function (el) { el.textContent = t; });
}
function douImg(g) {
return /^https?:/.test(g.img || '') ? g.img : EXCH_URL + String(g.img || '').replace(/^\//, '');
}
/* 一件商品的小卡(「我的」页 3 件 / 首页横滑 6 件共用),全用 DOM 拼,商品名不进 innerHTML */
function douItem(g, cls, more) {
const it = document.createElement('span'); it.className = cls;
const pic = document.createElement('span'); pic.className = 'gi-pic';
const im = document.createElement('img'); im.alt = ''; im.loading = 'lazy'; im.src = douImg(g);
pic.appendChild(im);
if (more && g.badge) { const bd = document.createElement('i'); bd.className = 'gi-badge'; bd.textContent = g.badge; pic.appendChild(bd); }
if (more && g.stock <= 5) { const st = document.createElement('i'); st.className = 'gi-left'; st.textContent = '仅剩 ' + g.stock + ' 件'; pic.appendChild(st); }
const nm = document.createElement('span'); nm.className = 'bd-nm'; nm.textContent = g.name || '';
const pr = document.createElement('b'); pr.textContent = g.price + ' 兜豆';
it.append(pic, nm, pr);
return it;
}
function renderDrop(d) {
const box = $('#meExchDrop'); if (!box) return;
const now = d && d.server_time ? new Date(d.server_time).getTime() : Date.now();
const goods = ((d && d.goods) || []).filter(function (g) {
return g.stock > 0 && (!g.opens_at || new Date(g.opens_at).getTime() <= now);
})
.sort(function (x, y) { return (y.heat_30m || 0) - (x.heat_30m || 0); }).slice(0, 3);
if (!goods.length) { box.classList.add('hide'); return; }
const dr = d.drop || {};
$('#meExchDropT').textContent = (dr.title || '本场好物') + (dr.open && dr.left != null ? ' · 剩 ' + dr.left + ' 件' : '');
const wrap = $('#meExchGoods'); wrap.textContent = '';
goods.forEach(function (g) {
const it = document.createElement('span'); it.className = 'bd-it';
const im = document.createElement('img'); im.alt = ''; im.loading = 'lazy';
im.src = /^https?:/.test(g.img || '') ? g.img : EXCH_URL + String(g.img || '').replace(/^\//, '');
const nm = document.createElement('span'); nm.className = 'bd-nm'; nm.textContent = g.name || '';
const pr = document.createElement('b'); pr.textContent = g.price + ' 兜豆';
it.append(im, nm, pr); wrap.appendChild(it);
});
box.classList.remove('hide');
}).sort(function (x, y) { return (y.heat_30m || 0) - (x.heat_30m || 0); });
/* 「我的」页:本场标题 + 热门前 3 */
const box = $('#meExchDrop');
if (box) {
if (!goods.length) box.classList.add('hide');
else {
const dr = d.drop || {};
$('#meExchDropT').textContent = (dr.title || '本场好物') + (dr.open && dr.left != null ? ' · 剩 ' + dr.left + ' 件' : '');
const wrap = $('#meExchGoods'); wrap.textContent = '';
goods.slice(0, 3).forEach(function (g) { wrap.appendChild(douItem(g, 'bd-it')); });
box.classList.remove('hide');
}
}
/* 首页:横滑前 6 件,带「热门」角标和「仅剩 N 件」 */
const row = $('#homeExchGoods');
if (row) {
row.textContent = '';
goods.slice(0, 6).forEach(function (g) { row.appendChild(douItem(g, 'ex-it', true)); });
row.classList.toggle('hide', !goods.length);
}
}
const _hxg = $('#homeExchGoods');
if (_hxg) _hxg.addEventListener('click', function (e) { if (e.target.closest('.ex-it')) openExchange(); });
function loadDou() {
const oid = hashParam('openid'), uid = hashParam('unionid');
const dropP = fetch(DOU_API + '/mall/drop').then(function (r) { return r.ok ? r.json() : null; }).catch(function () { return null; });