ddshi 8725108195 fix: 修复多个交互问题
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>
2026-02-27 15:58:08 +08:00

148 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 }),
});
},
},
};