feat: 更新训练计划和打卡功能
- 在训练计划中新增训练项目的添加、更新和删除功能,支持用户灵活管理训练内容 - 优化训练计划排课界面,提升用户体验 - 更新打卡功能,支持按日期加载和展示打卡记录 - 删除不再使用的打卡相关页面,简化代码结构 - 新增今日训练页面,集成今日训练计划和动作展示 - 更新样式以适应新功能的展示和交互
This commit is contained in:
@@ -1,18 +1,18 @@
|
||||
import { HeaderBar } from '@/components/ui/HeaderBar';
|
||||
import { palette } from '@/constants/Colors';
|
||||
import { useAppSelector } from '@/hooks/redux';
|
||||
import { fetchExerciseConfig, normalizeToLibraryItems } from '@/services/exercises';
|
||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
import { loadExerciseLibrary } from '@/store/exerciseLibrarySlice';
|
||||
import { EXERCISE_LIBRARY, getCategories } from '@/utils/exerciseLibrary';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import * as Haptics from 'expo-haptics';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { useLocalSearchParams, useRouter } from 'expo-router';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Animated, FlatList, LayoutAnimation, Modal, Platform, SafeAreaView, StyleSheet, Text, TextInput, TouchableOpacity, UIManager, View } from 'react-native';
|
||||
import { Alert, Animated, FlatList, LayoutAnimation, Modal, Platform, SafeAreaView, StyleSheet, Text, TextInput, TouchableOpacity, UIManager, View } from 'react-native';
|
||||
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import type { ScheduleExercise } from './index';
|
||||
import { addExercise } from '@/store/scheduleExerciseSlice';
|
||||
import { addWorkoutExercise } from '@/store/workoutSlice';
|
||||
|
||||
const GOAL_TEXT: Record<string, { title: string; color: string; description: string }> = {
|
||||
postpartum_recovery: { title: '产后恢复', color: '#9BE370', description: '温和激活,核心重建' },
|
||||
@@ -40,12 +40,20 @@ function DynamicBackground({ color }: { color: string }) {
|
||||
|
||||
export default function SelectExerciseForScheduleScreen() {
|
||||
const router = useRouter();
|
||||
const params = useLocalSearchParams<{ planId?: string }>();
|
||||
const dispatch = useAppDispatch();
|
||||
const params = useLocalSearchParams<{ planId?: string; sessionId?: string }>();
|
||||
const { plans } = useAppSelector((s) => s.trainingPlan);
|
||||
const { currentSession } = useAppSelector((s) => s.workout);
|
||||
|
||||
const planId = params.planId;
|
||||
const sessionId = params.sessionId;
|
||||
const plan = useMemo(() => plans.find(p => p.id === planId), [plans, planId]);
|
||||
const goalConfig = plan ? (GOAL_TEXT[plan.goal] || { title: '训练计划', color: palette.primary, description: '开始你的训练之旅' }) : null;
|
||||
const session = useMemo(() => sessionId ? currentSession : null, [sessionId, currentSession]);
|
||||
|
||||
// 根据是否有sessionId来确定是训练计划模式还是训练会话模式
|
||||
const isSessionMode = !!sessionId;
|
||||
const targetGoal = plan?.goal || session?.trainingPlan?.goal;
|
||||
const goalConfig = targetGoal ? (GOAL_TEXT[targetGoal] || { title: isSessionMode ? '添加动作' : '训练计划', color: palette.primary, description: isSessionMode ? '选择要添加的动作' : '开始你的训练之旅' }) : null;
|
||||
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const [category, setCategory] = useState<string>('全部');
|
||||
@@ -55,8 +63,12 @@ export default function SelectExerciseForScheduleScreen() {
|
||||
const [showCustomReps, setShowCustomReps] = useState(false);
|
||||
const [customRepsInput, setCustomRepsInput] = useState('');
|
||||
const [showCategoryPicker, setShowCategoryPicker] = useState(false);
|
||||
const [serverLibrary, setServerLibrary] = useState<{ key: string; name: string; description: string; category: string }[] | null>(null);
|
||||
const [serverCategories, setServerCategories] = useState<string[] | null>(null);
|
||||
const [showRestModal, setShowRestModal] = useState(false);
|
||||
const [showNoteModal, setShowNoteModal] = useState(false);
|
||||
const [restDuration, setRestDuration] = useState(30);
|
||||
const [noteContent, setNoteContent] = useState('');
|
||||
const { categories: serverCategoryDtos, exercises: serverExercises } = useAppSelector((s) => s.exerciseLibrary);
|
||||
const [adding, setAdding] = useState(false);
|
||||
|
||||
const controlsOpacity = useRef(new Animated.Value(0)).current;
|
||||
|
||||
@@ -67,39 +79,16 @@ export default function SelectExerciseForScheduleScreen() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let aborted = false;
|
||||
const CACHE_KEY = '@exercise_config_v1';
|
||||
(async () => {
|
||||
try {
|
||||
const cached = await AsyncStorage.getItem(CACHE_KEY);
|
||||
if (cached && !aborted) {
|
||||
const parsed = JSON.parse(cached);
|
||||
const items = normalizeToLibraryItems(parsed);
|
||||
if (items.length) {
|
||||
setServerLibrary(items);
|
||||
const cats = Array.from(new Set(items.map((i) => i.category)));
|
||||
setServerCategories(cats);
|
||||
}
|
||||
}
|
||||
} catch { }
|
||||
try {
|
||||
const resp = await fetchExerciseConfig();
|
||||
console.log('fetchExerciseConfig', resp);
|
||||
if (aborted) return;
|
||||
const items = normalizeToLibraryItems(resp);
|
||||
setServerLibrary(items);
|
||||
const cats = Array.from(new Set(items.map((i) => i.category)));
|
||||
setServerCategories(cats);
|
||||
try { await AsyncStorage.setItem(CACHE_KEY, JSON.stringify(resp)); } catch { }
|
||||
} catch (err) { }
|
||||
})();
|
||||
return () => { aborted = true; };
|
||||
}, []);
|
||||
dispatch(loadExerciseLibrary());
|
||||
}, [dispatch]);
|
||||
|
||||
const categories = useMemo(() => {
|
||||
const base = serverCategories ?? getCategories();
|
||||
return ['全部', ...base];
|
||||
}, [serverCategories]);
|
||||
const base = serverCategoryDtos && serverCategoryDtos.length
|
||||
? serverCategoryDtos.map((c) => c.name)
|
||||
: getCategories();
|
||||
const unique = Array.from(new Set(base));
|
||||
return ['全部', ...unique];
|
||||
}, [serverCategoryDtos]);
|
||||
|
||||
const mainCategories = useMemo(() => {
|
||||
const preferred = ['全部', '核心与腹部', '脊柱与后链', '侧链与髋', '平衡与支撑'];
|
||||
@@ -110,7 +99,7 @@ export default function SelectExerciseForScheduleScreen() {
|
||||
return picked;
|
||||
}, [categories]);
|
||||
|
||||
const library = useMemo(() => serverLibrary ?? EXERCISE_LIBRARY, [serverLibrary]);
|
||||
const library = useMemo(() => (serverExercises && serverExercises.length ? serverExercises : EXERCISE_LIBRARY), [serverExercises]);
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const kw = keyword.trim().toLowerCase();
|
||||
@@ -131,29 +120,126 @@ export default function SelectExerciseForScheduleScreen() {
|
||||
}).start();
|
||||
}, [selected, controlsOpacity]);
|
||||
|
||||
const handleAdd = () => {
|
||||
if (!selected || !plan) return;
|
||||
const handleAdd = async () => {
|
||||
if (!selected || adding) return;
|
||||
|
||||
const exerciseData: ScheduleExercise = {
|
||||
key: `${selected.key}_${Date.now()}`,
|
||||
console.log('选择动作:', selected);
|
||||
|
||||
const newExerciseDto = {
|
||||
exerciseKey: selected.key,
|
||||
name: selected.name,
|
||||
category: selected.category,
|
||||
sets: Math.max(1, sets),
|
||||
reps: reps && reps > 0 ? reps : undefined,
|
||||
itemType: 'exercise',
|
||||
completed: false,
|
||||
plannedSets: sets,
|
||||
plannedReps: reps,
|
||||
itemType: 'exercise' as const,
|
||||
note: `${selected.category}训练`,
|
||||
};
|
||||
|
||||
console.log('添加动作到排课:', exerciseData);
|
||||
|
||||
// 通过路由参数传递数据回到排课页面
|
||||
router.push({
|
||||
pathname: '/training-plan/schedule',
|
||||
params: {
|
||||
planId: planId,
|
||||
newExercise: JSON.stringify(exerciseData)
|
||||
setAdding(true);
|
||||
try {
|
||||
if (isSessionMode && sessionId) {
|
||||
// 训练会话模式:添加到训练会话
|
||||
await dispatch(addWorkoutExercise({ sessionId, dto: newExerciseDto })).unwrap();
|
||||
} else if (plan) {
|
||||
// 训练计划模式:添加到训练计划
|
||||
const planExerciseDto = {
|
||||
exerciseKey: selected.key,
|
||||
name: selected.name,
|
||||
sets: sets,
|
||||
reps: reps,
|
||||
itemType: 'exercise' as const,
|
||||
note: `${selected.category}训练`,
|
||||
};
|
||||
await dispatch(addExercise({ planId: plan.id, dto: planExerciseDto })).unwrap();
|
||||
} else {
|
||||
throw new Error('缺少必要的参数');
|
||||
}
|
||||
} as any);
|
||||
|
||||
// 返回到上一页
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.error('添加动作失败:', error);
|
||||
Alert.alert('添加失败', '添加动作时出现错误,请稍后重试');
|
||||
} finally {
|
||||
setAdding(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 添加休息项目
|
||||
const handleAddRest = () => {
|
||||
setShowRestModal(true);
|
||||
Haptics.selectionAsync();
|
||||
};
|
||||
|
||||
// 添加备注项目
|
||||
const handleAddNote = () => {
|
||||
setShowNoteModal(true);
|
||||
Haptics.selectionAsync();
|
||||
};
|
||||
|
||||
// 确认添加休息
|
||||
const confirmAddRest = async () => {
|
||||
if (adding) return;
|
||||
|
||||
const restDto = {
|
||||
name: `间隔休息 ${restDuration}s`,
|
||||
restSec: restDuration,
|
||||
itemType: 'rest' as const,
|
||||
};
|
||||
|
||||
setAdding(true);
|
||||
try {
|
||||
if (isSessionMode && sessionId) {
|
||||
// 训练会话模式
|
||||
await dispatch(addWorkoutExercise({ sessionId, dto: restDto })).unwrap();
|
||||
} else if (plan) {
|
||||
// 训练计划模式
|
||||
await dispatch(addExercise({ planId: plan.id, dto: restDto })).unwrap();
|
||||
} else {
|
||||
throw new Error('缺少必要的参数');
|
||||
}
|
||||
|
||||
setShowRestModal(false);
|
||||
setRestDuration(30);
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.error('添加休息失败:', error);
|
||||
Alert.alert('添加失败', '添加休息时出现错误,请稍后重试');
|
||||
} finally {
|
||||
setAdding(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 确认添加备注
|
||||
const confirmAddNote = async () => {
|
||||
if (adding || !noteContent.trim()) return;
|
||||
|
||||
const noteDto = {
|
||||
name: '训练提示',
|
||||
note: noteContent.trim(),
|
||||
itemType: 'note' as const,
|
||||
};
|
||||
|
||||
setAdding(true);
|
||||
try {
|
||||
if (isSessionMode && sessionId) {
|
||||
// 训练会话模式
|
||||
await dispatch(addWorkoutExercise({ sessionId, dto: noteDto })).unwrap();
|
||||
} else if (plan) {
|
||||
// 训练计划模式
|
||||
await dispatch(addExercise({ planId: plan.id, dto: noteDto })).unwrap();
|
||||
} else {
|
||||
throw new Error('缺少必要的参数');
|
||||
}
|
||||
|
||||
setShowNoteModal(false);
|
||||
setNoteContent('');
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.error('添加备注失败:', error);
|
||||
Alert.alert('添加失败', '添加备注时出现错误,请稍后重试');
|
||||
} finally {
|
||||
setAdding(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onSelectItem = (key: string) => {
|
||||
@@ -162,19 +248,22 @@ export default function SelectExerciseForScheduleScreen() {
|
||||
setSelectedKey(null);
|
||||
return;
|
||||
}
|
||||
setSets(3);
|
||||
setReps(undefined);
|
||||
const sel = library.find((e) => e.key === key) as any;
|
||||
setSets(sel?.beginnerSets ?? 3);
|
||||
setReps(sel?.beginnerReps);
|
||||
setShowCustomReps(false);
|
||||
setCustomRepsInput('');
|
||||
setSelectedKey(key);
|
||||
};
|
||||
|
||||
if (!plan || !goalConfig) {
|
||||
if (!goalConfig || (!plan && !isSessionMode)) {
|
||||
return (
|
||||
<SafeAreaView style={styles.safeArea}>
|
||||
<HeaderBar title="选择动作" onBack={() => router.back()} />
|
||||
<View style={styles.errorContainer}>
|
||||
<ThemedText style={styles.errorText}>找不到指定的训练计划</ThemedText>
|
||||
<ThemedText style={styles.errorText}>
|
||||
{isSessionMode ? '找不到指定的训练会话' : '找不到指定的训练计划'}
|
||||
</ThemedText>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
@@ -187,7 +276,7 @@ export default function SelectExerciseForScheduleScreen() {
|
||||
|
||||
<SafeAreaView style={styles.contentWrapper}>
|
||||
<HeaderBar
|
||||
title="选择动作"
|
||||
title={isSessionMode ? "添加动作" : "选择动作"}
|
||||
onBack={() => router.back()}
|
||||
withSafeTop={false}
|
||||
transparent={true}
|
||||
@@ -200,10 +289,31 @@ export default function SelectExerciseForScheduleScreen() {
|
||||
<View style={[styles.planColorIndicator, { backgroundColor: goalConfig.color }]} />
|
||||
<View style={styles.planInfo}>
|
||||
<ThemedText style={styles.planTitle}>{goalConfig.title}</ThemedText>
|
||||
<ThemedText style={styles.planDescription}>从动作库里选择一个动作,设置组数与每组次数</ThemedText>
|
||||
<ThemedText style={styles.planDescription}>
|
||||
{isSessionMode ? '为当前训练会话添加动作' : '选择动作或添加休息、备注项目'}
|
||||
</ThemedText>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 快捷添加区域 */}
|
||||
<View style={styles.quickAddSection}>
|
||||
<TouchableOpacity
|
||||
style={[styles.quickAddBtn, { backgroundColor: `${goalConfig.color}15`, borderColor: goalConfig.color }]}
|
||||
onPress={handleAddRest}
|
||||
>
|
||||
<Ionicons name="time-outline" size={20} color={goalConfig.color} />
|
||||
<Text style={[styles.quickAddText, { color: goalConfig.color }]}>添加休息</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[styles.quickAddBtn, { backgroundColor: `${goalConfig.color}15`, borderColor: goalConfig.color }]}
|
||||
onPress={handleAddNote}
|
||||
>
|
||||
<Ionicons name="document-text-outline" size={20} color={goalConfig.color} />
|
||||
<Text style={[styles.quickAddText, { color: goalConfig.color }]}>添加备注</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* 大分类宫格 */}
|
||||
<View style={styles.catGrid}>
|
||||
{[...mainCategories, '更多'].map((item) => {
|
||||
@@ -327,6 +437,16 @@ export default function SelectExerciseForScheduleScreen() {
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={styles.itemTitle}>{item.name}</Text>
|
||||
<Text style={styles.itemMeta}>{item.category}</Text>
|
||||
{((item as any).targetMuscleGroups || (item as any).equipmentName) && (
|
||||
<Text style={styles.itemMeta}>
|
||||
{[(item as any).targetMuscleGroups, (item as any).equipmentName].filter(Boolean).join(' · ')}
|
||||
</Text>
|
||||
)}
|
||||
{(((item as any).beginnerSets || (item as any).beginnerReps)) && (
|
||||
<Text style={styles.itemMeta}>
|
||||
建议 {(item as any).beginnerSets ?? '-'} 组 × {(item as any).beginnerReps ?? '-'} 次
|
||||
</Text>
|
||||
)}
|
||||
<Text style={styles.itemDesc}>{item.description}</Text>
|
||||
</View>
|
||||
{isSelected && <Ionicons name="chevron-down" size={20} color={goalConfig.color} />}
|
||||
@@ -414,12 +534,14 @@ export default function SelectExerciseForScheduleScreen() {
|
||||
style={[
|
||||
styles.addBtn,
|
||||
{ backgroundColor: goalConfig.color },
|
||||
(!reps || reps <= 0) && { opacity: 0.5 }
|
||||
((!reps || reps <= 0) || adding) && { opacity: 0.5 }
|
||||
]}
|
||||
disabled={!reps || reps <= 0}
|
||||
disabled={!reps || reps <= 0 || adding}
|
||||
onPress={handleAdd}
|
||||
>
|
||||
<Text style={styles.addBtnText}>添加到训练计划</Text>
|
||||
<Text style={styles.addBtnText}>
|
||||
{adding ? '添加中...' : (isSessionMode ? '添加到训练会话' : '添加到训练计划')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
)}
|
||||
@@ -429,6 +551,104 @@ export default function SelectExerciseForScheduleScreen() {
|
||||
/>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
|
||||
{/* 休息时间配置模态框 */}
|
||||
<Modal visible={showRestModal} transparent animationType="fade" onRequestClose={() => setShowRestModal(false)}>
|
||||
<TouchableOpacity activeOpacity={1} style={styles.modalOverlay} onPress={() => setShowRestModal(false)}>
|
||||
<TouchableOpacity activeOpacity={1} style={styles.modalSheet} onPress={(e) => e.stopPropagation() as any}>
|
||||
<Text style={styles.modalTitle}>设置休息时间</Text>
|
||||
|
||||
<View style={styles.restTimeRow}>
|
||||
{[15, 30, 45, 60, 90, 120].map((v) => {
|
||||
const active = restDuration === v;
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={v}
|
||||
style={[
|
||||
styles.restChip,
|
||||
active && { backgroundColor: goalConfig.color, borderColor: goalConfig.color }
|
||||
]}
|
||||
onPress={() => {
|
||||
setRestDuration(v);
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
||||
}}
|
||||
>
|
||||
<Text style={[styles.restChipText, active && { color: '#FFFFFF' }]}>{v}s</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
|
||||
<View style={styles.customRestSection}>
|
||||
<Text style={styles.sectionLabel}>自定义时间</Text>
|
||||
<View style={styles.customRestRow}>
|
||||
<TextInput
|
||||
value={restDuration.toString()}
|
||||
onChangeText={(text) => {
|
||||
const num = parseInt(text) || 30;
|
||||
setRestDuration(Math.max(10, Math.min(300, num)));
|
||||
}}
|
||||
keyboardType="number-pad"
|
||||
style={styles.customRestInput}
|
||||
/>
|
||||
<Text style={styles.customRestUnit}>秒</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[styles.confirmBtn, { backgroundColor: goalConfig.color }]}
|
||||
onPress={confirmAddRest}
|
||||
disabled={adding}
|
||||
>
|
||||
<Text style={styles.confirmBtnText}>{adding ? '添加中...' : '确认添加'}</Text>
|
||||
</TouchableOpacity>
|
||||
</TouchableOpacity>
|
||||
</TouchableOpacity>
|
||||
</Modal>
|
||||
|
||||
{/* 备注配置模态框 */}
|
||||
<Modal visible={showNoteModal} transparent animationType="fade" onRequestClose={() => setShowNoteModal(false)}>
|
||||
<TouchableOpacity activeOpacity={1} style={styles.modalOverlay} onPress={() => setShowNoteModal(false)}>
|
||||
<TouchableOpacity activeOpacity={1} style={styles.modalSheet} onPress={(e) => e.stopPropagation() as any}>
|
||||
<Text style={styles.modalTitle}>添加训练提示</Text>
|
||||
|
||||
<TextInput
|
||||
value={noteContent}
|
||||
onChangeText={setNoteContent}
|
||||
placeholder="输入训练提醒或注意事项..."
|
||||
placeholderTextColor="#888F92"
|
||||
style={styles.noteModalInput}
|
||||
multiline
|
||||
maxLength={100}
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
<View style={styles.noteModalInfo}>
|
||||
<Text style={styles.noteCounter}>{noteContent.length}/100</Text>
|
||||
{noteContent.length > 0 && (
|
||||
<TouchableOpacity
|
||||
onPress={() => setNoteContent('')}
|
||||
style={styles.noteClearBtn}
|
||||
>
|
||||
<Ionicons name="close-circle" size={20} color="#888F92" />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.confirmBtn,
|
||||
{ backgroundColor: goalConfig.color },
|
||||
(!noteContent.trim() || adding) && { opacity: 0.5 }
|
||||
]}
|
||||
onPress={confirmAddNote}
|
||||
disabled={!noteContent.trim() || adding}
|
||||
>
|
||||
<Text style={styles.confirmBtnText}>{adding ? '添加中...' : '确认添加'}</Text>
|
||||
</TouchableOpacity>
|
||||
</TouchableOpacity>
|
||||
</TouchableOpacity>
|
||||
</Modal>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -492,6 +712,28 @@ const styles = StyleSheet.create({
|
||||
opacity: 0.8,
|
||||
},
|
||||
|
||||
// 快捷添加区域
|
||||
quickAddSection: {
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
marginBottom: 16,
|
||||
},
|
||||
quickAddBtn: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 16,
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
gap: 8,
|
||||
},
|
||||
quickAddText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '700',
|
||||
},
|
||||
|
||||
// 分类网格
|
||||
catGrid: {
|
||||
paddingTop: 10,
|
||||
@@ -671,7 +913,104 @@ const styles = StyleSheet.create({
|
||||
color: '#FFFFFF',
|
||||
fontSize: 12,
|
||||
},
|
||||
|
||||
// 休息时间配置
|
||||
restTimeRow: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
gap: 8,
|
||||
marginBottom: 16,
|
||||
},
|
||||
restChip: {
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 16,
|
||||
borderWidth: 1,
|
||||
borderColor: '#E5E7EB',
|
||||
backgroundColor: '#FFFFFF',
|
||||
},
|
||||
restChipText: {
|
||||
fontSize: 12,
|
||||
fontWeight: '700',
|
||||
color: '#384046',
|
||||
},
|
||||
|
||||
// 模态框自定义休息时间
|
||||
customRestSection: {
|
||||
marginBottom: 20,
|
||||
},
|
||||
sectionLabel: {
|
||||
fontSize: 14,
|
||||
fontWeight: '700',
|
||||
color: '#384046',
|
||||
marginBottom: 10,
|
||||
},
|
||||
customRestRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
},
|
||||
customRestInput: {
|
||||
flex: 1,
|
||||
height: 40,
|
||||
borderWidth: 1,
|
||||
borderColor: '#E5E7EB',
|
||||
borderRadius: 10,
|
||||
paddingHorizontal: 12,
|
||||
color: '#384046',
|
||||
fontSize: 16,
|
||||
textAlign: 'center',
|
||||
},
|
||||
customRestUnit: {
|
||||
fontSize: 14,
|
||||
color: '#384046',
|
||||
fontWeight: '600',
|
||||
},
|
||||
|
||||
// 备注模态框
|
||||
noteModalInput: {
|
||||
minHeight: 100,
|
||||
maxHeight: 150,
|
||||
borderWidth: 1,
|
||||
borderColor: '#E5E7EB',
|
||||
borderRadius: 12,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 10,
|
||||
color: '#384046',
|
||||
fontSize: 14,
|
||||
textAlignVertical: 'top',
|
||||
backgroundColor: '#FFFFFF',
|
||||
marginBottom: 10,
|
||||
},
|
||||
noteModalInfo: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 20,
|
||||
},
|
||||
noteCounter: {
|
||||
fontSize: 11,
|
||||
color: '#888F92',
|
||||
},
|
||||
noteClearBtn: {
|
||||
padding: 4,
|
||||
},
|
||||
|
||||
// 确认按钮
|
||||
confirmBtn: {
|
||||
paddingVertical: 12,
|
||||
borderRadius: 12,
|
||||
alignItems: 'center',
|
||||
marginTop: 10,
|
||||
},
|
||||
confirmBtnText: {
|
||||
color: '#FFFFFF',
|
||||
fontWeight: '800',
|
||||
fontSize: 14,
|
||||
},
|
||||
|
||||
addBtn: {
|
||||
marginTop: 20,
|
||||
paddingVertical: 12,
|
||||
borderRadius: 12,
|
||||
alignItems: 'center',
|
||||
|
||||
Reference in New Issue
Block a user