feat(#1718): 微信登录/绑定改为弹窗内嵌二维码,不再整页跳转 #1726

Merged
auto-approve-bot merged 1 commits from feat/1718-wechat-modal-qrcode into develop 2026-09-06 00:31:49 +08:00
Owner

背景

Issue #1718 体验优化:登录页点「微信登录」和设置页「绑定微信」从整页跳转微信授权页,改为站内弹窗内嵌官方二维码(WxLogin JS),扫码完成后弹窗内闭环,不再离开当前页面。

改动

新增

  • src/api/auth/wxLogin.ts
    • loadWxLoginScript():动态加载微信官方 res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js(单例 promise、8s 超时、onerror 检测),失败/超时由调用方回退整页跳转
    • parseWxAuthUrl(authUrl):从后端返回的 auth_url query 中解析 appid / redirect_uri(自动解码)/ state,解析失败回退整页跳转
  • src/components/auth/WechatQrModal/(登录/绑定复用)
    • index.tsx:扫码弹窗。open 后请求 /auth/wechat/url/auth/wechat/bind/url → 加载 WxLogin JS → new WxLogin({ self_redirect: true, appid, scope: "snsapi_login", redirect_uri, state }) 在常驻容器内内嵌渲染二维码;监听 iframe 回调页 postMessage:成功→同步登录态(getCurrentUser + setAuth + scheduleProactiveRefresh,token 由 iframe 内回调页写入同源 localStorage)/ 绑定→刷新用户;失败→弹窗内错误区展示真实 detail + 刷新二维码 + 整页方式打开兜底
    • messages.ts:postMessage 协议({source:"xiaoxia-wechat-qr", scene:"login"|"bind", success, detail, payload})、isWechatQrMessage 同源+场景过滤、isInIframe()postWechatQrResult()
    • WechatQrModal.css:二维码容器常驻 + loading/error 遮罩样式

修改

  • pages/auth/WechatCallback.tsx / WechatBindCallback.tsx:检测 window.parent !== window(弹窗 iframe 场景)时,成功/失败均通过 postMessage 上报父窗口,不做 navigate;非 iframe(整页跳转/兜底)保持原有跳转/本页展示逻辑
  • pages/auth/Login.tsx:微信按钮改为打开 WechatQrModal(login 场景);成功回调按 needOnboarding 跳昵称引导页或来源页/首页;弹窗打开期间按钮禁用;WxLogin 加载失败时弹窗内自动整页跳转,流程不丢
  • pages/profile/Settings.tsx:「绑定微信」按钮改为打开同一弹窗(bind 场景);成功后 invalidateQueries 刷新 + 成功提示;?wechat_bind=success|failed 整页回调提示逻辑保留(兜底路径)

兜底链路

  1. WxLogin JS 加载失败/超时 → 自动 window.location.href = auth_url 整页跳转(旧流程)
  2. auth_url 解析失败/容器缺失 → 同上整页跳转
  3. 弹窗错误区提供「刷新二维码」和「使用整页方式打开」

测试

  • 新增 wxLogin.test.ts(parseWxAuthUrl 3 例 + 脚本加载 2 例)、WechatQrModal.test.tsx(8 例:open=false、登录/绑定渲染、state 写入、WxLogin 参数、接口失败重试、登录成功同步登录态、失败 detail 展示、绑定成功、跨源/错场景消息忽略)
  • WechatCallback/WechatBindCallback 测试补 iframe 场景 6 例(成功 postMessage 不 navigate、needOnboarding 上报、失败 detail 上报、error 重定向上报)
  • 全量 663 passed(141 文件);tsc / eslint --max-warnings 0 / prettier / build 全绿

自测项(部署后)

  • 登录页弹窗扫码:老微信直接进首页、新微信进昵称引导页
  • 设置页弹窗扫码绑定/解绑
  • 弹窗内失败(过期 state、拒绝授权)错误提示可见
  • WxLogin JS 加载失败时自动回退整页跳转
## 背景 Issue #1718 体验优化:登录页点「微信登录」和设置页「绑定微信」从整页跳转微信授权页,改为站内弹窗内嵌官方二维码(WxLogin JS),扫码完成后弹窗内闭环,不再离开当前页面。 ## 改动 ### 新增 - `src/api/auth/wxLogin.ts` - `loadWxLoginScript()`:动态加载微信官方 `res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js`(单例 promise、8s 超时、onerror 检测),失败/超时由调用方回退整页跳转 - `parseWxAuthUrl(authUrl)`:从后端返回的 auth_url query 中解析 `appid` / `redirect_uri`(自动解码)/ `state`,解析失败回退整页跳转 - `src/components/auth/WechatQrModal/`(登录/绑定复用) - `index.tsx`:扫码弹窗。open 后请求 `/auth/wechat/url` 或 `/auth/wechat/bind/url` → 加载 WxLogin JS → `new WxLogin({ self_redirect: true, appid, scope: "snsapi_login", redirect_uri, state })` 在常驻容器内内嵌渲染二维码;监听 iframe 回调页 postMessage:成功→同步登录态(getCurrentUser + setAuth + scheduleProactiveRefresh,token 由 iframe 内回调页写入同源 localStorage)/ 绑定→刷新用户;失败→弹窗内错误区展示真实 detail + 刷新二维码 + 整页方式打开兜底 - `messages.ts`:postMessage 协议(`{source:"xiaoxia-wechat-qr", scene:"login"|"bind", success, detail, payload}`)、`isWechatQrMessage` 同源+场景过滤、`isInIframe()`、`postWechatQrResult()` - `WechatQrModal.css`:二维码容器常驻 + loading/error 遮罩样式 ### 修改 - `pages/auth/WechatCallback.tsx` / `WechatBindCallback.tsx`:检测 `window.parent !== window`(弹窗 iframe 场景)时,成功/失败均通过 postMessage 上报父窗口,不做 navigate;非 iframe(整页跳转/兜底)保持原有跳转/本页展示逻辑 - `pages/auth/Login.tsx`:微信按钮改为打开 WechatQrModal(login 场景);成功回调按 needOnboarding 跳昵称引导页或来源页/首页;弹窗打开期间按钮禁用;WxLogin 加载失败时弹窗内自动整页跳转,流程不丢 - `pages/profile/Settings.tsx`:「绑定微信」按钮改为打开同一弹窗(bind 场景);成功后 invalidateQueries 刷新 + 成功提示;?wechat_bind=success|failed 整页回调提示逻辑保留(兜底路径) ### 兜底链路 1. WxLogin JS 加载失败/超时 → 自动 `window.location.href = auth_url` 整页跳转(旧流程) 2. auth_url 解析失败/容器缺失 → 同上整页跳转 3. 弹窗错误区提供「刷新二维码」和「使用整页方式打开」 ## 测试 - 新增 `wxLogin.test.ts`(parseWxAuthUrl 3 例 + 脚本加载 2 例)、`WechatQrModal.test.tsx`(8 例:open=false、登录/绑定渲染、state 写入、WxLogin 参数、接口失败重试、登录成功同步登录态、失败 detail 展示、绑定成功、跨源/错场景消息忽略) - WechatCallback/WechatBindCallback 测试补 iframe 场景 6 例(成功 postMessage 不 navigate、needOnboarding 上报、失败 detail 上报、error 重定向上报) - 全量 **663 passed**(141 文件);tsc / eslint --max-warnings 0 / prettier / build 全绿 ## 自测项(部署后) - [ ] 登录页弹窗扫码:老微信直接进首页、新微信进昵称引导页 - [ ] 设置页弹窗扫码绑定/解绑 - [ ] 弹窗内失败(过期 state、拒绝授权)错误提示可见 - [ ] WxLogin JS 加载失败时自动回退整页跳转
xiaoxia added 1 commit 2026-09-06 00:25:31 +08:00
feat(#1718): 微信登录/绑定改为弹窗内嵌二维码(WxLogin self_redirect + iframe postMessage),脚本加载失败回退整页跳转
AI Code Review / AI Code Review (pull_request) Successful in 2m12s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m14s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m18s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 3m58s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 9s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 22s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 6s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
4ee53a0cab
Collaborator

【阻塞级判定】

  • 是否存在阻塞级问题:否
  • 阻塞级问题数量:0 个

📊 审查概览

  • 整体评价:通过
  • 建议级问题数量:2 个

🔴 阻塞级问题(必须修复)

💡 改进建议(不阻塞合并)

  1. [apps/web/src/pages/auth/WechatCallback.tsx: 76] iframe 内页面状态优化

    • 具体内容:在 inIframetrue 的成功分支中,代码仅调用了 postWechatQrResultreturn,并未更新组件内部的 loadingerror 状态。导致 iframe 内部会一直显示 <Spin /> 加载中...。虽然父窗口弹窗的遮罩层会覆盖住 iframe,用户看不见,但在 DOM 层面 iframe 仍在消耗渲染资源。建议在 iframe 模式下,设置一个内部状态(如 setIframeHandled(true))并渲染一个极简的空白或成功提示,避免无意义的 Spinner 持续运行。
  2. [apps/web/src/components/auth/WechatQrModal/index.tsx: 88] useEffect 依赖项优化

    • 具体内容:useEffect 的依赖数组中包含了 isLogin。由于 isLogin 是由 scene 派生出的常量(const isLogin = scene === "login"),只要 scene 在依赖数组中,isLogin 的变化必然被覆盖。移除 isLogin 可以减少依赖项冗余,使代码更简洁。

良好实践

  • 安全处理postMessage 通信严格校验了 event.origin,且使用了特定的 source 字段进行协议验证,有效防止了恶意消息注入。
  • 降级策略:在 WxLogin 脚本加载失败、超时或容器挂载失败时,均能自动回退到整页跳转授权,确保了核心登录功能的可用性。
  • 竞态处理WechatQrModal 中使用了 cancelled 标志位来处理组件卸载后的异步操作,避免了在已卸载的组件上执行状态更新导致的内存泄漏或报错。
  • 类型安全wxLogin.ts 中对 window.WxLogin 进行了类型声明,增强了代码的健壮性和可维护性。

格式检查通过 | 逻辑审查通过 | 性能无明显问题


🤖 由 AI 代码审查机器人自动生成 | 2026-09-05 16:27:42 | 模型:

### 【阻塞级判定】 - 是否存在阻塞级问题:否 - 阻塞级问题数量:0 个 ### 📊 审查概览 - 整体评价:通过 - 建议级问题数量:2 个 ### 🔴 阻塞级问题(必须修复) 无 ### 💡 改进建议(不阻塞合并) 1. **[apps/web/src/pages/auth/WechatCallback.tsx: 76] iframe 内页面状态优化** - 具体内容:在 `inIframe` 为 `true` 的成功分支中,代码仅调用了 `postWechatQrResult` 并 `return`,并未更新组件内部的 `loading` 或 `error` 状态。导致 iframe 内部会一直显示 `<Spin /> 加载中...`。虽然父窗口弹窗的遮罩层会覆盖住 iframe,用户看不见,但在 DOM 层面 iframe 仍在消耗渲染资源。建议在 iframe 模式下,设置一个内部状态(如 `setIframeHandled(true)`)并渲染一个极简的空白或成功提示,避免无意义的 Spinner 持续运行。 2. **[apps/web/src/components/auth/WechatQrModal/index.tsx: 88] useEffect 依赖项优化** - 具体内容:`useEffect` 的依赖数组中包含了 `isLogin`。由于 `isLogin` 是由 `scene` 派生出的常量(`const isLogin = scene === "login"`),只要 `scene` 在依赖数组中,`isLogin` 的变化必然被覆盖。移除 `isLogin` 可以减少依赖项冗余,使代码更简洁。 ### ✅ 良好实践 - **安全处理**:`postMessage` 通信严格校验了 `event.origin`,且使用了特定的 `source` 字段进行协议验证,有效防止了恶意消息注入。 - **降级策略**:在 `WxLogin` 脚本加载失败、超时或容器挂载失败时,均能自动回退到整页跳转授权,确保了核心登录功能的可用性。 - **竞态处理**:`WechatQrModal` 中使用了 `cancelled` 标志位来处理组件卸载后的异步操作,避免了在已卸载的组件上执行状态更新导致的内存泄漏或报错。 - **类型安全**:`wxLogin.ts` 中对 `window.WxLogin` 进行了类型声明,增强了代码的健壮性和可维护性。 --- ✅ 格式检查通过 | ✅ 逻辑审查通过 | ✅ 性能无明显问题 --- <sub>🤖 由 AI 代码审查机器人自动生成 | 2026-09-05 16:27:42 | 模型: </sub> <!-- AI_CODE_REVIEW_AUTO_COMMENT -->

🚀 预览环境已部署

项目 详情
PR号 #1726
预览链接 https://pr-1726.preview.xiaoxiajianji.com
API环境 staging

💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。

🔄 每次提交新代码后预览环境会自动更新。

🗑️ PR 关闭或合并后,预览环境会自动清理。

🚀 **预览环境已部署** | 项目 | 详情 | |------|------| | PR号 | #1726 | | 预览链接 | [https://pr-1726.preview.xiaoxiajianji.com](https://pr-1726.preview.xiaoxiajianji.com) | | API环境 | staging | > 💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。 > > 🔄 每次提交新代码后预览环境会自动更新。 > > 🗑️ PR 关闭或合并后,预览环境会自动清理。
auto-approve-bot approved these changes 2026-09-06 00:27:50 +08:00
auto-approve-bot left a comment
Collaborator

CI全绿,自动审批通过。

CI全绿,自动审批通过。
auto-approve-bot approved these changes 2026-09-06 00:27:50 +08:00
auto-approve-bot left a comment
Collaborator

CI全绿,自动审批通过。

CI全绿,自动审批通过。
auto-approve-bot merged commit a83ed58864 into develop 2026-09-06 00:31:49 +08:00
auto-approve-bot deleted branch feat/1718-wechat-modal-qrcode 2026-09-06 00:31:49 +08:00

🗑️ 预览环境已清理

PR #1726 已关闭或合并,对应的预览环境已被清理。

如有需要,可以重新打开 PR 来重新生成预览环境。

🗑️ **预览环境已清理** PR #1726 已关闭或合并,对应的预览环境已被清理。 > 如有需要,可以重新打开 PR 来重新生成预览环境。
Sign in to join this conversation.