- LandingPage: 全新水墨晕染算法背景,循环墨迹动画 - 登录/注册页: 禅意黑白极简风格 - HomePage: 三栏布局优化,标题颜色语义化 - 纪念日组件: 分类逻辑优化,颜色语义统一 - 提醒组件: 分组标题颜色优化,逾期提示更醒目 - 修复农历日期边界问题(29/30天月份) - 添加 lunar-javascript 类型声明 - 清理未使用的导入和代码
143 lines
3.6 KiB
TypeScript
143 lines
3.6 KiB
TypeScript
import type { User, Event, Note, EventType } from '../types';
|
||
|
||
// API Base URL - from environment variable
|
||
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000/api';
|
||
|
||
// Token storage
|
||
const TOKEN_KEY = 'qia_token';
|
||
const REFRESH_TOKEN_KEY = 'qia_refresh_token';
|
||
|
||
export const api = {
|
||
// Token management
|
||
getToken: () => localStorage.getItem(TOKEN_KEY),
|
||
getRefreshToken: () => localStorage.getItem(REFRESH_TOKEN_KEY),
|
||
setTokens: (token: string, refreshToken: string) => {
|
||
localStorage.setItem(TOKEN_KEY, token);
|
||
localStorage.setItem(REFRESH_TOKEN_KEY, refreshToken);
|
||
},
|
||
clearTokens: () => {
|
||
localStorage.removeItem(TOKEN_KEY);
|
||
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
||
},
|
||
|
||
// Generic request helper
|
||
async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
||
const token = api.getToken();
|
||
|
||
const headers: HeadersInit = {
|
||
'Content-Type': 'application/json',
|
||
...(token && { Authorization: `Bearer ${token}` }),
|
||
...options.headers,
|
||
};
|
||
|
||
const response = await fetch(`${API_URL}${endpoint}`, {
|
||
...options,
|
||
headers,
|
||
credentials: 'include', // 确保跨域请求时发送凭证(cookie)
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (!response.ok) {
|
||
throw new Error(data.error || 'Request failed');
|
||
}
|
||
|
||
return data;
|
||
},
|
||
|
||
// Auth API
|
||
auth: {
|
||
register: async (email: string, password: string, nickname?: string) => {
|
||
return api.request<{ user: User; token: string; refreshToken: string }>(
|
||
'/auth/register',
|
||
{
|
||
method: 'POST',
|
||
body: JSON.stringify({ email, password, nickname }),
|
||
}
|
||
);
|
||
},
|
||
|
||
login: async (email: string, password: string) => {
|
||
return api.request<{ user: User; token: string; refreshToken: string }>(
|
||
'/auth/login',
|
||
{
|
||
method: 'POST',
|
||
body: JSON.stringify({ email, password }),
|
||
}
|
||
);
|
||
},
|
||
|
||
logout: async () => {
|
||
return api.request('/auth/logout', { method: 'POST' });
|
||
},
|
||
|
||
getCurrentUser: async () => {
|
||
return api.request<{ user: User }>('/auth/me');
|
||
},
|
||
|
||
refreshToken: async () => {
|
||
const refreshToken = api.getRefreshToken();
|
||
if (!refreshToken) throw new Error('No refresh token');
|
||
|
||
return api.request<{ token: string }>('/auth/refresh', {
|
||
method: 'POST',
|
||
body: JSON.stringify({ refreshToken }),
|
||
});
|
||
},
|
||
},
|
||
|
||
// Events API
|
||
events: {
|
||
list: async (type?: EventType) => {
|
||
const query = type ? `?type=${type}` : '';
|
||
return api.request<Event[]>(`/events${query}`);
|
||
},
|
||
|
||
getById: async (id: string) => {
|
||
return api.request<Event>(`/events/${id}`);
|
||
},
|
||
|
||
create: async (event: Partial<Event>) => {
|
||
return api.request<Event>('/events', {
|
||
method: 'POST',
|
||
body: JSON.stringify(event),
|
||
});
|
||
},
|
||
|
||
update: async (id: string, updates: Partial<Event>) => {
|
||
return api.request<Event>(`/events/${id}`, {
|
||
method: 'PUT',
|
||
body: JSON.stringify(updates),
|
||
});
|
||
},
|
||
|
||
delete: async (id: string) => {
|
||
return api.request(`/events/${id}`, { method: 'DELETE' });
|
||
},
|
||
},
|
||
|
||
// Notes API
|
||
notes: {
|
||
get: async () => {
|
||
return api.request<Note>('/notes');
|
||
},
|
||
|
||
update: async (content: string) => {
|
||
return api.request<Note>('/notes', {
|
||
method: 'PUT',
|
||
body: JSON.stringify({ content }),
|
||
});
|
||
},
|
||
},
|
||
|
||
// AI API
|
||
ai: {
|
||
parse: async (message: string) => {
|
||
return api.request<{ parsed: any; response: string }>('/ai/parse', {
|
||
method: 'POST',
|
||
body: JSON.stringify({ message }),
|
||
});
|
||
},
|
||
},
|
||
};
|