diff --git a/README.md b/README.md index 18a9599..3109a50 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ | --- | --- | --- | | **场景1** | 微信(浏览器)打开 H5 链接 → 跳**小程序授权页**授权 → 授权后小程序用 web-view 加载本 H5(结果在 URL)→ “回到 H5” | `miniapp/scenario1-auth-bridge.*`(授权桥页) | | **场景2** | 微信跳到**小程序 web-view 的 H5 应用** → H5 `postMessage` 请求授权 → 宿主弹原生授权面板 → 授权 → 更新 `web-view src`(带结果)→ H5 重载显示 | `miniapp/scenario2-host-page.*`(web-view 宿主页) | +| **场景3** | web-view 里的 H5 **分享给微信好友** → 接收者应打开**发送者当时那一页**(含 hash/SPA 深链),而不是小程序首页 | `miniapp/scenario3-share-webview.*`(✅ 正确宿主页)、`miniapp/scenario3-home-bug.*`(❌ 反面首页,复现 bug) | > 说明:场景2 的宿主页 `scenario2-host-page` 同时是场景1 授权后 redirectTo 的落地页(从 `globalData` 读结果拼 H5 url)。 @@ -15,10 +16,13 @@ ``` wx-h5-demo/ ├─ index.html # 示例 H5(自包含):环境检测(webview/微信浏览器/纯浏览器)、 -│ # 场景1 跳小程序授权 或 场景2 postMessage 授权、读回传参数展示。 +│ # 场景1 跳小程序授权 或 场景2 postMessage 授权、读回传参数展示、 +│ # 场景3 分享深链验证(正确分享 / 复现旧 bug 两个按钮)。 ├─ miniapp/ │ ├─ scenario1-auth-bridge.wxml/.js # 场景1 小程序授权桥页(launch-weapp/URL Scheme 进入) -│ └─ scenario2-host-page.wxml/.js # 场景2 web-view 宿主页(也作场景1 落地页) +│ ├─ scenario2-host-page.wxml/.js # 场景2 web-view 宿主页(也作场景1 落地页) +│ ├─ scenario3-share-webview.wxml/.js # 场景3 ✅ 正确:onShareAppMessage(options).webViewUrl 拼深链 +│ └─ scenario3-home-bug.wxml/.js # 场景3 ❌ 反面:写死 path 的「首页」,复现「分享跳首页」 └─ README.md ``` @@ -35,6 +39,39 @@ wx-h5-demo/ - **微信浏览器(非 web-view)**:给 `LAUNCH_SCHEME` 填一个**小程序 URL Scheme**(后端用 appid+path 换),或把页面放进公众号/网页用 ``(需 jsapi 签名,并在微信内打开)。 - 场景1 能真正走通的前提:**你的小程序里要有 `/pages/demo/auth`(= `miniapp/scenario1-auth-bridge.*`)和 `/pages/demo/webview`(=`scenario2-host-page.*`),并在 `app.json` 注册这两个页面**;否则 navigateTo 会「页面不存在」。 +## 验证分享深链(场景3)· 2026-09 + +> 症状:web-view 里的 H5 分享给微信好友后,接收者点开卡片落到**小程序首页**,不是对应的 H5 页面。 + +### 先记住平台约束 +- web-view 里的 H5 **不能自己拉起分享面板**;卡片只能由承载 `` 的**宿主小程序页**的 + `onShareAppMessage` 产出。 +- `onShareAppMessage(options)` 的 **`options.webViewUrl`** 才是当前 web-view 的真实地址 + (含 H5 的 hash / SPA 路由)—— 这是唯一可靠的深链来源。 +- `wx.miniProgram.postMessage` **不是实时投递**,只在「后退 / 组件销毁 / 分享」等时机批量派发, + 不能拿它的 `href` 当深链来源。 +- **禁止**在 H5 分享时调 `wx.miniProgram.navigateBack()`(旧版 `bridge.share` 为了“触发投递”这么干, + 它会把 web-view 页弹掉,转发被上一页接管 → 接收者进首页)。 + +### A. 在真机 CRMEB 小程序里验证(推荐,无需注册 demo 页) +1. 用 CRMEB 小程序打开:`/pages/annex/web_view/index?url=https%3A%2F%2Fmp.yizhezhe.com%2Fwx-h5-demo%2F` + (或在「我的应用 / 应用宫格」里点 `wx-h5-demo`)。 +2. H5 里点 ①「生成唯一深链标记」→ 记录页面地址里的 `#/share-probe/xxxx`。 +3. 点 ②「✅ 正确分享」→ 本页应**停留不动** → 点右上角「···」转发。 +4. 用另一个微信号点开卡片:应打开**同一个地址**(能看到相同 `#/share-probe/xxxx`),不是首页。 +5. 反向对照:重来一次,第 3 步改点 ③「❌ 复现旧 bug:postMessage + navigateBack」, + 会立刻退回上一页;此时再转发,接收者就落到那个上一页(bug 复现)。 + +### B. 用独立 demo 小程序页验证 +把 `miniapp/scenario3-home-bug.*`(路径 `/pages/demo/home`)和 +`miniapp/scenario3-share-webview.*`(路径 `/pages/demo/share-webview`)并入你的小程序并在 `app.json` 注册, +以 `/pages/demo/home` 为起点:本页 → 进 web-view → H5 点 ③ → 退回本页 → 转发(bug 复现); +改点 ② → 停留 web-view → 转发(接收者进 H5)。对照 `scenario3-share-webview.js` 与 +`scenario3-home-bug.js` 两个 `onShareAppMessage` 的差别即可。 + +> 线上真实实现:`CRMEB pages/annex/web_view/index.vue`(容器)与 `pages/index/index.vue`(首页壳), +> 见 `crmeb/docs/分享图片获取逻辑.md` 第四节。 + ## 注意 - 手机号:前端只拿 `code`,**明文必须后端** `wx.business.getuserphonenumber` 换(旧 `code2Session`+解密已收紧)。 - 头像:`e.detail.avatarUrl` 是临时 URL,需 `wx.uploadFile` 上传存后端,回传正式地址。 diff --git a/index.html b/index.html index 920befb..be32c6b 100644 --- a/index.html +++ b/index.html @@ -63,6 +63,24 @@
头像宝
+
+

场景3 · 分享深链验证(web-view 内 H5 分享)

+
当前页面—
+
深链标记(未设置)
+ + + +

+ 判定标准:点 ② 后本页停留不动,再点右上角「···」转发;接收者点开卡片, + 打开的应是上面「当前页面」那个地址(含 hash 标记),而不是小程序首页。
+ 点 ③ 会立即退回上一页(旧版 bridge.share() 的 navigateBack 副作用)—— + 此时再点「···」转发,卡片会由上一页产出,接收者落到上一页:即线上 bug 的复现。
+ ⚠️ 仅在小程序 web-view 内有效(宿主页需按 onShareAppMessage(options).webViewUrl 拼深链, + 见 miniapp/scenario3-share-webview.js / CRMEB pages/annex/web_view)。 + 微信浏览器/纯浏览器只能看 UI。 +

+
+

本页演示共享授权规范:wx-auth.js(wxAuth.init/requestProfile/goMall/readAuth/isAuthorized)。 授权流程:点「授权登录」→ wx.miniProgram.navigateTo('/pages/annex/auth/index') → @@ -99,7 +117,7 @@ /* ============================================================ ① 页面版本号(YYMMDD + 两位序号)—— 用于排查 webview 是否缓存了旧页面 ============================================================ */ - var VER = 26090901; + var VER = 26091001; var verNoEl = el('verNo'); if (verNoEl) verNoEl.textContent = VER; @@ -215,6 +233,26 @@ }; } + // 给 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) { @@ -241,6 +279,51 @@ if (a.readAuth().avatar) { el('rAvatarWrap').innerHTML = '头像'; } } + /* ============================================================ + ④-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('请点右上角「···」→ 转发给朋友'); + } + } + /* ============================================================ ④ 初始化:环境检测 + 读取授权 ============================================================ */ @@ -257,6 +340,14 @@ 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)) { diff --git a/miniapp/scenario3-home-bug.js b/miniapp/scenario3-home-bug.js new file mode 100644 index 0000000..c66d738 --- /dev/null +++ b/miniapp/scenario3-home-bug.js @@ -0,0 +1,22 @@ +// 场景3-反面(❌ 复现线上 bug):写死分享首页 +// 路径:/pages/demo/home.js +// ------------------------------------------------------------ +// 用途:扮演「小程序首页」。H5 点「❌ 复现旧 bug」会 postMessage + navigateBack, +// 退回本页;此时用户点右上角「···」转发,触发的是本页的 onShareAppMessage。 +// 本页故意写死 path: '/pages/demo/home' 且忽略 options.webViewUrl —— +// 接收者点开卡片就落到本页(= 线上「分享后跳首页」的现象)。 +Page({ + data: {}, + + goWebview() { + wx.navigateTo({ url: '/pages/demo/share-webview' }); + }, + + // ❌ 反面:写死首页、不带 H5 地址(旧版 pages/index/index.vue 的写法) + onShareAppMessage() { + return { + title: '兜爸心选 · 首页(bug 复现)', + path: '/pages/demo/home' + }; + } +}); diff --git a/miniapp/scenario3-home-bug.wxml b/miniapp/scenario3-home-bug.wxml new file mode 100644 index 0000000..259ef2d --- /dev/null +++ b/miniapp/scenario3-home-bug.wxml @@ -0,0 +1,27 @@ + + + 兜爸心选 · 首页 + + 本页是 bug 复现用的「首页」:
+ onShareAppMessage 写死 path: '/pages/demo/home'
+ 并忽略回调里的 webViewUrl(旧版实现)。 +
+ + + 操作步骤:
+ 1. 点上面按钮进入 web-view(加载 wx-h5-demo);
+ 2. H5 里点 ③「❌ 复现旧 bug:postMessage + navigateBack」;
+ 3. 会立刻退回本页 —— 点右上角「···」转发;
+ 4. 接收者打开卡片会落到本页(bug 现象)。
+ 5. 改点 ②「✅ 正确分享」重走一遍,接收者应打开 H5 页面。 +
+
diff --git a/miniapp/scenario3-share-webview.js b/miniapp/scenario3-share-webview.js new file mode 100644 index 0000000..d691993 --- /dev/null +++ b/miniapp/scenario3-share-webview.js @@ -0,0 +1,84 @@ +// 场景3(✅ 正确实现):web-view 承载 H5 + 分享深链 +// 路径:/pages/demo/share-webview.js +// ------------------------------------------------------------ +// 关键点:web-view 里的 H5 不能自己拉起分享面板,卡片只能由本页的 +// onShareAppMessage 产出。深链必须取微信回调参数 +// options.webViewUrl(= 当前 web-view 的真实地址,含 H5 的 hash / SPA 路由), +// 再拼回本页 ?url=...,接收者打开的就是「发送者当时那一页」,而不是首页。 +// +// ❌ 反面写法(复现 bug):scenario3-home-bug.js —— 写死 path、忽略 webViewUrl。 +// 线上真实实现见 CRMEB:pages/annex/web_view/index.vue、pages/index/index.vue。 +const H5_BASE = 'https://mp.yizhezhe.com/wx-h5-demo/'; + +// 剥掉分享链接里的身份/缓存参数:不能把发送者的 openid 带给接收者, +// 接收者身份由宿主页/容器按自己的 openid 重新注入。 +const SHARE_DROP_KEYS = ['openid', 'unionid', 't', '_v', 'phone', 'nickname', 'avatar']; +function dropKeyValuePairs(str, keys) { + return String(str || '') + .split('&') + .filter(p => !!p && keys.indexOf(p.split('=')[0]) === -1) + .join('&'); +} +function stripAuthParams(url) { + let out = String(url || ''); + const hashIdx = out.indexOf('#'); + let base = hashIdx >= 0 ? out.slice(0, hashIdx) : out; + let hash = hashIdx >= 0 ? out.slice(hashIdx + 1) : ''; + const qIdx = base.indexOf('?'); + let query = qIdx >= 0 ? base.slice(qIdx + 1) : ''; + base = qIdx >= 0 ? base.slice(0, qIdx) : base; + query = dropKeyValuePairs(query, SHARE_DROP_KEYS); + hash = dropKeyValuePairs(hash, SHARE_DROP_KEYS); + return base + (query ? '?' + query : '') + (hash ? '#' + hash : ''); +} + +Page({ + data: { url: '', shareTitle: '', shareImageUrl: '' }, + + onLoad(options) { + // 开启「转发给好友 / 分享到朋友圈」两项菜单 + wx.showShareMenu({ menus: ['shareAppMessage', 'shareTimeline'] }); + + // 被分享卡片打开时带 ?url=;否则加载默认 H5 + let target = H5_BASE; + if (options && options.url) { + try { target = decodeURIComponent(options.url); } catch (e) { target = options.url; } + } + console.log('[scenario3] 加载 H5:', target); + this.setData({ url: target }); + }, + + // H5 点「分享」→ postMessage(action:'share') → 这里只缓存标题/图; + // 链接以 options.webViewUrl 为准(postMessage 投递有延迟,不能当深链来源)。 + onMessage(e) { + const data = (e.detail && e.detail.data) || []; + (Array.isArray(data) ? data : [data]).forEach((m) => { + if (!m || m.action !== 'share') return; + console.log('[scenario3] H5 请求分享:', JSON.stringify(m)); + if (m.title) this.setData({ shareTitle: m.title }); + if (m.imageUrl) this.setData({ shareImageUrl: m.imageUrl }); + wx.showToast({ title: '点击右上角 ··· 分享给好友', icon: 'none', duration: 2500 }); + }); + }, + + // ✅ 关键:用 options.webViewUrl 拼深链 + onShareAppMessage(options) { + const target = (options && options.webViewUrl) || this.data.url; + const path = '/pages/demo/share-webview?url=' + encodeURIComponent(stripAuthParams(target)); + console.log('[scenario3] onShareAppMessage → path=' + path); + return { + title: this.data.shareTitle || 'wx-h5-demo · 分享深链验证', + path: path, + imageUrl: this.data.shareImageUrl || '' + }; + }, + + // onShareTimeline 没有回调参数(拿不到 webViewUrl),也不能自定义 path; + // 只能把目标 H5 放进 query,由本页 onLoad 读 ?url= 落地。 + onShareTimeline() { + return { + title: this.data.shareTitle || 'wx-h5-demo · 分享深链验证', + query: 'url=' + encodeURIComponent(stripAuthParams(this.data.url)) + }; + } +}); diff --git a/miniapp/scenario3-share-webview.wxml b/miniapp/scenario3-share-webview.wxml new file mode 100644 index 0000000..c079147 --- /dev/null +++ b/miniapp/scenario3-share-webview.wxml @@ -0,0 +1,11 @@ + + + +正在加载 H5…