From 1d8b6936f22fdd89f13589f726323b1bde4a978a Mon Sep 17 00:00:00 2001 From: Karlcarlo Date: Thu, 25 Jun 2026 18:39:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=BA=AB=E4=BB=BD?= =?UTF-8?q?=E4=B8=80=E8=87=B4=E6=80=A7=E6=A3=80=E6=9F=A5=EF=BC=8C=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E7=99=BB=E5=BD=95=E9=80=BB=E8=BE=91=E4=BB=A5=E6=B8=85?= =?UTF-8?q?=E9=99=A4=E4=B8=8D=E4=B8=80=E8=87=B4=E7=9A=84=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/app/pages/LoginPage.tsx | 167 +++++++++++++++------------ 1 file changed, 91 insertions(+), 76 deletions(-) diff --git a/frontend/src/app/pages/LoginPage.tsx b/frontend/src/app/pages/LoginPage.tsx index 19528b3..6f748d4 100644 --- a/frontend/src/app/pages/LoginPage.tsx +++ b/frontend/src/app/pages/LoginPage.tsx @@ -1,76 +1,91 @@ -import { useEffect, useState } from "react"; -import { useNavigate } from "react-router"; -import { motion } from "motion/react"; -import { Heart } from "lucide-react"; -import { useUser } from "../context/UserContext"; -import { devLogin, fetchApi } from "../services/api"; -import { debugLog, clearDebugLogs } from "../services/debug"; - -export function LoginPage() { - const navigate = useNavigate(); - const { setTokenAndUser, fetchProfile } = useUser(); - const [error, setError] = useState(""); - - useEffect(() => { - clearDebugLogs(); - - const params = new URLSearchParams(window.location.search); - debugLog(`URL: ${window.location.href.substring(0, 100)}`); - debugLog(`search: ${window.location.search}`); - - const openidParam = params.get('openid'); - if (openidParam && openidParam.trim()) { - debugLog(`openid: ${openidParam}`); - fetchApi("/auth/openid-login", { - method: "POST", - body: JSON.stringify({ openid: openidParam }), - }).then((res: any) => { - debugLog(`openid-login OK: id=${res.user_id}, phone=${res.mobile}`); - setTokenAndUser(res.token, { id: res.user_id, openid: res.openid, phone: res.mobile }); - fetchProfile().then((dto) => { - if (dto) debugLog(`fetchProfile: name=${dto.name}, phone=${dto.phone}, onboarded=${dto.is_onboarded}`); - navigate(dto?.is_onboarded ? "/home" : "/onboarding", { replace: true }); - }).catch((e: any) => { - debugLog(`fetchProfile error: ${e?.message}`); - setError("登录已失效,请在小程序重新进入"); - }); - }).catch((e: any) => { - debugLog(`openid-login error: ${e?.message}`); - setError("登录失败: " + (e?.message || "")); - }); - return; - } - - debugLog(`no openid param (val=${openidParam || '(empty)'}), fallback to dev-login`); - devLogin().then((res) => { - debugLog(`dev-login: openid=${res.openid}, phone=${res.mobile}`); - setTokenAndUser(res.token, { id: res.user_id, openid: res.openid, phone: res.mobile }); - fetchProfile().then((dto) => { - if (dto) debugLog(`fetchProfile: name=${dto.name}, phone=${dto.phone}`); - navigate(dto?.is_onboarded ? "/home" : "/onboarding", { replace: true }); - }); - }).catch((err) => { - debugLog(`dev-login error: ${err?.message}`); - setError(err?.message || "登录失败,请刷新重试"); - }); - }, []); // eslint-disable-line react-hooks/exhaustive-deps - - return ( -
-
- - - - {error ? ( -

{error}

- ) : ( -

加载中...

- )} -
-
- ); -} +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router"; +import { motion } from "motion/react"; +import { Heart } from "lucide-react"; +import { useUser } from "../context/UserContext"; +import { devLogin, fetchApi, getToken, clearToken, decodeToken } from "../services/api"; +import { debugLog, clearDebugLogs } from "../services/debug"; + +export function LoginPage() { + const navigate = useNavigate(); + const { setTokenAndUser, fetchProfile } = useUser(); + const [error, setError] = useState(""); + + useEffect(() => { + clearDebugLogs(); + + // 身份一致性检查:URL 中的 openid 与缓存 token 不一致时清除缓存 + const urlOpenid = new URLSearchParams(window.location.search).get('openid'); + const cachedToken = getToken(); + if (urlOpenid && cachedToken) { + try { + const payload = decodeToken(cachedToken); + if (payload.openid !== urlOpenid) { + debugLog(`[auth] openid 不一致: URL=${urlOpenid}, cache=${payload.openid},清除缓存`); + clearToken(); + } + } catch (e) { + debugLog(`[auth] token 解码失败: ${e}`); + } + } + + const params = new URLSearchParams(window.location.search); + debugLog(`URL: ${window.location.href.substring(0, 100)}`); + debugLog(`search: ${window.location.search}`); + + const openidParam = params.get('openid'); + if (openidParam && openidParam.trim()) { + debugLog(`openid: ${openidParam}`); + fetchApi("/auth/openid-login", { + method: "POST", + body: JSON.stringify({ openid: openidParam }), + }).then((res: any) => { + debugLog(`openid-login OK: id=${res.user_id}, phone=${res.mobile}`); + setTokenAndUser(res.token, { id: res.user_id, openid: res.openid, phone: res.mobile }); + fetchProfile().then((dto) => { + if (dto) debugLog(`fetchProfile: name=${dto.name}, phone=${dto.phone}, onboarded=${dto.is_onboarded}`); + navigate(dto?.is_onboarded ? "/home" : "/onboarding", { replace: true }); + }).catch((e: any) => { + debugLog(`fetchProfile error: ${e?.message}`); + setError("登录已失效,请在小程序重新进入"); + }); + }).catch((e: any) => { + debugLog(`openid-login error: ${e?.message}`); + setError("登录失败: " + (e?.message || "")); + }); + return; + } + + debugLog(`no openid param (val=${openidParam || '(empty)'}), fallback to dev-login`); + devLogin().then((res) => { + debugLog(`dev-login: openid=${res.openid}, phone=${res.mobile}`); + setTokenAndUser(res.token, { id: res.user_id, openid: res.openid, phone: res.mobile }); + fetchProfile().then((dto) => { + if (dto) debugLog(`fetchProfile: name=${dto.name}, phone=${dto.phone}`); + navigate(dto?.is_onboarded ? "/home" : "/onboarding", { replace: true }); + }); + }).catch((err) => { + debugLog(`dev-login error: ${err?.message}`); + setError(err?.message || "登录失败,请刷新重试"); + }); + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + return ( +
+
+ + + + {error ? ( +

{error}

+ ) : ( +

加载中...

+ )} +
+
+ ); +}