diff --git a/src/api/auth.ts b/src/api/auth.ts index 5afc556..3a336c1 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -18,43 +18,27 @@ export type AuthUser = Record; /** 发送短信验证码时 `scene` 取值(与后端约定一致) */ export const AUTH_SMS_SCENE_LOGIN = "login"; -/** `POST .../auth/sms/send` 响应(含开发环境可能返回的 `testCode`) */ +/** `POST .../auth/sms/send` 响应(联调环境可能返回 `testCode`) */ export type SmsSendResponse = { testCode?: string; - data?: { - testCode?: string; - }; }; -function pickSmsSendPayload(body: SmsSendResponse & { data?: SmsSendResponse }): SmsSendResponse { - if (body.data && typeof body.data === "object") { - return { ...body, testCode: body.data.testCode ?? body.testCode }; - } - return body; -} - -/** `POST .../auth/sms/login` 成功后的典型响应(若后端包一层 `data`,见 `pickAuthPayload`) */ +/** + * `POST .../auth/sms/login` 成功响应(与后端一致:根级 token + `user`)。 + * 例:`{ accessToken, refreshToken, user: { id, phone, nickname, avatarUrl } }` + */ export type SmsLoginResponse = { accessToken: string; refreshToken: string; user?: AuthUser; }; -/** `POST .../auth/refresh` 成功后的典型响应 */ +/** `POST .../auth/refresh` 成功响应 */ export type RefreshTokenResponse = { accessToken: string; refreshToken: string; }; -function pickAuthPayload( - body: T & { data?: T }, -): T { - if (body.data && typeof body.data.accessToken === "string") { - return body.data; - } - return body; -} - /** 写入一对令牌,并清理历史 mock 键 `token` */ export function persistTokens(accessToken: string, refreshToken: string): void { window.localStorage.setItem(ACCESS_TOKEN_KEY, accessToken); @@ -66,6 +50,7 @@ export function persistTokens(accessToken: string, refreshToken: string): void { export function persistUser(user: AuthUser | undefined): void { if (!user) return; window.localStorage.setItem(USER_KEY, JSON.stringify(user)); + window.dispatchEvent(new CustomEvent("chatone-user-changed")); } /** 清除本地会话(含兼容旧键) */ @@ -74,6 +59,7 @@ export function clearSession(): void { window.localStorage.removeItem(REFRESH_TOKEN_KEY); window.localStorage.removeItem(USER_KEY); window.localStorage.removeItem("token"); + window.dispatchEvent(new CustomEvent("chatone-user-changed")); } /** @@ -119,12 +105,8 @@ export async function sendAuthSmsCode( phone: string, scene: string = AUTH_SMS_SCENE_LOGIN, ): Promise<{ testCode?: string }> { - const raw = await postJson(ApiPath.authSmsSend, { - phone, - scene, - }); - const merged = pickSmsSendPayload(raw); - const testCode = merged.testCode; + const raw = await postJson(ApiPath.authSmsSend, { phone, scene }); + const testCode = raw.testCode; if (typeof testCode === "string" && testCode.trim()) { return { testCode: testCode.trim() }; } @@ -137,11 +119,7 @@ export async function sendAuthSmsCode( * @param code 短信验证码 */ export async function smsLogin(phone: string, code: string): Promise { - const raw = await postJson(ApiPath.authSmsLogin, { - phone, - code, - }); - return pickAuthPayload(raw); + return postJson(ApiPath.authSmsLogin, { phone, code }); } /** @@ -149,9 +127,5 @@ export async function smsLogin(phone: string, code: string): Promise { - const raw = await postJson( - ApiPath.authRefresh, - { refreshToken }, - ); - return pickAuthPayload(raw); + return postJson(ApiPath.authRefresh, { refreshToken }); } diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 9cb462e..87d7f2c 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,5 +1,5 @@ import type { CSSProperties } from "react"; -import { useEffect, useMemo, useRef, useState } from "react"; +import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react"; import { ArrowUpOutlined, LogoutOutlined, @@ -44,13 +44,13 @@ type UiMessage = { thinking?: string; }; -function readStoredUserProfile(): { displayName: string; avatar?: string } { +function parseStoredUserProfileJson(raw: string | null): { displayName: string; avatar?: string } { try { - const raw = window.localStorage.getItem(USER_KEY); if (!raw) return { displayName: "用户" }; const u = JSON.parse(raw) as Record; + const nickname = typeof u.nickname === "string" && u.nickname.trim() ? u.nickname.trim() : ""; const displayName = - (typeof u.nickname === "string" && u.nickname) || + nickname || (typeof u.name === "string" && u.name) || (typeof u.username === "string" && u.username) || (typeof u.phone === "string" && u.phone) || @@ -65,6 +65,19 @@ function readStoredUserProfile(): { displayName: string; avatar?: string } { } } +/** 与 `persistUser` 内触发的 `chatone-user-changed` 及跨标签 `storage` 对齐,用于刷新侧栏昵称 */ +function subscribeUserProfile(onChange: () => void) { + const onStorage = (e: StorageEvent) => { + if (e.key === USER_KEY || e.key === null) onChange(); + }; + window.addEventListener("chatone-user-changed", onChange); + window.addEventListener("storage", onStorage); + return () => { + window.removeEventListener("chatone-user-changed", onChange); + window.removeEventListener("storage", onStorage); + }; +} + const INITIAL_MESSAGES: UiMessage[] = [ { id: "init-assistant", @@ -108,7 +121,15 @@ export default function HomePage() { [], ); - const userProfile = useMemo(() => readStoredUserProfile(), []); + const userJsonSnapshot = useSyncExternalStore( + subscribeUserProfile, + () => window.localStorage.getItem(USER_KEY) ?? "", + () => "", + ); + const userProfile = useMemo( + () => parseStoredUserProfileJson(userJsonSnapshot || null), + [userJsonSnapshot], + ); /** 用户信息下拉:略增大行高与项间距 */ const userMenuItemStyle: CSSProperties = {