diff --git a/app/(tabs)/coach.tsx b/app/(tabs)/coach.tsx
index aa1f4cc..e6356b8 100644
--- a/app/(tabs)/coach.tsx
+++ b/app/(tabs)/coach.tsx
@@ -2012,7 +2012,7 @@ export default function CoachScreen() {
)}
-
+
('all');
+ const [modalKey, setModalKey] = useState(0); // 用于强制重新渲染弹窗
// 页面聚焦时重新加载数据
useFocusEffect(
@@ -109,12 +113,34 @@ export default function GoalsScreen() {
}
}, [tasksError, createError, completeError, skipError, dispatch]);
+ // 重置弹窗表单数据
+ const handleModalSuccess = () => {
+ // 不需要在这里改变 modalKey,因为弹窗已经关闭了
+ // 下次打开时会自动使用新的 modalKey
+ };
+
// 创建目标处理函数
const handleCreateGoal = async (goalData: CreateGoalRequest) => {
try {
await dispatch(createGoal(goalData)).unwrap();
setShowCreateModal(false);
- Alert.alert('成功', '目标创建成功!');
+
+ // 使用确认弹窗显示成功消息
+ showConfirm(
+ {
+ title: '目标创建成功',
+ message: '恭喜!您的目标已成功创建。系统将自动生成相应的任务,帮助您实现目标。',
+ confirmText: '确定',
+ cancelText: '',
+ icon: 'checkmark-circle',
+ iconColor: '#10B981',
+ },
+ () => {
+ // 用户点击确定后的回调
+ console.log('用户确认了目标创建成功');
+ }
+ );
+
// 创建目标后重新加载任务列表
loadTasks();
} catch (error) {
@@ -134,6 +160,7 @@ export default function GoalsScreen() {
all: tasks.length,
pending: tasks.filter(task => task.status === 'pending').length,
completed: tasks.filter(task => task.status === 'completed').length,
+ skipped: tasks.filter(task => task.status === 'skipped').length,
};
// 根据筛选条件过滤任务
@@ -143,6 +170,8 @@ export default function GoalsScreen() {
return tasks.filter(task => task.status === 'pending');
case 'completed':
return tasks.filter(task => task.status === 'completed');
+ case 'skipped':
+ return tasks.filter(task => task.status === 'skipped');
default:
return tasks;
}
@@ -171,6 +200,9 @@ export default function GoalsScreen() {
} else if (selectedFilter === 'completed') {
title = '暂无已完成的任务';
subtitle = '完成一些任务后,它们会显示在这里';
+ } else if (selectedFilter === 'skipped') {
+ title = '暂无已跳过的任务';
+ subtitle = '跳过一些任务后,它们会显示在这里';
}
return (
@@ -262,7 +294,10 @@ export default function GoalsScreen() {
setShowCreateModal(true)}
+ onPress={() => {
+ setModalKey(prev => prev + 1); // 每次打开弹窗时使用新的 key
+ setShowCreateModal(true);
+ }}
>
+
@@ -303,9 +338,11 @@ export default function GoalsScreen() {
{/* 创建目标弹窗 */}
setShowCreateModal(false)}
onSubmit={handleCreateGoal}
+ onSuccess={handleModalSuccess}
loading={createLoading}
/>
@@ -415,7 +452,7 @@ const styles = StyleSheet.create({
taskList: {
paddingHorizontal: 20,
paddingTop: 20,
- paddingBottom: 20,
+ paddingBottom: TAB_BAR_HEIGHT + TAB_BAR_BOTTOM_OFFSET + 20, // 避让底部导航栏 + 额外间距
},
emptyState: {
alignItems: 'center',
diff --git a/components/CreateGoalModal.tsx b/components/CreateGoalModal.tsx
index 952a88e..f70b7c8 100644
--- a/components/CreateGoalModal.tsx
+++ b/components/CreateGoalModal.tsx
@@ -6,16 +6,16 @@ import DateTimePicker from '@react-native-community/datetimepicker';
import { LinearGradient } from 'expo-linear-gradient';
import React, { useState } from 'react';
import {
- Alert,
- Modal,
- Platform,
- ScrollView,
- StyleSheet,
- Switch,
- Text,
- TextInput,
- TouchableOpacity,
- View,
+ Alert,
+ Modal,
+ Platform,
+ ScrollView,
+ StyleSheet,
+ Switch,
+ Text,
+ TextInput,
+ TouchableOpacity,
+ View,
} from 'react-native';
import WheelPickerExpo from 'react-native-wheel-picker-expo';
@@ -23,6 +23,7 @@ interface CreateGoalModalProps {
visible: boolean;
onClose: () => void;
onSubmit: (goalData: CreateGoalRequest) => void;
+ onSuccess?: () => void;
loading?: boolean;
}
@@ -39,6 +40,7 @@ export const CreateGoalModal: React.FC = ({
visible,
onClose,
onSubmit,
+ onSuccess,
loading = false,
}) => {
const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
@@ -65,7 +67,7 @@ export const CreateGoalModal: React.FC = ({
setRepeatType('daily');
setFrequency(1);
setHasReminder(false);
- setReminderTime('19:00');
+ setReminderTime('20:00');
setCategory('');
setPriority(5);
};
@@ -113,6 +115,11 @@ export const CreateGoalModal: React.FC = ({
console.log('goalData', goalData);
onSubmit(goalData);
+
+ // 通知父组件提交成功
+ if (onSuccess) {
+ onSuccess();
+ }
};
// 时间选择器
diff --git a/components/TaskCard.tsx b/components/TaskCard.tsx
index 1686f4b..36ab748 100644
--- a/components/TaskCard.tsx
+++ b/components/TaskCard.tsx
@@ -100,7 +100,7 @@ export const TaskCard: React.FC = ({
showConfirm(
{
title: '确认跳过任务',
- message: `确定要跳过任务"${task.title}"吗?跳过后将无法恢复。`,
+ message: `确定要跳过任务"${task.title}"吗?\n\n跳过后的任务将不会显示在任务列表中,且无法恢复。`,
confirmText: '跳过',
cancelText: '取消',
destructive: true,
@@ -189,11 +189,24 @@ export const TaskCard: React.FC = ({
style={[
styles.progressFill,
{
- width: `${Math.min(task.progressPercentage, 100)}%`,
- backgroundColor: colorTokens.primary,
+ width: task.progressPercentage > 0 ? `${Math.min(task.progressPercentage, 100)}%` : '2%',
+ backgroundColor: task.progressPercentage >= 100
+ ? '#10B981'
+ : task.progressPercentage >= 50
+ ? '#F59E0B'
+ : task.progressPercentage > 0
+ ? colorTokens.primary
+ : '#E5E7EB',
},
]}
/>
+ {task.progressPercentage > 0 && task.progressPercentage < 100 && (
+
+ )}
+ {/* 进度百分比文本 */}
+
+ {Math.round(task.progressPercentage)}%
+
{/* 底部信息 */}
@@ -314,15 +327,57 @@ const styles = StyleSheet.create({
color: '#FFFFFF',
},
progressBar: {
- height: 2,
- backgroundColor: '#E5E7EB',
- borderRadius: 1,
+ height: 6,
+ backgroundColor: '#F3F4F6',
+ borderRadius: 3,
marginBottom: 16,
- overflow: 'hidden',
+ overflow: 'visible',
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 1 },
+ shadowOpacity: 0.1,
+ shadowRadius: 2,
+ elevation: 2,
+ position: 'relative',
},
progressFill: {
height: '100%',
- borderRadius: 1,
+ borderRadius: 3,
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 1 },
+ shadowOpacity: 0.2,
+ shadowRadius: 2,
+ elevation: 3,
+ },
+ progressGlow: {
+ position: 'absolute',
+ right: 0,
+ top: 0,
+ width: 8,
+ height: '100%',
+ backgroundColor: 'rgba(255, 255, 255, 0.6)',
+ borderRadius: 3,
+ },
+ progressTextContainer: {
+ position: 'absolute',
+ right: 0,
+ top: -6,
+ backgroundColor: '#FFFFFF',
+ paddingHorizontal: 6,
+ paddingVertical: 2,
+ borderRadius: 8,
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 1 },
+ shadowOpacity: 0.1,
+ shadowRadius: 2,
+ elevation: 2,
+ borderWidth: 1,
+ borderColor: '#E5E7EB',
+ zIndex: 1,
+ },
+ progressText: {
+ fontSize: 10,
+ fontWeight: '600',
+ color: '#374151',
},
footer: {
flexDirection: 'row',
diff --git a/components/TaskFilterTabs.tsx b/components/TaskFilterTabs.tsx
index 1cb278f..875523a 100644
--- a/components/TaskFilterTabs.tsx
+++ b/components/TaskFilterTabs.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
-export type TaskFilterType = 'all' | 'pending' | 'completed';
+export type TaskFilterType = 'all' | 'pending' | 'completed' | 'skipped';
interface TaskFilterTabsProps {
selectedFilter: TaskFilterType;
@@ -10,6 +10,7 @@ interface TaskFilterTabsProps {
all: number;
pending: number;
completed: number;
+ skipped: number;
};
}
@@ -101,6 +102,33 @@ export const TaskFilterTabs: React.FC = ({
+
+ {/* 已跳过 Tab */}
+ onFilterChange('skipped')}
+ >
+
+ 已跳过
+
+
+
+ {taskCounts.skipped}
+
+
+
);
diff --git a/components/TaskProgressCard.tsx b/components/TaskProgressCard.tsx
index 647369d..92f5a94 100644
--- a/components/TaskProgressCard.tsx
+++ b/components/TaskProgressCard.tsx
@@ -55,7 +55,7 @@ export const TaskProgressCard: React.FC = ({
{/* 已跳过 卡片 */}
-
+
已跳过
{skippedTasks.length}
@@ -106,7 +106,7 @@ const styles = StyleSheet.create({
statusCards: {
flexDirection: 'row',
justifyContent: 'space-between',
- gap: 12,
+ gap: 8,
},
statusCard: {
flex: 1,
diff --git a/components/ui/ConfirmDialog.tsx b/components/ui/ConfirmDialog.tsx
index d147dcc..9af07cd 100644
--- a/components/ui/ConfirmDialog.tsx
+++ b/components/ui/ConfirmDialog.tsx
@@ -146,20 +146,26 @@ export function ConfirmDialog({
{message && {message}}
{/* 按钮组 */}
-
-
- {cancelText}
-
+
+ {cancelText && (
+
+ {cancelText}
+
+ )}
+ {cancelText}
+
+)}
+```
+
+### 2. 样式适配
+
+根据是否显示取消按钮,动态调整按钮容器的样式:
+
+```typescript
+
+```
+
+### 3. 确认按钮样式
+
+当只有确认按钮时,应用特殊样式:
+
+```typescript
+
+```
+
+## 使用方法
+
+### 显示双按钮(默认行为)
+
+```typescript
+showConfirm(
+ {
+ title: '确认操作',
+ message: '确定要执行此操作吗?',
+ confirmText: '确定',
+ cancelText: '取消', // 设置取消按钮文本
+ icon: 'warning',
+ iconColor: '#F59E0B',
+ },
+ () => {
+ // 确认回调
+ }
+);
+```
+
+### 只显示确认按钮
+
+```typescript
+showConfirm(
+ {
+ title: '目标创建成功',
+ message: '恭喜!您的目标已成功创建。',
+ confirmText: '确定',
+ cancelText: '', // 空字符串或不设置
+ icon: 'checkmark-circle',
+ iconColor: '#10B981',
+ },
+ () => {
+ // 确认回调
+ }
+);
+```
+
+### 完全不显示取消按钮
+
+```typescript
+showConfirm(
+ {
+ title: '操作成功',
+ message: '操作已完成',
+ confirmText: '确定',
+ // 不设置 cancelText 或设置为 undefined
+ icon: 'checkmark-circle',
+ iconColor: '#10B981',
+ },
+ () => {
+ // 确认回调
+ }
+);
+```
+
+## 样式变化
+
+### 双按钮布局
+- 两个按钮并排显示
+- 按钮之间有 12px 间距
+- 每个按钮占据 50% 宽度
+
+### 单按钮布局
+- 只显示确认按钮
+- 按钮占据 100% 宽度
+- 无按钮间距
+
+## 应用场景
+
+### 1. 成功提示(单按钮)
+- 目标创建成功
+- 操作完成提示
+- 信息确认
+
+### 2. 确认操作(双按钮)
+- 删除确认
+- 跳过任务确认
+- 重要操作确认
+
+### 3. 警告提示(双按钮)
+- 危险操作确认
+- 数据丢失警告
+- 权限确认
+
+## 技术细节
+
+### 接口定义
+
+```typescript
+export interface DialogConfig {
+ title: string;
+ message?: string;
+ confirmText?: string;
+ cancelText?: string; // 可选参数,控制取消按钮显示
+ destructive?: boolean;
+ icon?: keyof typeof Ionicons.glyphMap;
+ iconColor?: string;
+}
+```
+
+### 样式类
+
+```typescript
+buttonContainer: {
+ flexDirection: 'row',
+ gap: 12,
+ width: '100%',
+},
+singleButtonContainer: {
+ gap: 0, // 单按钮时移除间距
+},
+singleConfirmButton: {
+ flex: 1, // 单按钮时占满宽度
+},
+```
+
+## 注意事项
+
+1. **空字符串 vs undefined**:两者都会隐藏取消按钮
+2. **按钮文本**:建议为取消按钮提供有意义的文本
+3. **用户体验**:单按钮适合成功提示,双按钮适合需要确认的操作
+4. **无障碍访问**:确保按钮有足够的点击区域和清晰的文本
diff --git a/docs/goal-creation-success-dialog.md b/docs/goal-creation-success-dialog.md
new file mode 100644
index 0000000..d0ac882
--- /dev/null
+++ b/docs/goal-creation-success-dialog.md
@@ -0,0 +1,88 @@
+# 目标创建成功弹窗优化
+
+## 修改概述
+
+将目标创建成功后的提示从系统默认的 `Alert.alert` 改为使用自定义的确认弹窗,提供更好的用户体验和视觉一致性。
+
+## 修改内容
+
+### 1. 导入依赖
+
+在 `app/(tabs)/goals.tsx` 中添加了 `useGlobalDialog` hook 的导入:
+
+```typescript
+import { useGlobalDialog } from '@/components/ui/DialogProvider';
+```
+
+### 2. 使用 Dialog Hook
+
+在组件中添加了 `useGlobalDialog` hook 的使用:
+
+```typescript
+const { showConfirm } = useGlobalDialog();
+```
+
+### 3. 修改成功处理逻辑
+
+将 `handleCreateGoal` 函数中的成功提示从:
+
+```typescript
+Alert.alert('成功', '目标创建成功!');
+```
+
+改为:
+
+```typescript
+showConfirm(
+ {
+ title: '目标创建成功',
+ message: '恭喜!您的目标已成功创建。系统将自动生成相应的任务,帮助您实现目标。',
+ confirmText: '确定',
+ cancelText: '',
+ icon: 'checkmark-circle',
+ iconColor: '#10B981',
+ },
+ () => {
+ // 用户点击确定后的回调
+ console.log('用户确认了目标创建成功');
+ }
+);
+```
+
+## 改进效果
+
+### 视觉一致性
+- 使用与应用其他部分相同的弹窗样式
+- 保持设计语言的一致性
+
+### 用户体验提升
+- 更友好的成功消息文案
+- 使用绿色勾选图标表示成功状态
+- 更详细的说明信息,告知用户系统将自动生成任务
+
+### 交互优化
+- 只显示"确定"按钮,简化用户操作
+- 提供触觉反馈(通过 DialogProvider 实现)
+- 流畅的动画效果
+
+## 技术细节
+
+### 图标选择
+- 使用 `checkmark-circle` 图标表示成功
+- 图标颜色设置为 `#10B981`(绿色)
+
+### 文案优化
+- 标题:简洁明了
+- 消息:详细说明后续流程
+- 按钮:只保留"确定"按钮,避免用户困惑
+
+### 回调处理
+- 在用户确认后执行回调函数
+- 可以在这里添加额外的逻辑处理
+
+## 注意事项
+
+1. 确保 `DialogProvider` 已正确配置在应用的根级别
+2. 图标名称必须符合 Ionicons 的命名规范
+3. 颜色值使用十六进制格式
+4. 保持与其他弹窗使用的一致性