qia-client/src/stores/index.ts
ddshi 9e4b4022bd feat: 优化提醒功能,修复状态保存问题
- 优化提醒分组逻辑,精确判断过期时间
- 已完成但未过期的提醒仍显示在主列表(划掉状态)
- 修复 checkbox 点击事件处理
- 添加乐观更新,UI 即时响应
- 添加归档页和设置页路由
- 修复后端 is_completed 字段验证问题
2026-02-03 13:19:06 +08:00

241 lines
7.5 KiB
TypeScript

import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import type { User, Event, Note, AIConversation, EventType } from '../types';
import { api } from '../services/api';
// 应用设置类型
interface AppSettings {
showHolidays: boolean; // 是否显示节假日
}
// 默认设置
const defaultSettings: AppSettings = {
showHolidays: true,
};
interface AppState {
// Auth state
user: User | null;
isAuthenticated: boolean;
isLoading: boolean;
// Settings state
settings: AppSettings;
updateSettings: (settings: Partial<AppSettings>) => void;
// Data state
events: Event[];
notes: Note | null;
conversations: AIConversation[];
// Actions
setUser: (user: User | null) => void;
setLoading: (loading: boolean) => void;
setEvents: (events: Event[]) => void;
addEvent: (event: Event) => void;
updateEvent: (id: string, updates: Partial<Event>) => void;
deleteEvent: (id: string) => void;
setNotes: (notes: Note | null) => void;
updateNotesContent: (content: string) => void;
setConversations: (conversations: AIConversation[]) => void;
addConversation: (conversation: AIConversation) => void;
// Data fetch actions
fetchEvents: (type?: EventType) => Promise<void>;
fetchNotes: () => Promise<void>;
saveNotes: (content: string) => Promise<void>;
createEvent: (event: Partial<Event>) => Promise<{ error: any }>;
updateEventById: (id: string, event: Partial<Event>) => Promise<{ error: any }>;
deleteEventById: (id: string) => Promise<{ error: any }>;
// Auth actions
login: (email: string, password: string) => Promise<{ error: any }>;
register: (email: string, password: string, nickname?: string) => Promise<{ error: any }>;
logout: () => Promise<void>;
checkAuth: () => Promise<void>;
}
export const useAppStore = create<AppState>()(
persist(
(set) => ({
// Initial state
user: null,
isAuthenticated: false,
isLoading: true,
settings: defaultSettings,
events: [],
notes: null,
conversations: [],
// Settings
updateSettings: (newSettings) =>
set((state) => ({
settings: { ...state.settings, ...newSettings },
})),
// Setters
setUser: (user) => set({ user, isAuthenticated: !!user }),
setLoading: (isLoading) => set({ isLoading }),
setEvents: (events) => set({ events }),
addEvent: (event) => set((state) => ({ events: [...state.events, event] })),
updateEvent: (id, updates) => set((state) => ({
events: state.events.map((e) => (e.id === id ? { ...e, ...updates } : e)),
})),
deleteEvent: (id) => set((state) => ({
events: state.events.filter((e) => e.id !== id),
})),
setNotes: (notes) => set({ notes }),
updateNotesContent: (content) => set((state) => ({
notes: state.notes ? { ...state.notes, content } : null,
})),
setConversations: (conversations) => set({ conversations }),
addConversation: (conversation) => set((state) => ({
conversations: [conversation, ...state.conversations],
})),
// Data fetch actions
fetchEvents: async (type) => {
try {
const events = await api.events.list(type);
set({ events });
} catch (error) {
console.error('Failed to fetch events:', error);
}
},
fetchNotes: async () => {
try {
const notes = await api.notes.get();
set({ notes });
} catch (error) {
console.error('Failed to fetch notes:', error);
}
},
saveNotes: async (content) => {
try {
const notes = await api.notes.update(content);
set({ notes });
} catch (error) {
console.error('Failed to save notes:', error);
throw error;
}
},
createEvent: async (event) => {
try {
const newEvent = await api.events.create(event);
set((state) => ({ events: [...state.events, newEvent] }));
return { error: null };
} catch (error: any) {
return { error: error.message || '创建失败' };
}
},
updateEventById: async (id, event) => {
try {
// 乐观更新:立即更新本地状态
set((state) => ({
events: state.events.map((e) =>
e.id === id ? { ...e, ...event } : e
),
}));
// 发送 API 请求
await api.events.update(id, event);
// 乐观更新已生效,不需要用 API 返回数据覆盖
// 避免 API 返回数据不完整导致状态丢失
return { error: null };
} catch (error: any) {
// 失败时回滚,重新获取数据
const events = await api.events.list();
set({ events });
return { error: error.message || '更新失败' };
}
},
deleteEventById: async (id) => {
try {
await api.events.delete(id);
set((state) => ({
events: state.events.filter((e) => e.id !== id),
}));
return { error: null };
} catch (error: any) {
return { error: error.message || '删除失败' };
}
},
// Auth actions
login: async (email, password) => {
try {
const { user, token, refreshToken } = await api.auth.login(email, password);
api.setTokens(token, refreshToken);
set({ user, isAuthenticated: true });
return { error: null };
} catch (error: any) {
const errorMessage = error.message || '登录失败,请检查邮箱和密码';
return { error: errorMessage };
}
},
register: async (email, password, nickname) => {
try {
const { user, token, refreshToken } = await api.auth.register(email, password, nickname);
api.setTokens(token, refreshToken);
set({ user, isAuthenticated: true });
return { error: null };
} catch (error: any) {
const errorMessage = error.message || '注册失败,请稍后重试';
return { error: errorMessage };
}
},
logout: async () => {
try {
await api.auth.logout();
} catch {
// Ignore logout error
}
api.clearTokens();
set({ user: null, isAuthenticated: false, events: [], notes: null });
},
checkAuth: async () => {
set({ isLoading: true });
const token = api.getToken();
if (!token) {
set({ isAuthenticated: false, isLoading: false });
return;
}
try {
const { user } = await api.auth.getCurrentUser();
set({ user, isAuthenticated: true, isLoading: false });
} catch {
// Token invalid, try refresh
try {
const { token: newToken } = await api.auth.refreshToken();
const { user } = await api.auth.getCurrentUser();
const refreshToken = api.getRefreshToken()!;
api.setTokens(newToken, refreshToken);
set({ user, isAuthenticated: true, isLoading: false });
} catch {
api.clearTokens();
set({ isAuthenticated: false, isLoading: false });
}
}
},
}),
{
name: 'qia-storage',
storage: createJSONStorage(() => localStorage),
partialize: (state) => ({
user: state.user,
isAuthenticated: state.isAuthenticated,
settings: state.settings,
}),
}
)
);