feat: 新增目标创建弹窗的重复周期选择功能
- 在目标创建弹窗中添加重复周期选择功能,支持用户选择每日、每周和每月的重复类型 - 实现每周和每月的具体日期选择,用户可自定义选择周几和每月的日期 - 更新相关样式,提升用户体验和界面美观性 - 新增图标资源,替换原有文本图标,增强视觉效果
This commit is contained in:
BIN
assets/images/icons/icon-bell.png
Normal file
BIN
assets/images/icons/icon-bell.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
assets/images/icons/icon-calender.png
Normal file
BIN
assets/images/icons/icon-calender.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
BIN
assets/images/icons/icon-clock.png
Normal file
BIN
assets/images/icons/icon-clock.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
assets/images/icons/icon-fire.png
Normal file
BIN
assets/images/icons/icon-fire.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
BIN
assets/images/icons/icon-remind.png
Normal file
BIN
assets/images/icons/icon-remind.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
@@ -6,16 +6,17 @@ 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,
|
||||
Image,
|
||||
Modal,
|
||||
Platform,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Switch,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import WheelPickerExpo from 'react-native-wheel-picker-expo';
|
||||
|
||||
@@ -31,7 +32,6 @@ const REPEAT_TYPE_OPTIONS: { value: RepeatType; label: string }[] = [
|
||||
{ value: 'daily', label: '每日' },
|
||||
{ value: 'weekly', label: '每周' },
|
||||
{ value: 'monthly', label: '每月' },
|
||||
{ value: 'custom', label: '自定义' },
|
||||
];
|
||||
|
||||
const FREQUENCY_OPTIONS = Array.from({ length: 30 }, (_, i) => i + 1);
|
||||
@@ -59,6 +59,11 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
||||
const [priority, setPriority] = useState<GoalPriority>(5);
|
||||
const [showTimePicker, setShowTimePicker] = useState(false);
|
||||
const [tempSelectedTime, setTempSelectedTime] = useState<Date | null>(null);
|
||||
|
||||
// 周几选择状态
|
||||
const [selectedWeekdays, setSelectedWeekdays] = useState<number[]>([1, 2, 3, 4, 5]); // 默认周一到周五
|
||||
// 每月日期选择状态
|
||||
const [selectedMonthDays, setSelectedMonthDays] = useState<number[]>([1, 15]); // 默认1号和15号
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
@@ -70,6 +75,8 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
||||
setReminderTime('20:00');
|
||||
setCategory('');
|
||||
setPriority(5);
|
||||
setSelectedWeekdays([1, 2, 3, 4, 5]);
|
||||
setSelectedMonthDays([1, 15]);
|
||||
};
|
||||
|
||||
// 处理关闭
|
||||
@@ -105,10 +112,10 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
||||
priority,
|
||||
hasReminder,
|
||||
reminderTime: hasReminder ? reminderTime : undefined,
|
||||
reminderSettings: hasReminder ? {
|
||||
enabled: true,
|
||||
weekdays: [1, 2, 3, 4, 5, 6, 0], // 默认每天
|
||||
} : undefined,
|
||||
customRepeatRule: {
|
||||
weekdays: repeatType === 'weekly' ? selectedWeekdays : [1, 2, 3, 4, 5, 6, 0], // 根据重复类型设置周几
|
||||
dayOfMonth: repeatType === 'monthly' ? selectedMonthDays : undefined, // 根据重复类型设置月几
|
||||
},
|
||||
startTime,
|
||||
};
|
||||
|
||||
@@ -225,13 +232,25 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
||||
<TouchableOpacity style={styles.optionValue} onPress={() => setShowRepeatTypePicker(true)}>
|
||||
<View style={styles.optionHeader}>
|
||||
<View style={styles.optionIcon}>
|
||||
<Text style={styles.optionIconText}>🔄</Text>
|
||||
<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 }]}>
|
||||
{REPEAT_TYPE_OPTIONS.find(opt => opt.value === repeatType)?.label}
|
||||
{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 }]}>
|
||||
›
|
||||
@@ -252,34 +271,117 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
||||
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 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>
|
||||
{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>
|
||||
|
||||
{/* 周几选择 - 仅在选择每周时显示 */}
|
||||
{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>
|
||||
|
||||
@@ -289,7 +391,10 @@ export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
|
||||
|
||||
<View style={styles.optionHeader}>
|
||||
<View style={styles.optionIcon}>
|
||||
<Text style={styles.optionIconText}>📊</Text>
|
||||
<Image
|
||||
source={require('@/assets/images/icons/icon-fire.png')}
|
||||
style={styles.optionIconImage}
|
||||
/>
|
||||
</View>
|
||||
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
|
||||
频率
|
||||
@@ -352,7 +457,10 @@ 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>
|
||||
<Image
|
||||
source={require('@/assets/images/icons/icon-bell.png')}
|
||||
style={styles.optionIconImage}
|
||||
/>
|
||||
</View>
|
||||
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
|
||||
提醒
|
||||
@@ -370,7 +478,10 @@ 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>
|
||||
<Image
|
||||
source={require('@/assets/images/icons/icon-clock.png')}
|
||||
style={styles.optionIconImage}
|
||||
/>
|
||||
</View>
|
||||
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
|
||||
时间
|
||||
@@ -577,6 +688,11 @@ const styles = StyleSheet.create({
|
||||
optionIconText: {
|
||||
fontSize: 16,
|
||||
},
|
||||
optionIconImage: {
|
||||
width: 20,
|
||||
height: 20,
|
||||
resizeMode: 'contain',
|
||||
},
|
||||
optionLabel: {
|
||||
flex: 1,
|
||||
fontSize: 16,
|
||||
@@ -655,6 +771,146 @@ const styles = StyleSheet.create({
|
||||
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',
|
||||
},
|
||||
});
|
||||
|
||||
export default CreateGoalModal;
|
||||
@@ -1,6 +1,6 @@
|
||||
// 目标管理相关类型定义
|
||||
|
||||
export type RepeatType = 'daily' | 'weekly' | 'monthly' | 'custom';
|
||||
export type RepeatType = 'daily' | 'weekly' | 'monthly';
|
||||
|
||||
export type GoalStatus = 'active' | 'paused' | 'completed' | 'cancelled';
|
||||
|
||||
@@ -8,16 +8,15 @@ export type GoalPriority = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
|
||||
|
||||
// 自定义重复规则
|
||||
export interface CustomRepeatRule {
|
||||
type: 'weekly' | 'monthly';
|
||||
weekdays?: number[]; // 0-6,0为周日
|
||||
monthDays?: number[]; // 1-31
|
||||
interval?: number; // 间隔周数或月数
|
||||
dayOfMonth?: number[]; // 1-31
|
||||
}
|
||||
|
||||
// 提醒设置
|
||||
export interface ReminderSettings {
|
||||
enabled: boolean;
|
||||
weekdays?: number[]; // 0-6,0为周日
|
||||
monthDays?: number[]; // 1-31,每月几号
|
||||
sound?: string;
|
||||
vibration?: boolean;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user