feat: 更新目标创建功能及相关组件
- 在 CreateGoalModal 中新增目标创建表单,支持设置标题、描述、重复周期、频率、提醒时间和结束日期 - 更新 GoalCard 组件,增加显示结束日期的功能 - 修改 goals.tsx 文件,调整 CreateGoalModal 的导入路径 - 更新 eslint 配置,增加对 node_modules 的忽略设置,优化代码检查
This commit is contained in:
@@ -11,11 +11,11 @@ interface GoalCardProps {
|
||||
showStatus?: boolean;
|
||||
}
|
||||
|
||||
export const GoalCard: React.FC<GoalCardProps> = ({
|
||||
goal,
|
||||
onPress,
|
||||
export const GoalCard: React.FC<GoalCardProps> = ({
|
||||
goal,
|
||||
onPress,
|
||||
onDelete,
|
||||
showStatus = true
|
||||
showStatus = true
|
||||
}) => {
|
||||
const swipeableRef = useRef<Swipeable>(null);
|
||||
|
||||
@@ -70,7 +70,7 @@ export const GoalCard: React.FC<GoalCardProps> = ({
|
||||
// 根据目标类别或标题返回不同的图标
|
||||
const title = goal.title.toLowerCase();
|
||||
const category = goal.category?.toLowerCase();
|
||||
|
||||
|
||||
if (title.includes('运动') || title.includes('健身') || title.includes('跑步')) {
|
||||
return 'fitness-center';
|
||||
} else if (title.includes('喝水') || title.includes('饮水')) {
|
||||
@@ -118,7 +118,9 @@ export const GoalCard: React.FC<GoalCardProps> = ({
|
||||
onPress={handleDelete}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<MaterialIcons name="delete" size={24} color="#EF4444" />
|
||||
<MaterialIcons style={{
|
||||
marginBottom: 10
|
||||
}} name="delete" size={24} color="#EF4444" />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
@@ -149,7 +151,7 @@ export const GoalCard: React.FC<GoalCardProps> = ({
|
||||
<Text style={styles.goalTitle} numberOfLines={1}>
|
||||
{goal.title}
|
||||
</Text>
|
||||
|
||||
|
||||
{/* 底部信息行 */}
|
||||
<View style={styles.goalInfo}>
|
||||
{/* 积分 */}
|
||||
@@ -187,6 +189,19 @@ export const GoalCard: React.FC<GoalCardProps> = ({
|
||||
<View style={styles.infoItem}>
|
||||
<Text style={styles.infoText}>{getRepeatTypeText(goal)}</Text>
|
||||
</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>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ 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, useEffect } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Alert,
|
||||
Image,
|
||||
@@ -61,7 +61,7 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
||||
const [priority, setPriority] = useState<GoalPriority>(initialData?.priority || 5);
|
||||
const [showTimePicker, setShowTimePicker] = useState(false);
|
||||
const [tempSelectedTime, setTempSelectedTime] = useState<Date | null>(null);
|
||||
|
||||
|
||||
// 周几选择状态
|
||||
const [selectedWeekdays, setSelectedWeekdays] = useState<number[]>(
|
||||
initialData?.customRepeatRule?.weekdays || [1, 2, 3, 4, 5]
|
||||
@@ -71,6 +71,11 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
||||
initialData?.customRepeatRule?.dayOfMonth || [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 变化时更新表单状态
|
||||
useEffect(() => {
|
||||
if (initialData) {
|
||||
@@ -84,6 +89,7 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
||||
setPriority(initialData.priority || 5);
|
||||
setSelectedWeekdays(initialData.customRepeatRule?.weekdays || [1, 2, 3, 4, 5]);
|
||||
setSelectedMonthDays(initialData.customRepeatRule?.dayOfMonth || [1, 15]);
|
||||
setEndDate(initialData.endDate || null);
|
||||
}
|
||||
}, [initialData]);
|
||||
|
||||
@@ -99,6 +105,7 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
||||
setPriority(5);
|
||||
setSelectedWeekdays([1, 2, 3, 4, 5]);
|
||||
setSelectedMonthDays([1, 15]);
|
||||
setEndDate(null);
|
||||
};
|
||||
|
||||
// 处理关闭
|
||||
@@ -139,12 +146,13 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
||||
dayOfMonth: repeatType === 'monthly' ? selectedMonthDays : undefined, // 根据重复类型设置月几
|
||||
},
|
||||
startTime,
|
||||
endDate: endDate || undefined,
|
||||
};
|
||||
|
||||
console.log('goalData', goalData);
|
||||
|
||||
onSubmit(goalData);
|
||||
|
||||
|
||||
// 通知父组件提交成功
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
@@ -196,6 +204,55 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
||||
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 (
|
||||
<Modal
|
||||
visible={visible}
|
||||
@@ -254,7 +311,7 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
||||
<TouchableOpacity style={styles.optionValue} onPress={() => setShowRepeatTypePicker(true)}>
|
||||
<View style={styles.optionHeader}>
|
||||
<View style={styles.optionIcon}>
|
||||
<Image
|
||||
<Image
|
||||
source={require('@/assets/images/icons/icon-calender.png')}
|
||||
style={styles.optionIconImage}
|
||||
/>
|
||||
@@ -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.slice(0, 2).map(day => ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][day]).join(' ')}等${selectedWeekdays.length}天`
|
||||
: repeatType === 'monthly' && selectedMonthDays.length > 0
|
||||
? 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.slice(0, 2).map(day => `${day}号`).join(' ')}等${selectedMonthDays.length}天`
|
||||
: REPEAT_TYPE_OPTIONS.find(opt => opt.value === repeatType)?.label
|
||||
? 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.slice(0, 2).map(day => `${day}号`).join(' ')}等${selectedMonthDays.length}天`
|
||||
: REPEAT_TYPE_OPTIONS.find(opt => opt.value === repeatType)?.label
|
||||
}
|
||||
</Text>
|
||||
<Text style={[styles.chevron, { color: colorTokens.textSecondary }]}>
|
||||
@@ -295,16 +352,16 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
||||
/>
|
||||
<View style={styles.repeatTypeModalSheet}>
|
||||
{/* 关闭按钮 */}
|
||||
<TouchableOpacity
|
||||
<TouchableOpacity
|
||||
style={styles.modalCloseButton}
|
||||
onPress={() => setShowRepeatTypePicker(false)}
|
||||
>
|
||||
<Text style={styles.modalCloseButtonText}>×</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
|
||||
{/* 标题 */}
|
||||
<Text style={styles.repeatTypeModalTitle}>目标重复周期</Text>
|
||||
|
||||
|
||||
{/* 重复类型选择 */}
|
||||
<View style={styles.repeatTypeOptions}>
|
||||
{REPEAT_TYPE_OPTIONS.map((option) => (
|
||||
@@ -325,7 +382,7 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
|
||||
|
||||
{/* 周几选择 - 仅在选择每周时显示 */}
|
||||
{repeatType === 'weekly' && (
|
||||
<View style={styles.weekdaySelection}>
|
||||
@@ -396,7 +453,7 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
|
||||
{/* 完成按钮 */}
|
||||
<TouchableOpacity
|
||||
style={styles.repeatTypeCompleteButton}
|
||||
@@ -413,10 +470,10 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
||||
|
||||
<View style={styles.optionHeader}>
|
||||
<View style={styles.optionIcon}>
|
||||
<Image
|
||||
source={require('@/assets/images/icons/icon-fire.png')}
|
||||
style={styles.optionIconImage}
|
||||
/>
|
||||
<Image
|
||||
source={require('@/assets/images/icons/icon-fire.png')}
|
||||
style={styles.optionIconImage}
|
||||
/>
|
||||
</View>
|
||||
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
|
||||
频率
|
||||
@@ -479,10 +536,10 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
||||
<View style={[styles.optionCard, { backgroundColor: colorTokens.card }]}>
|
||||
<View style={styles.optionHeader}>
|
||||
<View style={styles.optionIcon}>
|
||||
<Image
|
||||
source={require('@/assets/images/icons/icon-bell.png')}
|
||||
style={styles.optionIconImage}
|
||||
/>
|
||||
<Image
|
||||
source={require('@/assets/images/icons/icon-bell.png')}
|
||||
style={styles.optionIconImage}
|
||||
/>
|
||||
</View>
|
||||
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
|
||||
提醒
|
||||
@@ -500,10 +557,10 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
||||
<View style={[styles.optionCard, { backgroundColor: colorTokens.card }]}>
|
||||
<View style={styles.optionHeader}>
|
||||
<View style={styles.optionIcon}>
|
||||
<Image
|
||||
source={require('@/assets/images/icons/icon-clock.png')}
|
||||
style={styles.optionIconImage}
|
||||
/>
|
||||
<Image
|
||||
source={require('@/assets/images/icons/icon-clock.png')}
|
||||
style={styles.optionIconImage}
|
||||
/>
|
||||
</View>
|
||||
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
|
||||
时间
|
||||
@@ -522,6 +579,40 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
||||
</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
|
||||
visible={showTimePicker}
|
||||
@@ -561,6 +652,46 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
||||
</View>
|
||||
</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 }]}>
|
||||
<TextInput
|
||||
@@ -933,6 +1064,19 @@ const styles = StyleSheet.create({
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
// 清除结束日期按钮样式
|
||||
clearEndDateButton: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
alignItems: 'center',
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#F1F5F9',
|
||||
},
|
||||
clearEndDateText: {
|
||||
color: '#EF4444',
|
||||
fontSize: 14,
|
||||
fontWeight: '500',
|
||||
},
|
||||
});
|
||||
|
||||
export default CreateGoalModal;
|
||||
Reference in New Issue
Block a user