140 lines
8.0 KiB
JavaScript
140 lines
8.0 KiB
JavaScript
/* ============================================================
|
||
host-nav.js · 小程序里换页开「容器页」(2026-11-09 用户)
|
||
------------------------------------------------------------
|
||
「在任何一个子页面点小程序『复制链接』,打开应该进到那个子页面,现在都进首页」——
|
||
「复制链接」复制的是**小程序当前页面**的路径和参数。首页壳里 location.href 换页,小程序页面一直是首页壳,
|
||
复制出来永远是首页。所以在小程序里进子页面 / 应用 / 广场笔记时,navigateTo 通用容器
|
||
pages/annex/web_view/index?url=<净地址>(分享卡片落地用的同一个页,容器按打开者自己的身份注入),
|
||
小程序当前页就是那一页。
|
||
|
||
MBHost.open(url, fallback) 小程序里开容器页;不在小程序 → false(调用方照旧 location.href);
|
||
navigateTo 失败(页面栈满 10 层等)→ 调 fallback,没给就 location.href = url
|
||
MBHost.back(fallback) 本页是 open() 开出来的容器页(地址带 _hv=1)→ navigateBack 回上一页,返回 true;
|
||
后退失败(复制链接直接打开、没有上一页)→ 调 fallback
|
||
MBHost.home(fallback) 同上条件 → switchTab 回小程序首页(首页壳是 tabBar 页,原来停的位置还在)
|
||
⚠️ url 只放净地址:去掉 openid/unionid/__no_identity/return_url 和 hash 里的身份,复制出去不带分享人身份。
|
||
⚠️ 加 _hv=1 只为让页面知道「我在容器页里」,返回时用 navigateBack,不用在容器里再开一个首页。
|
||
11-09 二修(用户:「复制链接打开笔记,点左上角返回回不到首页」):复制链接 / 分享卡片直接打开的容器页没有上一页,
|
||
navigateBack 不动。open() 换页前在本机记一笔 mbhost.open,新容器页加载时对上了 = 「从上一页点进来的」,
|
||
本页 sessionStorage 记 mbhost.child;back() 只有 child 才 navigateBack,否则 switchTab 回小程序首页。
|
||
MBHost.onLeave:open() 跳走前调一下(首页用来记滚动位置,宿主回前台重载首页后回到原位置)。
|
||
============================================================ */
|
||
(function (g) {
|
||
'use strict';
|
||
if (g.MBHost) return;
|
||
var VIEW = '/pages/annex/web_view/index';
|
||
var at = 0;
|
||
function mp() { return g.wx && g.wx.miniProgram; }
|
||
function inMp() { return g.__wxjs_environment === 'miniprogram' || /miniProgram/i.test(navigator.userAgent); }
|
||
function inView() { return /[?&]_hv=1(&|$)/.test(location.search); }
|
||
var OK = 'mbhost.open', CK = 'mbhost.child', POS = 'mmbox.homepos.v1'; /* POS:首页跳走前记的滚动位置(mama-box app.js saveHomePos) */
|
||
function key(href) { /* 比对用:去掉容器注入的身份 / 时间戳参数 */
|
||
var u = new URL(href, location.href);
|
||
['openid', 'unionid', 't', '_v', '__no_identity'].forEach(function (k) { u.searchParams.delete(k); });
|
||
u.searchParams.sort && u.searchParams.sort();
|
||
return u.pathname + u.search;
|
||
}
|
||
/* 11-09 产后恢复会话报:接了 maternal-bridge 的应用里 window.localStorage 被换成云端镜像(内存 Map),
|
||
标记写进镜像、新容器页读不到 → 误判成「直接打开」。这里只用浏览器真实的 localStorage:
|
||
加载时是原生的就抓住;已被换掉就从同源空白 iframe 里拿一份;标记另外再写一份 cookie 兜底。 */
|
||
var nat = null;
|
||
function isNative(x) { try { return !!x && Object.prototype.toString.call(x) === '[object Storage]'; } catch (e) { return false; } }
|
||
try { if (isNative(g.localStorage)) nat = g.localStorage; } catch (e) {}
|
||
function LS() {
|
||
if (nat) return nat;
|
||
try {
|
||
var f = document.createElement('iframe');
|
||
f.style.display = 'none'; f.setAttribute('aria-hidden', 'true');
|
||
(document.body || document.documentElement).appendChild(f);
|
||
if (isNative(f.contentWindow.localStorage)) nat = f.contentWindow.localStorage;
|
||
} catch (e) {}
|
||
return nat;
|
||
}
|
||
function getMark() {
|
||
try { var l = LS(), v = l && l.getItem(OK); if (v) return JSON.parse(v); } catch (e) {}
|
||
try { var m = document.cookie.match(/(?:^|;\s*)mbhost_open=([^;]*)/); if (m) return JSON.parse(decodeURIComponent(m[1])); } catch (e) {}
|
||
return null;
|
||
}
|
||
function setMark(v) {
|
||
try { var l = LS(); if (l) l.setItem(OK, v); } catch (e) {}
|
||
try { document.cookie = 'mbhost_open=' + encodeURIComponent(v) + ';path=/;max-age=30;SameSite=Lax'; } catch (e) {}
|
||
}
|
||
function clearMark() {
|
||
try { var l = LS(); if (l) l.removeItem(OK); } catch (e) {}
|
||
try { document.cookie = 'mbhost_open=;path=/;max-age=0'; } catch (e) {}
|
||
}
|
||
function isChild() { try { return sessionStorage.getItem(CK) === '1'; } catch (e) { return false; } }
|
||
(function () {
|
||
if (!inView()) return;
|
||
try {
|
||
var m = getMark();
|
||
if (m && Date.now() - m.at < 30000 && key(m.u) === key(location.href)) { sessionStorage.setItem(CK, '1'); clearMark(); }
|
||
/* 11-09 用户:复制链接 / 分享直接打开的页面,返回首页就停在顶部,不要回到「自己上次点进笔记时」的首页位置 */
|
||
if (!isChild()) { var l = LS(); if (l) l.removeItem(POS); }
|
||
} catch (e) {}
|
||
})();
|
||
function toHome(fallback) {
|
||
var m = mp();
|
||
if (!m || typeof m.switchTab !== 'function') { if (fallback) fallback(); return; }
|
||
m.switchTab({ url: '/pages/index/index', fail: function () {
|
||
if (typeof m.reLaunch === 'function') m.reLaunch({ url: '/pages/index/index', fail: function () { if (fallback) fallback(); } });
|
||
else if (fallback) fallback();
|
||
} });
|
||
}
|
||
function clean(url) {
|
||
var u = new URL(url, location.href);
|
||
['openid', 'unionid', '__no_identity', 'return_url', 'norestore'].forEach(function (k) { u.searchParams.delete(k); });
|
||
if (/(^|[#&])(openid|unionid|phone|nickname|avatar|norestore)=/.test(u.hash)) u.hash = '';
|
||
u.searchParams.set('_hv', '1');
|
||
return u.href;
|
||
}
|
||
var H = {
|
||
inMp: inMp,
|
||
inView: inView,
|
||
isChild: isChild,
|
||
onLeave: null,
|
||
open: function (url, fallback) {
|
||
var m = mp();
|
||
if (!inMp() || !m || typeof m.navigateTo !== 'function') return false;
|
||
if (Date.now() - at < 1000) return true; // 连点:上一次 navigateTo 还在路上
|
||
var c;
|
||
try { c = clean(url); } catch (e) { return false; }
|
||
at = Date.now();
|
||
try { if (typeof H.onLeave === 'function') H.onLeave(); } catch (e) {}
|
||
setMark(JSON.stringify({ u: c, at: at }));
|
||
m.navigateTo({
|
||
url: VIEW + '?url=' + encodeURIComponent(c),
|
||
fail: function () { at = 0; if (fallback) fallback(); else location.href = url; }
|
||
});
|
||
return true;
|
||
},
|
||
back: function (fallback) {
|
||
var m = mp();
|
||
if (!inView() || !inMp() || !m || typeof m.navigateBack !== 'function') return false;
|
||
if (isChild()) m.navigateBack({ delta: 1, fail: function () { toHome(fallback); } });
|
||
else toHome(fallback); /* 复制链接 / 分享直接打开:没有上一页 → 回小程序首页 */
|
||
return true;
|
||
},
|
||
home: function (fallback) {
|
||
var m = mp();
|
||
if (!inView() || !inMp() || !m || typeof m.switchTab !== 'function') return false;
|
||
toHome(fallback);
|
||
return true;
|
||
}
|
||
};
|
||
g.MBHost = H;
|
||
|
||
/* 页面里的 <a href> 指向本站别的页面:小程序里也开容器页(别人已经 preventDefault 的不管) */
|
||
document.addEventListener('click', function (e) {
|
||
if (e.defaultPrevented || e.button || e.metaKey || e.ctrlKey || !inMp()) return;
|
||
var a = e.target && e.target.closest && e.target.closest('a[href]');
|
||
if (!a || a.target || a.hasAttribute('download')) return;
|
||
var href = a.getAttribute('href') || '';
|
||
if (!href || href.charAt(0) === '#' || /^(javascript|mailto|tel):/i.test(href)) return;
|
||
var u;
|
||
try { u = new URL(href, location.href); } catch (err) { return; }
|
||
if (u.origin !== location.origin || u.pathname === location.pathname) return;
|
||
if (H.open(u.href)) e.preventDefault();
|
||
});
|
||
})(window);
|