feat: 更新应用版本和集成腾讯云 COS 上传功能
- 将应用版本更新至 1.0.2,修改相关配置文件 - 集成腾讯云 COS 上传功能,新增相关服务和钩子 - 更新 AI 体态评估页面,支持照片上传和评估结果展示 - 添加雷达图组件以展示评估结果 - 更新样式以适应新功能的展示和交互 - 修改登录页面背景效果,提升用户体验
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import DateTimePicker from '@react-native-community/datetimepicker';
|
||||
import { useRouter } from 'expo-router';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Pressable, SafeAreaView, ScrollView, StyleSheet, TextInput, View } from 'react-native';
|
||||
import DateTimePickerModal from 'react-native-modal-datetime-picker';
|
||||
import { Modal, Platform, Pressable, SafeAreaView, ScrollView, StyleSheet, TextInput, View } from 'react-native';
|
||||
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { ThemedView } from '@/components/ThemedView';
|
||||
@@ -39,6 +39,7 @@ export default function TrainingPlanScreen() {
|
||||
const { draft, current } = useAppSelector((s) => s.trainingPlan);
|
||||
const [weightInput, setWeightInput] = useState<string>('');
|
||||
const [datePickerVisible, setDatePickerVisible] = useState(false);
|
||||
const [pickerDate, setPickerDate] = useState<Date>(new Date());
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(loadTrainingPlan());
|
||||
@@ -60,12 +61,31 @@ export default function TrainingPlanScreen() {
|
||||
return true;
|
||||
}, [draft]);
|
||||
|
||||
const formattedStartDate = useMemo(() => {
|
||||
const d = new Date(draft.startDate);
|
||||
try {
|
||||
return new Intl.DateTimeFormat('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
weekday: 'short',
|
||||
}).format(d);
|
||||
} catch {
|
||||
return d.toLocaleDateString('zh-CN');
|
||||
}
|
||||
}, [draft.startDate]);
|
||||
|
||||
const handleSave = async () => {
|
||||
await dispatch(saveTrainingPlan()).unwrap().catch(() => { });
|
||||
router.back();
|
||||
};
|
||||
|
||||
const openDatePicker = () => setDatePickerVisible(true);
|
||||
const openDatePicker = () => {
|
||||
const base = draft.startDate ? new Date(draft.startDate) : new Date();
|
||||
base.setHours(0, 0, 0, 0);
|
||||
setPickerDate(base);
|
||||
setDatePickerVisible(true);
|
||||
};
|
||||
const closeDatePicker = () => setDatePickerVisible(false);
|
||||
const onConfirmDate = (date: Date) => {
|
||||
// 只允许今天之后(含今天)的日期
|
||||
@@ -161,7 +181,7 @@ export default function TrainingPlanScreen() {
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
<ThemedText style={styles.dateHint}>{new Date(draft.startDate).toLocaleDateString()}</ThemedText>
|
||||
<ThemedText style={styles.dateHint}>{formattedStartDate}</ThemedText>
|
||||
|
||||
<View style={styles.rowBetween}>
|
||||
<ThemedText style={styles.label}>开始体重 (kg)</ThemedText>
|
||||
@@ -197,13 +217,44 @@ export default function TrainingPlanScreen() {
|
||||
<View style={{ height: 32 }} />
|
||||
</ScrollView>
|
||||
</ThemedView>
|
||||
<DateTimePickerModal
|
||||
isVisible={datePickerVisible}
|
||||
mode="date"
|
||||
minimumDate={new Date()}
|
||||
onConfirm={onConfirmDate}
|
||||
onCancel={closeDatePicker}
|
||||
/>
|
||||
<Modal
|
||||
visible={datePickerVisible}
|
||||
transparent
|
||||
animationType="fade"
|
||||
onRequestClose={closeDatePicker}
|
||||
>
|
||||
<Pressable style={styles.modalBackdrop} onPress={closeDatePicker} />
|
||||
<View style={styles.modalSheet}>
|
||||
<DateTimePicker
|
||||
value={pickerDate}
|
||||
mode="date"
|
||||
display={Platform.OS === 'ios' ? 'inline' : 'calendar'}
|
||||
minimumDate={new Date()}
|
||||
{...(Platform.OS === 'ios' ? { locale: 'zh-CN' } : {})}
|
||||
onChange={(event, date) => {
|
||||
if (Platform.OS === 'ios') {
|
||||
if (date) setPickerDate(date);
|
||||
} else {
|
||||
if (event.type === 'set' && date) {
|
||||
onConfirmDate(date);
|
||||
} else {
|
||||
closeDatePicker();
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{Platform.OS === 'ios' && (
|
||||
<View style={styles.modalActions}>
|
||||
<Pressable onPress={closeDatePicker} style={[styles.modalBtn]}>
|
||||
<ThemedText style={styles.modalBtnText}>取消</ThemedText>
|
||||
</Pressable>
|
||||
<Pressable onPress={() => { onConfirmDate(pickerDate); }} style={[styles.modalBtn, styles.modalBtnPrimary]}>
|
||||
<ThemedText style={[styles.modalBtnText, styles.modalBtnTextPrimary]}>确定</ThemedText>
|
||||
</Pressable>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</Modal>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
@@ -239,12 +290,14 @@ const styles = StyleSheet.create({
|
||||
fontSize: 28,
|
||||
fontWeight: '800',
|
||||
color: '#1A1A1A',
|
||||
lineHeight: 36,
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 14,
|
||||
color: '#5E6468',
|
||||
marginTop: 6,
|
||||
marginBottom: 16,
|
||||
lineHeight: 20,
|
||||
},
|
||||
card: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
@@ -463,6 +516,42 @@ const styles = StyleSheet.create({
|
||||
fontSize: 16,
|
||||
fontWeight: '800',
|
||||
},
|
||||
modalBackdrop: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: 'rgba(0,0,0,0.35)',
|
||||
},
|
||||
modalSheet: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
padding: 16,
|
||||
backgroundColor: '#FFFFFF',
|
||||
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: palette.primary,
|
||||
},
|
||||
modalBtnText: {
|
||||
color: '#334155',
|
||||
fontWeight: '700',
|
||||
},
|
||||
modalBtnTextPrimary: {
|
||||
color: palette.ink,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user