feat: 更新目标创建功能及相关组件
- 在 CreateGoalModal 中新增目标创建表单,支持设置标题、描述、重复周期、频率、提醒时间和结束日期 - 更新 GoalCard 组件,增加显示结束日期的功能 - 修改 goals.tsx 文件,调整 CreateGoalModal 的导入路径 - 更新 eslint 配置,增加对 node_modules 的忽略设置,优化代码检查
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
import { CreateGoalModal } from '@/components/CreateGoalModal';
|
|
||||||
import GoalTemplateModal from '@/components/GoalTemplateModal';
|
import GoalTemplateModal from '@/components/GoalTemplateModal';
|
||||||
import { GoalsPageGuide } from '@/components/GoalsPageGuide';
|
import { GoalsPageGuide } from '@/components/GoalsPageGuide';
|
||||||
import { GuideTestButton } from '@/components/GuideTestButton';
|
import { GuideTestButton } from '@/components/GuideTestButton';
|
||||||
import { TaskCard } from '@/components/TaskCard';
|
import { TaskCard } from '@/components/TaskCard';
|
||||||
import { TaskFilterTabs, TaskFilterType } from '@/components/TaskFilterTabs';
|
import { TaskFilterTabs, TaskFilterType } from '@/components/TaskFilterTabs';
|
||||||
import { TaskProgressCard } from '@/components/TaskProgressCard';
|
import { TaskProgressCard } from '@/components/TaskProgressCard';
|
||||||
|
import { CreateGoalModal } from '@/components/model/CreateGoalModal';
|
||||||
import { useGlobalDialog } from '@/components/ui/DialogProvider';
|
import { useGlobalDialog } from '@/components/ui/DialogProvider';
|
||||||
import { Colors } from '@/constants/Colors';
|
import { Colors } from '@/constants/Colors';
|
||||||
import { TAB_BAR_BOTTOM_OFFSET, TAB_BAR_HEIGHT } from '@/constants/TabBar';
|
import { TAB_BAR_BOTTOM_OFFSET, TAB_BAR_HEIGHT } from '@/constants/TabBar';
|
||||||
|
|||||||
@@ -118,7 +118,9 @@ export const GoalCard: React.FC<GoalCardProps> = ({
|
|||||||
onPress={handleDelete}
|
onPress={handleDelete}
|
||||||
activeOpacity={0.8}
|
activeOpacity={0.8}
|
||||||
>
|
>
|
||||||
<MaterialIcons name="delete" size={24} color="#EF4444" />
|
<MaterialIcons style={{
|
||||||
|
marginBottom: 10
|
||||||
|
}} name="delete" size={24} color="#EF4444" />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -187,6 +189,19 @@ export const GoalCard: React.FC<GoalCardProps> = ({
|
|||||||
<View style={styles.infoItem}>
|
<View style={styles.infoItem}>
|
||||||
<Text style={styles.infoText}>{getRepeatTypeText(goal)}</Text>
|
<Text style={styles.infoText}>{getRepeatTypeText(goal)}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* 结束日期 */}
|
||||||
|
{goal.endDate && (
|
||||||
|
<View style={styles.infoItem}>
|
||||||
|
<MaterialIcons
|
||||||
|
name="calendar-month"
|
||||||
|
size={12}
|
||||||
|
color="#9CA3AF"
|
||||||
|
style={{ marginRight: 4 }}
|
||||||
|
/>
|
||||||
|
<Text style={styles.infoText}>{goal.endDate}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { CreateGoalRequest, GoalPriority, RepeatType } from '@/types/goals';
|
|||||||
import { Ionicons } from '@expo/vector-icons';
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
import DateTimePicker from '@react-native-community/datetimepicker';
|
import DateTimePicker from '@react-native-community/datetimepicker';
|
||||||
import { LinearGradient } from 'expo-linear-gradient';
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Image,
|
Image,
|
||||||
@@ -71,6 +71,11 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
|||||||
initialData?.customRepeatRule?.dayOfMonth || [1, 15]
|
initialData?.customRepeatRule?.dayOfMonth || [1, 15]
|
||||||
); // 默认1号和15号
|
); // 默认1号和15号
|
||||||
|
|
||||||
|
// 结束日期选择状态
|
||||||
|
const [endDate, setEndDate] = useState<string | null>(initialData?.endDate || null);
|
||||||
|
const [showDatePicker, setShowDatePicker] = useState(false);
|
||||||
|
const [tempSelectedDate, setTempSelectedDate] = useState<Date | null>(null);
|
||||||
|
|
||||||
// 当 initialData 变化时更新表单状态
|
// 当 initialData 变化时更新表单状态
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialData) {
|
if (initialData) {
|
||||||
@@ -84,6 +89,7 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
|||||||
setPriority(initialData.priority || 5);
|
setPriority(initialData.priority || 5);
|
||||||
setSelectedWeekdays(initialData.customRepeatRule?.weekdays || [1, 2, 3, 4, 5]);
|
setSelectedWeekdays(initialData.customRepeatRule?.weekdays || [1, 2, 3, 4, 5]);
|
||||||
setSelectedMonthDays(initialData.customRepeatRule?.dayOfMonth || [1, 15]);
|
setSelectedMonthDays(initialData.customRepeatRule?.dayOfMonth || [1, 15]);
|
||||||
|
setEndDate(initialData.endDate || null);
|
||||||
}
|
}
|
||||||
}, [initialData]);
|
}, [initialData]);
|
||||||
|
|
||||||
@@ -99,6 +105,7 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
|||||||
setPriority(5);
|
setPriority(5);
|
||||||
setSelectedWeekdays([1, 2, 3, 4, 5]);
|
setSelectedWeekdays([1, 2, 3, 4, 5]);
|
||||||
setSelectedMonthDays([1, 15]);
|
setSelectedMonthDays([1, 15]);
|
||||||
|
setEndDate(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理关闭
|
// 处理关闭
|
||||||
@@ -139,6 +146,7 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
|||||||
dayOfMonth: repeatType === 'monthly' ? selectedMonthDays : undefined, // 根据重复类型设置月几
|
dayOfMonth: repeatType === 'monthly' ? selectedMonthDays : undefined, // 根据重复类型设置月几
|
||||||
},
|
},
|
||||||
startTime,
|
startTime,
|
||||||
|
endDate: endDate || undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('goalData', goalData);
|
console.log('goalData', goalData);
|
||||||
@@ -196,6 +204,55 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
|||||||
return date;
|
return date;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 日期选择器处理
|
||||||
|
const handleDateChange = (event: any, selectedDate?: Date) => {
|
||||||
|
if (Platform.OS === 'android') {
|
||||||
|
if (event.type === 'set' && selectedDate) {
|
||||||
|
const isoDate = selectedDate.toISOString().split('T')[0];
|
||||||
|
setEndDate(isoDate);
|
||||||
|
}
|
||||||
|
setShowDatePicker(false);
|
||||||
|
} else {
|
||||||
|
if (selectedDate) {
|
||||||
|
setTempSelectedDate(selectedDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmDate = () => {
|
||||||
|
setShowDatePicker(false);
|
||||||
|
if (tempSelectedDate) {
|
||||||
|
const isoDate = tempSelectedDate.toISOString().split('T')[0];
|
||||||
|
setEndDate(isoDate);
|
||||||
|
}
|
||||||
|
setTempSelectedDate(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancelDate = () => {
|
||||||
|
setShowDatePicker(false);
|
||||||
|
setTempSelectedDate(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showDatePickerModal = () => {
|
||||||
|
setShowDatePicker(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取当前结束日期对应的Date对象
|
||||||
|
const getCurrentEndDate = () => {
|
||||||
|
if (endDate) {
|
||||||
|
return new Date(endDate + 'T00:00:00');
|
||||||
|
}
|
||||||
|
const tomorrow = new Date();
|
||||||
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||||
|
return tomorrow;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化显示日期
|
||||||
|
const formatDisplayDate = (dateString: string) => {
|
||||||
|
const date = new Date(dateString + 'T00:00:00');
|
||||||
|
return `${date.getFullYear()}年${(date.getMonth() + 1).toString().padStart(2, '0')}月${date.getDate().toString().padStart(2, '0')}日`;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
visible={visible}
|
visible={visible}
|
||||||
@@ -268,10 +325,10 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
|||||||
? `${REPEAT_TYPE_OPTIONS.find(opt => opt.value === repeatType)?.label} ${selectedWeekdays.map(day => ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][day]).join(' ')}`
|
? `${REPEAT_TYPE_OPTIONS.find(opt => opt.value === repeatType)?.label} ${selectedWeekdays.map(day => ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][day]).join(' ')}`
|
||||||
: `${REPEAT_TYPE_OPTIONS.find(opt => opt.value === repeatType)?.label} ${selectedWeekdays.slice(0, 2).map(day => ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][day]).join(' ')}等${selectedWeekdays.length}天`
|
: `${REPEAT_TYPE_OPTIONS.find(opt => opt.value === repeatType)?.label} ${selectedWeekdays.slice(0, 2).map(day => ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][day]).join(' ')}等${selectedWeekdays.length}天`
|
||||||
: repeatType === 'monthly' && selectedMonthDays.length > 0
|
: repeatType === 'monthly' && selectedMonthDays.length > 0
|
||||||
? selectedMonthDays.length <= 3
|
? selectedMonthDays.length <= 3
|
||||||
? `${REPEAT_TYPE_OPTIONS.find(opt => opt.value === repeatType)?.label} ${selectedMonthDays.map(day => `${day}号`).join(' ')}`
|
? `${REPEAT_TYPE_OPTIONS.find(opt => opt.value === repeatType)?.label} ${selectedMonthDays.map(day => `${day}号`).join(' ')}`
|
||||||
: `${REPEAT_TYPE_OPTIONS.find(opt => opt.value === repeatType)?.label} ${selectedMonthDays.slice(0, 2).map(day => `${day}号`).join(' ')}等${selectedMonthDays.length}天`
|
: `${REPEAT_TYPE_OPTIONS.find(opt => opt.value === repeatType)?.label} ${selectedMonthDays.slice(0, 2).map(day => `${day}号`).join(' ')}等${selectedMonthDays.length}天`
|
||||||
: REPEAT_TYPE_OPTIONS.find(opt => opt.value === repeatType)?.label
|
: REPEAT_TYPE_OPTIONS.find(opt => opt.value === repeatType)?.label
|
||||||
}
|
}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[styles.chevron, { color: colorTokens.textSecondary }]}>
|
<Text style={[styles.chevron, { color: colorTokens.textSecondary }]}>
|
||||||
@@ -414,9 +471,9 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
|||||||
<View style={styles.optionHeader}>
|
<View style={styles.optionHeader}>
|
||||||
<View style={styles.optionIcon}>
|
<View style={styles.optionIcon}>
|
||||||
<Image
|
<Image
|
||||||
source={require('@/assets/images/icons/icon-fire.png')}
|
source={require('@/assets/images/icons/icon-fire.png')}
|
||||||
style={styles.optionIconImage}
|
style={styles.optionIconImage}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
|
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
|
||||||
频率
|
频率
|
||||||
@@ -480,9 +537,9 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
|||||||
<View style={styles.optionHeader}>
|
<View style={styles.optionHeader}>
|
||||||
<View style={styles.optionIcon}>
|
<View style={styles.optionIcon}>
|
||||||
<Image
|
<Image
|
||||||
source={require('@/assets/images/icons/icon-bell.png')}
|
source={require('@/assets/images/icons/icon-bell.png')}
|
||||||
style={styles.optionIconImage}
|
style={styles.optionIconImage}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
|
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
|
||||||
提醒
|
提醒
|
||||||
@@ -501,9 +558,9 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
|||||||
<View style={styles.optionHeader}>
|
<View style={styles.optionHeader}>
|
||||||
<View style={styles.optionIcon}>
|
<View style={styles.optionIcon}>
|
||||||
<Image
|
<Image
|
||||||
source={require('@/assets/images/icons/icon-clock.png')}
|
source={require('@/assets/images/icons/icon-clock.png')}
|
||||||
style={styles.optionIconImage}
|
style={styles.optionIconImage}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
|
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
|
||||||
时间
|
时间
|
||||||
@@ -522,6 +579,40 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* 结束日期设置 */}
|
||||||
|
<View style={[styles.optionCard, { backgroundColor: colorTokens.card }]}>
|
||||||
|
<View style={styles.optionHeader}>
|
||||||
|
<View style={styles.optionIcon}>
|
||||||
|
<Image
|
||||||
|
source={require('@/assets/images/icons/icon-calender.png')}
|
||||||
|
style={styles.optionIconImage}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
|
||||||
|
结束日期
|
||||||
|
</Text>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.optionValue]}
|
||||||
|
onPress={showDatePickerModal}
|
||||||
|
>
|
||||||
|
<Text style={[styles.optionValueText, { color: colorTokens.textSecondary }]}>
|
||||||
|
{endDate ? formatDisplayDate(endDate) : '无限制'}
|
||||||
|
</Text>
|
||||||
|
<Text style={[styles.chevron, { color: colorTokens.textSecondary }]}>
|
||||||
|
›
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
{endDate && (
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.clearEndDateButton}
|
||||||
|
onPress={() => setEndDate(null)}
|
||||||
|
>
|
||||||
|
<Text style={styles.clearEndDateText}>清除结束日期</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
{/* 时间选择器弹窗 */}
|
{/* 时间选择器弹窗 */}
|
||||||
<Modal
|
<Modal
|
||||||
visible={showTimePicker}
|
visible={showTimePicker}
|
||||||
@@ -561,6 +652,46 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
|||||||
</View>
|
</View>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
{/* 日期选择器弹窗 */}
|
||||||
|
<Modal
|
||||||
|
visible={showDatePicker}
|
||||||
|
transparent
|
||||||
|
animationType="fade"
|
||||||
|
onRequestClose={() => setShowDatePicker(false)}
|
||||||
|
>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.modalBackdrop}
|
||||||
|
activeOpacity={1}
|
||||||
|
onPress={handleCancelDate}
|
||||||
|
/>
|
||||||
|
<View style={styles.modalSheet}>
|
||||||
|
<DateTimePicker
|
||||||
|
value={tempSelectedDate || getCurrentEndDate()}
|
||||||
|
mode="date"
|
||||||
|
display={Platform.OS === 'ios' ? 'calendar' : 'default'}
|
||||||
|
onChange={handleDateChange}
|
||||||
|
minimumDate={new Date()}
|
||||||
|
locale="zh-CN"
|
||||||
|
/>
|
||||||
|
{Platform.OS === 'ios' && (
|
||||||
|
<View style={styles.modalActions}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.modalBtn]}
|
||||||
|
onPress={handleCancelDate}
|
||||||
|
>
|
||||||
|
<Text style={styles.modalBtnText}>取消</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.modalBtn, styles.modalBtnPrimary]}
|
||||||
|
onPress={handleConfirmDate}
|
||||||
|
>
|
||||||
|
<Text style={[styles.modalBtnText, styles.modalBtnTextPrimary]}>确定</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
{/* 描述输入(可选) */}
|
{/* 描述输入(可选) */}
|
||||||
<View style={[styles.optionCard, { backgroundColor: colorTokens.card }]}>
|
<View style={[styles.optionCard, { backgroundColor: colorTokens.card }]}>
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -933,6 +1064,19 @@ const styles = StyleSheet.create({
|
|||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
|
// 清除结束日期按钮样式
|
||||||
|
clearEndDateButton: {
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
paddingVertical: 8,
|
||||||
|
alignItems: 'center',
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderTopColor: '#F1F5F9',
|
||||||
|
},
|
||||||
|
clearEndDateText: {
|
||||||
|
color: '#EF4444',
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '500',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default CreateGoalModal;
|
export default CreateGoalModal;
|
||||||
@@ -5,6 +5,6 @@ const expoConfig = require('eslint-config-expo/flat');
|
|||||||
module.exports = defineConfig([
|
module.exports = defineConfig([
|
||||||
expoConfig,
|
expoConfig,
|
||||||
{
|
{
|
||||||
ignores: ['dist/*'],
|
ignores: ['dist/*', 'node_modules/*'],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
Reference in New Issue
Block a user