feat: 支持食物库接口

This commit is contained in:
richarjiang
2025-08-29 09:41:05 +08:00
parent c15a9176f4
commit 8d567fb4cb
14 changed files with 1349 additions and 234 deletions

225
store/foodLibrarySlice.ts Normal file
View File

@@ -0,0 +1,225 @@
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;