feat: 优化 AI 教练聊天和打卡功能
- 在 AI 教练聊天界面中添加会话缓存功能,支持冷启动时恢复聊天记录 - 实现轻量防抖机制,确保会话变动时及时保存缓存 - 在打卡功能中集成按月加载打卡记录,提升用户体验 - 更新 Redux 状态管理,支持打卡记录的按月加载和缓存 - 新增打卡日历页面,允许用户查看每日打卡记录 - 优化样式以适应新功能的展示和交互
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { createCheckin, fetchDailyCheckins, updateCheckin } from '@/services/checkins';
|
||||
import { createCheckin, fetchCheckinsInRange, fetchDailyCheckins, updateCheckin } from '@/services/checkins';
|
||||
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
export type CheckinExercise = {
|
||||
@@ -21,11 +21,13 @@ export type CheckinRecord = {
|
||||
export type CheckinState = {
|
||||
byDate: Record<string, CheckinRecord>;
|
||||
currentDate: string | null;
|
||||
monthLoaded: Record<string, boolean>; // key: YYYY-MM, 标记该月数据是否已加载
|
||||
};
|
||||
|
||||
const initialState: CheckinState = {
|
||||
byDate: {},
|
||||
currentDate: null,
|
||||
monthLoaded: {},
|
||||
};
|
||||
|
||||
function ensureRecord(state: CheckinState, date: string): CheckinRecord {
|
||||
@@ -105,6 +107,14 @@ const checkinSlice = createSlice({
|
||||
items: mergedItems,
|
||||
note,
|
||||
};
|
||||
})
|
||||
.addCase(loadMonthCheckins.fulfilled, (state, action) => {
|
||||
const monthKey = action.payload.monthKey;
|
||||
const merged = action.payload.byDate;
|
||||
for (const d of Object.keys(merged)) {
|
||||
state.byDate[d] = merged[d];
|
||||
}
|
||||
state.monthLoaded[monthKey] = true;
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -140,4 +150,61 @@ export const getDailyCheckins = createAsyncThunk('checkin/getDaily', async (date
|
||||
return { date, list } as { date?: string; list: any[] };
|
||||
});
|
||||
|
||||
// 按月加载:优先使用区间接口,失败则逐日回退
|
||||
export const loadMonthCheckins = createAsyncThunk(
|
||||
'checkin/loadMonth',
|
||||
async (payload: { year: number; month1Based: number }, { getState }) => {
|
||||
const { year, month1Based } = payload;
|
||||
const pad = (n: number) => `${n}`.padStart(2, '0');
|
||||
const monthKey = `${year}-${pad(month1Based)}`;
|
||||
|
||||
const start = `${year}-${pad(month1Based)}-01`;
|
||||
const endDate = new Date(year, month1Based, 0).getDate();
|
||||
const end = `${year}-${pad(month1Based)}-${pad(endDate)}`;
|
||||
|
||||
try {
|
||||
const list = await fetchCheckinsInRange(start, end);
|
||||
const byDate: Record<string, CheckinRecord> = {};
|
||||
for (const rec of list) {
|
||||
const date = rec?.checkinDate || rec?.date;
|
||||
if (!date) continue;
|
||||
const id = rec?.id ? String(rec.id) : `rec_${date}`;
|
||||
const items = (rec?.metrics?.items ?? rec?.items) ?? [];
|
||||
const note = typeof rec?.notes === 'string' ? rec.notes : undefined;
|
||||
byDate[date] = { id, date, items, note };
|
||||
}
|
||||
return { monthKey, byDate } as { monthKey: string; byDate: Record<string, CheckinRecord> };
|
||||
} catch {
|
||||
// 回退逐日请求(并行)
|
||||
const endNum = new Date(year, month1Based, 0).getDate();
|
||||
const dates = Array.from({ length: endNum }, (_, i) => `${year}-${pad(month1Based)}-${pad(i + 1)}`);
|
||||
const results = await Promise.all(
|
||||
dates.map(async (d) => ({ d, list: await fetchDailyCheckins(d) }))
|
||||
);
|
||||
const byDate: Record<string, CheckinRecord> = {};
|
||||
for (const { d, list } of results) {
|
||||
let items: CheckinExercise[] = [];
|
||||
let note: string | undefined;
|
||||
let id: string | undefined;
|
||||
for (const rec of list) {
|
||||
if (rec?.id && !id) id = String(rec.id);
|
||||
const metricsItems = rec?.metrics?.items ?? rec?.items;
|
||||
if (Array.isArray(metricsItems)) items = metricsItems as CheckinExercise[];
|
||||
if (typeof rec?.notes === 'string') note = rec.notes as string;
|
||||
}
|
||||
byDate[d] = { id: id || `rec_${d}`, date: d, items, note };
|
||||
}
|
||||
return { monthKey, byDate } as { monthKey: string; byDate: Record<string, CheckinRecord> };
|
||||
}
|
||||
},
|
||||
{
|
||||
condition: (payload, { getState }) => {
|
||||
const state = getState() as any;
|
||||
const pad = (n: number) => `${n}`.padStart(2, '0');
|
||||
const monthKey = `${payload.year}-${pad(payload.month1Based)}`;
|
||||
return !state?.checkin?.monthLoaded?.[monthKey];
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user