1. 修复便签保存后无法编辑的问题 - 移除 handleSave 中对 updateNotesContent 的调用 - 避免触发 notes useEffect 导致 content 被重置 2. 修复提醒顺延后列表不刷新 - 在 handlePostpone 函数末尾添加 fetchEvents() 3. 优化提醒完成状态切换的错误处理 - stores 会在更新失败时自动回滚数据 4. 优化便签保存状态显示 - 添加 hasUnsavedChanges 状态 - 区分"未保存"、"保存中"、"已保存"三种状态 5. 修复列表底部填充问题 - 纪念日列表和提醒列表在内容刚好一屏时 - 添加基础填充避免被 AI 输入框遮挡 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
148 lines
3.8 KiB
TypeScript
148 lines
3.8 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) {
|
||
const errorMsg = data.error || 'Request failed';
|
||
// 如果有详细信息,显示出来
|
||
if (data.details) {
|
||
console.error('Validation details:', JSON.stringify(data.details));
|
||
}
|
||
throw new Error(errorMsg);
|
||
}
|
||
|
||
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 }),
|
||
});
|
||
},
|
||
},
|
||
};
|