feat(#1718): 微信登录/绑定改为弹窗内嵌二维码,不再整页跳转 #1726
Reference in New Issue
Block a user
Delete Branch "feat/1718-wechat-modal-qrcode"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
背景
Issue #1718 体验优化:登录页点「微信登录」和设置页「绑定微信」从整页跳转微信授权页,改为站内弹窗内嵌官方二维码(WxLogin JS),扫码完成后弹窗内闭环,不再离开当前页面。
改动
新增
src/api/auth/wxLogin.tsloadWxLoginScript():动态加载微信官方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 整页回调提示逻辑保留(兜底路径)兜底链路
window.location.href = auth_url整页跳转(旧流程)测试
wxLogin.test.ts(parseWxAuthUrl 3 例 + 脚本加载 2 例)、WechatQrModal.test.tsx(8 例:open=false、登录/绑定渲染、state 写入、WxLogin 参数、接口失败重试、登录成功同步登录态、失败 detail 展示、绑定成功、跨源/错场景消息忽略)自测项(部署后)
【阻塞级判定】
📊 审查概览
🔴 阻塞级问题(必须修复)
无
💡 改进建议(不阻塞合并)
[apps/web/src/pages/auth/WechatCallback.tsx: 76] iframe 内页面状态优化
inIframe为true的成功分支中,代码仅调用了postWechatQrResult并return,并未更新组件内部的loading或error状态。导致 iframe 内部会一直显示<Spin /> 加载中...。虽然父窗口弹窗的遮罩层会覆盖住 iframe,用户看不见,但在 DOM 层面 iframe 仍在消耗渲染资源。建议在 iframe 模式下,设置一个内部状态(如setIframeHandled(true))并渲染一个极简的空白或成功提示,避免无意义的 Spinner 持续运行。[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 | 模型:
🚀 预览环境已部署
CI全绿,自动审批通过。
CI全绿,自动审批通过。
🗑️ 预览环境已清理
PR #1726 已关闭或合并,对应的预览环境已被清理。