import { createCheckin, fetchDailyCheckins, updateCheckin } from '@/services/checkins'; import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; export type CheckinExercise = { key: string; name: string; category: string; sets: number; // 组数 reps?: number; // 每组重复(计次型) durationSec?: number; // 每组时长(计时型) completed?: boolean; // 是否已完成该动作 }; export type CheckinRecord = { id: string; date: string; // YYYY-MM-DD items: CheckinExercise[]; note?: string; }; export type CheckinState = { byDate: Record; currentDate: string | null; }; const initialState: CheckinState = { byDate: {}, currentDate: null, }; function ensureRecord(state: CheckinState, date: string): CheckinRecord { if (!state.byDate[date]) { state.byDate[date] = { id: `rec_${date}`, date, items: [], }; } return state.byDate[date]; } const checkinSlice = createSlice({ name: 'checkin', initialState, reducers: { setCurrentDate(state, action: PayloadAction) { state.currentDate = action.payload; // 期望格式 YYYY-MM-DD ensureRecord(state, action.payload); }, addExercise(state, action: PayloadAction<{ date: string; item: CheckinExercise }>) { const rec = ensureRecord(state, action.payload.date); // 若同 key 已存在则覆盖参数(更接近用户“重新选择/编辑”的心智) const idx = rec.items.findIndex((it) => it.key === action.payload.item.key); const normalized: CheckinExercise = { ...action.payload.item, completed: false }; if (idx >= 0) rec.items[idx] = normalized; else rec.items.push(normalized); }, removeExercise(state, action: PayloadAction<{ date: string; key: string }>) { const rec = ensureRecord(state, action.payload.date); rec.items = rec.items.filter((it) => it.key !== action.payload.key); }, toggleExerciseCompleted(state, action: PayloadAction<{ date: string; key: string }>) { const rec = ensureRecord(state, action.payload.date); const idx = rec.items.findIndex((it) => it.key === action.payload.key); if (idx >= 0) rec.items[idx].completed = !rec.items[idx].completed; }, setNote(state, action: PayloadAction<{ date: string; note: string }>) { const rec = ensureRecord(state, action.payload.date); rec.note = action.payload.note; }, resetDate(state, action: PayloadAction) { delete state.byDate[action.payload]; }, }, extraReducers: (builder) => { builder .addCase(syncCheckin.fulfilled, (state, action) => { if (!action.payload) return; const { date, items, note, id } = action.payload; state.byDate[date] = { id: id || state.byDate[date]?.id || `rec_${date}`, date, items: items || [], note, }; }) .addCase(getDailyCheckins.fulfilled, (state, action) => { const date = action.payload.date as string | undefined; const list = action.payload.list || []; if (!date) return; let mergedItems: CheckinExercise[] = []; let note: string | undefined = undefined; let id: string | undefined = state.byDate[date]?.id; for (const rec of list) { if (!rec) continue; if (rec.id && !id) id = String(rec.id); const itemsFromMetrics = rec?.metrics?.items ?? rec?.items; if (Array.isArray(itemsFromMetrics)) { mergedItems = itemsFromMetrics as CheckinExercise[]; } if (typeof rec.notes === 'string') note = rec.notes as string; } state.byDate[date] = { id: id || state.byDate[date]?.id || `rec_${date}`, date, items: mergedItems, note, }; }); }, }); export const { setCurrentDate, addExercise, removeExercise, toggleExerciseCompleted, setNote, resetDate } = checkinSlice.actions; export default checkinSlice.reducer; // Thunks export const syncCheckin = createAsyncThunk('checkin/sync', async (record: { date: string; items: CheckinExercise[]; note?: string; id?: string }, { getState }) => { const state = getState() as any; const existingId: string | undefined = record.id || state?.checkin?.byDate?.[record.date]?.id; const metrics = { items: record.items } as any; if (!existingId || existingId.startsWith('rec_')) { const created = await createCheckin({ title: '每日训练打卡', checkinDate: record.date, notes: record.note, metrics, startedAt: new Date().toISOString(), }); const newId = (created as any)?.id; return { id: newId, date: record.date, items: record.items, note: record.note }; } const updated = await updateCheckin({ id: existingId, notes: record.note, metrics }); const newId = (updated as any)?.id ?? existingId; return { id: newId, date: record.date, items: record.items, note: record.note }; }); // 获取当天打卡列表(用于进入页面时拉取最新云端数据) export const getDailyCheckins = createAsyncThunk('checkin/getDaily', async (date?: string) => { const list = await fetchDailyCheckins(date); return { date, list } as { date?: string; list: any[] }; });