fix(首页): 来信卡默认收起,等 /letters 结果再决定显示 —— 修掉「先冒出来再缩回去」的闪烁 (v26092003)

现象:从商城切回首页加载 mama-box 时,先渲染出一张「宝宝来信/宝宝回信啦」卡
(用内置样例信 + 本地待投递队列拼的),几百毫秒后 /letters 回来说没有未读,
卡又缩回去,闪一下。

原因:卡在 index.html 里默认可见,pushLatestToHome() 是「先显示、数据回来再决定」;
而首屏渲染远早于 /letters。

改法(反过来:没确定之前一律不显示):
- index.html:#homeLetter 默认带 .hide、#homeBody 默认带 .no-letter(首屏按「无卡」排版,不跳)
- app.js:新增 lettersResolved 开关;pushLatestToHome() 在未确定、访客态(无 openid)、
  或后端明确未建档(pregEmpty)时一律保持收起;applyRemoteLetters 的每条终态
  (成功/空列表/接口挂/无身份)与 applyRemoteProfile 的访客分支都置位并重算
- 访客分支顺手补了一次 pushLatestToHome():此前清空 LETTERS 后没人重算,
  那张卡会顶着一封已不存在的信留在首页

验证(本地 + 延迟代理四态,浏览器实测):
- 有建档 + 0 封信:全程 hidden(修的就是这个)
- 有建档 + 1 封未读:接口回来前 hidden → 回来后显示「小汤圆来信」
- 接口全挂:回退内置样例照旧显示(离线行为不变)
- 访客(无 openid):hidden
This commit is contained in:
2026-09-20 02:02:47 +08:00
parent e9873040f3
commit e29052f107
2 changed files with 54 additions and 10 deletions

View File

@@ -3,7 +3,7 @@
(function () {
'use strict';
/* 页面版本号(YYMMDD+序号):显示在「关于兜爸心选」页底部;bump 时同步 head 注释与 ?v= */
var VER = '26092002';
var VER = '26092003';
const $ = (s, c) => (c || document).querySelector(s);
const $$ = (s, c) => Array.from((c || document).querySelectorAll(s));
@@ -1319,10 +1319,30 @@
全读完了还硬顶一封旧信在首页正中间,是在假装有新消息。
收起来会空出 64px 的流高(卡是 margin-top:-16 + height:80),
所以同时给 .home-body 挂 .no-letter 把下面的标题往下推一点,
免得「孕期必用工具」直接贴到橙色头图底边上。 */
免得「孕期必用工具」直接贴到橙色头图底边上。
⚠️⚠️ 2026-09-20:卡的显隐**要等 /letters 回来才算数**(lettersResolved)。
之前的写法是「先把内置样例/本地队列拼出来的卡显示出来,数据回来再决定」——
结果商城↔首页来回切时会闪一下:先冒出一张(甚至写着「宝宝回信啦」)的卡,
几百毫秒后数据说没有未读,又缩回去。现在反过来:
· index.html 里 #homeLetter 默认带 .hide、#homeBody 默认带 .no-letter;
· 没确定(!lettersResolved)→ 一律保持收起;
· 确定之后才可能显示,且只有在真有未读/新回信时才显示。
代价是「有信」的用户看到卡出现在数据回来那一刻(晚一点点,但不闪)。 */
let lettersResolved = false; // /letters 已经问过结果(含失败/访客态)—— 见 applyRemoteLetters
function hideLetterCard() {
const card = $('#homeLetter'), body = $('#homeBody');
if (card) { card.classList.add('hide'); card.classList.remove('has-new'); }
if (body) body.classList.add('no-letter');
}
function pushLatestToHome() {
const card = $('#homeLetter');
const body = $('#homeBody');
/* 还没问出结果、访客态(没身份)、或后端明确说该用户没建档 —— 都别显示。
⚠️ hashParam 是函数声明(在文件靠后定义),这里调用靠提升,没问题。 */
if (!lettersResolved || !hashParam('openid') || state.pregEmpty) { hideLetterCard(); return; }
const show = () => { card.classList.remove('hide'); body.classList.remove('no-letter'); };
let msg = null, owner = null;
LETTERS.forEach(L => L.thread.forEach(m => {
@@ -3988,6 +4008,10 @@
state.curLetter = ''; state.homeLetter = '';
syncPregnancy(); /* 占位 + 隐藏脚印时间轴 + 信件引导态 */
syncLetterCounts(); /* hero 计数切 '--'(角标 #tabBadge 已不由本地信件数驱动) */
/* ⚠️ 必须重算首页来信卡:上面刚把 LETTERS 清空、pregEmpty 置真,
不重算的话那张卡会停在「显示」状态,顶着一封已经不存在的信。 */
lettersResolved = true;
pushLatestToHome();
return;
}
const base = '/api/mp/public/mama-box/';
@@ -4119,14 +4143,22 @@
拉不到 —— 无 openid / 接口挂 / 返回码不对 —— 就静默回落内置样例,页面照常可用。 */
function applyRemoteLetters() {
var openid = hashParam('openid');
if (!openid) return;
/* ⚠️ 下面每条**终态**都必须把 lettersResolved 置 true(含失败/无身份),
否则首页那张来信卡会永远停在收起态(宁可收起也不能闪,见 pushLatestToHome)。
无身份这条:卡本来就不该出现,置 true 只是让状态机收敛。 */
if (!openid) { lettersResolved = true; pushLatestToHome(); return; }
var base = '/api/mp/public/mama-box/';
var j = function (u) { return fetch(u).then(function (r) { return r.ok ? r.json() : null; }).catch(function () { return null; }); };
j(base + 'letters?openid=' + encodeURIComponent(openid)).then(function (lj) {
if (!lj || lj.code !== 200 || !lj.result || !lj.result.items) return;
/* 拉不到(接口挂/断网/返回码不对)→ 静默回落内置样例。
这**也是**一个终态:样板信照样要能上首页卡(离线可用),所以这里要置位并重算。 */
if (!lj || lj.code !== 200 || !lj.result || !lj.result.items) {
lettersResolved = true; pushLatestToHome(); return;
}
/* 新用户空态:后端成功返回空列表 → 清掉内置样例信,展示「这里还没有内容」。
⚠️ 不能直接 return —— 那会把 LETTERS 留在内置样例上,新用户看到演示信。 */
if (!lj.result.items.length) {
lettersResolved = true;
LETTERS.splice(0, LETTERS.length);
state.curLetter = '';
state.homeLetter = '';
@@ -4155,12 +4187,13 @@
LETTERS.splice(0, LETTERS.length, ...apiList);
state.curLetter = LETTERS[0].id;
state.homeLetter = LETTERS[0].id;
lettersResolved = true;
renderLetters();
syncLetterCounts();
pushLatestToHome();
} catch (e) { /* 映射 / 渲染异常就回退内置样例 */ }
}).catch(function () { /* 静默回退内置样例 */ });
}).catch(function () { /* 静默回退内置样例 */ });
}).catch(function () { lettersResolved = true; pushLatestToHome(); /* 静默回退内置样例 */ });
}).catch(function () { lettersResolved = true; pushLatestToHome(); /* 静默回退内置样例 */ });
}
/* ---------- 今日脚印(box_app → 数胎动对外 API,动态获取) ----------