commit eaaf2b67732c76fdd591f78cc5759633992811f9 Author: Karlcarlo Date: Mon Sep 7 16:22:51 2026 +0800 box_app: add wx-h5-demo standalone project (two mini-program auth scenarios); mount /wx-h5-demo; update auth doc; drop old examples/wx-h5-auth diff --git a/README.md b/README.md new file mode 100644 index 0000000..d1dffea --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# wx-h5-demo · 独立示例 H5(小程序授权 / 跳转) + +> 独立二级目录项目,由 box_app 挂载到 `/wx-h5-demo`(即 `https://mp.yizhezhe.com/wx-h5-demo/`)。 +> 配套说明:`docs/h5-miniprogram-auth.md`。 + +## 两种场景 +| 场景 | 流程 | 用到的小程序页 | +| --- | --- | --- | +| **场景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 宿主页) | + +> 说明:场景2 的宿主页 `scenario2-host-page` 同时是场景1 授权后 redirectTo 的落地页(从 `globalData` 读结果拼 H5 url)。 + +## 文件 +``` +wx-h5-demo/ +├─ index.html # 示例 H5(自包含):环境检测(webview/微信浏览器/纯浏览器)、 +│ # 场景1 跳小程序授权 或 场景2 postMessage 授权、读回传参数展示。 +├─ miniapp/ +│ ├─ scenario1-auth-bridge.wxml/.js # 场景1 小程序授权桥页(launch-weapp/URL Scheme 进入) +│ └─ scenario2-host-page.wxml/.js # 场景2 web-view 宿主页(也作场景1 落地页) +└─ README.md +``` + +## 怎么做 +1. **H5**:改 `index.html`(H5 地址 = `https://mp.yizhezhe.com/wx-h5-demo/`)。 +2. **小程序端**:把 `miniapp/*` 的两个页面代码并入你的小程序(如 CRMEB),改 3 处: + - `app.globalData.openid`(登录态)。 + - `H5_BASE` 指向你的 H5。 + - `https://YOUR_API/phone|avatar`(或注释里说明;手机号 `code`→后端换明文;头像需上传换正式地址)。 +3. **入口**: + - 场景2:宿主页已用 `` 加载 H5,H5 点「在 web-view 里请求授权」即可。 + - 场景1:H5 点「去小程序授权」→ 需给 `jumpToMiniAuth()` 配一个**小程序 URL Scheme**(后端用 appid+path 换)或把页面放进公众号/env 用 ``。 + +## 注意 +- 手机号:前端只拿 `code`,**明文必须后端** `wx.business.getuserphonenumber` 换(旧 `code2Session`+解密已收紧)。 +- 头像:`e.detail.avatarUrl` 是临时 URL,需 `wx.uploadFile` 上传存后端,回传正式地址。 +- `getUserProfile` 已废弃(匿名);用 `getPhoneNumber` / `` / `chooseAvatar`。 +- 身份:保留 `openid`(query);授权结果走 **hash**(`#phone/nickname/avatar`,微信 webview 丢 query 不丢 hash)。 +- 纯浏览器打开 `index.html`:仅 UI 演示(有降级提示),无法真授权。 diff --git a/index.html b/index.html new file mode 100644 index 0000000..582845c --- /dev/null +++ b/index.html @@ -0,0 +1,121 @@ + + + + + + +wx-h5-demo · 小程序授权演示(独立项目) + + + +
+
wx-h5-demo 独立示例 · 小程序授权
+
场景:检测中… 纯浏览器
+ +
+

当前可用的授权方式

+

检测环境后,这里会给出「场景1 或 场景2」对应的授权入口。

+ + +
+ +
+

授权结果(小程序回传后读取)

+
手机号—
+
昵称—
+
头像宝
+
+ +

+ 场景1(微信浏览器打开 H5):点「去小程序授权」→ 经 wx-open-launch-weapp / 小程序 URL Scheme 跳进小程序授权页 → 授权后,小程序用 web-view 加载本 H5(结果在 URL),即「回到 H5」。 + 场景2(web-view 里的 H5):点「在 web-view 里请求授权」→ postMessage → 宿主小程序弹原生授权面板 → 更新 web-view src(带 #phone/nickname/avatar)→ 本页重载显示。 + 完整说明见 docs/h5-miniprogram-auth.md;小程序端配套代码见本目录 miniapp/。 +

+
+
+ + + + + diff --git a/miniapp/scenario1-auth-bridge.js b/miniapp/scenario1-auth-bridge.js new file mode 100644 index 0000000..2d261b1 --- /dev/null +++ b/miniapp/scenario1-auth-bridge.js @@ -0,0 +1,33 @@ +// 场景1:授权桥页(H5 在微信浏览器 → 跳进本页 → 授权 → 回到 H5) +// 路径:/pages/demo/auth.js +// 入口: 或 小程序 URL Scheme +// ⚠️ openid 由登录态/onLoad 取;授权结果放 globalData,webview 页再拼进 H5 url。 +const app = getApp(); +const H5_BASE = 'https://mp.yizhezhe.com/wx-h5-demo/'; // 示例 H5(独立二级目录项目) + +Page({ + data: { nickname: '', avatar: '' }, + _phone: '', _nick: '', _avatar: '', _openid: '', + + onLoad(q) { this._openid = q.openid || app.globalData.openid || 'YOUR_OPENID'; }, + + onPhone(e) { + if (e.detail.errMsg === 'getPhoneNumber:ok' && e.detail.code) { + wx.request({ url: 'https://YOUR_API/phone', method: 'POST', data: { code: e.detail.code }, + success: r => { this._phone = r.data.phoneNumber; wx.showToast({ title: '已获取手机号', icon: 'none' }); } }); + } else { wx.showToast({ title: '未授权手机号', icon: 'none' }); } + }, + onNick(e) { this._nick = e.detail.value; this.setData({ nickname: this._nick }); }, + onAvatar(e) { + const p = e.detail.avatarUrl; + wx.uploadFile({ url: 'https://YOUR_API/avatar', filePath: p, name: 'file', + success: (res) => { const r = JSON.parse(res.data || '{}'); this._avatar = r.url || p; this.setData({ avatar: this._avatar }); } }); + }, + + // 授权完成 → 写 globalData → 去 webview 页加载 H5(结果在 URL)→ 即“回到 H5” + finishAuth() { + app.globalData.wxH5Result = { openid: this._openid, phone: this._phone, nickname: this._nick, avatar: this._avatar }; + wx.redirectTo({ url: '/pages/demo/webview' }); + }, + cancel() { wx.navigateBack({}); }, +}); diff --git a/miniapp/scenario1-auth-bridge.wxml b/miniapp/scenario1-auth-bridge.wxml new file mode 100644 index 0000000..1fdb313 --- /dev/null +++ b/miniapp/scenario1-auth-bridge.wxml @@ -0,0 +1,16 @@ + + + + 授权获取资料 + 授权后自动回到「wx-h5-demo」H5 + + + + + + + 取消 + diff --git a/miniapp/scenario2-host-page.js b/miniapp/scenario2-host-page.js new file mode 100644 index 0000000..de32325 --- /dev/null +++ b/miniapp/scenario2-host-page.js @@ -0,0 +1,51 @@ +// 场景2(也是场景1落地页):web-view 承载 H5 + postMessage 授权 + 回传 +// 路径:/pages/demo/webview.js +// H5 地址(独立二级目录项目) +const H5_BASE = 'https://mp.yizhezhe.com/wx-h5-demo/'; +const app = getApp(); + +Page({ + data: { url: '', showAuth: false, nickname: '', avatar: '' }, + _phone: '', _nick: '', _avatar: '', _openid: '', + + onLoad() { + this._openid = app.globalData.openid || 'YOUR_OPENID'; + // 场景1:auth-bridge 授权后 redirectTo 本页,结果在 globalData + const r = app.globalData.wxH5Result; + if (r) { this._phone = r.phone || ''; this._nick = r.nickname || ''; this._avatar = r.avatar || ''; } + this.applyUrl(); + }, + + // 拼 H5 url:openid 走 query;授权结果走 hash(webview 丢 query、不丢 hash) + applyUrl() { + const u = H5_BASE + '?openid=' + encodeURIComponent(this._openid) + + '#phone=' + encodeURIComponent(this._phone || '') + + '&nickname=' + encodeURIComponent(this._nick || '') + + '&avatar=' + encodeURIComponent(this._avatar || ''); + this.setData({ url: u }); + }, + + // 场景2:H5 通过 postMessage 请求授权 + onMessage(e) { + const data = (e.detail && e.detail.data) || []; + (Array.isArray(data) ? data : [data]).forEach((m) => { + if (m && m.action === 'requestProfile') this.setData({ showAuth: true }); + }); + }, + + onPhone(e) { + if (e.detail.errMsg === 'getPhoneNumber:ok' && e.detail.code) { + wx.request({ url: 'https://YOUR_API/phone', method: 'POST', data: { code: e.detail.code }, + success: r => { this._phone = r.data.phoneNumber; this.finishAuth(); } }); + } else { wx.showToast({ title: '未授权手机号', icon: 'none' }); } + }, + onNick(e) { this._nick = e.detail.value; this.setData({ nickname: this._nick }); }, + onAvatar(e) { + const p = e.detail.avatarUrl; + wx.uploadFile({ url: 'https://YOUR_API/avatar', filePath: p, name: 'file', + success: (res) => { const r = JSON.parse(res.data || '{}'); this._avatar = r.url || p; this.setData({ avatar: this._avatar }); } }); + }, + finishAuth() { this.setData({ showAuth: false }); this.applyUrl(); }, // 更新 web-view src → H5 重载显示结果 + closeAuth() { this.setData({ showAuth: false }); }, + noop() {}, +}); diff --git a/miniapp/scenario2-host-page.wxml b/miniapp/scenario2-host-page.wxml new file mode 100644 index 0000000..18c68fd --- /dev/null +++ b/miniapp/scenario2-host-page.wxml @@ -0,0 +1,19 @@ + + + + + + + 授权获取资料 + 用于在 wx-h5-demo 中展示昵称/头像/手机号 + + + + 取消 + +