Files
wx-h5-demo/index.html

369 lines
20 KiB
HTML
Raw 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.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<meta name="theme-color" content="#F4593C">
<title>wx-h5-demo · 共享授权规范示例</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; -webkit-tap-highlight-color:transparent; }
body { font-family:-apple-system,"PingFang SC","Microsoft YaHei",sans-serif; background:#F8F3EF; color:#2B2320; min-height:100vh; display:flex; justify-content:center; }
.wrap { width:100%; max-width:430px; padding:24px 20px 40px; }
.brand { font-size:22px; font-weight:800; color:#F4593C; } .brand small{color:#A79C95;font-size:13px;font-weight:500;margin-left:8px}
.env { margin:14px 0 8px; font-size:13px; color:#6B615B; } .env b{color:#2B2320}
.tag { display:inline-block; padding:2px 8px; border-radius:8px; font-size:12px; }
.tag.ok{background:#E8F2EB;color:#5F8A4A} .tag.no{background:#F6DCD6;color:#C4553A}
.card { background:#fff; border:1px solid #EFE7E1; border-radius:16px; padding:16px; margin-top:14px; }
.card h2{font-size:15px;font-weight:800;margin-bottom:10px}
.row{display:flex;justify-content:space-between;align-items:center;padding:10px 0;border-bottom:1px dashed #EFE7E1;font-size:14px}
.row:last-child{border-bottom:none} .row .k{color:#6B615B} .row .v{font-weight:600} .row .v.placeholder{color:#C9B7AC;font-weight:500}
.avatar{width:44px;height:44px;border-radius:50%;background:#F1E6E0;overflow:hidden;display:flex;align-items:center;justify-content:center;font-size:18px}
.avatar img{width:100%;height:100%;object-fit:cover}
.btn{display:block;width:100%;padding:15px;border:none;border-radius:14px;font-size:15px;font-weight:700;margin-top:14px;cursor:pointer}
.btn.main{background:#F4593C;color:#fff} .btn.ghost{background:#fff;color:#6B615B;border:1px solid #EFE7E1}
.hint{font-size:12px;color:#A79C95;margin-top:12px;line-height:1.6}
.env-hint{font-size:12.5px;color:#C4553A;background:#FBF0EE;border:1px solid #F0D8D2;border-radius:10px;padding:10px 12px;margin-top:12px;line-height:1.6}
#toast{position:fixed;left:50%;top:14%;transform:translateX(-50%);background:rgba(30,22,18,.9);color:#fff;padding:10px 18px;border-radius:10px;font-size:13px;opacity:0;pointer-events:none;transition:opacity .25s;z-index:99}
#toast.show{opacity:1}
/* ==== 调试日志面板 ==== */
#dbgToggle{position:fixed;right:12px;bottom:12px;z-index:200;background:#2B2320;color:#fff;border:none;border-radius:20px;padding:9px 14px;font-size:13px;font-weight:700;box-shadow:0 4px 14px rgba(0,0,0,.25);cursor:pointer;opacity:.92}
#dbgPanel{position:fixed;right:0;bottom:0;left:0;z-index:210;background:#1A1512;color:#E7DAD1;font-family:Menlo,Consolas,monospace;font-size:12px;line-height:1.5;border-top:2px solid #F4593C;transform:translateY(100%);transition:transform .25s;max-height:62vh;display:flex;flex-direction:column}
#dbgPanel.open{transform:translateY(0)}
#dbgHead{display:flex;justify-content:space-between;align-items:center;padding:9px 12px;background:#241D18;border-bottom:1px solid #3A2F28}
#dbgHead b{font-size:13px;color:#F4593C}
#dbgHead span{font-size:11px;color:#A79C95}
#dbgHead button{margin-left:8px;background:none;border:1px solid #4A3D34;color:#E7DAD1;border-radius:6px;padding:3px 10px;font-size:12px;cursor:pointer}
#dbgLog{overflow-y:auto;padding:8px 12px;flex:1;word-break:break-all}
#dbgLog .ln{margin:3px 0;padding:3px 6px;border-left:3px solid #4A3D34;color:#CBBDB2}
#dbgLog .ln.err{border-left-color:#F4593C;color:#FF9E86;background:rgba(244,89,60,.08)}
#dbgLog .ln.ok{border-left-color:#5F8A4A;color:#9BC48A}
#dbgLog .ln.warn{border-left-color:#E0B33E;color:#EED49A}
#dbgLog .ln.muted{color:#8B7E75;border-left-color:#3A2F28}
#dbgLog .ln b{color:#fff;font-weight:700}
</style>
</head>
<body>
<div class="wrap">
<div class="brand">wx-h5-demo <small>共享授权规范示例</small></div>
<div class="env">环境:<b id="envTxt">检测中…</b> <span id="envTag" class="tag no">纯浏览器</span></div>
<div class="card">
<h2>授权状态</h2>
<p id="authState" style="font-size:13px;color:#6B615B;line-height:1.6">检测中…</p>
<button class="btn main" id="btnAuth" style="display:none">授权登录</button>
<button class="btn ghost" id="btnMall">进商城</button>
<p id="envHint" class="env-hint" style="display:none">⚠️ 当前不是小程序 webview,无法完成授权登录。请在<strong>微信小程序</strong>里打开本页后使用(浏览器/微信内直开仅能查看)。</p>
</div>
<div class="card">
<h2>授权结果(宿主页回传后读取 #phone/nickname/avatar)</h2>
<div class="row"><span class="k">手机号</span><span class="v placeholder" id="rPhone">—</span></div>
<div class="row"><span class="k">昵称</span><span class="v placeholder" id="rNick">—</span></div>
<div class="row"><span class="k">头像</span><span class="v"><span class="avatar" id="rAvatarWrap"><span id="rAvatarText">宝</span></span></span></div>
</div>
<div class="card">
<h2>场景3 · 分享深链验证(web-view 内 H5 分享)</h2>
<div class="row"><span class="k">当前页面</span><span class="v" id="shCur" style="word-break:break-all;text-align:right;max-width:62%">—</span></div>
<div class="row"><span class="k">深链标记</span><span class="v" id="shMark">(未设置)</span></div>
<button class="btn ghost" id="btnShMark">① 生成唯一深链标记(写入 hash 路由)</button>
<button class="btn main" id="btnShOk">② ✅ 正确分享:只 postMessage(本页不跳)</button>
<button class="btn ghost" id="btnShBad" style="color:#C4553A;border-color:#F0D8D2">③ ❌ 复现旧 bug:postMessage + navigateBack</button>
<p class="hint">
<b>判定标准</b>:点 ② 后本页<b>停留不动</b>,再点右上角「···」转发;接收者点开卡片,
打开的应是上面「当前页面」那个地址(含 hash 标记),<b>而不是小程序首页</b>。<br>
点 ③ 会立即退回上一页(旧版 <code>bridge.share()</code> 的 <code>navigateBack</code> 副作用)——
此时再点「···」转发,卡片会由上一页产出,接收者落到上一页:即线上 bug 的复现。<br>
⚠️ 仅在<b>小程序 web-view</b> 内有效(宿主页需按 <code>onShareAppMessage(options).webViewUrl</code> 拼深链,
见 <code>miniapp/scenario3-share-webview.js</code> / CRMEB <code>pages/annex/web_view</code>)。
微信浏览器/纯浏览器只能看 UI。
</p>
</div>
<p class="hint">
本页演示<b>共享授权规范</b>:<code>wx-auth.js</code>(<code>wxAuth.init/requestProfile/goMall/readAuth/isAuthorized</code>)。
授权流程:点「授权登录」→ <code>wx.miniProgram.navigateTo('/pages/annex/auth/index')</code> →
小程序端独立授权页(头像/昵称/手机号,非 web-view 原生组件页)→ <code>POST /api/mp/public/auth/profile</code> 落库共享 UserAuth →
授权返回后宿主页更新 <code>web-view src</code>(hash 带结果)→ 本页重载 → <code>wxAuth.readAuth()</code> 显示。
「进商城」→ <code>wx.miniProgram.switchTab('/pages/goods_cate/goods_cate')</code>。
</p>
<div id="ver" style="text-align:center;color:#B3A69C;font-size:11px;margin-top:22px;padding-bottom:8px">v<span id="verNo"></span></div>
</div>
<div id="toast"></div>
<!-- 调试日志面板 -->
<button id="dbgToggle">🐛 调试日志</button>
<div id="dbgPanel">
<div id="dbgHead">
<b>调试日志</b>
<span id="dbgCount"></span>
<span>
<button id="dbgCopy">复制</button>
<button id="dbgClear">清空</button>
<button id="dbgClose">收起</button>
</span>
</div>
<div id="dbgLog"></div>
</div>
<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
<script src="/mama-box/js/wx-auth.js?v=20260908"></script>
<script>
(function () {
'use strict';
/* ============================================================
① 页面版本号(YYMMDD + 两位序号)—— 用于排查 webview 是否缓存了旧页面
============================================================ */
var VER = 26091001;
var verNoEl = el('verNo');
if (verNoEl) verNoEl.textContent = VER;
/* ============================================================
② 日志面板(真机上 console/alert 看不到,用可视化面板)
============================================================ */
var dbgCount = 0;
function el(id) { return document.getElementById(id); }
function dbg(msg, cls) {
dbgCount++;
var logBox = el('dbgLog');
var ln = document.createElement('div');
ln.className = 'ln' + (cls ? ' ' + cls : '');
var t = new Date().toTimeString().replace(/.*(\d\d:\d\d:\d\d).*/, '$1');
ln.innerHTML = '<b>' + t + '</b> ' + msg;
logBox.appendChild(ln);
logBox.scrollTop = logBox.scrollHeight;
el('dbgCount').textContent = '共 ' + dbgCount + ' 条';
try { console.log('[dbg]', msg); } catch (e) {}
}
function dbgErr(msg) { dbg('❌ ' + msg, 'err'); }
window.__dbg = dbg;
// 全局错误捕获
window.addEventListener('error', function (e) {
dbgErr('window.onerror: ' + (e.message || '未知') + ' @ ' + (e.filename || '') + ':' + (e.lineno || '') + ':' + (e.colno || ''));
});
window.addEventListener('unhandledrejection', function (e) {
var r = e.reason;
dbgErr('unhandledrejection: ' + (r && (r.message || r.stack) ? (r.message || r.stack) : r));
});
// 面板开关
el('dbgToggle').addEventListener('click', function () { el('dbgPanel').classList.toggle('open'); });
el('dbgClose').addEventListener('click', function () { el('dbgPanel').classList.remove('open'); });
el('dbgClear').addEventListener('click', function () { el('dbgLog').innerHTML = ''; dbgCount = 0; el('dbgCount').textContent = ''; });
// 复制日志:隐藏 textarea + execCommand('copy'),微信 webview 通用做法
el('dbgCopy').addEventListener('click', function () {
var text = '';
var lns = el('dbgLog').querySelectorAll('.ln');
for (var i = 0; i < lns.length; i++) {
var t = (lns[i].querySelector('b') ? lns[i].querySelector('b').textContent : '');
text += t + ' ' + lns[i].textContent.replace(t, '').trim() + '\n';
}
text = '[wx-h5-demo 调试日志 v' + VER + ']\n' + text;
function ok() { dbg('已复制 ' + lns.length + ' 条日志到剪贴板', 'ok'); }
try {
var ta = document.createElement('textarea');
ta.value = text;
ta.style.cssText = 'position:fixed;top:0;left:0;opacity:0;height:1px;width:1px;overflow:hidden';
document.body.appendChild(ta);
ta.select();
var did = document.execCommand('copy');
document.body.removeChild(ta);
if (did) ok(); else dbgErr('复制失败(execCommand 返回 false)');
} catch (e) {
dbgErr('复制失败: ' + (e && e.message ? e.message : e));
}
});
function show(tag, title) {
el('envTag').textContent = title; el('envTag').className = 'tag ' + (tag === 'ok' ? 'ok' : 'no');
}
/* ============================================================
② jweixin / wx.miniProgram 环境检测(先记录关键前提)
============================================================ */
dbg('脚本初始化开始');
dbg('jweixin 已加载: window.wx = ' + (typeof window.wx !== 'undefined' ? '✅ 存在' : '❌ 不存在(CDN 加载失败或未进入微信环境)'));
dbg('window.wx.miniProgram = ' + ((window.wx && window.wx.miniProgram) ? '✅ 存在' : '❌ 不存在'), 'muted');
dbg('navigator.userAgent 含 MicroMessenger: ' + (/MicroMessenger/i.test(navigator.userAgent) ? '✅ 是' : '❌ 否'), 'muted');
dbg('location.hash = ' + (location.hash || '(空)') + ' location.search = ' + (location.search || '(空)'), 'muted');
// 给 navigateTo 打补丁:记录每一次跳转调用 + 捕获异常 + 转发 fail/success
// (navigateTo 跳不存在的页面 / 页面栈超 10 层时是静默失败,必须靠 fail 才能看到原因)
if (window.wx && window.wx.miniProgram && window.wx.miniProgram.navigateTo) {
var _nav = window.wx.miniProgram.navigateTo.bind(window.wx.miniProgram);
window.wx.miniProgram.navigateTo = function (o) {
var opt = o || {};
dbg('navigateTo 调用 → ' + opt.url, 'ok');
var _fail = opt.fail, _succ = opt.success;
opt.fail = function (err) {
dbgErr('navigateTo fail: ' + ((err && err.errMsg) || '未知原因'));
if (_fail) { try { _fail(err); } catch (e) {} }
};
opt.success = function (res) {
dbg('navigateTo success', 'ok');
if (_succ) { try { _succ(res); } catch (e) {} }
};
try { _nav(opt); dbg('navigateTo 已调用(结果见 success/fail)', 'ok'); }
catch (ex) { dbgErr('navigateTo 抛异常: ' + (ex && ex.message ? ex.message : ex)); }
};
}
// 给 switchTab 打补丁:记录每一次跳转调用 + 捕获异常 + 转发 fail/success
if (window.wx && window.wx.miniProgram && window.wx.miniProgram.switchTab) {
var _st = window.wx.miniProgram.switchTab.bind(window.wx.miniProgram);
window.wx.miniProgram.switchTab = function (o) {
var opt = o || {};
dbg('switchTab 调用 → ' + opt.url, 'ok');
var _fail = opt.fail, _succ = opt.success;
opt.fail = function (err) {
dbgErr('switchTab fail: ' + ((err && err.errMsg) || '未知原因'));
if (_fail) { try { _fail(err); } catch (e) {} }
};
opt.success = function (res) {
dbg('switchTab success', 'ok');
if (_succ) { try { _succ(res); } catch (e) {} }
};
try { _st(opt); dbg('switchTab 已调用(结果见 success/fail)', 'ok'); }
catch (ex) { dbgErr('switchTab 抛异常: ' + (ex && ex.message ? ex.message : ex)); }
};
}
// 给 navigateBack 打补丁:旧版 bridge.share 用它「触发 postMessage 投递」,
// 副作用是把 web-view 页弹掉、转发被上一页接管(场景3 要复现的 bug)。
if (window.wx && window.wx.miniProgram && window.wx.miniProgram.navigateBack) {
var _back = window.wx.miniProgram.navigateBack.bind(window.wx.miniProgram);
window.wx.miniProgram.navigateBack = function (o) {
var opt = o || {};
dbg('navigateBack 调用 → delta=' + (opt.delta || 1), 'warn');
var _bfail = opt.fail, _bsucc = opt.success;
opt.fail = function (err) {
dbgErr('navigateBack fail: ' + ((err && err.errMsg) || '未知原因'));
if (_bfail) { try { _bfail(err); } catch (e) {} }
};
opt.success = function (res) {
dbg('navigateBack success(已退回上一页)', 'warn');
if (_bsucc) { try { _bsucc(res); } catch (e) {} }
};
try { _back(opt); } catch (ex) { dbgErr('navigateBack 抛异常: ' + (ex && ex.message ? ex.message : ex)); }
};
}
// 给 postMessage 打补丁:记录每一次调用 + 捕获异常
var origPost = (window.wx && window.wx.miniProgram && window.wx.miniProgram.postMessage) ? window.wx.miniProgram.postMessage.bind(window.wx.miniProgram) : null;
if (window.wx && window.wx.miniProgram) {
window.wx.miniProgram.postMessage = function (msg) {
dbg('postMessage 调用 → ' + JSON.stringify(msg), 'ok');
if (!origPost) { dbgErr('postMessage: 无原始方法,无法发送'); return; }
try { origPost(msg); dbg('postMessage 已发送', 'ok'); }
catch (ex) { dbgErr('postMessage 抛异常: ' + (ex && ex.message ? ex.message : ex)); }
};
} else {
dbg('未安装 postMessage 补丁(wx.miniProgram 不存在)', 'warn');
}
/* ============================================================
③ 页面数据渲染
============================================================ */
function render(a) {
var authed = a.isAuthorized();
dbg('render(): isAuthorized=' + authed + ' readAuth=' + JSON.stringify(a.readAuth()), 'muted');
el('authState').textContent = authed ? '✅ 已授权(昵称/头像/手机号)' : '🔓 未授权,点下方「授权登录」';
el('btnAuth').style.display = authed ? 'none' : 'block';
el('rPhone').textContent = a.readAuth().phone || '—';
el('rNick').textContent = a.readAuth().nickname || '—';
if (a.readAuth().avatar) { el('rAvatarWrap').innerHTML = '<img src="' + a.readAuth().avatar + '" alt="头像">'; }
}
/* ============================================================
④-b 场景3:分享深链验证
------------------------------------------------------------
规则:web-view 里的 H5 **不能**自己拉起分享面板,卡片只能由宿主小程序页
的 onShareAppMessage 产出。正确做法是 H5 只发 postMessage(传标题/图),
宿主用 options.webViewUrl(当前 web-view 真实地址,含 hash/SPA 路由)拼深链。
❌ 旧做法:postMessage 后调 navigateBack「触发投递」——会把 web-view 页弹掉,
转发被上一页接管,接收者落到首页(本页可复现)。
============================================================ */
function shRefresh() {
if (!el('shCur')) return;
el('shCur').textContent = location.href;
var m = (location.hash || '').replace(/^#\/?/, '');
el('shMark').textContent = m || '(未设置)';
}
function shMakeMarker() {
var n = Date.now().toString(36);
location.hash = '#/share-probe/' + n; // 模拟 H5 应用的 hash 深链(webview 不丢 hash)
shRefresh();
dbg('已写入深链标记 → #/share-probe/' + n, 'ok');
wxAuth.toast('深链标记:' + n);
}
function shRequest(useNavigateBack) {
var mp = window.wx && window.wx.miniProgram;
if (!mp || !mp.postMessage) {
dbgErr('不在小程序 web-view(无 wx.miniProgram),无法分享');
wxAuth.toast('请在小程序 web-view 中打开本页');
return;
}
mp.postMessage({ data: {
action: 'share',
title: 'wx-h5-demo · 分享深链验证',
href: location.href,
summary: '接收者应打开本页(含 hash 标记),而不是小程序首页',
imageUrl: 'https://mp.yizhezhe.com/mama-box/assets/resources/report.png'
} });
if (useNavigateBack) {
dbg('❌ 复现旧 bug:postMessage 后调用 navigateBack({delta:1}),会退出本 web-view 页', 'warn');
mp.navigateBack({ delta: 1, fail: function (err) { dbgErr('navigateBack fail: ' + ((err && err.errMsg) || '未知原因')); } });
} else {
dbg('✅ 已请求分享(本页未跳)。请点右上角「···」→ 转发;卡片链接应为:' + location.href, 'ok');
wxAuth.toast('请点右上角「···」→ 转发给朋友');
}
}
/* ============================================================
④ 初始化:环境检测 + 读取授权
============================================================ */
function init() {
wxAuth.init(function (a) {
var envTxt = a.inWebview ? '小程序 web-view' : (a.MODE === 'wechat-browser' ? '微信浏览器' : '纯浏览器');
dbg('init 回调: envTxt=' + envTxt + ' MODE=' + a.MODE + ' inWebview=' + a.inWebview, 'ok');
el('envTxt').textContent = envTxt;
show(a.inWebview ? 'ok' : 'no', envTxt); // 修:之前 show() 从未被调用,标签永远停在初始「纯浏览器」
el('envHint').style.display = a.inWebview ? 'none' : 'block';
render(a);
});
}
document.addEventListener('DOMContentLoaded', function () {
init();
// 场景3:分享深链验证
shRefresh();
window.addEventListener('hashchange', shRefresh);
el('btnShMark').addEventListener('click', function () { dbg('🖱️ 点击「生成深链标记」'); shMakeMarker(); });
el('btnShOk').addEventListener('click', function () { dbg('🖱️ 点击「✅ 正确分享」'); shRequest(false); });
el('btnShBad').addEventListener('click', function () { dbg('🖱️ 点击「❌ 复现旧 bug」'); shRequest(true); });
el('btnAuth').addEventListener('click', function () {
dbg('🖱️ 点击「授权登录」按钮');
if (!(window.wx && window.wx.miniProgram)) {
dbgErr('requestProfile 被拒绝:window.wx.miniProgram 不存在(不在微信 web-view 或 jweixin 未加载)');
} else {
dbg('requestProfile: UA 含 MicroMessenger = ' + (/MicroMessenger/i.test(navigator.userAgent) ? '✅ 是' : '❌ 否') + '(否→秒提示;是→getEnv 确认后跳转)', 'ok');
}
wxAuth.requestProfile();
});
el('btnMall').addEventListener('click', function () {
dbg('🖱️ 点击「进商城」按钮');
wxAuth.goMall();
});
});
})();
</script>
</body>
</html>