fix(身份): 宿主未注入 openid 时给出可见提示 + jumpApp 带 __no_identity 标记 (v26092008)

背景(2026-09-20 线上排查):小程序首页壳拿不到 openid 时会**匿名加载**本页,
页面照常渲染但全是占位(孕周 `--`、档案查不到),而跳应用时 jumpApp 因为没有身份
就跳**裸 URL** —— 从商城返回首页加载本页时很容易被误判成「数据丢了」。
根因在小程序侧(App.vue 丢掉无 token 分支的 openid;首页壳没有像 web_view 那样的
silenceAuthNoLogin 兜底),H5 这侧先做**可见 + 可观测**两件缓解:

· 新增顶部提示条(#idNotice):仅在没有 openid 时显示,文案「未获取到微信身份,
  页面数据可能不完整」;带「重试」按钮(location.replace 换 URL 重载,重试过一次后
  文案升级为「请退出小程序后重新进入」,状态落 sessionStorage,活过那次跳转)。
  有身份加载时(applyRemoteProfile 拿到 openid)自动隐藏。
  ⚠️ 刻意不做自动轮询/自动 reload:宿主只在它自己重设 web-view src 或 onShow 时注入身份,
     页面自己刷同一 URL 并不会让身份出现,反而可能循环。
· jumpApp():一个身份都没有时显式带 `__no_identity=1` —— 目标应用能区分「宿主没注入身份」
  与「参数被平台丢了」,nginx/后端日志里也能一眼查到(排查 openid 为空的关键指纹)。

验证(本地代理 + 浏览器):访客态提示可见且默认文案正确;有 openid 时隐藏;
点重试后 URL 变 `?t=…`、提示仍在且文案升级;点宫格:无身份 → `…/?__no_identity=1`,
有身份 → `?openid=…&unionid=…`(无标记);卡片/红点/资源库 8 份/弹窗/我的页/tab 顺序
全部回归正常,errors 与 console 为空。
This commit is contained in:
2026-09-20 23:08:26 +08:00
parent 340fc911b1
commit 932e42e050
3 changed files with 90 additions and 2 deletions

View File

@@ -7,7 +7,7 @@
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<title>兜爸心选 · 孕育陪伴</title>
<!-- V:26092007 缓存版本标记:改 css/js 时同步 bump 该值(与 js/app.js 的 VER 同源),静态资源按 mtime 走 ETag 自动刷新 -->
<!-- V:26092008 缓存版本标记:改 css/js 时同步 bump 该值(与 js/app.js 的 VER 同源),静态资源按 mtime 走 ETag 自动刷新 -->
<link rel="stylesheet" href="mama-box.css">
<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
<!-- 共享助手:授权/跳商城/位置上报(分享卡片同步,见 docs/h5-auth-standard.md 第四节) -->
@@ -16,6 +16,17 @@
<body>
<a class="skip" href="#screens">跳到内容</a>
<div class="phone" id="phone">
<!-- 身份未就绪提示(2026-09-20 新增)
⚠️ 数据源是「宿主有没有把 openid 注入进来」,由 app.js 的 showIdNotice()/hideIdNotice() 控制。
背景:小程序首页壳在拿不到 openid 时会**匿名加载**本页(见 js/app.js 的访客态分支),
此时页面看着能用、其实全是占位(孕周 `--`、查不到任何档案)。以前是静默的,
用户/测试者看不出异常 —— 从商城返回首页加载本页时尤其容易被误判成「数据丢了」。
所以这里给一条**可见**的提示,并提供重试;排查时也能一眼看出是身份问题而不是数据问题。 -->
<div class="id-notice hide" id="idNotice" role="status">
<span class="idn-ico" aria-hidden="true">!</span>
<span class="idn-txt" id="idNoticeTxt">未获取到微信身份,页面数据可能不完整</span>
<button class="idn-retry" id="idNoticeRetry">重试</button>
</div>
<nav class="tabbar" id="tabbar">
<button class="tab is-active" data-tab="home">
<span class="t-ico ic-home"></span><span class="t-txt">首页</span>

View File

@@ -3,7 +3,7 @@
(function () {
'use strict';
/* 页面版本号(YYMMDD+序号):显示在「关于兜爸心选」页底部;bump 时同步 head 注释与 ?v= */
var VER = '26092007';
var VER = '26092008';
const $ = (s, c) => (c || document).querySelector(s);
const $$ = (s, c) => Array.from((c || document).querySelectorAll(s));
@@ -192,6 +192,12 @@
const q = [];
if (oid) q.push('openid=' + encodeURIComponent(oid));
if (uid) q.push('unionid=' + encodeURIComponent(uid));
/* ⚠️ 2026-09-20:一个身份都没有时**显式标记**,别让目标应用/日志去猜
「是宿主没注入身份」还是「参数在路上被平台丢了」。
· 被跳的 H5 可以据此显示自己的身份提示(或走自己的兜底);
· nginx/后端日志里 __no_identity=1 一眼可查(排查 openid 为空时很快)。
排查背景见 docs/mama-box-change-list-20260919.md 与 showIdNotice() 的注释。 */
if (!oid && !uid) q.push('__no_identity=1');
if (q.length) url += (url.indexOf('?') >= 0 ? '&' : '?') + q.join('&');
reportHostLocation(url);
location.href = url;
@@ -2470,6 +2476,51 @@
return m ? decodeURIComponent(m[1]).split('?')[0] : '';
} catch (e) { return ''; }
}
/* ---------- 身份未就绪提示(2026-09-20) ----------
宿主(小程序首页壳)拿不到 openid 时会**匿名加载**本页 —— 页面照常渲染,
但全是占位数据(孕周 `--`、档案空、跳应用不带身份)。以前这种情况完全静默,
排查时只能靠服务端日志反推;现在给一条可见提示。
⚠️ 只在「确实没有 openid」时显示;一旦宿主把身份注入回来(onShow 重载),
applyRemoteProfile 会再跑一次,这里要 hide。
⚠️ 不做自动轮询/自动 reload:宿主只在它自己重设 web-view src 或 onShow 时注入身份,
页面自己 reload 同一 URL 并不会让身份出现(反而可能循环),所以只留手动重试。 */
function showIdNotice(reason) {
const el = $('#idNotice'), txt = $('#idNoticeTxt');
if (!el) return;
if (txt) {
/* ⚠️ 重试过一次之后(sessionStorage 计数)文案要保留「请退出小程序重进」,
否则重载回来又变回温和版,用户以为重试没发生过(重试是 location.replace,
文案状态活不过那一次跳转,只能落到 sessionStorage)。 */
let tried = reason === 'retried';
if (!tried) {
try { tried = (parseInt(sessionStorage.getItem('mmbox.idretry') || '0', 10) || 0) > 0; } catch (e) {}
}
txt.textContent = tried
? '仍未获取到微信身份,请退出小程序后重新进入'
: '未获取到微信身份,页面数据可能不完整';
}
el.classList.remove('hide');
}
function hideIdNotice() {
const el = $('#idNotice');
if (el) el.classList.add('hide');
}
/* 重试:只做一次「换 URL 重载」——带上 ?t= 让宿主/缓存放行。
宿主 onShow 也会重新注入身份(切到别的 tab 再回来即可),提示文案里已说明。 */
function retryIdentity() {
let n = 0;
try { n = parseInt(sessionStorage.getItem('mmbox.idretry') || '0', 10) || 0; } catch (e) {}
try { sessionStorage.setItem('mmbox.idretry', String(n + 1)); } catch (e) {}
showIdNotice('retried');
setTimeout(() => {
try {
const u = location.href.split('#')[0].split('?')[0] + '?t=' + Date.now();
location.replace(u);
} catch (e) { location.reload(); }
}, 500);
}
$('#idNoticeRetry').addEventListener('click', retryIdentity);
let _profileApplied = false;
function applyRemoteProfile() {
if (_profileApplied) return; // init 只跑一次,防重复 fetch
@@ -2479,6 +2530,9 @@
⚠️ 绝不能停在内置演示默认值上(小汤圆 / 孕27周 都是前端现算的假数据)——
全部切占位/引导;宿主拿到身份后会带 openid 重载本页,届时自动切真实数据。 */
if (!openid) {
/* ⚠️ 可见提示(2026-09-20):宿主没注入身份时别静默装正常。
排查路径见 showIdNotice() 上方那段注释。 */
showIdNotice();
state.pregEmpty = true;
state.due = null; state.week = null; state.day = null;
state.babyName = ''; state.userName = '';
@@ -2492,6 +2546,8 @@
- profile:babyName/userName/babySex/dueDate(身份 + 建档字段)
- pregnancy:week/day/due 以服务端权威派生为准(避免客户端重复算、口径统一)
拉不到(无 openid / 未返回 200 / 断网)就静默保持内置默认值,页面照常可用。 */
/* 走到这里说明这次加载是带身份的(openid 非空)→ 把上一次的提示收掉 */
hideIdNotice();
Promise.all([
fetch(base + 'profile?openid=' + encodeURIComponent(openid)).then(r => (r.ok ? r.json() : null)),
fetch(base + 'pregnancy?openid=' + encodeURIComponent(openid)).then(r => (r.ok ? r.json() : null))

View File

@@ -2099,3 +2099,24 @@ a.skip:focus{left:16px;top:16px}
/* 「关于妈妈盒子」页底部版本号 */
.about-ver{margin-top:24px;padding-bottom:34px;text-align:center;color:#C9BFB8;font-size:11px;letter-spacing:.5px}
/* ---- 身份未就绪提示(2026-09-20)----
宿主没注入 openid 时显示(见 index.html 里那段注释)。绝对定位在 .phone 顶部,
z-index 高于 tabbar(60)、低于 modal(120+),避免盖住弹窗。 */
.id-notice{
position:absolute;left:26px;right:26px;top:calc(10px + env(safe-area-inset-top));z-index:66;
display:flex;align-items:center;gap:8px;padding:9px 12px;border-radius:14px;
background:rgba(255,246,232,.97);border:1px solid #F2D8AE;color:#8A5A18;
box-shadow:0 6px 18px rgba(150,110,60,.16);font-size:12.5px;line-height:1.3;
}
.id-notice.hide{display:none}
.idn-ico{
flex:0 0 auto;width:18px;height:18px;border-radius:50%;background:#E8A33D;color:#fff;
font-size:12px;font-weight:800;display:grid;place-items:center;
}
.idn-txt{flex:1;min-width:0}
.idn-retry{
flex:0 0 auto;border:0;background:#E8A33D;color:#fff;font-weight:700;
font-size:12px;padding:6px 12px;border-radius:9px;
}
.idn-retry:active{transform:scale(.95)}