feat: 新增任务管理功能及相关组件

- 将目标页面改为任务列表,支持任务的创建、完成和跳过功能
- 新增任务卡片和任务进度卡片组件,展示任务状态和进度
- 实现任务数据的获取和管理,集成Redux状态管理
- 更新API服务,支持任务相关的CRUD操作
- 编写任务管理功能实现文档,详细描述功能和组件架构
This commit is contained in:
richarjiang
2025-08-22 17:30:14 +08:00
parent 231620d778
commit 259f10540e
21 changed files with 2756 additions and 608 deletions

View File

@@ -1,7 +1,9 @@
import { Colors } from '@/constants/Colors';
import { useColorScheme } from '@/hooks/useColorScheme';
import { CreateGoalRequest, GoalPriority, RepeatType } from '@/types/goals';
import { Ionicons } from '@expo/vector-icons';
import DateTimePicker from '@react-native-community/datetimepicker';
import { LinearGradient } from 'expo-linear-gradient';
import React, { useState } from 'react';
import {
Alert,
@@ -15,6 +17,7 @@ import {
TouchableOpacity,
View,
} from 'react-native';
import WheelPickerExpo from 'react-native-wheel-picker-expo';
interface CreateGoalModalProps {
visible: boolean;
@@ -30,7 +33,7 @@ const REPEAT_TYPE_OPTIONS: { value: RepeatType; label: string }[] = [
{ value: 'custom', label: '自定义' },
];
const FREQUENCY_OPTIONS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const FREQUENCY_OPTIONS = Array.from({ length: 30 }, (_, i) => i + 1);
export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
visible,
@@ -47,6 +50,8 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
const [repeatType, setRepeatType] = useState<RepeatType>('daily');
const [frequency, setFrequency] = useState(1);
const [hasReminder, setHasReminder] = useState(false);
const [showFrequencyPicker, setShowFrequencyPicker] = useState(false);
const [showRepeatTypePicker, setShowRepeatTypePicker] = useState(false);
const [reminderTime, setReminderTime] = useState('20:00');
const [category, setCategory] = useState('');
const [priority, setPriority] = useState<GoalPriority>(5);
@@ -163,12 +168,21 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
onRequestClose={handleClose}
>
<View style={[styles.container, { backgroundColor: colorTokens.background }]}>
{/* 渐变背景 */}
<LinearGradient
colors={['#F0F9FF', '#E0F2FE']}
style={styles.gradientBackground}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
/>
{/* 装饰性圆圈 */}
<View style={styles.decorativeCircle1} />
<View style={styles.decorativeCircle2} />
{/* 头部 */}
<View style={styles.header}>
<TouchableOpacity onPress={handleClose} disabled={loading}>
<Text style={[styles.cancelButton, { color: colorTokens.text }]}>
</Text>
<Ionicons name="close" size={24} color={colorTokens.text} />
</TouchableOpacity>
<Text style={[styles.title, { color: colorTokens.text }]}>
@@ -180,13 +194,13 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
{/* 目标标题输入 */}
<View style={styles.section}>
<View style={styles.iconTitleContainer}>
<View style={styles.iconPlaceholder}>
{/* <View style={styles.iconPlaceholder}>
<Text style={styles.iconText}>图标</Text>
</View>
</View> */}
<TextInput
style={[styles.titleInput, { color: colorTokens.text }]}
placeholder="写点什么..."
placeholderTextColor={colorTokens.textSecondary}
// placeholderTextColor={colorTokens.textSecondary}
value={title}
onChangeText={setTitle}
multiline
@@ -201,44 +215,132 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
{/* 目标重复周期 */}
<View style={[styles.optionCard, { backgroundColor: colorTokens.card }]}>
<View style={styles.optionHeader}>
<View style={styles.optionIcon}>
<Text style={styles.optionIconText}>🔄</Text>
</View>
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
</Text>
<TouchableOpacity style={styles.optionValue}>
<TouchableOpacity style={styles.optionValue} onPress={() => setShowRepeatTypePicker(true)}>
<View style={styles.optionHeader}>
<View style={styles.optionIcon}>
<Text style={styles.optionIconText}>🔄</Text>
</View>
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
</Text>
<Text style={[styles.optionValueText, { color: colorTokens.textSecondary }]}>
{REPEAT_TYPE_OPTIONS.find(opt => opt.value === repeatType)?.label}
</Text>
<Text style={[styles.chevron, { color: colorTokens.textSecondary }]}>
</Text>
</TouchableOpacity>
</View>
</View>
</TouchableOpacity>
</View>
{/* 重复周期选择器弹窗 */}
<Modal
visible={showRepeatTypePicker}
transparent
animationType="fade"
onRequestClose={() => setShowRepeatTypePicker(false)}
>
<TouchableOpacity
style={styles.modalBackdrop}
activeOpacity={1}
onPress={() => setShowRepeatTypePicker(false)}
/>
<View style={styles.modalSheet}>
<View style={{ height: 200 }}>
<WheelPickerExpo
height={200}
width={150}
initialSelectedIndex={REPEAT_TYPE_OPTIONS.findIndex(opt => opt.value === repeatType)}
items={REPEAT_TYPE_OPTIONS.map(opt => ({ label: opt.label, value: opt.value }))}
onChange={({ item }) => setRepeatType(item.value)}
backgroundColor={colorTokens.card}
haptics
/>
</View>
{Platform.OS === 'ios' && (
<View style={styles.modalActions}>
<TouchableOpacity
style={[styles.modalBtn]}
onPress={() => setShowRepeatTypePicker(false)}
>
<Text style={styles.modalBtnText}></Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.modalBtn, styles.modalBtnPrimary]}
onPress={() => setShowRepeatTypePicker(false)}
>
<Text style={[styles.modalBtnText, styles.modalBtnTextPrimary]}></Text>
</TouchableOpacity>
</View>
)}
</View>
</Modal>
{/* 频率设置 */}
<View style={[styles.optionCard, { backgroundColor: colorTokens.card }]}>
<View style={styles.optionHeader}>
<View style={styles.optionIcon}>
<Text style={styles.optionIconText}>📊</Text>
</View>
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
</Text>
<TouchableOpacity style={styles.optionValue}>
<TouchableOpacity style={styles.optionValue} onPress={() => setShowFrequencyPicker(true)}>
<View style={styles.optionHeader}>
<View style={styles.optionIcon}>
<Text style={styles.optionIconText}>📊</Text>
</View>
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
</Text>
<Text style={[styles.optionValueText, { color: colorTokens.textSecondary }]}>
{frequency}
</Text>
<Text style={[styles.chevron, { color: colorTokens.textSecondary }]}>
</Text>
</TouchableOpacity>
</View>
</View>
</TouchableOpacity>
</View>
{/* 频率选择器弹窗 */}
<Modal
visible={showFrequencyPicker}
transparent
animationType="fade"
onRequestClose={() => setShowFrequencyPicker(false)}
>
<TouchableOpacity
style={styles.modalBackdrop}
activeOpacity={1}
onPress={() => setShowFrequencyPicker(false)}
/>
<View style={styles.modalSheet}>
<View style={{ height: 200 }}>
<WheelPickerExpo
height={200}
width={150}
initialSelectedIndex={frequency - 1}
items={FREQUENCY_OPTIONS.map(num => ({ label: num.toString(), value: num }))}
onChange={({ item }) => setFrequency(item.value)}
backgroundColor={colorTokens.card}
// selectedStyle={{ borderColor: colorTokens.primary, borderWidth: 2 }}
haptics
/>
</View>
{Platform.OS === 'ios' && (
<View style={styles.modalActions}>
<TouchableOpacity
style={[styles.modalBtn]}
onPress={() => setShowFrequencyPicker(false)}
>
<Text style={styles.modalBtnText}></Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.modalBtn, styles.modalBtnPrimary]}
onPress={() => setShowFrequencyPicker(false)}
>
<Text style={[styles.modalBtnText, styles.modalBtnTextPrimary]}></Text>
</TouchableOpacity>
</View>
)}
</View>
</Modal>
{/* 提醒设置 */}
<View style={[styles.optionCard, { backgroundColor: colorTokens.card }]}>
<View style={styles.optionHeader}>
@@ -354,6 +456,34 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
};
const styles = StyleSheet.create({
gradientBackground: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
opacity: 0.6,
},
decorativeCircle1: {
position: 'absolute',
top: -20,
right: -20,
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: '#0EA5E9',
opacity: 0.1,
},
decorativeCircle2: {
position: 'absolute',
bottom: -15,
left: -15,
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: '#0EA5E9',
opacity: 0.05,
},
container: {
flex: 1,
},
@@ -378,15 +508,13 @@ const styles = StyleSheet.create({
paddingHorizontal: 20,
},
section: {
marginBottom: 24,
},
iconTitleContainer: {
flexDirection: 'row',
alignItems: 'flex-start',
backgroundColor: '#FFFFFF',
borderRadius: 16,
padding: 20,
marginBottom: 16,
padding: 16,
},
iconPlaceholder: {
width: 60,
@@ -492,6 +620,8 @@ const styles = StyleSheet.create({
bottom: 0,
padding: 16,
backgroundColor: '#FFFFFF',
justifyContent: 'center',
alignItems: 'center',
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
},