- 添加 Redux 状态管理,支持用户登录和个人信息的持久化 - 新增目标管理页面,允许用户设置每日卡路里和步数目标 - 更新首页,移除旧的活动展示,改为固定的热点功能卡片 - 修改布局以适应新功能的展示和交互 - 更新依赖,添加 @reduxjs/toolkit 和 react-redux 库以支持状态管理 - 新增 API 服务模块,处理与后端的交互
89 lines
2.5 KiB
TypeScript
89 lines
2.5 KiB
TypeScript
import { buildApiUrl } from '@/constants/Api';
|
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
|
|
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
|
|
let inMemoryToken: string | null = null;
|
|
|
|
export async function setAuthToken(token: string | null): Promise<void> {
|
|
inMemoryToken = token;
|
|
}
|
|
|
|
export function getAuthToken(): string | null {
|
|
return inMemoryToken;
|
|
}
|
|
|
|
export type ApiRequestOptions = {
|
|
method?: HttpMethod;
|
|
headers?: Record<string, string>;
|
|
body?: any;
|
|
signal?: AbortSignal;
|
|
};
|
|
|
|
export type ApiResponse<T> = {
|
|
data: T;
|
|
};
|
|
|
|
async function doFetch<T>(path: string, options: ApiRequestOptions = {}): Promise<T> {
|
|
const url = buildApiUrl(path);
|
|
const headers: Record<string, string> = {
|
|
'Content-Type': 'application/json',
|
|
...(options.headers || {}),
|
|
};
|
|
|
|
const token = getAuthToken();
|
|
if (token) {
|
|
headers['Authorization'] = `Bearer ${token}`;
|
|
}
|
|
|
|
const response = await fetch(url, {
|
|
method: options.method ?? 'GET',
|
|
headers,
|
|
body: options.body != null ? JSON.stringify(options.body) : undefined,
|
|
signal: options.signal,
|
|
});
|
|
|
|
const text = await response.text();
|
|
let json: any = null;
|
|
try {
|
|
json = text ? JSON.parse(text) : null;
|
|
} catch {
|
|
// 非 JSON 响应
|
|
}
|
|
|
|
if (!response.ok) {
|
|
const errorMessage = (json && (json.message || json.error)) || `HTTP ${response.status}`;
|
|
const error = new Error(errorMessage);
|
|
// @ts-expect-error augment
|
|
error.status = response.status;
|
|
throw error;
|
|
}
|
|
|
|
// 支持后端返回 { data: ... } 或直接返回对象
|
|
return (json && (json.data ?? json)) as T;
|
|
}
|
|
|
|
export const api = {
|
|
get: <T>(path: string, options?: ApiRequestOptions) => doFetch<T>(path, { ...options, method: 'GET' }),
|
|
post: <T>(path: string, body?: any, options?: ApiRequestOptions) => doFetch<T>(path, { ...options, method: 'POST', body }),
|
|
put: <T>(path: string, body?: any, options?: ApiRequestOptions) => doFetch<T>(path, { ...options, method: 'PUT', body }),
|
|
patch: <T>(path: string, body?: any, options?: ApiRequestOptions) => doFetch<T>(path, { ...options, method: 'PATCH', body }),
|
|
delete: <T>(path: string, options?: ApiRequestOptions) => doFetch<T>(path, { ...options, method: 'DELETE' }),
|
|
};
|
|
|
|
export const STORAGE_KEYS = {
|
|
authToken: '@auth_token',
|
|
userProfile: '@user_profile',
|
|
} as const;
|
|
|
|
export async function loadPersistedToken(): Promise<string | null> {
|
|
try {
|
|
const t = await AsyncStorage.getItem(STORAGE_KEYS.authToken);
|
|
return t || null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
|