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

@@ -14,6 +14,7 @@ import {
TouchableOpacity,
View,
} from 'react-native';
import ImageViewing from 'react-native-image-viewing';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { HeaderBar } from '@/components/ui/HeaderBar';
@@ -31,17 +32,17 @@ type Sample = { uri: string; correct: boolean };
const SAMPLES: Record<PoseView, Sample[]> = {
front: [
{ uri: 'https://images.unsplash.com/photo-1594737625785-c6683fc87c73?w=400&q=80&auto=format', correct: true },
{ uri: 'https://plates-1251306435.cos.ap-guangzhou.myqcloud.com/images/imagedemo.jpeg', correct: true },
{ uri: 'https://images.unsplash.com/photo-1544716278-ca5e3f4abd8c?w=400&q=80&auto=format', correct: false },
{ uri: 'https://images.unsplash.com/photo-1571019614242-c5c5dee9f50b?w=400&q=80&auto=format', correct: false },
],
side: [
{ uri: 'https://images.unsplash.com/photo-1554463529-e27854014799?w=400&q=80&auto=format', correct: true },
{ uri: 'https://plates-1251306435.cos.ap-guangzhou.myqcloud.com/images/imagedemo.jpeg', correct: true },
{ uri: 'https://images.unsplash.com/photo-1596357395104-5bcae0b1a5eb?w=400&q=80&auto=format', correct: false },
{ uri: 'https://images.unsplash.com/photo-1526506118085-60ce8714f8c5?w=400&q=80&auto=format', correct: false },
],
back: [
{ uri: 'https://images.unsplash.com/photo-1517836357463-d25dfeac3438?w=400&q=80&auto=format', correct: true },
{ uri: 'https://plates-1251306435.cos.ap-guangzhou.myqcloud.com/images/imagedemo.jpeg', correct: true },
{ uri: 'https://images.unsplash.com/photo-1571721797421-f4c9f2b13107?w=400&q=80&auto=format', correct: false },
{ uri: 'https://images.unsplash.com/photo-1518611012118-696072aa579a?w=400&q=80&auto=format', correct: false },
],
@@ -50,7 +51,7 @@ const SAMPLES: Record<PoseView, Sample[]> = {
export default function AIPostureAssessmentScreen() {
const router = useRouter();
const insets = useSafeAreaInsets();
const theme = Colors.dark;
const theme = Colors.light;
const [uploadState, setUploadState] = useState<UploadState>({});
const canStart = useMemo(
@@ -167,13 +168,13 @@ export default function AIPostureAssessmentScreen() {
function handleStart() {
if (!canStart) return;
// TODO: 调用后端或进入分析页面
Alert.alert('开始测评', '已收集三视角照片准备开始AI体态分析');
// 进入评估中间页面
router.push('/ai-posture-processing');
}
return (
<View style={[styles.screen, { backgroundColor: theme.background }]}>
<HeaderBar title="AI体态测评" onBack={() => router.back()} tone="dark" transparent />
<View style={[styles.screen, { backgroundColor: Colors.light.pageBackgroundEmphasis }]}>
<HeaderBar title="AI体态测评" onBack={() => router.back()} tone="light" transparent />
<ScrollView
contentContainerStyle={{ paddingBottom: insets.bottom + 120 }}
@@ -207,10 +208,8 @@ export default function AIPostureAssessmentScreen() {
{/* Intro */}
<View style={styles.introBox}>
<Text style={styles.title}>姿</Text>
<Text style={styles.description}>
线
</Text>
<Text style={[styles.title, { color: '#192126' }]}>姿</Text>
<Text style={[styles.description, { color: '#5E6468' }]}>线</Text>
</View>
{/* Upload sections */}
@@ -272,6 +271,10 @@ function UploadTile({
onPickLibrary: () => void;
samples: Sample[];
}) {
const [viewerVisible, setViewerVisible] = React.useState(false);
const [viewerIndex, setViewerIndex] = React.useState(0);
const imagesForViewer = React.useMemo(() => samples.map((s) => ({ uri: s.uri })), [samples]);
return (
<View style={styles.section}>
<View style={styles.sectionHeader}>
@@ -294,7 +297,7 @@ function UploadTile({
) : (
<View style={styles.placeholder}>
<View style={styles.plusBadge}>
<Ionicons name="camera" size={16} color="#192126" />
<Ionicons name="camera" size={16} color="#BBF246" />
</View>
<Text style={styles.placeholderTitle}></Text>
<Text style={styles.placeholderDesc}></Text>
@@ -302,19 +305,27 @@ function UploadTile({
)}
</TouchableOpacity>
<BlurView intensity={18} tint="dark" style={styles.sampleBox}>
<BlurView intensity={12} tint="light" style={styles.sampleBox}>
<Text style={styles.sampleTitle}></Text>
<View style={styles.sampleRow}>
{samples.map((s, idx) => (
<View key={idx} style={styles.sampleItem}>
<Image source={{ uri: s.uri }} style={styles.sampleImg} />
<View style={[styles.sampleTag, { backgroundColor: s.correct ? '#2BCC7F' : '#E24D4D' }]}>
<TouchableOpacity activeOpacity={0.9} onPress={() => { setViewerIndex(idx); setViewerVisible(true); }}>
<Image source={{ uri: s.uri }} style={styles.sampleImg} />
</TouchableOpacity>
<View style={[styles.sampleTag, { backgroundColor: s.correct ? '#2BCC7F' : 'rgba(25,33,38,0.08)' }]}>
<Text style={styles.sampleTagText}>{s.correct ? '正确示范' : '错误示范'}</Text>
</View>
</View>
))}
</View>
</BlurView>
<ImageViewing
images={imagesForViewer}
imageIndex={viewerIndex}
visible={viewerVisible}
onRequestClose={() => setViewerVisible(false)}
/>
</View>
);
}
@@ -328,15 +339,15 @@ const styles = StyleSheet.create({
marginHorizontal: 16,
padding: 14,
borderRadius: 16,
backgroundColor: 'rgba(255,255,255,0.04)'
backgroundColor: 'rgba(25,33,38,0.06)'
},
permTitle: {
color: '#ECEDEE',
color: '#192126',
fontSize: 16,
fontWeight: '700',
},
permDesc: {
color: 'rgba(255,255,255,0.75)',
color: '#5E6468',
marginTop: 6,
fontSize: 13,
},
@@ -367,10 +378,10 @@ const styles = StyleSheet.create({
height: 40,
borderRadius: 12,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.18)',
borderColor: 'rgba(25,33,38,0.14)',
},
permSecondaryText: {
color: 'rgba(255,255,255,0.85)',
color: '#384046',
fontSize: 14,
fontWeight: '700',
},
@@ -420,12 +431,12 @@ const styles = StyleSheet.create({
justifyContent: 'space-between',
},
sectionTitle: {
color: '#ECEDEE',
color: '#192126',
fontSize: 18,
fontWeight: '700',
},
retakeHint: {
color: 'rgba(255,255,255,0.55)',
color: '#888F92',
fontSize: 13,
},
uploader: {
@@ -433,8 +444,8 @@ const styles = StyleSheet.create({
borderRadius: 18,
borderWidth: 1,
borderStyle: 'dashed',
borderColor: 'rgba(255,255,255,0.18)',
backgroundColor: '#1E262C',
borderColor: 'rgba(25,33,38,0.14)',
backgroundColor: '#FFFFFF',
overflow: 'hidden',
},
preview: {
@@ -453,25 +464,27 @@ const styles = StyleSheet.create({
borderRadius: 18,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#BBF246',
backgroundColor: '#FFFFFF',
borderWidth: 2,
borderColor: '#BBF246',
},
placeholderTitle: {
color: '#ECEDEE',
color: '#192126',
fontSize: 16,
fontWeight: '700',
},
placeholderDesc: {
color: 'rgba(255,255,255,0.65)',
color: '#888F92',
fontSize: 12,
},
sampleBox: {
marginTop: 8,
borderRadius: 16,
padding: 12,
backgroundColor: 'rgba(255,255,255,0.04)',
backgroundColor: 'rgba(255,255,255,0.72)',
},
sampleTitle: {
color: 'rgba(255,255,255,0.8)',
color: '#192126',
fontSize: 14,
marginBottom: 8,
fontWeight: '600',
@@ -487,7 +500,7 @@ const styles = StyleSheet.create({
width: '100%',
height: 90,
borderRadius: 12,
backgroundColor: '#111',
backgroundColor: '#F2F4F5',
},
sampleTag: {
alignSelf: 'flex-start',