feat: 更新应用版本和集成腾讯云 COS 上传功能

- 将应用版本更新至 1.0.2,修改相关配置文件
- 集成腾讯云 COS 上传功能,新增相关服务和钩子
- 更新 AI 体态评估页面,支持照片上传和评估结果展示
- 添加雷达图组件以展示评估结果
- 更新样式以适应新功能的展示和交互
- 修改登录页面背景效果,提升用户体验
This commit is contained in:
richarjiang
2025-08-13 15:21:54 +08:00
parent 5814044cee
commit 321947db98
20 changed files with 1664 additions and 342 deletions

View File

@@ -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,
},
});