diff --git a/index.html b/index.html
index a7d72f0..0646bfa 100644
--- a/index.html
+++ b/index.html
@@ -7,7 +7,7 @@
兜爸心选 · 孕育陪伴
-
+
diff --git a/js/app.js b/js/app.js
index 14abf43..13b7ce8 100644
--- a/js/app.js
+++ b/js/app.js
@@ -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 演示行同构(时间/圆点/文案/来源)。
+ 文案里的数字段加粗对齐演示排版(「数胎动 2 次」),
+ 全程 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('请在微信小程序中打开商城');
}
diff --git a/js/wx-auth.js b/js/wx-auth.js
index edd3349..d741d6f 100644
--- a/js/wx-auth.js
+++ b/js/wx-auth.js
@@ -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);