底tab「兜爸心选」/「进入商城」落点改商城首页:小程序原装装修首页已恢复为 pages/index/mall(DIY 底部导航第2项),goMall 改 switchTab 该页并补 fail 提示;首页新增今日脚印(数胎动对外 API,box_app 封装,拉不到回退内置);版本 v26091802

This commit is contained in:
2026-09-18 21:12:49 +08:00
parent 3ddcfe3e43
commit 627a819e46
3 changed files with 103 additions and 5 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:26091602 缓存版本标记:改 css/js 时同步 bump 该值(与 js/app.js 的 VER 同源),静态资源按 mtime 走 ETag 自动刷新 -->
<!-- V:26091802 缓存版本标记:改 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 第四节) -->

100
js/app.js
View File

@@ -3,7 +3,7 @@
(function () {
'use strict';
/* 页面版本号(YYMMDD+序号):显示在「关于兜爸心选」页底部;bump 时同步 head 注释与 ?v= */
var VER = '26091602';
var VER = '26091802';
const $ = (s, c) => (c || document).querySelector(s);
const $$ = (s, c) => Array.from((c || document).querySelectorAll(s));
@@ -4187,6 +4187,9 @@
applyRemoteAll();
// 第三批远程数据(宝宝来信)—— 同样随 init 一起拉,失败回退内置样例
applyRemoteLetters();
// 第四批远程数据(今日脚印,来自数胎动)—— 渲染首页 .today-line 时间轴,
// 拉不到/未关联都各有回退,见 applyRemoteFootprint 内的分支说明
applyRemoteFootprint();
}
/* ---------- 远程配置(box_app 后台下发) ----------
本页托管在 box_app(/mama-box/)时,启动后拉一次运营配置:
@@ -4435,10 +4438,93 @@
}).catch(function () { /* 静默回退内置样例 */ });
}
/* ---------- 今日脚印(box_app → 数胎动对外 API,动态获取) ----------
首页 .today-line 时间轴原来是纯演示内容(无真实数据源,访客/未完善态整块隐藏),
现在启动后拉 /footprint/today 动态渲染。后端封装见 box_app 的 FootprintService:
上游数胎动按 unionid 建档(openid 跨应用互不相干),H5 身份注入惯例带
openid+unionid,unionid 缺失时后端用映射表兜底,这里只管把参数带上。
各分支(对齐全站「远程数据为准、拉不到回退内置」的惯例):
- linked=false(未关联数胎动 / 上游查无此人)→ 整块隐藏。演示行是假数据,
这用户在数胎动体系里没有档案,不能留着冒充真足迹。
- linked=true + events → 重建 .steps 行。textContent 拼装(上游文案直接进
DOM,杜绝注入);最多 8 条(接口升序,取最近 8 条,更早的让给「查看全部」)。
- linked=true + 无事件 → 一条占位,别让卡空成一截竖线。
- 拉取失败 → 什么都不动,保持内置演示行(跟 letters 的回退一致);
访客态不请求(pregEmpty 分支已整块隐藏,不会露出)。
⚠️ 竞态:本函数与 applyRemoteProfile 并行。profile 回来若判 pregEmpty 会
隐藏时间轴,所以这里落笔前再看一眼 state.pregEmpty,别把刚藏好的放出来。 */
var _footprintApplied = false;
function applyRemoteFootprint() {
if (_footprintApplied) return; // init 只跑一次,防重复 fetch
_footprintApplied = true;
const openid = hashParam('openid');
if (!openid) return; // 访客态: pregEmpty 分支已隐藏,保持
const qs = ['openid=' + encodeURIComponent(openid)];
const unionid = hashParam('unionid');
if (unionid) qs.push('unionid=' + encodeURIComponent(unionid));
fetch('/api/mp/public/mama-box/footprint/today?' + qs.join('&'))
.then(r => (r.ok ? r.json() : null))
.then(j => {
if (!j || j.code !== 200 || !j.result) return; // 拉不到 → 回退演示行
const r = j.result;
const tl = document.querySelector('.today-line');
const steps = $('#tlSteps');
if (!tl || !steps) return;
if (!r.linked) { tl.style.display = 'none'; return; }
if (state.pregEmpty) return; // 别把 profile 刚藏好的放出来
const events = (r.events || []).slice(-8); // 升序取最近 8 条
steps.innerHTML = '';
if (!events.length) { steps.append(emptyFootprintStep()); return; }
events.forEach(e => steps.append(buildFootprintStep(e)));
})
.catch(function () { /* 静默回退演示行 */ });
}
/* 一行脚印:与 index.html 演示行同构(时间/圆点/文案/来源)。
文案里的数字段加粗对齐演示排版(「数胎动 <b>2</b> 次」),
全程 createElement+textContent,上游字段再离谱也进不来标签。 */
function buildFootprintStep(e) {
const d = document.createElement('div'); d.className = 'step';
const time = document.createElement('span'); time.className = 'st-time';
time.textContent = e.at || '--:--';
const dot = document.createElement('span'); dot.className = 'st-dot';
const txt = document.createElement('span'); txt.className = 'st-txt';
const m = String(e.label || '').match(/^(.*?)(\d[\d.]*)(.*)$/);
if (m) {
if (m[1]) txt.append(m[1]);
const b = document.createElement('b'); b.textContent = m[2]; txt.append(b);
if (m[3]) txt.append(m[3]);
} else {
txt.textContent = String(e.label || '');
}
const from = document.createElement('span'); from.className = 'st-from';
from.textContent = e.app ? '来源 · ' + e.app : '';
d.append(time, dot, txt, from);
return d;
}
/* 无足迹占位行:同构的一行,读屏和眼睛都不会把空卡当成坏卡。 */
function emptyFootprintStep() {
const d = document.createElement('div'); d.className = 'step';
const time = document.createElement('span'); time.className = 'st-time';
time.textContent = '今天';
const dot = document.createElement('span'); dot.className = 'st-dot';
const txt = document.createElement('span'); txt.className = 'st-txt';
txt.textContent = '还没有足迹';
const from = document.createElement('span'); from.className = 'st-from';
from.textContent = '去逛逛上面的工具吧';
d.append(time, dot, txt, from);
return d;
}
/* ---------- 进商城(web-view 内跳商城原生页) ----------
兜爸心选 H5 由小程序 web-view 承载;点「商城」跳回商城原生页。
需微信 JS-SDK 的 wx.miniProgram.switchTab —— tabBar 页必须用 switchTab
(详见 docs/mama-box-api-plan.md 2.4)。
落点 260918 起改为「商城」tab(/pages/index/mall)=原装 CRMEB 装修首页
恢复成的第 5 个 tabBar 页;此前一直落在分类页 /pages/goods_cate/goods_cate
(商品列表),按用户要求改成进商城先看首页。
仅在 web-view 环境生效;浏览器独立打开时 wx.miniProgram 不可用,降级为 toast。 */
function goMall() {
haptic();
@@ -4449,7 +4535,17 @@
window.wx.miniProgram.getEnv(function (r) {
responded = true;
if (r && r.miniprogram) {
window.wx.miniProgram.switchTab({ url: '/pages/goods_cate/goods_cate' });
/* fail 兜底:宿主小程序若还是旧版(没有「商城」tab /pages/index/mall),
switchTab 会失败且无任何反应 —— 这里给出提示,别让用户点了没动静。
(发布顺序:先发小程序版再加这个 H5 落点;反过来也不会白屏/死寂) */
window.wx.miniProgram.switchTab({
url: '/pages/index/mall',
fail: function (err) {
var m = (err && err.errMsg) || '未知原因';
toast('打开商城失败:' + m);
try { console.warn('[goMall] switchTab fail:', m); } catch (e) {}
}
});
} else {
toast('请在微信小程序中打开商城');
}

View File

@@ -101,12 +101,14 @@
},
/* ⑤ 跳小程序商城(tabBar 页用 switchTab;非 tabBar 用 navigateTo) */
/* ⑤ 跳商城:环境判定与 requestProfile 同一套 —— 仅小程序 webview 内 switchTab,
UA 非微信/微信内非 webview(或 getEnv 不回调)都提示「请在小程序中打开商城」。 */
UA 非微信/微信内非 webview(或 getEnv 不回调)都提示「请在小程序中打开商城」。
默认落点 260918 起为「商城」tab(/pages/index/mall,原装装修首页恢复页),
与 app.js 的 goMall() 保持一致;此前是分类页 /pages/goods_cate/goods_cate。 */
goMall: function (url) {
var self = this;
function go() {
window.wx.miniProgram.switchTab({
url: url || '/pages/goods_cate/goods_cate',
url: url || '/pages/index/mall',
fail: function (err) {
var m = (err && err.errMsg) || '未知原因';
self.toast('打开商城失败:' + m);