feat: 更新个人信息和打卡功能
- 在个人信息页面中修改用户姓名字段为“name”,并添加注销帐号功能,支持用户删除帐号及相关数据 - 在打卡页面中集成从后端获取当天打卡列表的功能,确保用户数据的实时同步 - 更新 Redux 状态管理,支持打卡记录的同步和更新 - 新增打卡服务,提供创建、更新和删除打卡记录的 API 接口 - 优化样式以适应新功能的展示和交互
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createCheckin, fetchDailyCheckins, updateCheckin } from '@/services/checkins';
|
||||
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
export type CheckinExercise = {
|
||||
key: string;
|
||||
@@ -70,9 +71,73 @@ const checkinSlice = createSlice({
|
||||
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[] };
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
export type Gender = 'male' | 'female' | '';
|
||||
|
||||
export type UserProfile = {
|
||||
fullName?: string;
|
||||
name?: string;
|
||||
email?: string;
|
||||
gender?: Gender;
|
||||
age?: string; // 个人中心是字符串展示
|
||||
@@ -29,7 +29,7 @@ export const DEFAULT_MEMBER_NAME = '普拉提星球学员';
|
||||
const initialState: UserState = {
|
||||
token: null,
|
||||
profile: {
|
||||
fullName: DEFAULT_MEMBER_NAME,
|
||||
name: DEFAULT_MEMBER_NAME,
|
||||
},
|
||||
loading: false,
|
||||
error: null,
|
||||
@@ -164,8 +164,8 @@ const userSlice = createSlice({
|
||||
state.loading = false;
|
||||
state.token = action.payload.token;
|
||||
state.profile = action.payload.profile;
|
||||
if (!state.profile?.fullName || !state.profile.fullName.trim()) {
|
||||
state.profile.fullName = DEFAULT_MEMBER_NAME;
|
||||
if (!state.profile?.name || !state.profile.name.trim()) {
|
||||
state.profile.name = DEFAULT_MEMBER_NAME;
|
||||
}
|
||||
})
|
||||
.addCase(login.rejected, (state, action) => {
|
||||
@@ -175,14 +175,14 @@ const userSlice = createSlice({
|
||||
.addCase(rehydrateUser.fulfilled, (state, action) => {
|
||||
state.token = action.payload.token;
|
||||
state.profile = action.payload.profile;
|
||||
if (!state.profile?.fullName || !state.profile.fullName.trim()) {
|
||||
state.profile.fullName = DEFAULT_MEMBER_NAME;
|
||||
if (!state.profile?.name || !state.profile.name.trim()) {
|
||||
state.profile.name = DEFAULT_MEMBER_NAME;
|
||||
}
|
||||
})
|
||||
.addCase(fetchMyProfile.fulfilled, (state, action) => {
|
||||
state.profile = action.payload || {};
|
||||
if (!state.profile?.fullName || !state.profile.fullName.trim()) {
|
||||
state.profile.fullName = DEFAULT_MEMBER_NAME;
|
||||
if (!state.profile?.name || !state.profile.name.trim()) {
|
||||
state.profile.name = DEFAULT_MEMBER_NAME;
|
||||
}
|
||||
})
|
||||
.addCase(fetchMyProfile.rejected, (state, action) => {
|
||||
|
||||
Reference in New Issue
Block a user