feat(盲盒): 价值感改造一期前端 —— 待解锁任务 / 宝宝周记 / 集齐奖励 / 下周预告 (v26100107)
- 待解锁:错过的周 + 三个工具任务(进度、去完成跳应用、解锁按钮),卡册格子 🔒 - 翻卡层加「宝宝周记」;卡册点开带周记与宝宝的话 - 卡册顶部集齐奖励:三段 + 全集,差 N 张 / 待发放 / 已发放 / 填收货信息 - 集齐庆祝弹层 + 收货信息表单(POST /blindbox/claim) - 本周已开时显示下周卡片剪影 + 倒计时;横幅优先提示可解锁 / 离 XX 大奖还差 N 张 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
134
js/app.js
134
js/app.js
@@ -3,7 +3,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
/* 页面版本号(YYMMDD+序号):显示在「关于兜爸心选」页底部;bump 时同步 head 注释与 ?v= */
|
||||
var VER = '26100106';
|
||||
var VER = '26100107';
|
||||
const $ = (s, c) => (c || document).querySelector(s);
|
||||
const $$ = (s, c) => Array.from((c || document).querySelectorAll(s));
|
||||
|
||||
@@ -3281,11 +3281,24 @@
|
||||
return bbApi('blindbox').then(j => {
|
||||
if (!j || j.code !== 200 || !j.result) return;
|
||||
BB = j.result;
|
||||
(BB.newRewards || []).forEach(g => bxPrizeQ.push(g));
|
||||
renderBanner();
|
||||
if (BB.backfilled > 0) setTimeout(() => toast('错过的 ' + BB.backfilled + ' 张宝宝卡已补给你,去卡册看看吧'), 1800);
|
||||
if ($('#detailBox').classList.contains('show')) renderBox();
|
||||
if ($('#detailBox').classList.contains('show')) {
|
||||
renderBox();
|
||||
if (bxPrizeQ.length && !bbBusy && !$('#bxReveal').classList.contains('show')) setTimeout(showPrize, 400);
|
||||
}
|
||||
}).catch(() => { /* 静默:横幅保持现状 */ });
|
||||
}
|
||||
/* 最近的一个集齐目标:先看当前孕周所在的那段,集齐了再看全集。用于「离孕中期大奖还差 3 张」 */
|
||||
function bxGoal() {
|
||||
const ss = (BB && BB.stages) || [];
|
||||
const cur = ss.find(x => x.key !== 'all' && BB.week >= x.from && BB.week <= x.to && !x.complete);
|
||||
const g = cur || ss.find(x => x.key === 'all' && !x.complete);
|
||||
return g && g.left > 0 ? g : null;
|
||||
}
|
||||
const BX_TASK_ICO = { kick3: '👶', weight: '⚖️', tool: '📅' };
|
||||
let bxLockSel = null, bxPrizeQ = [];
|
||||
function renderBanner() {
|
||||
const ban = $('#bbBanner');
|
||||
const set = (t, s, cta, ready, calm) => {
|
||||
@@ -3309,8 +3322,13 @@
|
||||
if (op.length > 1) { badge.textContent = op.length; badge.classList.remove('hide'); }
|
||||
if (BB.ended) set('我的孕期卡册', got + ' 张宝宝卡,永久保留', '去看看', false, true);
|
||||
else if (!BB.currentOpened && op.indexOf(BB.week) >= 0) set('孕 ' + BB.week + ' 周的盲盒到了', got + ' · 免费开启', '去开启', true);
|
||||
else if (op.length) set('还有 ' + op.length + ' 个盲盒没开', '错过的周也能补开 · ' + got, '去补开', true);
|
||||
else set(BB.nextIn ? '下一个盲盒 ' + BB.nextIn + ' 天后到' : '本周盲盒已开启', got + (BB.flash ? ' · 闪卡 <b>' + BB.flash + '</b> 张' : ''), '看卡册', false, true);
|
||||
else if (op.length) set('还有 ' + op.length + ' 个盲盒没开', '已解锁,随时补开 · ' + got, '去补开', true);
|
||||
else if ((BB.locked || []).length && BB.credits) set('可以解锁 ' + BB.credits + ' 个错过的盲盒', '你完成了孕期小任务 · ' + got, '去解锁', true);
|
||||
else {
|
||||
const g = bxGoal();
|
||||
set(BB.nextIn ? '下一个盲盒 ' + BB.nextIn + ' 天后到' : '本周盲盒已开启',
|
||||
g ? '离' + g.name + '大奖还差 <b>' + g.left + '</b> 张' : got + (BB.flash ? ' · 闪卡 <b>' + BB.flash + '</b> 张' : ''), '看卡册', false, true);
|
||||
}
|
||||
}
|
||||
/* 盲盒入口:首页横幅 + 「我的」页卡册行(#meBoxCard)走同一条路 */
|
||||
function openBoxEntry() {
|
||||
@@ -3319,6 +3337,7 @@
|
||||
bbSel = null;
|
||||
renderBox();
|
||||
openPanel('detailBox');
|
||||
if (bxPrizeQ.length) setTimeout(showPrize, 600);
|
||||
}
|
||||
$('#bbBanner').addEventListener('click', openBoxEntry);
|
||||
$('#meBoxCard').addEventListener('click', openBoxEntry);
|
||||
@@ -3349,6 +3368,43 @@
|
||||
btn.textContent = '看看这张卡';
|
||||
}
|
||||
$('#bxMisc').textContent = (BB.weekOpeners >= 50) ? '本周已有 ' + fmtN(BB.weekOpeners) + ' 位妈妈开了盲盒' : '每周一个 · 完全免费';
|
||||
/* 下周预告:剪影 + 倒计时(本周已开时才露出来,免得和「开启盲盒」抢视线) */
|
||||
const nx = BB.next, nxEl = $('#bxNext');
|
||||
nxEl.classList.toggle('hide', !(nx && !bbSel && !BB.ended));
|
||||
if (nx && !bbSel && !BB.ended) {
|
||||
$('#bxNextImg').innerHTML = cardImgHTML(nx.week);
|
||||
$('#bxNextW').textContent = '孕 ' + nx.week + ' 周 · 下周预告';
|
||||
$('#bxNextS').textContent = nx.size ? '宝宝会长到' + nx.size.name + '那么大' : '';
|
||||
$('#bxNextD').textContent = nx.inDays;
|
||||
}
|
||||
/* 待解锁 + 任务 */
|
||||
const lk = BB.locked || [], lkEl = $('#bxLock');
|
||||
lkEl.classList.toggle('hide', !lk.length);
|
||||
if (lk.length) {
|
||||
if (lk.indexOf(bxLockSel) < 0) bxLockSel = lk[lk.length - 1];
|
||||
$('#bxLockN').textContent = lk.length;
|
||||
$('#bxLockChips').innerHTML = lk.map(w => '<button class="bx-chip lk' + (w === bxLockSel ? ' on' : '') + '" data-w="' + w + '" type="button">🔒 孕 ' + w + ' 周</button>').join('');
|
||||
$('#bxTasks').innerHTML = (BB.tasks || []).map(t => {
|
||||
const btn = t.ready ? '<button class="bx-tk-b go" data-unlock="' + t.id + '" type="button">解锁孕 ' + bxLockSel + ' 周</button>'
|
||||
: t.used ? '<span class="bx-tk-b done">' + (t.period === 'week' ? '本周已用' : '今天已用') + '</span>'
|
||||
: '<button class="bx-tk-b" data-app="' + bbEsc(t.identifier) + '" type="button">去完成</button>';
|
||||
const prog = t.need > 1 ? '<i class="bx-tk-p"><s style="width:' + Math.round(t.progress / t.need * 100) + '%"></s></i><em>' + t.progress + '/' + t.need + '</em>' : '';
|
||||
return '<div class="bx-tk' + (t.ready ? ' is-ready' : '') + '"><span class="bx-tk-i" aria-hidden="true">' + (BX_TASK_ICO[t.id] || '✨') + '</span>' +
|
||||
'<div class="bx-tk-m"><b>' + bbEsc(t.title) + '</b><span>' + bbEsc(t.desc) + '</span>' + (prog ? '<div class="bx-tk-pr">' + prog + '</div>' : '') + '</div>' + btn + '</div>';
|
||||
}).join('');
|
||||
}
|
||||
/* 集齐奖励 */
|
||||
$('#bxGoal').innerHTML = (BB.stages || []).map(g => {
|
||||
const e = g.earned, pct = Math.round(g.got / g.total * 100);
|
||||
const prize = (g.doudou ? g.doudou + ' 兜豆' : '') + (g.gift ? (g.doudou ? ' + ' : '') + '实物纪念礼' : '');
|
||||
let tail;
|
||||
if (e) tail = e.needAddr ? '<button class="bx-gl-b" data-addr="1" type="button">填收货信息</button>'
|
||||
: '<span class="bx-gl-s' + (e.status === 'sent' ? ' sent' : '') + '">' + (e.status === 'sent' ? '已发放' : '待发放') + '</span>';
|
||||
else if (g.complete) tail = '<span class="bx-gl-s off">补发集齐</span>';
|
||||
else tail = '<span class="bx-gl-l">差 <b>' + g.left + '</b> 张</span>';
|
||||
return '<div class="bx-gl' + (g.key === 'all' ? ' all' : '') + (e ? ' got' : '') + '"><div class="bx-gl-h"><b>' + g.name + '</b><em>' + prize + '</em>' + tail + '</div>' +
|
||||
'<i class="bx-gl-bar"><s style="width:' + pct + '%"></s></i></div>';
|
||||
}).join('');
|
||||
/* 待补开 */
|
||||
const pend = $('#bxPend');
|
||||
pend.classList.toggle('hide', op.length < 2);
|
||||
@@ -3361,7 +3417,8 @@
|
||||
$('#bxAll').textContent = BB.totalWeeks;
|
||||
requestAnimationFrame(() => { $('#bxBar').style.width = Math.round(BB.collected / BB.totalWeeks * 100) + '%'; });
|
||||
const nBack = (BB.album || []).filter(a => a.src === 'backfill').length;
|
||||
$('#bxAnote').textContent = (BB.flash ? '其中闪卡 ' + BB.flash + ' 张。' : '') + (nBack ? '标「补」的是来之前的孕周,已自动补发普通款。' : '');
|
||||
$('#bxAnote').textContent = (BB.flash ? '其中闪卡 ' + BB.flash + ' 张。' : '') + (nBack ? '标「补」的是来之前的孕周,已自动补发普通款。' : '') +
|
||||
(lk.length ? '标 🔒 的是错过的周,完成小任务即可解锁。' : '');
|
||||
const byW = {};
|
||||
(BB.album || []).forEach(a => { byW[a.week] = a; });
|
||||
$('#bxGroups').innerHTML = BX_GROUPS.map(g => {
|
||||
@@ -3373,6 +3430,8 @@
|
||||
cells += '<button class="bx-t got' + (a.v === 'flash' ? ' flash' : '') + (bbNew === w ? ' is-new' : '') + '" data-w="' + w + '" data-v="' + (a.v || 'normal') + '" type="button">' +
|
||||
(CARD_IMG[w] ? '<img src="assets/img/' + CARD_IMG[w] + '.jpg" alt="" loading="lazy">' : '<span class="fb">' + babySize(w).emoji + '</span>') +
|
||||
(a.src === 'backfill' ? '<span class="bf">补</span>' : '') + '<span class="wk">' + w + ' 周</span></button>';
|
||||
} else if (lk.indexOf(w) >= 0) {
|
||||
cells += '<button class="bx-t lockd" data-lk="' + w + '" type="button"><span class="li" aria-hidden="true">🔒</span><span class="wk">' + w + ' 周</span></button>';
|
||||
} else if (op.indexOf(w) >= 0) {
|
||||
cells += '<button class="bx-t can" data-op="' + w + '" type="button">' + giftSVG() + '<span class="wk">' + w + ' 周</span></button>';
|
||||
} else {
|
||||
@@ -3402,13 +3461,69 @@
|
||||
const t = e.target.closest('.bx-t');
|
||||
if (!t || bbBusy) return;
|
||||
if (t.dataset.op) { bbSel = +t.dataset.op; renderBox(); $('#detailBox .detail-body').scrollTo({ top: 0, behavior: 'smooth' }); return; }
|
||||
if (t.dataset.lk) { bxLockSel = +t.dataset.lk; renderBox(); $('#bxLock').scrollIntoView({ behavior: 'smooth', block: 'center' }); return; }
|
||||
const w = +t.dataset.w;
|
||||
if (!w) return;
|
||||
/* 卡册里点开一张:大小来自本地 SIZE 表,发育说明取孕期小知识的「宝宝这周」 */
|
||||
const c = { week: w, v: t.dataset.v, size: babySize(w), say: '' };
|
||||
/* 卡册里点开一张:大小来自本地 SIZE 表,发育说明取孕期小知识的「宝宝这周」;周记 / 宝宝的话由接口 album 带回 */
|
||||
const a = (BB.album || []).find(x => x.week === w) || {};
|
||||
const c = { week: w, v: t.dataset.v, size: babySize(w), say: a.say || '', diary: a.diary || [] };
|
||||
showReveal(c, null, false);
|
||||
loadTips().then(() => { const x = TIPS && TIPS[w]; if (x && x.baby && $('#bxReveal').classList.contains('show')) $('#bxDev').textContent = x.baby; });
|
||||
});
|
||||
$('#bxLockChips').addEventListener('click', e => {
|
||||
const b = e.target.closest('.bx-chip');
|
||||
if (!b || bbBusy) return;
|
||||
bxLockSel = +b.dataset.w; haptic(); renderBox();
|
||||
});
|
||||
$('#bxTasks').addEventListener('click', e => {
|
||||
const go = e.target.closest('[data-app]');
|
||||
if (go) {
|
||||
const app = APP_BY_ID[go.dataset.app];
|
||||
if (app && app.url) jumpApp(app.url, app.identifier); else toast('这个工具暂时打不开,换一个任务试试');
|
||||
return;
|
||||
}
|
||||
const u = e.target.closest('[data-unlock]');
|
||||
if (!u || bbBusy || !bxLockSel) return;
|
||||
bbBusy = true; u.disabled = true; u.textContent = '解锁中…';
|
||||
const w = bxLockSel;
|
||||
bbApi('blindbox/unlock?week=' + w + '&task=' + u.dataset.unlock, 'POST').catch(() => null).then(j => {
|
||||
bbBusy = false;
|
||||
if (!j || j.code !== 200) { toast((j && j.message) || '网络开小差了,再试一次'); loadBlindbox(); return; }
|
||||
if (navigator.vibrate) navigator.vibrate([10, 30, 14]);
|
||||
toast('孕 ' + w + ' 周的盲盒解锁啦,快打开看看');
|
||||
bbSel = w;
|
||||
loadBlindbox().then(() => { bbSel = w; renderBox(); $('#detailBox .detail-body').scrollTo({ top: 0, behavior: 'smooth' }); });
|
||||
});
|
||||
});
|
||||
$('#bxGoal').addEventListener('click', e => { if (e.target.closest('[data-addr]')) openModal('modalBxAddr'); });
|
||||
$('#bxAddrSave').addEventListener('click', () => {
|
||||
const v = id => $('#' + id).value.trim();
|
||||
const body = { name: v('bxAddrName'), phone: v('bxAddrPhone'), addr: v('bxAddrAddr') };
|
||||
if (!body.name || !body.phone || !body.addr) { toast('请把收货人、电话、地址填完整'); return; }
|
||||
if (!/^1\d{10}$/.test(body.phone)) { toast('手机号好像不对,再检查一下'); return; }
|
||||
fetch('/api/mp/public/mama-box/blindbox/claim?openid=' + encodeURIComponent(hashParam('openid')), {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body)
|
||||
}).then(r => r.json()).catch(() => null).then(j => {
|
||||
if (!j || j.code !== 200) { toast((j && j.message) || '提交失败,再试一次'); return; }
|
||||
closeModal(); toast('收到啦,工作人员会尽快寄出'); loadBlindbox();
|
||||
});
|
||||
});
|
||||
/* 集齐庆祝:开盒 / 刷新时接口带回 newRewards(服务端只返回一次),排队逐个弹 */
|
||||
function showPrize() {
|
||||
const g = bxPrizeQ.shift();
|
||||
if (!g) return;
|
||||
$('#bxPrizeT').textContent = g.key === 'all' ? '37 张宝宝卡全部集齐!' : '集齐' + g.name + '!';
|
||||
$('#bxPrizeP').textContent = (g.doudou ? g.doudou + ' 兜豆' : '') + (g.gift ? (g.doudou ? '和' : '') + g.gift : '') + '已为你记下,工作人员会在 7 个工作日内发放' +
|
||||
(g.gift ? ',先填一下收货信息吧。' : '。');
|
||||
$('#bxPrizeOk').textContent = g.gift ? '去填收货信息' : '太好了';
|
||||
$('#bxPrizeOk').dataset.addr = g.gift ? '1' : '';
|
||||
openModal('modalBxPrize');
|
||||
}
|
||||
$('#bxPrizeOk').addEventListener('click', () => {
|
||||
const addr = $('#bxPrizeOk').dataset.addr;
|
||||
closeModal();
|
||||
if (addr) openModal('modalBxAddr'); else setTimeout(showPrize, 250);
|
||||
});
|
||||
$('#bxRuleBtn').addEventListener('click', () => {
|
||||
const d = $('#bxRules'); d.open = true;
|
||||
d.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
@@ -3437,6 +3552,7 @@
|
||||
st.classList.add('is-popping');
|
||||
if (navigator.vibrate) navigator.vibrate([12, 40, 18]);
|
||||
const r = j.result;
|
||||
(r.newRewards || []).forEach(g => bxPrizeQ.push(g));
|
||||
bbNew = r.card.week;
|
||||
setTimeout(() => { showReveal(r.card, r.reward, true); bbBusy = false; }, 420);
|
||||
loadBlindbox(); // 后台刷新横幅与卡册(翻卡层盖着,用户看不到重绘)
|
||||
@@ -3462,6 +3578,8 @@
|
||||
if (!$('#bxBack').firstChild) $('#bxBack').innerHTML = giftSVG() + '<b>兜爸心选 · 孕期盲盒</b>';
|
||||
$('#bxTag').textContent = fresh ? (flash ? '✦ 哇!开出了闪卡 ✦' : '新卡 · 孕 ' + card.week + ' 周') : (flash ? '✦ 闪卡 · 孕 ' + card.week + ' 周' : '孕 ' + card.week + ' 周');
|
||||
$('#bxDev').textContent = card.dev || '';
|
||||
const dl = card.diary || [];
|
||||
$('#bxDiary').innerHTML = dl.length ? '<div class="bx-dy-h">📖 宝宝周记 · 孕 ' + card.week + ' 周</div>' + dl.map(x => '<p>' + bbEsc(x) + '</p>').join('') : '';
|
||||
let rw = '';
|
||||
if (reward) {
|
||||
const tj = reward.type === 'taijiao';
|
||||
@@ -3493,7 +3611,7 @@
|
||||
rv.classList.remove('show', 'is-done', 'is-burst');
|
||||
rv.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
$('#bxKeep').addEventListener('click', () => { hideReveal(); renderBox(); });
|
||||
$('#bxKeep').addEventListener('click', () => { hideReveal(); renderBox(); if (bxPrizeQ.length) setTimeout(showPrize, 350); });
|
||||
$('#detailBox .dt-back').addEventListener('click', hideReveal);
|
||||
|
||||
/* ================= 孕期小知识(2026-09-30) =================
|
||||
|
||||
Reference in New Issue
Block a user