feat(此刻): 陪伴版文案 + 7 个头像 + 同周悄悄话(预设句/自由输入、抱抱) (v26100157)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
173
js/app.js
173
js/app.js
@@ -3,7 +3,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
/* 页面版本号(YYMMDD+序号):显示在「关于兜爸心选」页底部;bump 时同步 head 注释与 ?v= */
|
||||
var VER = '26100156';
|
||||
var VER = '26100157';
|
||||
const $ = (s, c) => (c || document).querySelector(s);
|
||||
const $$ = (s, c) => Array.from((c || document).querySelectorAll(s));
|
||||
|
||||
@@ -3296,6 +3296,146 @@
|
||||
if (k < 1) requestAnimationFrame(step);
|
||||
})(t0);
|
||||
}
|
||||
/* ================= 此刻 · 同周悄悄话(2026-10-02) =================
|
||||
用户原话:「升级成同周妈妈的悄悄话,就是匿名一句心愿墙……不做复杂社区,就是只做一点陪伴」;发言 = 预设句 + 自由输入。
|
||||
GET /api/mp/public/mama-box/whisper?openid=&week= → { show, week, canPost, left, maxLen, presets[], list[] }
|
||||
POST /api/mp/public/mama-box/whisper?openid= {text} → 新的一条(自由输入由服务端过微信内容安全审核,不通就 code≠200)
|
||||
POST /api/mp/public/mama-box/whisper/hug?openid=&id=
|
||||
· 只能「抱抱」,没有评论、昵称、头像 —— 刻意不做成社区。自己说的那句排第一,显示「收到 N 个抱抱」。
|
||||
· 拉不到(show=false / 失败 / 访客没孕周)→ 整块隐藏,此刻卡退回滚动动态。不在前端造任何内容。
|
||||
· 由 pulseCard 的 load() 带着孕周触发(孕周回来 / 变了才重拉),另每 5 分钟静默刷新一次。 */
|
||||
let whLoad = null;
|
||||
(function whisperWall() {
|
||||
const wall = $('#whWall');
|
||||
if (!wall) return;
|
||||
const API = '/api/mp/public/mama-box/whisper';
|
||||
const esc = s => String(s == null ? '' : s).replace(/[&<>"]/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c]);
|
||||
const HEART = '<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"/></svg>';
|
||||
let data = null, wk = null, sig = '', busy = false;
|
||||
const oid = () => hashParam('openid');
|
||||
|
||||
function card(it, i) {
|
||||
const tint = 't' + (i % 4);
|
||||
const meta = '孕 ' + it.week + ' 周 · ' + esc(it.ago);
|
||||
const foot = it.mine
|
||||
? '<span class="wh-got">' + HEART + (it.hugs ? '收到 <b>' + it.hugs + '</b> 个抱抱' : '抱抱正在路上') + '</span>'
|
||||
: '<button class="wh-hug' + (it.hugged ? ' on' : '') + '" type="button" data-hug="' + esc(it.id) + '" aria-label="给她一个抱抱">' +
|
||||
HEART + '<span>抱抱</span><b>' + (it.hugs || '') + '</b></button>';
|
||||
return '<div class="wh-card ' + tint + (it.mine ? ' mine' : '') + '">' +
|
||||
(it.mine ? '<span class="wh-mine">我说的</span>' : '') +
|
||||
'<p class="wh-txt">' + esc(it.text) + '</p>' +
|
||||
'<div class="wh-meta"><span>' + meta + '</span>' + foot + '</div></div>';
|
||||
}
|
||||
function render(keepScroll) {
|
||||
const st = $('#whStrip');
|
||||
const x = st.scrollLeft;
|
||||
const list = (data && data.list) || [];
|
||||
st.innerHTML = list.map(card).join('');
|
||||
if (keepScroll) st.scrollLeft = x; else st.scrollLeft = 0;
|
||||
}
|
||||
function show(on) {
|
||||
wall.classList.toggle('hide', !on);
|
||||
const sec = $('#pulse');
|
||||
if (sec) sec.classList.toggle('has-wh', on);
|
||||
}
|
||||
function load(w, silent) {
|
||||
wk = w;
|
||||
if (!w) { data = null; show(false); return; }
|
||||
const o = oid();
|
||||
fetch(API + '?openid=' + encodeURIComponent(o || '') + '&week=' + w)
|
||||
.then(r => (r.ok ? r.json() : null))
|
||||
.then(j => {
|
||||
const r = j && j.code === 200 && j.result;
|
||||
if (!r || !r.show || !(r.list || []).length) { data = null; show(false); return; }
|
||||
data = r;
|
||||
const ns = r.list.map(x => x.id + ':' + x.hugs + (x.hugged ? 'h' : '')).join('|');
|
||||
if (ns !== sig) { sig = ns; render(silent); }
|
||||
show(true);
|
||||
})
|
||||
.catch(() => { if (!silent) show(false); });
|
||||
}
|
||||
whLoad = w => load(w, false);
|
||||
setInterval(() => { if (wk && !document.hidden && !busy) load(wk, true); }, 300000);
|
||||
|
||||
/* 抱抱:先变(心跳一下、数字 +1),失败再退回 */
|
||||
$('#whStrip').addEventListener('click', e => {
|
||||
const b = e.target.closest('[data-hug]');
|
||||
if (!b) return;
|
||||
const o = oid();
|
||||
if (!o) { toast('登录后就能给她抱抱啦'); return; }
|
||||
if (b.classList.contains('on')) { toast('你已经抱过她啦'); return; }
|
||||
const it = ((data && data.list) || []).find(x => x.id === b.dataset.hug);
|
||||
haptic();
|
||||
b.classList.add('on', 'pop');
|
||||
setTimeout(() => b.classList.remove('pop'), 600);
|
||||
const nb = $('b', b);
|
||||
const n0 = it ? it.hugs : 0;
|
||||
if (it) { it.hugs = n0 + 1; it.hugged = true; }
|
||||
nb.textContent = n0 + 1;
|
||||
fetch(API + '/hug?openid=' + encodeURIComponent(o) + '&id=' + encodeURIComponent(b.dataset.hug), { method: 'POST' })
|
||||
.then(r => r.json())
|
||||
.then(j => { if (!j || j.code !== 200) throw j; })
|
||||
.catch(j => {
|
||||
b.classList.remove('on');
|
||||
if (it) { it.hugs = n0; it.hugged = false; }
|
||||
nb.textContent = n0 || '';
|
||||
toast((j && j.message) || '网络不太好,再抱一次试试');
|
||||
});
|
||||
});
|
||||
|
||||
/* 说一句 */
|
||||
const ta = $('#whTa');
|
||||
const cnt = () => { $('#whCnt').textContent = ta.value.length + '/' + ((data && data.maxLen) || 40); };
|
||||
ta.addEventListener('input', () => {
|
||||
cnt();
|
||||
$$('.wh-pre', $('#whPresets')).forEach(c => c.classList.toggle('on', c.textContent === ta.value));
|
||||
});
|
||||
$('#whSay').addEventListener('click', () => {
|
||||
if (!data) return;
|
||||
if (!oid() || !data.canPost) { toast('填好预产期,就能和同周妈妈说悄悄话啦'); return; }
|
||||
if (!data.left) { toast('今天已经说了 3 句啦,明天再来'); return; }
|
||||
ta.value = '';
|
||||
ta.maxLength = data.maxLen || 40;
|
||||
cnt();
|
||||
$('#whPresets').innerHTML = (data.presets || []).map(t => '<button class="wh-pre" type="button">' + esc(t) + '</button>').join('');
|
||||
$('#whLeft').textContent = '今天还能说 ' + data.left + ' 句';
|
||||
openModal('modalWhisper');
|
||||
});
|
||||
$('#whPresets').addEventListener('click', e => {
|
||||
const c = e.target.closest('.wh-pre');
|
||||
if (!c) return;
|
||||
haptic();
|
||||
ta.value = c.textContent;
|
||||
ta.dispatchEvent(new Event('input'));
|
||||
});
|
||||
$('#whSend').addEventListener('click', () => {
|
||||
const t = ta.value.trim();
|
||||
if (t.length < 2) { toast('写一两个字,或者点一句吧'); return; }
|
||||
if (busy) return;
|
||||
busy = true;
|
||||
const btn = $('#whSend');
|
||||
btn.disabled = true;
|
||||
btn.textContent = '正在发出…';
|
||||
fetch(API + '?openid=' + encodeURIComponent(oid()), {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: t })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(j => {
|
||||
if (!j || j.code !== 200 || !j.result) { toast((j && j.message) || '没发出去,再试一次'); return; }
|
||||
const it = j.result;
|
||||
data.left = it.left;
|
||||
delete it.left;
|
||||
data.list = [it].concat(data.list || []);
|
||||
sig = '';
|
||||
render(false);
|
||||
closeModal();
|
||||
toast('发出去啦,同周妈妈会给你抱抱的');
|
||||
})
|
||||
.catch(() => toast('网络不太好,再试一次'))
|
||||
.then(() => { busy = false; btn.disabled = false; btn.textContent = '悄悄发出'; });
|
||||
});
|
||||
})();
|
||||
|
||||
(function pulseCard() {
|
||||
const sec = $('#pulse');
|
||||
if (!sec) return;
|
||||
@@ -3311,7 +3451,14 @@
|
||||
};
|
||||
/* 用户自己今天做没做(足迹 used[].appId 是上游 URL slug)。足迹没回来 / 读不到 → null,不显示「你」那半句 */
|
||||
const SELF_APP = { kick: 'taidong', weight: 'yunqi-tizhong' };
|
||||
const SELF_TXT = { kick: ['你今天还没数', '你也数过了'], weight: ['你今天还没记', '你也记过了'] };
|
||||
/* 2026-10-02 陪伴版:不说「你今天还没数」(像点名),改成宝宝在等你的邀请;做过了就一句肯定。
|
||||
同周人数那句也不再给百分比(「她们有 18%」是在和你比),只说多少位妈妈今天也在做。 */
|
||||
const nmB = () => state.babyName || '宝宝';
|
||||
const SELF_TXT = {
|
||||
kick: [() => nmB() + '在等你,陪它数一数', () => '你今天也陪' + nmB() + '数过啦'],
|
||||
weight: [() => '也记一下今天的自己', () => '你今天也记过啦']
|
||||
};
|
||||
const CROWD_TXT = { kick: '位妈妈陪宝宝数了胎动', weight: '位妈妈记下了体重' };
|
||||
function selfDone(k) {
|
||||
if (_fpMode !== 'rows' && _fpMode !== 'guide') return null;
|
||||
return ((_fpData && _fpData.used) || []).some(u => u.appId === SELF_APP[k]);
|
||||
@@ -3343,7 +3490,8 @@
|
||||
const avs = $('#ppAvs');
|
||||
if (avs.dataset.seed === String(seed)) return;
|
||||
avs.dataset.seed = seed;
|
||||
avs.innerHTML = [3, 11, 7].map(x => '<i>' + momAva(x + seed) + '</i>').join('');
|
||||
/* 2026-10-02 用户:「头像现在展示 3 个,给我展示 6 到 8 个」→ 7 个 */
|
||||
avs.innerHTML = [3, 11, 7, 19, 2, 14, 23].map(x => '<i>' + momAva(x + seed) + '</i>').join('');
|
||||
}
|
||||
function paintPeer(p) {
|
||||
paintAvs(p.week);
|
||||
@@ -3351,14 +3499,16 @@
|
||||
/* 只说一句:优先挑你今天还没做的那项;都做了(或读不到你的足迹)就取第一项 */
|
||||
const ds = (p.doing || []).filter(d => SELF_TXT[d.key]);
|
||||
const pick = ds.find(d => selfDone(d.key) === false) || ds[0];
|
||||
if (pick) {
|
||||
const me = selfDone(pick.key);
|
||||
const meH = me == null ? '' : me
|
||||
? '<span class="pp-me ok">' + SELF_TXT[pick.key][1] + ' ✓</span>'
|
||||
: '<button class="pp-me todo" type="button" data-go="' + pick.key + '">' + SELF_TXT[pick.key][0] + ' ›</button>';
|
||||
$('#ppDoing').innerHTML = '<div class="pp-do"><span class="pp-dt">她们有 <b>' + (+pick.pct || 0) + '%</b> ' + esc(pick.label) + '</span>' + meH + '</div>';
|
||||
const n = pick ? Math.round((+p.count || 0) * (+pick.pct || 0) / 100) : 0;
|
||||
$('#ppCrowd').innerHTML = pick && n >= 3 ? '今天 <b>' + fmtN(n) + '</b> ' + CROWD_TXT[pick.key] : '她们和你一样,在等宝宝来';
|
||||
const me = pick ? selfDone(pick.key) : null;
|
||||
if (me != null) {
|
||||
$('#ppDoing').innerHTML = me
|
||||
? '<span class="pp-me ok"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3.5 8.4l3 3 6-6.4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>' + esc(SELF_TXT[pick.key][1]()) + '</span>'
|
||||
: '<button class="pp-me todo" type="button" data-go="' + pick.key + '">' + esc(SELF_TXT[pick.key][0]()) +
|
||||
'<i aria-hidden="true"><svg viewBox="0 0 16 16"><path d="M6 3.5L10.5 8 6 12.5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></i></button>';
|
||||
}
|
||||
$('#ppDoing').classList.toggle('hide', !pick);
|
||||
$('#ppDoing').classList.toggle('hide', me == null);
|
||||
}
|
||||
function paint() {
|
||||
const r = last;
|
||||
@@ -3371,6 +3521,8 @@
|
||||
/* 全站版(访客 / real 模式):只留一行总人数 + 滚动动态 */
|
||||
paintAvs(0);
|
||||
$('#ppDoing').classList.add('hide');
|
||||
const td = (r.today || [])[0];
|
||||
$('#ppCrowd').innerHTML = td ? '今天 <b>' + fmtN(td.value) + '</b> ' + esc(td.label) : '她们和你一样,在等宝宝来';
|
||||
const t = r.total;
|
||||
/* 前缀「已有」省掉:一行放得下「10,037 位妈妈在这里迎接宝宝」,带上就会折出一个孤零零的「宝宝」 */
|
||||
$('#plTitle').innerHTML = t ? '<b data-n="' + t.value + '">0</b> ' + esc(t.suffix) : '孕妈们在做什么';
|
||||
@@ -3407,6 +3559,7 @@
|
||||
function load() {
|
||||
const w = myWeek();
|
||||
loadedWk = w;
|
||||
if (whLoad) whLoad(w);
|
||||
return fetch('/api/mp/public/mama-box/pulse' + (w ? '?week=' + w : ''))
|
||||
.then(r => (r.ok ? r.json() : null))
|
||||
.then(j => { if (j && j.code === 200 && j.result) { last = j.result; paint(); } })
|
||||
|
||||
Reference in New Issue
Block a user