Files
digital-pilates/components/model/CreateGoalModal.tsx
richarjiang 0a8b20f0ec feat: 增强目标管理功能及相关组件
- 在 GoalsListScreen 中新增目标编辑功能,支持用户编辑现有目标
- 更新 CreateGoalModal 组件,支持编辑模式下的目标更新
- 在 NutritionRecordsScreen 中新增删除营养记录功能,允许用户删除不需要的记录
- 更新 NutritionRecordCard 组件,增加操作选项,支持删除记录
- 修改 dietRecords 服务,添加删除营养记录的 API 调用
- 优化 goalsSlice,确保目标更新逻辑与 Redux 状态管理一致
2025-08-26 22:34:03 +08:00

1107 lines
35 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Colors } from '@/constants/Colors';
import { useColorScheme } from '@/hooks/useColorScheme';
import { CreateGoalRequest, GoalPriority, RepeatType, UpdateGoalRequest } from '@/types/goals';
import { Ionicons } from '@expo/vector-icons';
import DateTimePicker from '@react-native-community/datetimepicker';
import { LinearGradient } from 'expo-linear-gradient';
import React, { useEffect, useState } from 'react';
import {
Alert,
Image,
Modal,
Platform,
ScrollView,
StyleSheet,
Switch,
Text,
TextInput,
TouchableOpacity,
View,
} from 'react-native';
import WheelPickerExpo from 'react-native-wheel-picker-expo';
interface CreateGoalModalProps {
visible: boolean;
onClose: () => void;
onSubmit: (goalData: CreateGoalRequest) => void;
onUpdate?: (goalId: string, goalData: UpdateGoalRequest) => void;
onSuccess?: () => void;
loading?: boolean;
initialData?: Partial<CreateGoalRequest>;
editGoalId?: string;
}
const REPEAT_TYPE_OPTIONS: { value: RepeatType; label: string }[] = [
{ value: 'daily', label: '每日' },
{ value: 'weekly', label: '每周' },
{ value: 'monthly', label: '每月' },
];
const FREQUENCY_OPTIONS = Array.from({ length: 30 }, (_, i) => i + 1);
export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
visible,
onClose,
onSubmit,
onUpdate,
onSuccess,
loading = false,
initialData,
editGoalId,
}) => {
const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
const colorTokens = Colors[theme];
// 表单状态
const [title, setTitle] = useState(initialData?.title || '');
const [description, setDescription] = useState(initialData?.description || '');
const [repeatType, setRepeatType] = useState<RepeatType>(initialData?.repeatType || 'daily');
const [frequency, setFrequency] = useState(initialData?.frequency || 1);
const [hasReminder, setHasReminder] = useState(initialData?.hasReminder || false);
const [showFrequencyPicker, setShowFrequencyPicker] = useState(false);
const [showRepeatTypePicker, setShowRepeatTypePicker] = useState(false);
const [reminderTime, setReminderTime] = useState(initialData?.reminderTime || '20:00');
const [category, setCategory] = useState(initialData?.category || '');
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]
); // 默认周一到周五
// 每月日期选择状态
const [selectedMonthDays, setSelectedMonthDays] = useState<number[]>(
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) {
setTitle(initialData.title || '');
setDescription(initialData.description || '');
setRepeatType(initialData.repeatType || 'daily');
setFrequency(initialData.frequency || 1);
setHasReminder(initialData.hasReminder || false);
setReminderTime(initialData.reminderTime || '20:00');
setCategory(initialData.category || '');
setPriority(initialData.priority || 5);
setSelectedWeekdays(initialData.customRepeatRule?.weekdays || [1, 2, 3, 4, 5]);
setSelectedMonthDays(initialData.customRepeatRule?.dayOfMonth || [1, 15]);
setEndDate(initialData.endDate || null);
}
}, [initialData]);
// 重置表单
const resetForm = () => {
setTitle('');
setDescription('');
setRepeatType('daily');
setFrequency(1);
setHasReminder(false);
setReminderTime('20:00');
setCategory('');
setPriority(5);
setSelectedWeekdays([1, 2, 3, 4, 5]);
setSelectedMonthDays([1, 15]);
setEndDate(null);
};
// 处理关闭
const handleClose = () => {
if (!loading) {
resetForm();
onClose();
}
};
// 处理提交
const handleSubmit = () => {
if (!title.trim()) {
Alert.alert('提示', '请输入目标标题');
return;
}
// 计算startTime从reminderTime中获取小时和分钟转换为当天的分钟数
let startTime: number | undefined;
if (reminderTime) {
const [hours, minutes] = reminderTime.split(':').map(Number);
startTime = hours * 60 + minutes;
}
// 根据是否是编辑模式决定数据结构
if (editGoalId && onUpdate) {
// 更新模式:使用 UpdateGoalRequest 结构
const updateData: UpdateGoalRequest = {
title: title.trim(),
description: description.trim() || undefined,
repeatType,
frequency,
category: category.trim() || undefined,
priority,
hasReminder,
reminderTime: hasReminder ? reminderTime : undefined,
customRepeatRule: {
weekdays: repeatType === 'weekly' ? selectedWeekdays : [1, 2, 3, 4, 5, 6, 0],
dayOfMonth: repeatType === 'monthly' ? selectedMonthDays : undefined,
},
endDate: endDate || undefined,
};
console.log('updateData', updateData);
onUpdate(editGoalId, updateData);
} else {
// 创建模式:使用 CreateGoalRequest 结构
const goalData: CreateGoalRequest = {
title: title.trim(),
description: description.trim() || undefined,
repeatType,
frequency,
category: category.trim() || undefined,
priority,
hasReminder,
reminderTime: hasReminder ? reminderTime : undefined,
customRepeatRule: {
weekdays: repeatType === 'weekly' ? selectedWeekdays : [1, 2, 3, 4, 5, 6, 0],
dayOfMonth: repeatType === 'monthly' ? selectedMonthDays : undefined,
},
startTime,
endDate: endDate || undefined,
};
console.log('goalData', goalData);
onSubmit(goalData);
}
// 通知父组件提交成功
if (onSuccess) {
onSuccess();
}
};
// 时间选择器
const handleTimeChange = (event: any, selectedDate?: Date) => {
if (Platform.OS === 'android') {
// Android: 用户点击系统确认按钮后自动关闭
if (event.type === 'set' && selectedDate) {
const hours = selectedDate.getHours().toString().padStart(2, '0');
const minutes = selectedDate.getMinutes().toString().padStart(2, '0');
setReminderTime(`${hours}:${minutes}`);
}
setShowTimePicker(false);
} else {
// iOS: 只在用户点击自定义确认按钮时更新
if (selectedDate) {
setTempSelectedTime(selectedDate);
}
}
};
const handleConfirmTime = () => {
setShowTimePicker(false);
if (tempSelectedTime) {
const hours = tempSelectedTime.getHours().toString().padStart(2, '0');
const minutes = tempSelectedTime.getMinutes().toString().padStart(2, '0');
setReminderTime(`${hours}:${minutes}`);
}
setTempSelectedTime(null);
};
const handleCancelTime = () => {
setShowTimePicker(false);
setTempSelectedTime(null);
};
const showTimePickerModal = () => {
setShowTimePicker(true);
};
// 获取当前时间对应的Date对象
const getCurrentTimeDate = () => {
const [hours, minutes] = reminderTime.split(':').map(Number);
const date = new Date();
date.setHours(hours, minutes, 0, 0);
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}
animationType="slide"
presentationStyle="pageSheet"
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}>
<Ionicons name="close" size={24} color={colorTokens.text} />
</TouchableOpacity>
<Text style={[styles.title, { color: colorTokens.text }]}>
{editGoalId ? '编辑目标' : '创建新目标'}
</Text>
<View style={{ width: 24 }} />
</View>
<ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
{/* 目标标题输入 */}
<View style={styles.section}>
<View style={styles.iconTitleContainer}>
{/* <View style={styles.iconPlaceholder}>
<Text style={styles.iconText}>图标</Text>
</View> */}
<TextInput
style={[styles.titleInput, { color: colorTokens.text }]}
placeholder="写点什么..."
// placeholderTextColor={colorTokens.textSecondary}
value={title}
onChangeText={setTitle}
multiline
maxLength={100}
/>
</View>
{/* 装饰图案 */}
<View style={styles.decorationContainer}>
<View style={styles.decoration} />
</View>
</View>
{/* 目标重复周期 */}
<View style={[styles.optionCard, { backgroundColor: colorTokens.card }]}>
<TouchableOpacity style={styles.optionValue} onPress={() => setShowRepeatTypePicker(true)}>
<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>
<Text style={[styles.optionValueText, { color: colorTokens.textSecondary }]}>
{repeatType === 'weekly' && selectedWeekdays.length > 0
? selectedWeekdays.length <= 3
? `${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
}
</Text>
<Text style={[styles.chevron, { color: colorTokens.textSecondary }]}>
</Text>
</View>
</TouchableOpacity>
</View>
{/* 重复周期选择器弹窗 */}
<Modal
visible={showRepeatTypePicker}
transparent
animationType="fade"
onRequestClose={() => setShowRepeatTypePicker(false)}
>
<TouchableOpacity
style={styles.modalBackdrop}
activeOpacity={1}
onPress={() => setShowRepeatTypePicker(false)}
/>
<View style={styles.repeatTypeModalSheet}>
{/* 关闭按钮 */}
<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) => (
<TouchableOpacity
key={option.value}
style={[
styles.repeatTypeButton,
repeatType === option.value && styles.repeatTypeButtonSelected
]}
onPress={() => setRepeatType(option.value)}
>
<Text style={[
styles.repeatTypeButtonText,
repeatType === option.value && styles.repeatTypeButtonTextSelected
]}>
{option.label}
</Text>
</TouchableOpacity>
))}
</View>
{/* 周几选择 - 仅在选择每周时显示 */}
{repeatType === 'weekly' && (
<View style={styles.weekdaySelection}>
<View style={styles.weekdayOptions}>
{[
{ value: 0, label: '周日' },
{ value: 1, label: '周一' },
{ value: 2, label: '周二' },
{ value: 3, label: '周三' },
{ value: 4, label: '周四' },
{ value: 5, label: '周五' },
{ value: 6, label: '周六' },
].map((weekday) => (
<TouchableOpacity
key={weekday.value}
style={[
styles.weekdayButton,
selectedWeekdays.includes(weekday.value) && styles.weekdayButtonSelected
]}
onPress={() => {
if (selectedWeekdays.includes(weekday.value)) {
setSelectedWeekdays(selectedWeekdays.filter(day => day !== weekday.value));
} else {
setSelectedWeekdays([...selectedWeekdays, weekday.value]);
}
}}
>
<Text style={[
styles.weekdayButtonText,
selectedWeekdays.includes(weekday.value) && styles.weekdayButtonTextSelected
]}>
{weekday.label}
</Text>
</TouchableOpacity>
))}
</View>
</View>
)}
{/* 每月日期选择 - 仅在选择每月时显示 */}
{repeatType === 'monthly' && (
<View style={styles.monthDaySelection}>
<View style={styles.monthDayGrid}>
{Array.from({ length: 31 }, (_, i) => i + 1).map((day) => (
<TouchableOpacity
key={day}
style={[
styles.monthDayGridItem,
selectedMonthDays.includes(day) && styles.monthDayGridItemSelected
]}
onPress={() => {
if (selectedMonthDays.includes(day)) {
setSelectedMonthDays(selectedMonthDays.filter(d => d !== day));
} else {
setSelectedMonthDays([...selectedMonthDays, day]);
}
}}
activeOpacity={0.8}
>
<Text style={[
styles.monthDayGridText,
selectedMonthDays.includes(day) && styles.monthDayGridTextSelected
]}>
{day}
</Text>
</TouchableOpacity>
))}
</View>
</View>
)}
{/* 完成按钮 */}
<TouchableOpacity
style={styles.repeatTypeCompleteButton}
onPress={() => setShowRepeatTypePicker(false)}
>
<Text style={styles.repeatTypeCompleteButtonText}></Text>
</TouchableOpacity>
</View>
</Modal>
{/* 频率设置 */}
<View style={[styles.optionCard, { backgroundColor: colorTokens.card }]}>
<TouchableOpacity style={styles.optionValue} onPress={() => setShowFrequencyPicker(true)}>
<View style={styles.optionHeader}>
<View style={styles.optionIcon}>
<Image
source={require('@/assets/images/icons/icon-fire.png')}
style={styles.optionIconImage}
/>
</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>
</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}>
<View style={styles.optionIcon}>
<Image
source={require('@/assets/images/icons/icon-bell.png')}
style={styles.optionIconImage}
/>
</View>
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
</Text>
<Switch
value={hasReminder}
onValueChange={setHasReminder}
trackColor={{ false: '#E5E5E5', true: '#6366F1' }}
thumbColor={hasReminder ? '#FFFFFF' : '#FFFFFF'}
/>
</View>
</View>
{/* 时间设置 */}
<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}
/>
</View>
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
</Text>
<TouchableOpacity
style={[styles.optionValue]}
onPress={showTimePickerModal}
>
<Text style={[styles.optionValueText, { color: colorTokens.textSecondary }]}>
{reminderTime}
</Text>
<Text style={[styles.chevron, { color: colorTokens.textSecondary }]}>
</Text>
</TouchableOpacity>
</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}
transparent
animationType="fade"
onRequestClose={() => setShowTimePicker(false)}
>
<TouchableOpacity
style={styles.modalBackdrop}
activeOpacity={1}
onPress={handleCancelTime}
/>
<View style={styles.modalSheet}>
<DateTimePicker
value={tempSelectedTime || getCurrentTimeDate()}
mode="time"
is24Hour={true}
display={Platform.OS === 'ios' ? 'spinner' : 'default'}
onChange={handleTimeChange}
/>
{Platform.OS === 'ios' && (
<View style={styles.modalActions}>
<TouchableOpacity
style={[styles.modalBtn]}
onPress={handleCancelTime}
>
<Text style={styles.modalBtnText}></Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.modalBtn, styles.modalBtnPrimary]}
onPress={handleConfirmTime}
>
<Text style={[styles.modalBtnText, styles.modalBtnTextPrimary]}></Text>
</TouchableOpacity>
</View>
)}
</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
style={[styles.descriptionInput, { color: colorTokens.text }]}
placeholder="添加描述(可选)"
placeholderTextColor={colorTokens.textSecondary}
value={description}
onChangeText={setDescription}
multiline
maxLength={500}
/>
</View>
</ScrollView>
{/* 保存按钮 */}
<View style={styles.footer}>
<TouchableOpacity
style={[
styles.saveButton,
{ opacity: loading || !title.trim() ? 0.5 : 1 }
]}
onPress={handleSubmit}
disabled={loading || !title.trim()}
>
<Text style={styles.saveButtonText}>
{loading ? (editGoalId ? '更新中...' : '保存中...') : (editGoalId ? '更新' : '保存')}
</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
);
};
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,
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 20,
paddingTop: 60,
paddingBottom: 20,
},
cancelButton: {
fontSize: 24,
fontWeight: '600',
},
title: {
fontSize: 18,
fontWeight: '600',
},
content: {
flex: 1,
paddingHorizontal: 20,
},
section: {
},
iconTitleContainer: {
flexDirection: 'row',
alignItems: 'flex-start',
backgroundColor: '#FFFFFF',
borderRadius: 16,
padding: 16,
},
iconPlaceholder: {
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: '#F3F4F6',
alignItems: 'center',
justifyContent: 'center',
marginRight: 16,
},
iconText: {
fontSize: 12,
color: '#9CA3AF',
fontWeight: '500',
},
titleInput: {
flex: 1,
fontSize: 16,
fontWeight: '500',
minHeight: 60,
textAlignVertical: 'top',
},
decorationContainer: {
alignItems: 'flex-end',
paddingRight: 20,
},
decoration: {
width: 80,
height: 60,
backgroundColor: '#E0E7FF',
borderRadius: 40,
opacity: 0.6,
},
optionCard: {
borderRadius: 16,
marginBottom: 12,
overflow: 'hidden',
},
optionHeader: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
},
optionIcon: {
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: '#F3F4F6',
alignItems: 'center',
justifyContent: 'center',
marginRight: 12,
},
optionIconText: {
fontSize: 16,
},
optionIconImage: {
width: 20,
height: 20,
resizeMode: 'contain',
},
optionLabel: {
flex: 1,
fontSize: 16,
fontWeight: '500',
},
optionValue: {
flexDirection: 'row',
alignItems: 'center',
},
optionValueText: {
fontSize: 16,
fontWeight: '500',
marginRight: 8,
},
chevron: {
fontSize: 20,
fontWeight: '300',
},
descriptionInput: {
padding: 16,
fontSize: 16,
minHeight: 80,
textAlignVertical: 'top',
},
footer: {
padding: 20,
paddingBottom: 40,
},
saveButton: {
backgroundColor: '#6366F1',
borderRadius: 16,
paddingVertical: 16,
alignItems: 'center',
},
saveButtonText: {
color: '#FFFFFF',
fontSize: 18,
fontWeight: '600',
},
modalBackdrop: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0,0,0,0.35)',
},
modalSheet: {
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
padding: 16,
backgroundColor: '#FFFFFF',
justifyContent: 'center',
alignItems: 'center',
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
},
modalActions: {
flexDirection: 'row',
justifyContent: 'flex-end',
marginTop: 8,
gap: 12,
},
modalBtn: {
paddingHorizontal: 14,
paddingVertical: 10,
borderRadius: 10,
backgroundColor: '#F1F5F9',
},
modalBtnPrimary: {
backgroundColor: '#6366F1',
},
modalBtnText: {
color: '#334155',
fontWeight: '700',
},
modalBtnTextPrimary: {
color: '#FFFFFF',
fontWeight: '700',
},
// 重复类型选择器弹窗样式
repeatTypeModalSheet: {
position: 'absolute',
left: 20,
right: 20,
top: '50%',
transform: [{ translateY: -300 }],
backgroundColor: '#FFFFFF',
borderRadius: 16,
padding: 24,
alignItems: 'center',
maxHeight: 500,
},
modalCloseButton: {
position: 'absolute',
top: 16,
left: 16,
width: 24,
height: 24,
alignItems: 'center',
justifyContent: 'center',
},
modalCloseButtonText: {
fontSize: 20,
color: '#666666',
fontWeight: '300',
},
repeatTypeModalTitle: {
fontSize: 18,
fontWeight: '600',
color: '#333333',
marginBottom: 20,
textAlign: 'center',
},
repeatTypeOptions: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center',
gap: 8,
marginBottom: 20,
},
repeatTypeButton: {
paddingHorizontal: 16,
paddingVertical: 10,
borderRadius: 20,
backgroundColor: '#F5F5F5',
borderWidth: 1,
borderColor: '#E5E5E5',
marginHorizontal: 4,
},
repeatTypeButtonSelected: {
backgroundColor: '#E6E6FA',
borderColor: '#8A2BE2',
},
repeatTypeButtonText: {
fontSize: 14,
color: '#666666',
fontWeight: '500',
},
repeatTypeButtonTextSelected: {
color: '#8A2BE2',
fontWeight: '600',
},
weekdaySelection: {
width: '100%',
marginBottom: 20,
},
weekdayOptions: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center',
gap: 6,
},
weekdayButton: {
paddingHorizontal: 12,
paddingVertical: 8,
borderRadius: 16,
backgroundColor: '#F5F5F5',
borderWidth: 1,
borderColor: '#E5E5E5',
marginHorizontal: 2,
},
weekdayButtonSelected: {
backgroundColor: '#8A2BE2',
borderColor: '#8A2BE2',
},
weekdayButtonText: {
fontSize: 12,
color: '#666666',
fontWeight: '500',
},
weekdayButtonTextSelected: {
color: '#FFFFFF',
fontWeight: '600',
},
// 每月日期选择样式 - 日历网格布局
monthDaySelection: {
width: '100%',
marginBottom: 20,
},
monthDayGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'flex-start',
paddingHorizontal: 8,
},
monthDayGridItem: {
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: '#F5F5F5',
alignItems: 'center',
justifyContent: 'center',
marginBottom: 10,
marginRight: 8,
},
monthDayGridItemSelected: {
backgroundColor: '#8A2BE2',
},
monthDayGridText: {
fontSize: 12,
fontWeight: '600',
color: '#666666',
},
monthDayGridTextSelected: {
color: '#FFFFFF',
},
repeatTypeCompleteButton: {
width: '100%',
backgroundColor: '#8A2BE2',
borderRadius: 20,
paddingVertical: 14,
alignItems: 'center',
marginTop: 10,
},
repeatTypeCompleteButtonText: {
color: '#FFFFFF',
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;