chore: add frontend/src/stores/dataStore.ts
This commit is contained in:
@@ -0,0 +1,226 @@
|
|||||||
|
/**
|
||||||
|
* 数据状态管理 - DataStore
|
||||||
|
* 功能:使用 Zustand 管理全局数据状态,包括账户、账单、预算、统计数据的 CRUD
|
||||||
|
* 状态流转:每个 action 遵循 loading -> 请求 -> success/error -> loading=false 的流程
|
||||||
|
* 级联刷新:CRUD 操作成功后自动重新拉取列表和仪表盘汇总数据,保证跨页面数据一致性
|
||||||
|
* API 依赖:accountsApi、recordsApi、budgetsApi、statisticsApi
|
||||||
|
*/
|
||||||
|
import { create } from 'zustand';
|
||||||
|
import type { Account, Record, Budget, DashboardSummary, TrendStat, MonthlyCompare } from '../types';
|
||||||
|
import { accountsApi, recordsApi, budgetsApi, statisticsApi } from '../services';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataState 接口 - 定义全局数据状态的结构
|
||||||
|
*/
|
||||||
|
interface DataState {
|
||||||
|
// ===== 数据实体 =====
|
||||||
|
accounts: Account[]; // 账户列表
|
||||||
|
records: Record[]; // 账单记录列表
|
||||||
|
budgets: Budget[]; // 预算配置列表
|
||||||
|
dashboardSummary: DashboardSummary | null; // 仪表盘汇总数据(余额、收支、预算使用率)
|
||||||
|
trendData: TrendStat[]; // 趋势统计数据(用于折线图)
|
||||||
|
monthlyCompare: MonthlyCompare | null; // 月度对比数据(用于柱状对比图)
|
||||||
|
loading: boolean; // 全局加载状态
|
||||||
|
error: string | null; // 全局错误信息
|
||||||
|
|
||||||
|
// ===== Actions - 数据查询 =====
|
||||||
|
fetchAccounts: () => Promise<void>;
|
||||||
|
fetchRecords: (params?: {
|
||||||
|
accountId?: number;
|
||||||
|
type?: 'income' | 'expense';
|
||||||
|
category?: string;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
|
}) => Promise<void>;
|
||||||
|
fetchBudgets: (month?: string) => Promise<void>;
|
||||||
|
fetchDashboardSummary: () => Promise<void>;
|
||||||
|
fetchTrendData: (startDate: string, endDate: string) => Promise<void>;
|
||||||
|
fetchMonthlyCompare: (month: string) => Promise<void>;
|
||||||
|
|
||||||
|
// ===== Actions - 数据操作 =====
|
||||||
|
createRecord: (data: any) => Promise<void>;
|
||||||
|
updateRecord: (id: number, data: any) => Promise<void>;
|
||||||
|
deleteRecord: (id: number) => Promise<void>;
|
||||||
|
createBudget: (data: any) => Promise<void>;
|
||||||
|
updateBudget: (id: number, data: any) => Promise<void>;
|
||||||
|
deleteBudget: (id: number) => Promise<void>;
|
||||||
|
clearError: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDataStore = create<DataState>((set, get) => ({
|
||||||
|
accounts: [],
|
||||||
|
records: [],
|
||||||
|
budgets: [],
|
||||||
|
dashboardSummary: null,
|
||||||
|
trendData: [],
|
||||||
|
monthlyCompare: null,
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
|
||||||
|
// API: GET /api/accounts - 获取账户列表
|
||||||
|
fetchAccounts: async () => {
|
||||||
|
set({ loading: true, error: null });
|
||||||
|
try {
|
||||||
|
const data = await accountsApi.getAccounts();
|
||||||
|
set({ accounts: data });
|
||||||
|
} catch (error) {
|
||||||
|
set({ error: (error as Error).message });
|
||||||
|
} finally {
|
||||||
|
set({ loading: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// API: GET /api/records - 获取账单记录列表(支持多条件筛选)
|
||||||
|
fetchRecords: async (params) => {
|
||||||
|
set({ loading: true, error: null });
|
||||||
|
try {
|
||||||
|
const data = await recordsApi.getRecords(params);
|
||||||
|
set({ records: data });
|
||||||
|
} catch (error) {
|
||||||
|
set({ error: (error as Error).message });
|
||||||
|
} finally {
|
||||||
|
set({ loading: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// API: GET /api/budgets - 获取预算列表(可按月份筛选)
|
||||||
|
fetchBudgets: async (month) => {
|
||||||
|
set({ loading: true, error: null });
|
||||||
|
try {
|
||||||
|
const data = await budgetsApi.getBudgets(month);
|
||||||
|
set({ budgets: data });
|
||||||
|
} catch (error) {
|
||||||
|
set({ error: (error as Error).message });
|
||||||
|
} finally {
|
||||||
|
set({ loading: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// API: GET /api/statistics/dashboard - 获取仪表盘汇总数据
|
||||||
|
// 同时更新 accounts 状态,因为汇总数据中包含账户信息
|
||||||
|
fetchDashboardSummary: async () => {
|
||||||
|
set({ loading: true, error: null });
|
||||||
|
try {
|
||||||
|
const data = await statisticsApi.getDashboardSummary();
|
||||||
|
set({ dashboardSummary: data, accounts: data.accounts });
|
||||||
|
} catch (error) {
|
||||||
|
set({ error: (error as Error).message });
|
||||||
|
} finally {
|
||||||
|
set({ loading: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// API: fetchTrendData - 获取日期趋势数据(用于折线图)
|
||||||
|
fetchTrendData: async (startDate: string, endDate: string) => {
|
||||||
|
set({ loading: true, error: null });
|
||||||
|
try {
|
||||||
|
const data = await statisticsApi.getTrendStats(startDate, endDate);
|
||||||
|
set({ trendData: data });
|
||||||
|
} catch (error) {
|
||||||
|
set({ error: (error as Error).message });
|
||||||
|
} finally {
|
||||||
|
set({ loading: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// API: fetchMonthlyCompare - 获取本月与上月对比数据(用于柱状对比图)
|
||||||
|
fetchMonthlyCompare: async (month: string) => {
|
||||||
|
set({ loading: true, error: null });
|
||||||
|
try {
|
||||||
|
const data = await statisticsApi.getMonthlyCompare(month);
|
||||||
|
set({ monthlyCompare: data });
|
||||||
|
} catch (error) {
|
||||||
|
set({ error: (error as Error).message });
|
||||||
|
} finally {
|
||||||
|
set({ loading: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// API: POST /api/records - 创建账单记录,成功后级联刷新列表和仪表盘
|
||||||
|
// 级联刷新目的:确保其他页面(如 Dashboard、Budget)能立即看到最新数据
|
||||||
|
createRecord: async (data) => {
|
||||||
|
set({ loading: true, error: null });
|
||||||
|
try {
|
||||||
|
await recordsApi.createRecord(data);
|
||||||
|
await get().fetchRecords();
|
||||||
|
await get().fetchDashboardSummary();
|
||||||
|
} catch (error) {
|
||||||
|
set({ error: (error as Error).message });
|
||||||
|
} finally {
|
||||||
|
set({ loading: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// API: PUT /api/records/:id - 更新账单记录,成功后级联刷新
|
||||||
|
updateRecord: async (id, data) => {
|
||||||
|
set({ loading: true, error: null });
|
||||||
|
try {
|
||||||
|
await recordsApi.updateRecord(id, data);
|
||||||
|
await get().fetchRecords();
|
||||||
|
await get().fetchDashboardSummary();
|
||||||
|
} catch (error) {
|
||||||
|
set({ error: (error as Error).message });
|
||||||
|
} finally {
|
||||||
|
set({ loading: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// API: DELETE /api/records/:id - 删除账单记录,成功后级联刷新
|
||||||
|
deleteRecord: async (id) => {
|
||||||
|
set({ loading: true, error: null });
|
||||||
|
try {
|
||||||
|
await recordsApi.deleteRecord(id);
|
||||||
|
await get().fetchRecords();
|
||||||
|
await get().fetchDashboardSummary();
|
||||||
|
} catch (error) {
|
||||||
|
set({ error: (error as Error).message });
|
||||||
|
} finally {
|
||||||
|
set({ loading: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// API: POST /api/budgets - 创建预算,成功后级联刷新预算列表和仪表盘
|
||||||
|
createBudget: async (data) => {
|
||||||
|
set({ loading: true, error: null });
|
||||||
|
try {
|
||||||
|
await budgetsApi.createBudget(data);
|
||||||
|
await get().fetchBudgets();
|
||||||
|
await get().fetchDashboardSummary();
|
||||||
|
} catch (error) {
|
||||||
|
set({ error: (error as Error).message });
|
||||||
|
} finally {
|
||||||
|
set({ loading: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// API: PUT /api/budgets/:id - 更新预算,成功后级联刷新
|
||||||
|
updateBudget: async (id, data) => {
|
||||||
|
set({ loading: true, error: null });
|
||||||
|
try {
|
||||||
|
await budgetsApi.updateBudget(id, data);
|
||||||
|
await get().fetchBudgets();
|
||||||
|
await get().fetchDashboardSummary();
|
||||||
|
} catch (error) {
|
||||||
|
set({ error: (error as Error).message });
|
||||||
|
} finally {
|
||||||
|
set({ loading: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// API: DELETE /api/budgets/:id - 删除预算,成功后级联刷新
|
||||||
|
deleteBudget: async (id) => {
|
||||||
|
set({ loading: true, error: null });
|
||||||
|
try {
|
||||||
|
await budgetsApi.deleteBudget(id);
|
||||||
|
await get().fetchBudgets();
|
||||||
|
await get().fetchDashboardSummary();
|
||||||
|
} catch (error) {
|
||||||
|
set({ error: (error as Error).message });
|
||||||
|
} finally {
|
||||||
|
set({ loading: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 清除全局错误信息,用于错误状态重置
|
||||||
|
clearError: () => set({ error: null }),
|
||||||
|
}));
|
||||||
Reference in New Issue
Block a user