feat: 添加 H5 登录调试面板,显示初始 URL 信息并修复布尔值渲染问题
This commit is contained in:
196
docs/login-fix-plan.md
Normal file
196
docs/login-fix-plan.md
Normal file
@@ -0,0 +1,196 @@
|
||||
# H5 登录卡住问题分析与小程序端修复方案
|
||||
|
||||
---
|
||||
|
||||
## 一、问题描述
|
||||
|
||||
### 1.1 现象
|
||||
|
||||
从小程序跳转到 H5 页面后,页面一直显示"正在获取登录信息...",无法正常加载首页或引导页。
|
||||
|
||||
### 1.2 调试信息
|
||||
|
||||
H5 调试面板(`?debug=1`)输出:
|
||||
|
||||
```
|
||||
url https://mp.yizhezhe.com/app_03/
|
||||
isWxMp (空 — 实际为 true,因 false 渲染为空)
|
||||
hasTokenParam (空 — URL 中无 token 参数)
|
||||
step wx-postMessage-done (已发送 NEED_TOKEN 请求)
|
||||
```
|
||||
|
||||
### 1.3 根因分析
|
||||
|
||||
H5 登录流程有三个路径:
|
||||
|
||||
| 分支 | 触发条件 | 预期行为 |
|
||||
|------|---------|---------|
|
||||
| URL 有 token | `?token=***` | 解码 token → 登录 → 跳首页 |
|
||||
| 小程序环境 | 检测到 `MicroMessenger` + `miniprogram` | 发 `postMessage({NEED_TOKEN})` 等小程序响应 |
|
||||
| 浏览器开发 | 其他情况 | 调 `dev-login` 自动登录 |
|
||||
|
||||
当前状态:URL **没有 token**,而是进入了第 2 条路。H5 向小程序发送了 `postMessage({ type: "NEED_TOKEN" })`,但**小程序侧没有监听和处理这条消息**,导致 H5 永远停在"正在获取登录信息..."。
|
||||
|
||||
---
|
||||
|
||||
## 二、修复方案
|
||||
|
||||
### 方案 A(推荐):打开 web-view 时直接带上 token
|
||||
|
||||
H5 不需要发 `NEED_TOKEN` 消息。每次打开 web-view 时,小程序直接把 token 拼到 URL 里。
|
||||
|
||||
```javascript
|
||||
// 小程序侧 — 打开 web-view 的页面中
|
||||
Page({
|
||||
data: {
|
||||
// 小程序自己的 token(通过 wx.login / 微信登录获取)
|
||||
token: 'eyJ0e...Egnk',
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
// 确保 token 已就绪后再打开 web-view
|
||||
this.ensureToken().then(token => {
|
||||
this.setData({ token });
|
||||
});
|
||||
},
|
||||
|
||||
async ensureToken() {
|
||||
// 此处用小程序自己的登录逻辑获取 token
|
||||
// 示例:如果已有缓存则用缓存,否则先登录
|
||||
const cached = wx.getStorageSync('auth_token');
|
||||
if (cached) return cached;
|
||||
// 登录逻辑...
|
||||
return token;
|
||||
},
|
||||
|
||||
// WXML: <web-view src="{{webviewUrl}}">
|
||||
get webviewUrl() {
|
||||
const base = 'https://mp.yizhezhe.com/app_03/';
|
||||
const token = this.data.token;
|
||||
// 有 token 就加 ?token=,没有就不加(H5 会 fallback 到 dev-login)
|
||||
return token ? `${base}?token=${encodeURIComponent(token)}` : base;
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**优点:**
|
||||
- 最简单直接,无额外消息通信
|
||||
- H5 拿到 token 后一步到位完成登录
|
||||
- 不需要修改 H5 端代码
|
||||
|
||||
---
|
||||
|
||||
### 方案 B:监听 NEED_TOKEN 消息后回传
|
||||
|
||||
如果 web-view 先打开(URL 不带 token),H5 检测到小程序环境后发 `NEED_TOKEN` 消息,小程序接收后把 token 写回 URL。
|
||||
|
||||
```javascript
|
||||
// 小程序侧 — web-view 所在的页面
|
||||
Page({
|
||||
onLoad() {
|
||||
// 确保小程序侧准备好接收 postMessage
|
||||
},
|
||||
|
||||
// web-view 的 bindmessage 回调
|
||||
onMessage(e) {
|
||||
const data = e.detail.data || e.detail; // 兼容不同版本
|
||||
console.log('web-view 消息:', data);
|
||||
|
||||
if (data.type === 'NEED_TOKEN') {
|
||||
// 获取 token 并传递回 web-view
|
||||
const token = this.getToken();
|
||||
if (!token) {
|
||||
console.error('token 未就绪,无法响应 NEED_TOKEN');
|
||||
return;
|
||||
}
|
||||
|
||||
// 方式 1: 重新加载 web-view 的 src(推荐)
|
||||
const base = 'https://mp.yizhezhe.com/app_03/';
|
||||
const webview = this.selectComponent('#webview');
|
||||
// 注意: 小程序 web-view 不支持直接改 src,需要用 navigateTo 重新打开
|
||||
wx.navigateTo({
|
||||
url: `/pages/webview/webview?token=${encodeURIComponent(token)}`,
|
||||
});
|
||||
|
||||
// 方式 2: 如果 web-view 通过页面参数传递 token(适合小程序自定义导航)
|
||||
// 见方案 A,在打开时直接带上 token
|
||||
}
|
||||
},
|
||||
|
||||
getToken() {
|
||||
return wx.getStorageSync('auth_token');
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
对应 WXML:
|
||||
|
||||
```html
|
||||
<web-view
|
||||
id="webview"
|
||||
src="https://mp.yizhezhe.com/app_03/"
|
||||
bindmessage="onMessage"
|
||||
/>
|
||||
```
|
||||
|
||||
**缺点:**
|
||||
- 需要额外一轮消息通信,H5 会先显示"正在获取登录信息..."再刷新
|
||||
- 小程序 `web-view` 组件没有直接修改 `src` 的 API,只能重新 `navigateTo`
|
||||
- 用户体验不如方案 A 顺滑
|
||||
|
||||
---
|
||||
|
||||
## 三、推荐方案对比
|
||||
|
||||
| 维度 | 方案 A(URL 带 token) | 方案 B(消息回调) |
|
||||
|------|-----------------------|------------------|
|
||||
| 实现复杂度 | 低 | 中 |
|
||||
| 用户体验 | 流畅(一步到位) | 有闪烁(页面刷新) |
|
||||
| H5 端改动 | 不需要 | 不需要 |
|
||||
| 小程序端改动 | 加 URL 拼接 | 加 bindmessage 回调 |
|
||||
| 额外通信 | 无 | 一轮 postMessage |
|
||||
| token 过期处理 | 打开时即携带最新 token | 可能携带过期 token 再等 NEED_TOKEN |
|
||||
|
||||
**结论:采用方案 A。**
|
||||
|
||||
---
|
||||
|
||||
## 四、H5 端问题关联修改
|
||||
|
||||
### 4.1 Debug 面板布尔值修复
|
||||
|
||||
当前 `DebugPanel` 渲染布尔值 `false` 时会因为 `{v || "(空)"}` 显示为空。需要改:
|
||||
|
||||
```tsx
|
||||
// 当前(有 bug):
|
||||
<td>{v || <span className="text-gray-500">(空)</span>}</td>
|
||||
|
||||
// 修复后:
|
||||
<td>{v === false ? 'false' : v === true ? 'true' : v || <span className="text-gray-500">(空)</span>}</td>
|
||||
```
|
||||
|
||||
### 4.2 `isWechatMiniProgram()` 废弃
|
||||
|
||||
如果小程序采用方案 A(打开时直接带 token),H5 不再需要 `isWechatMiniProgram` 检测和 `NEED_TOKEN` 消息发送逻辑。上线后可清理:
|
||||
|
||||
```typescript
|
||||
// LoginPage.tsx — 删除以下代码段
|
||||
if (isWechatMiniProgram()) {
|
||||
try {
|
||||
(window as any).wx?.miniProgram?.postMessage({ type: "NEED_TOKEN" });
|
||||
} catch {}
|
||||
setError("正在获取登录信息...");
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、验证步骤
|
||||
|
||||
1. 小程序侧按方案 A 修改,确保打开 web-view 时 URL 带 `?token=***`
|
||||
2. 访问 `https://mp.yizhezhe.com/app_03/?token=***&debug=1`
|
||||
3. 检查调试面板:`hasTokenParam=true`、`step=decode-ok` 或 `step=fetch-ok`
|
||||
4. 确认跳转到 `/home` 或 `/onboarding`
|
||||
5. 关闭 `?debug=1` 正常使用
|
||||
6. (可选)清理 DebugPanel 和 `isWechatMiniProgram` 相关代码
|
||||
@@ -9,6 +9,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script>window.__INITIAL_URL__ = document.URL;</script>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
|
||||
@@ -33,6 +33,7 @@ const INIT_DEBUG: DebugInfo = {
|
||||
};
|
||||
|
||||
function DebugPanel({ info }: { info: DebugInfo }) {
|
||||
const [initialUrl] = useState(() => (window as any).__INITIAL_URL__ || '(N/A)');
|
||||
return (
|
||||
<div className="absolute top-0 left-0 right-0 z-50 bg-black/80 text-white text-xs font-mono p-3 text-left max-h-[60vh] overflow-auto">
|
||||
<div className="font-bold text-yellow-400 mb-1">🐞 调试信息 (关闭需去掉 ?debug=1)</div>
|
||||
@@ -41,9 +42,13 @@ function DebugPanel({ info }: { info: DebugInfo }) {
|
||||
{Object.entries(info).map(([k, v]) => (
|
||||
<tr key={k} className="border-b border-white/10">
|
||||
<td className="pr-2 text-gray-400 w-28 align-top whitespace-nowrap">{k}</td>
|
||||
<td className="break-all text-green-300">{v || <span className="text-gray-500">(空)</span>}</td>
|
||||
<td className="break-all text-green-300">{v === false ? 'false' : v === true ? 'true' : v || <span className="text-gray-500">(空)</span>}</td>
|
||||
</tr>
|
||||
))}
|
||||
<tr className="border-b border-yellow-400/50">
|
||||
<td className="pr-2 text-yellow-400 w-28 align-top whitespace-nowrap font-bold">initialUrl</td>
|
||||
<td className="break-all text-yellow-300">{initialUrl}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user