diff --git a/frontend/admin-web/src/layouts/AdminLayout.tsx b/frontend/admin-web/src/layouts/AdminLayout.tsx index 271661f..181ce98 100644 --- a/frontend/admin-web/src/layouts/AdminLayout.tsx +++ b/frontend/admin-web/src/layouts/AdminLayout.tsx @@ -100,7 +100,14 @@ export const AdminLayout: React.FC<{ children: React.ReactNode }> = ({ children const { isAuthenticated, isLoading, user, logout } = useAuth(); const [collapsed, setCollapsed] = useState(false); - // 未登录 → /login(必须在 useEffect 中跳转,不能在 render 期间调用 router) + // 未登录 → 跳转 /login + // + // ⚠️ 必须在 useEffect 中执行,不能直接在 render 函数体内调用 router.replace()。 + // 原因:render 期间调用 router 会触发父组件的状态更新(React #310 错误): + // "Cannot update a component while rendering a different component" + // + // useEffect 在 commit 阶段(DOM 更新后)执行,此时可以安全地触发导航。 + // 依赖数组包含 router 是为了满足 exhaustive-deps lint 规则,router 引用稳定不会重复触发。 useEffect(() => { if (!isLoading && !isAuthenticated) { router.replace('/login'); diff --git a/frontend/genex-mobile/lib/features/auth/presentation/pages/welcome_page.dart b/frontend/genex-mobile/lib/features/auth/presentation/pages/welcome_page.dart index 5d1e6e6..66e868c 100644 --- a/frontend/genex-mobile/lib/features/auth/presentation/pages/welcome_page.dart +++ b/frontend/genex-mobile/lib/features/auth/presentation/pages/welcome_page.dart @@ -50,13 +50,19 @@ class _WelcomePageState extends State { // 'profile': 获取用户显示名和头像 URL final _googleSignIn = GoogleSignIn(scopes: ['email', 'profile']); - // fluwx 5.x 使用实例方式调用,registerApi 在 main.dart 中已完成 + // fluwx 5.x API 迁移说明: + // 旧版 (3.x): 顶层 Stream `weChatResponseEventHandler.distinct().listen(...)` + // 新版 (5.x): 实例方法 `Fluwx().addSubscriber((response) {...})` + // + // Fluwx() 底层 MethodChannel 为单例,新建实例不会重复注册 SDK, + // registerApi 已在 main.dart 的 app 启动时完成,此处直接使用。 final _fluwx = Fluwx(); @override void initState() { super.initState(); - // 监听微信授权回调(用户授权后,微信 App 返回 code) + // 订阅微信平台所有回调事件(授权/支付/分享等均通过同一 subscriber 派发) + // 通过 `is WeChatAuthResponse` 类型守卫过滤出登录授权响应 _fluwx.addSubscriber((response) { if (response is WeChatAuthResponse && mounted) { _handleWechatAuthResp(response); @@ -77,10 +83,20 @@ class _WelcomePageState extends State { return; } setState(() => _wechatLoading = true); + // NormalAuth: 标准微信 OAuth 授权(弹出微信 App 授权页) + // scope: 'snsapi_userinfo' — 获取用户基本信息(需用户手动同意) + // 'snsapi_base' — 静默授权(只获取 openid,无需同意页) + // state: 任意字符串,原样返回,用于防 CSRF;后端可校验一致性 + // 旧版: sendWeChatAuth(scope: ..., state: ...) + // 新版: authBy(which: NormalAuth(scope: ..., state: ...)) await _fluwx.authBy(which: NormalAuth(scope: 'snsapi_userinfo', state: 'genex_login')); - // 授权结果通过 addSubscriber 异步回调 + // 授权结果异步通过 addSubscriber 回调,不在此处等待 } + // WeChatAuthResponse 字段说明: + // errCode: 0 = 成功, -4 = 用户拒绝, -2 = 用户取消 + // code: 一次性授权码(5 分钟有效),发送后端换取 access_token + unionid + // 旧版响应类: WXAuthResp,新版: WeChatAuthResponse Future _handleWechatAuthResp(WeChatAuthResponse resp) async { setState(() => _wechatLoading = false); if ((resp.errCode ?? -1) != 0 || resp.code == null) return; diff --git a/frontend/genex-mobile/lib/main.dart b/frontend/genex-mobile/lib/main.dart index 917b429..c6d6790 100644 --- a/frontend/genex-mobile/lib/main.dart +++ b/frontend/genex-mobile/lib/main.dart @@ -43,17 +43,23 @@ import 'features/profile/presentation/pages/share_page.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); - // ── 微信 SDK 初始化 ──────────────────────────────────────────────────── + // ── 微信 SDK 初始化 (fluwx 5.x) ────────────────────────────────────── // WECHAT_APP_ID 在构建时通过 --dart-define 注入,例如: // flutter build apk --dart-define=WECHAT_APP_ID=wx0000000000000000 // flutter build ipa --dart-define=WECHAT_APP_ID=wx0000000000000000 // // 未传入 WECHAT_APP_ID 时(本地开发 / CI 未配置),跳过初始化, - // WelcomePage 中点击微信按钮会提示「请先安装微信 App」(isWeChatInstalled=false)。 + // WelcomePage 中点击微信按钮会提示「微信未安装」(isWeChatInstalled=false)。 // // universalLink: iOS Universal Links 地址,需在微信开放平台填写并配置 // apple-app-site-association 文件(路径: https://www.gogenex.com/wechat/apple-app-site-association) // 详见: https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Universal_Links/Universal_Links.html + // + // fluwx 5.x 迁移说明: + // 旧版 (3.x): 顶层函数 registerWxApi(appId: ..., universalLink: ...) + // 新版 (5.x): 实例方法 Fluwx().registerApi(appId: ..., universalLink: ...) + // 注册只需执行一次(app 启动时),Fluwx 底层 MethodChannel 为单例, + // 后续在 WelcomePage 中新建 Fluwx() 实例可共享同一注册状态。 const wechatAppId = String.fromEnvironment('WECHAT_APP_ID', defaultValue: ''); if (wechatAppId.isNotEmpty) { await Fluwx().registerApi(appId: wechatAppId, universalLink: 'https://www.gogenex.com/wechat/');