Files
mama-box/js/mb-tabbar.js
deploy 7cc154d9b3 v26110903 广场/专题/计算器/测一测在小程序里也开容器页,复制链接直达;容器页返回走 navigateBack
新增 js/host-nav.js(MBHost.open/back/home + 页内同站 <a> 拦截):
- 首页进广场、专题问题、百宝箱测一测/计算器 → 容器页;广场点笔记 → 容器页 ?n=
- 容器页(_hv=1)里笔记关闭、广场/专题/计算器返回 → navigateBack;广场「返回」tab → switchTab 首页
- 失败或不在小程序照旧本页跳转

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:06:03 +00:00

205 lines
18 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 底部胶囊 Tab(11-05 用户:「孕期百问、孕期计算器加上跟趣味小测试一样的底部 Tab」)
样式 / 行为照搬 guangchang/index.html 那条(左:本页 · 中:「记一下」橙色圆钮 · 右:返回首页 + 用户头像渐变圈),
类名统一加 mbt- 前缀,避免和首页自己的 .tabbar / .tab 撞车。
用法:MBTab.init({
root: '../../', // 到 mama-box 根目录的相对路径(取头像 / 回首页用)
first: { t: '孕期计算器', ic: 'calc' }, // 左边那格:本页名字 + 图标(calc / qa)
onFirst: fn, // 点左边(默认:滚回顶部)
onHome: fn, // 点「返回首页」(默认:跳 root 带身份)
onRec: fn, // 点「记一下」(默认:自带弹层,列后台已上架的孕期工具,同广场)
mount: el, scroller: el // 挂在哪(默认 body,position:fixed);滚回顶部用的容器
}) */
(function (g) {
'use strict';
var CSS = '' +
'.mbt-fade{position:fixed;left:0;right:0;bottom:0;z-index:29;pointer-events:none;height:calc(96px + env(safe-area-inset-bottom));background:linear-gradient(180deg,rgba(255,246,241,0),rgba(255,246,241,.75))}' +
'.mbt{position:fixed;left:16px;right:16px;bottom:calc(22px + env(safe-area-inset-bottom));height:62px;z-index:30;border-radius:31px;padding:0 6px;display:grid;grid-template-columns:repeat(3,1fr);font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif}' +
'.mbt::before{content:"";position:absolute;inset:0;z-index:-1;border-radius:inherit;pointer-events:none;background:#FFF9F5;box-shadow:inset 0 0 0 1px rgba(255,255,255,.85),inset 0 1px 0 #fff;' +
'-webkit-mask:radial-gradient(circle 31px at 50% 6px,transparent 30.5px,#000 31.5px);mask:radial-gradient(circle 31px at 50% 6px,transparent 30.5px,#000 31.5px)}' +
'.mbt::after{content:"";position:absolute;inset:0;z-index:-2;border-radius:inherit;pointer-events:none;box-shadow:0 14px 34px rgba(200,100,70,.2),0 2px 6px rgba(200,100,70,.06)}' +
'.mbt-t{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;padding:0 0 6px;border:0;background:none;font-family:inherit;color:#A3958D;cursor:pointer;-webkit-tap-highlight-color:transparent}' +
'.mbt-t.on{color:#F4593C}' +
'.mbt-i{width:25px;height:25px;display:block;position:relative}' +
'.mbt-i svg{display:block;width:100%;height:100%;overflow:visible}' +
'.mbt-i .tf,.mbt-i .tl{fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}' +
'.mbt-t.on .tf{fill:currentColor}.mbt-t.on .tl{stroke:#fff}' +
'.mbt-x{font-size:12.5px;font-weight:600;line-height:1.1;letter-spacing:.2px;white-space:nowrap}.mbt-t.on .mbt-x{font-weight:800}' +
'.mbt-t::after{content:"";position:absolute;left:50%;bottom:6px;width:4px;height:4px;margin-left:-2px;border-radius:50%;background:#F4593C;opacity:0}.mbt-t.on::after{opacity:1}' +
'.mbt-fab::after{display:none}' +
'.mbt-fab .fab{position:absolute;left:50%;top:-20px;width:52px;height:52px;margin-left:-26px;border-radius:50%;display:grid;place-items:center;overflow:visible;' +
'background:linear-gradient(150deg,#FF8A6E,#F4593C);box-shadow:0 6px 14px rgba(244,89,60,.32),inset 0 1px 0 rgba(255,255,255,.3);transition:transform .3s cubic-bezier(.3,1.6,.5,1)}' +
'.mbt-fab .fab::before{content:"";display:block;width:20px;height:20px;opacity:.28;border-radius:2px;background:linear-gradient(#fff,#fff) center/100% 3px no-repeat,linear-gradient(#fff,#fff) center/3px 100% no-repeat}' +
'.mbt-fab:active .fab{transform:scale(.9)}' +
'.mbt-w{position:absolute;left:50%;top:50%;transform:translate(calc(-50% - 5px),-54%) rotate(-8deg);display:flex;align-items:center;white-space:nowrap;font-weight:900;font-style:normal;color:#4A1E14;line-height:1;pointer-events:none;' +
'text-shadow:1.6px 0 #fff,-1.6px 0 #fff,0 1.6px #fff,0 -1.6px #fff,1.2px 1.2px #fff,-1.2px 1.2px #fff,1.2px -1.2px #fff,-1.2px -1.2px #fff,0 3px 4px rgba(120,30,10,.25)}' +
'.mbt-w i{font-style:normal;font-size:31px}.mbt-w em{font-style:normal;font-size:12.5px;line-height:1;margin:9px 0 0;letter-spacing:-.5px}.mbt-w s{text-decoration:none;font-size:20px;display:inline-block;transform:skewX(-8deg)}' +
'.mbt-fab .mbt-x{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}' +
'.mbt-home .mbt-i{margin-bottom:6px}.mbt-home .mbt-x{font-size:11.5px;font-weight:600}' +
'.mbt-av{position:absolute;inset:-3px;width:calc(100% + 6px);height:calc(100% + 6px);border-radius:50%;object-fit:cover;display:none;background:#fff;box-shadow:0 0 0 1.5px #fff,0 3px 10px rgba(226,105,74,.3)}' +
'.mbt-hm{position:absolute;right:-8px;bottom:-5px;width:15px;height:15px;z-index:1;border-radius:50%;display:none;background:linear-gradient(150deg,#E2694A,#C24E31);box-shadow:0 0 0 1.5px #FFF6F1}.mbt-hm svg{display:block;width:10px;height:10px;margin:2.5px}' +
'.mbt-i.has-av .mbt-av,.mbt-i.has-av .mbt-hm{display:block}.mbt-i.has-av svg.ts{visibility:hidden}' +
'.mbt-i.has-av::before{content:"";position:absolute;inset:-7px;border-radius:50%;z-index:-1;pointer-events:none;background:conic-gradient(from 200deg,#FFB38A,#F4593C,#E2694A,#FF8FA3,#FFB38A);' +
'-webkit-mask:radial-gradient(circle closest-side,transparent 82%,#000 85%);mask:radial-gradient(circle closest-side,transparent 82%,#000 85%)}' +
/* 「记一下」弹层(同广场:标题一行 + 4 列工具宫格) */
'.mbt-mask{position:fixed;inset:0;z-index:60;background:rgba(40,20,14,.38);opacity:0;pointer-events:none;transition:opacity .25s}.mbt-mask.on{opacity:1;pointer-events:auto}' +
'.mbt-sh{position:fixed;left:0;right:0;bottom:0;z-index:61;max-height:80vh;overflow:auto;padding:8px 18px calc(20px + env(safe-area-inset-bottom));border-radius:22px 22px 0 0;background:#FFF9F5;transform:translateY(105%);transition:transform .3s cubic-bezier(.2,.9,.3,1);font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif}' +
'.mbt-sh.on{transform:none}' +
'.mbt-rh{display:flex;align-items:baseline;gap:10px;padding:10px 2px 14px}.mbt-rh b{font-size:19px;font-weight:800;color:#2A1E1E;flex:none}' +
'.mbt-rh span{font-size:12px;color:#9C8F88;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}' +
'.mbt-rx{flex:none;align-self:center;width:30px;height:30px;border:0;border-radius:50%;display:grid;place-items:center;background:rgba(120,70,50,.08);color:#9C8F88;font-size:18px;line-height:1;cursor:pointer}' +
'.mbt-apps{display:grid;grid-template-columns:repeat(4,1fr);gap:20px 6px;padding:4px 0 8px}' +
'.mbt-app{position:relative;display:flex;flex-direction:column;align-items:center;gap:6px;min-width:0;border:0;background:none;font-family:inherit;color:#2A1E1E;cursor:pointer}' +
'.mbt-app img,.mbt-app .rl{width:48px;height:48px;display:block;object-fit:contain;border-radius:14px}' +
'.mbt-app .rl{display:grid;place-items:center;background:#F6D9CB;color:#C2553A;font-size:19px;font-weight:800}' +
'.mbt-app span{display:block;max-width:100%;font-size:11.5px;font-weight:600;letter-spacing:-.2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}' +
'.mbt-app.off img,.mbt-app.off .rl{opacity:.45;filter:grayscale(.4)}' +
'.mbt-app.off i{position:absolute;top:-8px;left:calc(50% + 4px);font-style:normal;font-size:9px;font-weight:700;line-height:1;padding:3px 5px;border-radius:99px;background:#B9A9A2;color:#fff;white-space:nowrap}' +
'.mbt-empty{grid-column:1/-1;padding:30px 0;text-align:center;font-size:13px;color:#9C8F88}' +
'.mbt-toast{position:fixed;left:50%;bottom:calc(110px + env(safe-area-inset-bottom));z-index:70;max-width:80vw;padding:9px 14px;border-radius:12px;background:rgba(40,28,24,.86);color:#fff;font-size:13px;transform:translate(-50%,8px);opacity:0;transition:.25s;pointer-events:none}' +
'.mbt-toast.on{opacity:1;transform:translate(-50%,0)}';
var IC = {
calc: '<svg viewBox="0 0 24 24" aria-hidden="true"><rect class="tf" x="5" y="3.5" width="14" height="17" rx="2.8"/><path class="tl" d="M8.6 7.6h6.8M8.8 12h.01M12 12h.01M15.2 12h.01M8.8 15.6h.01M12 15.6h.01M15.2 15.6h.01" stroke-width="2.2"/></svg>',
qa: '<svg viewBox="0 0 24 24" aria-hidden="true"><path class="tf" d="M4 6.6A2.6 2.6 0 0 1 6.6 4h10.8A2.6 2.6 0 0 1 20 6.6v7.8a2.6 2.6 0 0 1-2.6 2.6H11l-4.2 3.2c-.4.3-.9 0-.9-.4V17A2.6 2.6 0 0 1 4 14.4z"/><path class="tl" d="M10.2 8.9a1.9 1.9 0 1 1 2.6 1.8c-.5.2-.8.6-.8 1.1v.4M12 14.2h.01"/></svg>'
};
var HOME = '<svg class="ts" viewBox="0 0 24 24" aria-hidden="true"><path class="tf" d="M4 10.4 12 4l8 6.4V18a2.4 2.4 0 0 1-2.4 2.4H6.4A2.4 2.4 0 0 1 4 18z"/><path class="tl" d="M9.8 20.2v-4.4a2.2 2.2 0 0 1 4.4 0v4.4"/></svg>';
function param(n) {
var m = location.search.match(new RegExp('[?&]' + n + '=([^&]*)')) || location.hash.match(new RegExp('[#&]' + n + '=([^&]*)'));
try { return m ? decodeURIComponent(m[1]) : ''; } catch (e) { return ''; }
}
function haptic() { try { navigator.vibrate && navigator.vibrate(8); } catch (e) {} }
function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]; }); }
var realAv = function (u) { return !!u && !/\/uploads\/system\//.test(u); };
function defAv(oid) { var h = 0, s = String(oid || ''); for (var i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) >>> 0; var n = (h % 20) + 1; return 'https://mp.yizhezhe.com/_bridge/avatars/mom-' + (n < 10 ? '0' : '') + n + '.png'; }
function init(o) {
o = o || {};
var root = o.root || '../../', mount = o.mount || document.body;
if (!document.getElementById('mbtCss')) { var st = document.createElement('style'); st.id = 'mbtCss'; st.textContent = CSS; document.head.appendChild(st); }
var fade = document.createElement('div'); fade.className = 'mbt-fade'; fade.setAttribute('aria-hidden', 'true');
var bar = document.createElement('nav'); bar.className = 'mbt';
var f = o.first || { t: '本页', ic: 'qa' };
bar.innerHTML = '<button type="button" class="mbt-t on" data-k="first"><span class="mbt-i">' + (IC[f.ic] || IC.qa) + '</span><span class="mbt-x">' + esc(f.t) + '</span></button>' +
'<button type="button" class="mbt-t mbt-fab" data-k="rec"><span class="fab"><b class="mbt-w" aria-hidden="true"><i>记</i><em>一下</em><s>!</s></b></span><span class="mbt-x">记一下</span></button>' +
'<button type="button" class="mbt-t mbt-home" data-k="home"><span class="mbt-i">' + HOME + '</span><span class="mbt-x">返回首页</span></button>';
mount.appendChild(fade); mount.appendChild(bar);
var OPENID = param('openid');
function homeURL() {
var q = [], oid = param('openid'), uid = param('unionid');
if (oid) q.push('openid=' + encodeURIComponent(oid));
if (uid) q.push('unionid=' + encodeURIComponent(uid));
return root + (q.length ? '#' + q.join('&') : location.hash);
}
function goApp(url) {
try { var u = new URL(url, location.href); if (u.origin === location.origin || /(^|\.)yizhezhe\.com$/.test(u.hostname)) url = u.pathname + u.search; } catch (e) {}
var q = [], oid = param('openid'), uid = param('unionid');
if (oid) q.push('openid=' + encodeURIComponent(oid));
if (uid) q.push('unionid=' + encodeURIComponent(uid));
if (!oid && !uid) q.push('__no_identity=1');
var target = url + (url.indexOf('?') >= 0 ? '&' : '?') + q.join('&');
try {
if (oid) {
var api = '/api/mp/public/last-app?openid=' + encodeURIComponent(oid) + '&url=' + encodeURIComponent(location.origin + url);
if (!(navigator.sendBeacon && navigator.sendBeacon(api)) && window.fetch) fetch(api, { method: 'POST', keepalive: true }).catch(function () {});
}
if (window.wxAuth && wxAuth.reportLocation) wxAuth.reportLocation({ href: target });
} catch (e) {}
/* 11-09:小程序里开容器页,「复制链接」才能直达这个应用(同 js/app.js openInHost,url 只放净地址) */
if (window.MBHost && MBHost.open(url, function () { location.href = target; })) return;
var mp = window.wx && window.wx.miniProgram;
if ((window.__wxjs_environment === 'miniprogram' || /miniProgram/i.test(navigator.userAgent)) && mp && typeof mp.navigateTo === 'function') {
try {
var c = new URL(url, location.origin);
['openid', 'unionid', '__no_identity', 'return_url'].forEach(function (k) { c.searchParams.delete(k); });
mp.navigateTo({ url: '/pages/annex/web_view/index?url=' + encodeURIComponent(c.href), fail: function () { location.href = target; } });
return;
} catch (e) {}
}
location.href = target;
}
var tt;
function toast(s) {
var t = document.querySelector('.mbt-toast');
if (!t) { t = document.createElement('div'); t.className = 'mbt-toast'; document.body.appendChild(t); }
t.textContent = s; t.classList.add('on'); clearTimeout(tt); tt = setTimeout(function () { t.classList.remove('on'); }, 2200);
}
/* 「记一下」:后台 App 表已上架、category=孕期工具(同首页 / 广场);数胎动 16 / 数宫缩 20 / 胎儿估重 15 周前灰掉 */
var GATE = { taidong: [16, '数胎动'], gongsuopinlv: [20, '数宫缩'], taierguzhong: [15, '胎儿估重'] }, apps = null, busy = false, mask, sh;
var myWeek = (function () { try { var w = JSON.parse(localStorage.getItem('mmbox.gcwk.v1') || 'null'); return w && !w.pp && w.w ? +w.w : 0; } catch (e) { return 0; } })();
function locked(a) { var x = GATE[a.identifier]; return x && myWeek && myWeek < x[0] ? x : null; }
function appsHTML() {
if (!apps) return '<div class="mbt-empty">加载中…</div>';
if (!apps.length) return '<div class="mbt-empty">暂时没取到工具,稍后再试</div>';
return apps.filter(function (a) { return !locked(a); }).concat(apps.filter(locked)).map(function (a) {
var x = locked(a), nm = a.name || a.identifier || '工具', ic = a.logo || a.icon;
return '<button type="button" class="mbt-app' + (x ? ' off' : '') + '" data-rid="' + esc(a.identifier || '') + '">' +
(ic ? '<img src="' + esc(ic) + '" alt="">' : '<b class="rl">' + esc(nm.slice(0, 1)) + '</b>') + '<span>' + esc(nm) + '</span>' + (x ? '<i>孕' + x[0] + '周开放</i>' : '') + '</button>';
}).join('');
}
function load() {
if (apps || busy) return; busy = true;
fetch('/api/mp/apps?isPublished=1&pageSize=200').then(function (r) { return r.ok ? r.json() : null; }).then(function (d) {
apps = ((d && d.result && d.result.items) || []).filter(function (a) { return a.url && a.category === '孕期工具'; })
.sort(function (x, y) { return (y.seq || 0) - (x.seq || 0) || (y.no || 0) - (x.no || 0); });
}).catch(function () { apps = []; }).then(function () { busy = false; var b = sh && sh.querySelector('.mbt-apps'); if (b) b.innerHTML = appsHTML(); });
}
function closeRec() { if (sh) { sh.classList.remove('on'); mask.classList.remove('on'); } }
function openRec() {
load();
if (!sh) {
mask = document.createElement('div'); mask.className = 'mbt-mask';
sh = document.createElement('div'); sh.className = 'mbt-sh'; sh.setAttribute('role', 'dialog');
document.body.appendChild(mask); document.body.appendChild(sh);
mask.addEventListener('click', closeRec);
sh.addEventListener('click', function (e) {
if (e.target.closest('.mbt-rx')) { closeRec(); return; }
var b = e.target.closest('[data-rid]'); if (!b || !apps) return;
var a = apps.filter(function (x) { return (x.identifier || '') === b.dataset.rid; })[0]; if (!a) return;
var x = locked(a);
if (x) { haptic(); toast(x[1] + '孕 ' + x[0] + ' 周以后开放,你现在孕 ' + myWeek + ' 周,还用不到哦'); return; }
haptic(); goApp(a.url);
});
}
sh.innerHTML = '<div class="mbt-rh"><b>记一下</b><span>兜爸心选的孕期工具</span><button type="button" class="mbt-rx" aria-label="关闭">×</button></div><div class="mbt-apps">' + appsHTML() + '</div>';
requestAnimationFrame(function () { mask.classList.add('on'); sh.classList.add('on'); });
}
bar.addEventListener('click', function (e) {
var b = e.target.closest('.mbt-t'); if (!b) return;
haptic();
var k = b.dataset.k;
if (k === 'first') { if (o.onFirst) o.onFirst(); else { var s = o.scroller || document.getElementById('scroll') || document.scrollingElement; s && s.scrollTo({ top: 0, behavior: 'smooth' }); } }
else if (k === 'rec') { if (o.onRec) o.onRec(); else openRec(); }
else if (o.onHome) o.onHome(); else location.href = homeURL();
});
/* 返回首页的头像(同广场):user-center/info 真头像 → hash 里的 avatar → 按 openid 固定默认头像;加载失败露回小房子 */
var ico = bar.querySelector('.mbt-home .mbt-i');
function paintAv(url) {
if (!url) return;
var im = ico.querySelector('.mbt-av');
if (!im) {
im = document.createElement('img'); im.className = 'mbt-av'; im.alt = ''; ico.appendChild(im);
ico.insertAdjacentHTML('beforeend', '<i class="mbt-hm" aria-hidden="true"><svg viewBox="0 0 24 24" fill="#fff"><path d="M12 4 3.5 11H6v8.5h4.5v-5h3v5H18V11h2.5z"/></svg></i>');
}
im.onload = function () { ico.classList.add('has-av'); };
im.onerror = function () { if (im.getAttribute('src') !== defAv(OPENID)) im.src = defAv(OPENID); else ico.classList.remove('has-av'); };
if (im.getAttribute('src') !== url) im.src = url;
}
function loadAv() {
if (!OPENID || !window.fetch) return;
fetch('/api/mp/public/user-center/info?openid=' + encodeURIComponent(OPENID) + (param('unionid') ? '&unionid=' + encodeURIComponent(param('unionid')) : ''))
.then(function (r) { return r.ok ? r.json() : null; }).then(function (d) {
var av = d && d.result && d.result.found && d.result.data && d.result.data.avatar;
if (realAv(av)) paintAv(av);
}).catch(function () {});
}
if (OPENID) { paintAv(realAv(param('avatar')) ? param('avatar') : defAv(OPENID)); loadAv(); }
window.addEventListener('pageshow', function (e) { if (e.persisted) loadAv(); });
return { bar: bar, fade: fade, openRec: openRec, closeRec: closeRec };
}
g.MBTab = { init: init };
})(window);