登录/刷新/发短信直接解析根级字段,移除 data 包装与 mergeUser 等多余逻辑。 首页侧栏用户信息随 localStorage 与 chatone-user-changed 更新,优先展示 nickname。 Made-with: Cursor
This commit is contained in:
@@ -18,43 +18,27 @@ export type AuthUser = Record<string, unknown>;
|
|||||||
/** 发送短信验证码时 `scene` 取值(与后端约定一致) */
|
/** 发送短信验证码时 `scene` 取值(与后端约定一致) */
|
||||||
export const AUTH_SMS_SCENE_LOGIN = "login";
|
export const AUTH_SMS_SCENE_LOGIN = "login";
|
||||||
|
|
||||||
/** `POST .../auth/sms/send` 响应(含开发环境可能返回的 `testCode`) */
|
/** `POST .../auth/sms/send` 响应(联调环境可能返回 `testCode`) */
|
||||||
export type SmsSendResponse = {
|
export type SmsSendResponse = {
|
||||||
testCode?: string;
|
testCode?: string;
|
||||||
data?: {
|
|
||||||
testCode?: string;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function pickSmsSendPayload(body: SmsSendResponse & { data?: SmsSendResponse }): SmsSendResponse {
|
/**
|
||||||
if (body.data && typeof body.data === "object") {
|
* `POST .../auth/sms/login` 成功响应(与后端一致:根级 token + `user`)。
|
||||||
return { ...body, testCode: body.data.testCode ?? body.testCode };
|
* 例:`{ accessToken, refreshToken, user: { id, phone, nickname, avatarUrl } }`
|
||||||
}
|
*/
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** `POST .../auth/sms/login` 成功后的典型响应(若后端包一层 `data`,见 `pickAuthPayload`) */
|
|
||||||
export type SmsLoginResponse = {
|
export type SmsLoginResponse = {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
user?: AuthUser;
|
user?: AuthUser;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** `POST .../auth/refresh` 成功后的典型响应 */
|
/** `POST .../auth/refresh` 成功响应 */
|
||||||
export type RefreshTokenResponse = {
|
export type RefreshTokenResponse = {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function pickAuthPayload<T extends { accessToken?: string; refreshToken?: string }>(
|
|
||||||
body: T & { data?: T },
|
|
||||||
): T {
|
|
||||||
if (body.data && typeof body.data.accessToken === "string") {
|
|
||||||
return body.data;
|
|
||||||
}
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 写入一对令牌,并清理历史 mock 键 `token` */
|
/** 写入一对令牌,并清理历史 mock 键 `token` */
|
||||||
export function persistTokens(accessToken: string, refreshToken: string): void {
|
export function persistTokens(accessToken: string, refreshToken: string): void {
|
||||||
window.localStorage.setItem(ACCESS_TOKEN_KEY, accessToken);
|
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 {
|
export function persistUser(user: AuthUser | undefined): void {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
window.localStorage.setItem(USER_KEY, JSON.stringify(user));
|
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(REFRESH_TOKEN_KEY);
|
||||||
window.localStorage.removeItem(USER_KEY);
|
window.localStorage.removeItem(USER_KEY);
|
||||||
window.localStorage.removeItem("token");
|
window.localStorage.removeItem("token");
|
||||||
|
window.dispatchEvent(new CustomEvent("chatone-user-changed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,12 +105,8 @@ export async function sendAuthSmsCode(
|
|||||||
phone: string,
|
phone: string,
|
||||||
scene: string = AUTH_SMS_SCENE_LOGIN,
|
scene: string = AUTH_SMS_SCENE_LOGIN,
|
||||||
): Promise<{ testCode?: string }> {
|
): Promise<{ testCode?: string }> {
|
||||||
const raw = await postJson<SmsSendResponse & { data?: SmsSendResponse }>(ApiPath.authSmsSend, {
|
const raw = await postJson<SmsSendResponse>(ApiPath.authSmsSend, { phone, scene });
|
||||||
phone,
|
const testCode = raw.testCode;
|
||||||
scene,
|
|
||||||
});
|
|
||||||
const merged = pickSmsSendPayload(raw);
|
|
||||||
const testCode = merged.testCode;
|
|
||||||
if (typeof testCode === "string" && testCode.trim()) {
|
if (typeof testCode === "string" && testCode.trim()) {
|
||||||
return { testCode: testCode.trim() };
|
return { testCode: testCode.trim() };
|
||||||
}
|
}
|
||||||
@@ -137,11 +119,7 @@ export async function sendAuthSmsCode(
|
|||||||
* @param code 短信验证码
|
* @param code 短信验证码
|
||||||
*/
|
*/
|
||||||
export async function smsLogin(phone: string, code: string): Promise<SmsLoginResponse> {
|
export async function smsLogin(phone: string, code: string): Promise<SmsLoginResponse> {
|
||||||
const raw = await postJson<SmsLoginResponse & { data?: SmsLoginResponse }>(ApiPath.authSmsLogin, {
|
return postJson<SmsLoginResponse>(ApiPath.authSmsLogin, { phone, code });
|
||||||
phone,
|
|
||||||
code,
|
|
||||||
});
|
|
||||||
return pickAuthPayload(raw);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -149,9 +127,5 @@ export async function smsLogin(phone: string, code: string): Promise<SmsLoginRes
|
|||||||
* 该请求在 `http` 层不会附带旧的 `Authorization`。
|
* 该请求在 `http` 层不会附带旧的 `Authorization`。
|
||||||
*/
|
*/
|
||||||
export async function refreshTokens(refreshToken: string): Promise<RefreshTokenResponse> {
|
export async function refreshTokens(refreshToken: string): Promise<RefreshTokenResponse> {
|
||||||
const raw = await postJson<RefreshTokenResponse & { data?: RefreshTokenResponse }>(
|
return postJson<RefreshTokenResponse>(ApiPath.authRefresh, { refreshToken });
|
||||||
ApiPath.authRefresh,
|
|
||||||
{ refreshToken },
|
|
||||||
);
|
|
||||||
return pickAuthPayload(raw);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { CSSProperties } from "react";
|
import type { CSSProperties } from "react";
|
||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
||||||
import {
|
import {
|
||||||
ArrowUpOutlined,
|
ArrowUpOutlined,
|
||||||
LogoutOutlined,
|
LogoutOutlined,
|
||||||
@@ -44,13 +44,13 @@ type UiMessage = {
|
|||||||
thinking?: string;
|
thinking?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function readStoredUserProfile(): { displayName: string; avatar?: string } {
|
function parseStoredUserProfileJson(raw: string | null): { displayName: string; avatar?: string } {
|
||||||
try {
|
try {
|
||||||
const raw = window.localStorage.getItem(USER_KEY);
|
|
||||||
if (!raw) return { displayName: "用户" };
|
if (!raw) return { displayName: "用户" };
|
||||||
const u = JSON.parse(raw) as Record<string, unknown>;
|
const u = JSON.parse(raw) as Record<string, unknown>;
|
||||||
|
const nickname = typeof u.nickname === "string" && u.nickname.trim() ? u.nickname.trim() : "";
|
||||||
const displayName =
|
const displayName =
|
||||||
(typeof u.nickname === "string" && u.nickname) ||
|
nickname ||
|
||||||
(typeof u.name === "string" && u.name) ||
|
(typeof u.name === "string" && u.name) ||
|
||||||
(typeof u.username === "string" && u.username) ||
|
(typeof u.username === "string" && u.username) ||
|
||||||
(typeof u.phone === "string" && u.phone) ||
|
(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[] = [
|
const INITIAL_MESSAGES: UiMessage[] = [
|
||||||
{
|
{
|
||||||
id: "init-assistant",
|
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 = {
|
const userMenuItemStyle: CSSProperties = {
|
||||||
|
|||||||
Reference in New Issue
Block a user