feat(fasting): 新增轻断食功能模块
新增完整的轻断食功能,包括: - 断食计划列表和详情页面,支持12-12、14-10、16-8、18-6四种计划 - 断食状态实时追踪和倒计时显示 - 自定义开始时间选择器 - 断食通知提醒功能 - Redux状态管理和数据持久化 - 新增tab导航入口和路由配置
This commit is contained in:
126
store/fastingSlice.ts
Normal file
126
store/fastingSlice.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import dayjs from 'dayjs';
|
||||
import { FASTING_PLANS, FastingPlan, getPlanById } from '@/constants/Fasting';
|
||||
import { calculateFastingWindow, getFastingPhase } from '@/utils/fasting';
|
||||
import type { RootState } from './index';
|
||||
|
||||
export type FastingScheduleOrigin = 'manual' | 'recommended' | 'quick-start';
|
||||
|
||||
export type FastingSchedule = {
|
||||
planId: string;
|
||||
startISO: string;
|
||||
endISO: string;
|
||||
createdAtISO: string;
|
||||
updatedAtISO: string;
|
||||
origin: FastingScheduleOrigin;
|
||||
};
|
||||
|
||||
type FastingState = {
|
||||
activeSchedule: FastingSchedule | null;
|
||||
history: FastingSchedule[];
|
||||
};
|
||||
|
||||
const initialState: FastingState = {
|
||||
activeSchedule: null,
|
||||
history: [],
|
||||
};
|
||||
|
||||
const fastingSlice = createSlice({
|
||||
name: 'fasting',
|
||||
initialState,
|
||||
reducers: {
|
||||
scheduleFastingPlan: (
|
||||
state,
|
||||
action: PayloadAction<{ planId: string; start: Date; origin?: FastingScheduleOrigin }>
|
||||
) => {
|
||||
const plan = getPlanById(action.payload.planId);
|
||||
if (!plan) return;
|
||||
|
||||
const { start, end } = calculateFastingWindow(action.payload.start, plan.fastingHours);
|
||||
const nowISO = new Date().toISOString();
|
||||
state.activeSchedule = {
|
||||
planId: plan.id,
|
||||
startISO: start.toISOString(),
|
||||
endISO: end.toISOString(),
|
||||
createdAtISO: nowISO,
|
||||
updatedAtISO: nowISO,
|
||||
origin: action.payload.origin ?? 'manual',
|
||||
};
|
||||
},
|
||||
rescheduleActivePlan: (
|
||||
state,
|
||||
action: PayloadAction<{ start: Date; origin?: FastingScheduleOrigin }>
|
||||
) => {
|
||||
if (!state.activeSchedule) return;
|
||||
const plan = getPlanById(state.activeSchedule.planId);
|
||||
if (!plan) return;
|
||||
|
||||
const { start, end } = calculateFastingWindow(action.payload.start, plan.fastingHours);
|
||||
state.activeSchedule = {
|
||||
...state.activeSchedule,
|
||||
startISO: start.toISOString(),
|
||||
endISO: end.toISOString(),
|
||||
updatedAtISO: new Date().toISOString(),
|
||||
origin: action.payload.origin ?? state.activeSchedule.origin,
|
||||
};
|
||||
},
|
||||
setRecommendedSchedule: (
|
||||
state,
|
||||
action: PayloadAction<{ planId: string; recommendedStart: Date }>
|
||||
) => {
|
||||
const plan = getPlanById(action.payload.planId);
|
||||
if (!plan) return;
|
||||
const { start, end } = calculateFastingWindow(action.payload.recommendedStart, plan.fastingHours);
|
||||
const nowISO = new Date().toISOString();
|
||||
state.activeSchedule = {
|
||||
planId: plan.id,
|
||||
startISO: start.toISOString(),
|
||||
endISO: end.toISOString(),
|
||||
createdAtISO: nowISO,
|
||||
updatedAtISO: nowISO,
|
||||
origin: 'recommended',
|
||||
};
|
||||
},
|
||||
completeActiveSchedule: (state) => {
|
||||
if (!state.activeSchedule) return;
|
||||
const phase = getFastingPhase(
|
||||
new Date(state.activeSchedule.startISO),
|
||||
new Date(state.activeSchedule.endISO)
|
||||
);
|
||||
|
||||
if (phase === 'fasting') {
|
||||
// Allow manual completion only when fasting window已经结束
|
||||
state.activeSchedule = {
|
||||
...state.activeSchedule,
|
||||
endISO: dayjs().toISOString(),
|
||||
updatedAtISO: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
state.history.unshift(state.activeSchedule);
|
||||
state.activeSchedule = null;
|
||||
},
|
||||
clearActiveSchedule: (state) => {
|
||||
state.activeSchedule = null;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
scheduleFastingPlan,
|
||||
rescheduleActivePlan,
|
||||
setRecommendedSchedule,
|
||||
completeActiveSchedule,
|
||||
clearActiveSchedule,
|
||||
} = fastingSlice.actions;
|
||||
|
||||
export default fastingSlice.reducer;
|
||||
|
||||
export const selectFastingRoot = (state: RootState) => state.fasting;
|
||||
export const selectActiveFastingSchedule = (state: RootState) => state.fasting.activeSchedule;
|
||||
export const selectFastingPlans = () => FASTING_PLANS;
|
||||
export const selectActiveFastingPlan = (state: RootState): FastingPlan | undefined => {
|
||||
const schedule = state.fasting.activeSchedule;
|
||||
if (!schedule) return undefined;
|
||||
return getPlanById(schedule.planId);
|
||||
};
|
||||
Reference in New Issue
Block a user