Files
digital-pilates/store/foodLibrarySlice.ts
2025-08-29 09:41:05 +08:00

225 lines
7.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { foodLibraryApi } from '@/services/foodLibraryApi';
import type {
FoodCategory,
FoodCategoryDto,
FoodItem,
FoodItemDto,
FoodLibraryState
} from '@/types/food';
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
// 数据转换工具函数
const transformFoodItemDto = (dto: FoodItemDto): FoodItem => ({
id: dto.id.toString(),
name: dto.name,
emoji: '🍽️', // 默认 emoji可以根据分类或其他逻辑设置
calories: dto.caloriesPer100g || 0,
unit: '100克',
description: dto.description,
protein: dto.proteinPer100g,
carbohydrate: dto.carbohydratePer100g,
fat: dto.fatPer100g,
fiber: dto.fiberPer100g,
sugar: dto.sugarPer100g,
sodium: dto.sodiumPer100g,
additionalNutrition: dto.additionalNutrition,
imageUrl: dto.imageUrl,
});
const transformFoodCategoryDto = (dto: FoodCategoryDto): FoodCategory => ({
id: dto.key,
name: dto.name,
foods: dto.foods.map(transformFoodItemDto),
icon: dto.icon,
sortOrder: dto.sortOrder,
isSystem: dto.isSystem,
});
// 异步 thunks
export const fetchFoodLibrary = createAsyncThunk(
'foodLibrary/fetchFoodLibrary',
async (_, { rejectWithValue }) => {
try {
const response = await foodLibraryApi.getFoodLibrary();
return response;
} catch (error: any) {
return rejectWithValue(error.message || '获取食物库失败');
}
}
);
export const searchFoods = createAsyncThunk(
'foodLibrary/searchFoods',
async (keyword: string, { rejectWithValue }) => {
try {
const response = await foodLibraryApi.searchFoods(keyword);
return response;
} catch (error: any) {
return rejectWithValue(error.message || '搜索食物失败');
}
}
);
export const getFoodById = createAsyncThunk(
'foodLibrary/getFoodById',
async (id: number, { rejectWithValue }) => {
try {
const response = await foodLibraryApi.getFoodById(id);
return response;
} catch (error: any) {
return rejectWithValue(error.message || '获取食物详情失败');
}
}
);
// 初始状态
const initialState: FoodLibraryState = {
categories: [],
loading: false,
error: null,
searchResults: [],
searchLoading: false,
lastUpdated: null,
};
// 创建 slice
const foodLibrarySlice = createSlice({
name: 'foodLibrary',
initialState,
reducers: {
// 清除错误
clearError: (state) => {
state.error = null;
},
// 清除搜索结果
clearSearchResults: (state) => {
state.searchResults = [];
},
// 添加自定义食物到指定分类
addCustomFood: (state, action: PayloadAction<{ categoryId: string; food: FoodItem }>) => {
const { categoryId, food } = action.payload;
const category = state.categories.find(cat => cat.id === categoryId);
if (category) {
category.foods.push(food);
}
},
// 从指定分类移除食物
removeFoodFromCategory: (state, action: PayloadAction<{ categoryId: string; foodId: string }>) => {
const { categoryId, foodId } = action.payload;
const category = state.categories.find(cat => cat.id === categoryId);
if (category) {
category.foods = category.foods.filter(food => food.id !== foodId);
}
},
// 添加食物到收藏
addToFavorites: (state, action: PayloadAction<FoodItem>) => {
const favoriteCategory = state.categories.find(cat => cat.id === 'favorite');
if (favoriteCategory) {
// 检查是否已存在
const exists = favoriteCategory.foods.some(food => food.id === action.payload.id);
if (!exists) {
favoriteCategory.foods.push(action.payload);
}
}
},
// 从收藏移除食物
removeFromFavorites: (state, action: PayloadAction<string>) => {
const favoriteCategory = state.categories.find(cat => cat.id === 'favorite');
if (favoriteCategory) {
favoriteCategory.foods = favoriteCategory.foods.filter(food => food.id !== action.payload);
}
},
},
extraReducers: (builder) => {
// 获取食物库
builder
.addCase(fetchFoodLibrary.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchFoodLibrary.fulfilled, (state, action) => {
state.loading = false;
state.categories = action.payload.categories.map(transformFoodCategoryDto);
state.lastUpdated = Date.now();
state.error = null;
})
.addCase(fetchFoodLibrary.rejected, (state, action) => {
state.loading = false;
state.error = action.payload as string;
});
// 搜索食物
builder
.addCase(searchFoods.pending, (state) => {
state.searchLoading = true;
state.error = null;
})
.addCase(searchFoods.fulfilled, (state, action) => {
state.searchLoading = false;
state.searchResults = action.payload.map(transformFoodItemDto);
state.error = null;
})
.addCase(searchFoods.rejected, (state, action) => {
state.searchLoading = false;
state.error = action.payload as string;
state.searchResults = [];
});
// 获取食物详情
builder
.addCase(getFoodById.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(getFoodById.fulfilled, (state, action) => {
state.loading = false;
state.error = null;
// 可以在这里处理获取到的食物详情,比如更新缓存等
})
.addCase(getFoodById.rejected, (state, action) => {
state.loading = false;
state.error = action.payload as string;
});
},
});
// 导出 actions
export const {
clearError,
clearSearchResults,
addCustomFood,
removeFoodFromCategory,
addToFavorites,
removeFromFavorites,
} = foodLibrarySlice.actions;
// 选择器
export const selectFoodLibrary = (state: { foodLibrary: FoodLibraryState }) => state.foodLibrary;
export const selectFoodCategories = (state: { foodLibrary: FoodLibraryState }) => state.foodLibrary.categories;
export const selectFoodLibraryLoading = (state: { foodLibrary: FoodLibraryState }) => state.foodLibrary.loading;
export const selectFoodLibraryError = (state: { foodLibrary: FoodLibraryState }) => state.foodLibrary.error;
export const selectSearchResults = (state: { foodLibrary: FoodLibraryState }) => state.foodLibrary.searchResults;
export const selectSearchLoading = (state: { foodLibrary: FoodLibraryState }) => state.foodLibrary.searchLoading;
// 复合选择器
export const selectFoodCategoryById = (categoryId: string) =>
(state: { foodLibrary: FoodLibraryState }) =>
state.foodLibrary.categories.find(cat => cat.id === categoryId);
export const selectFoodById = (foodId: string) =>
(state: { foodLibrary: FoodLibraryState }) => {
for (const category of state.foodLibrary.categories) {
const food = category.foods.find(f => f.id === foodId);
if (food) return food;
}
return null;
};
export const selectFavoritesFoods = (state: { foodLibrary: FoodLibraryState }) =>
state.foodLibrary.categories.find(cat => cat.id === 'favorite')?.foods || [];
export const selectCommonFoods = (state: { foodLibrary: FoodLibraryState }) =>
state.foodLibrary.categories.find(cat => cat.id === 'common')?.foods || [];
// 导出 reducer
export default foodLibrarySlice.reducer;