feat: 增强目标管理功能及相关组件

- 在 GoalsListScreen 中新增目标编辑功能,支持用户编辑现有目标
- 更新 CreateGoalModal 组件,支持编辑模式下的目标更新
- 在 NutritionRecordsScreen 中新增删除营养记录功能,允许用户删除不需要的记录
- 更新 NutritionRecordCard 组件,增加操作选项,支持删除记录
- 修改 dietRecords 服务,添加删除营养记录的 API 调用
- 优化 goalsSlice,确保目标更新逻辑与 Redux 状态管理一致
This commit is contained in:
2025-08-26 22:34:03 +08:00
parent 0610f287ee
commit 0a8b20f0ec
8 changed files with 244 additions and 131 deletions

View File

@@ -1,10 +1,12 @@
import { GoalCard } from '@/components/GoalCard';
import { CreateGoalModal } from '@/components/model/CreateGoalModal';
import { useGlobalDialog } from '@/components/ui/DialogProvider';
import { Colors } from '@/constants/Colors';
import { TAB_BAR_BOTTOM_OFFSET, TAB_BAR_HEIGHT } from '@/constants/TabBar';
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
import { useColorScheme } from '@/hooks/useColorScheme';
import { deleteGoal, fetchGoals, loadMoreGoals } from '@/store/goalsSlice';
import { GoalListItem } from '@/types/goals';
import { deleteGoal, fetchGoals, loadMoreGoals, updateGoal } from '@/store/goalsSlice';
import { CreateGoalRequest, GoalListItem, UpdateGoalRequest } from '@/types/goals';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
import { useFocusEffect } from '@react-navigation/native';
import { LinearGradient } from 'expo-linear-gradient';
@@ -18,6 +20,8 @@ export default function GoalsListScreen() {
const colorTokens = Colors[theme];
const dispatch = useAppDispatch();
const router = useRouter();
const { showConfirm } = useGlobalDialog();
// Redux状态
const {
@@ -25,9 +29,15 @@ export default function GoalsListScreen() {
goalsLoading,
goalsError,
goalsPagination,
updateLoading,
updateError,
} = useAppSelector((state) => state.goals);
const [refreshing, setRefreshing] = useState(false);
// 编辑目标相关状态
const [showEditModal, setShowEditModal] = useState(false);
const [editingGoal, setEditingGoal] = useState<GoalListItem | null>(null);
// 页面聚焦时重新加载数据
useFocusEffect(
@@ -162,16 +172,11 @@ export default function GoalsListScreen() {
if (goalsError) {
Alert.alert('错误', goalsError);
}
}, [goalsError]);
if (updateError) {
Alert.alert('更新失败', updateError);
}
}, [goalsError, updateError]);
// 计算各状态的目标数量
const goalCounts = useMemo(() => ({
all: goals.length,
active: goals.filter(goal => goal.status === 'active').length,
paused: goals.filter(goal => goal.status === 'paused').length,
completed: goals.filter(goal => goal.status === 'completed').length,
cancelled: goals.filter(goal => goal.status === 'cancelled').length,
}), [goals]);
// 根据筛选条件过滤目标
const filteredGoals = useMemo(() => {
@@ -182,6 +187,58 @@ export default function GoalsListScreen() {
// 处理目标点击
const handleGoalPress = (goal: GoalListItem) => {
setEditingGoal(goal);
setShowEditModal(true);
};
// 将 GoalListItem 转换为 CreateGoalRequest 格式
const convertGoalToModalData = (goal: GoalListItem): Partial<CreateGoalRequest> => {
return {
title: goal.title,
description: goal.description,
repeatType: goal.repeatType,
frequency: goal.frequency,
category: goal.category,
priority: goal.priority,
hasReminder: goal.hasReminder,
reminderTime: goal.reminderTime,
customRepeatRule: goal.customRepeatRule,
endDate: goal.endDate,
};
};
// 处理更新目标
const handleUpdateGoal = async (goalId: string, goalData: UpdateGoalRequest) => {
try {
await dispatch(updateGoal({ goalId, goalData })).unwrap();
setShowEditModal(false);
setEditingGoal(null);
// 使用全局弹窗显示成功消息
showConfirm(
{
title: '目标更新成功',
message: '恭喜!您的目标已成功更新。',
confirmText: '确定',
cancelText: '',
icon: 'checkmark-circle',
iconColor: '#10B981',
},
() => {
console.log('用户确认了目标更新成功');
}
);
} catch (error) {
console.error('Failed to update goal:', error);
Alert.alert('错误', '更新目标失败,请重试');
// 更新失败时不关闭弹窗,保持编辑状态
}
};
// 处理编辑弹窗关闭
const handleCloseEditModal = () => {
setShowEditModal(false);
setEditingGoal(null);
};
// 渲染目标项
@@ -292,6 +349,19 @@ export default function GoalsListScreen() {
ListFooterComponent={renderLoadMore}
/>
</View>
{/* 编辑目标弹窗 */}
{editingGoal && (
<CreateGoalModal
visible={showEditModal}
onClose={handleCloseEditModal}
onSubmit={() => {}} // 编辑模式下不使用这个回调
onUpdate={handleUpdateGoal}
loading={updateLoading}
initialData={convertGoalToModalData(editingGoal)}
editGoalId={editingGoal.id}
/>
)}
</View>
</SafeAreaView>
);