From 4ae419754a3a93a6e006e5d3f455d836faa91fa6 Mon Sep 17 00:00:00 2001 From: richarjiang Date: Thu, 4 Sep 2025 10:52:00 +0800 Subject: [PATCH] =?UTF-8?q?feat(food):=20=E6=B7=BB=E5=8A=A0=E6=8B=8D?= =?UTF-8?q?=E6=91=84=E6=8C=87=E5=BC=95=E5=BC=B9=E7=AA=97=E4=B8=8E=E7=9B=B8?= =?UTF-8?q?=E5=86=8C=E9=80=89=E6=8B=A9=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在相机界面新增“拍摄示例”弹窗,展示正确/错误拍摄对比图 - 底部控制栏增加相册选择按钮与帮助按钮 - 优化控制栏布局为左右分布,提升操作便捷性 - 移除 food-recognition 中冗余的 isUploading 状态,简化上传流程 --- app/food/camera.tsx | 178 +++++++++++++++++++++++++++++++++- app/food/food-recognition.tsx | 7 +- 2 files changed, 179 insertions(+), 6 deletions(-) diff --git a/app/food/camera.tsx b/app/food/camera.tsx index 96c5925..978d6f7 100644 --- a/app/food/camera.tsx +++ b/app/food/camera.tsx @@ -2,12 +2,14 @@ import { HeaderBar } from '@/components/ui/HeaderBar'; import { Colors } from '@/constants/Colors'; import { Ionicons } from '@expo/vector-icons'; import { CameraType, CameraView, useCameraPermissions } from 'expo-camera'; +import { Image } from 'expo-image'; import * as ImagePicker from 'expo-image-picker'; import { useLocalSearchParams, useRouter } from 'expo-router'; import React, { useRef, useState } from 'react'; import { Alert, Dimensions, + Modal, StatusBar, StyleSheet, Text, @@ -30,6 +32,7 @@ export default function FoodCameraScreen() { ); const [facing, setFacing] = useState('back'); const [permission, requestPermission] = useCameraPermissions(); + const [showInstructionModal, setShowInstructionModal] = useState(false); // 餐次选择选项 const mealOptions = [ @@ -199,13 +202,79 @@ export default function FoodCameraScreen() { {/* 底部控制栏 */} + {/* 相册选择按钮 */} + + + + {/* 拍照按钮 */} + + {/* 帮助按钮 */} + setShowInstructionModal(true)}> + + + + {/* 拍摄说明弹窗 */} + setShowInstructionModal(false)} + > + + + 拍摄示例 + + + {/* 好的示例 */} + + + + + + {/* 这里可以放置好的示例图片 */} + + + + + {/* 不好的示例 */} + + + + + + + + + + + + 请上传或拍摄如左图所示的食物照片 + + + setShowInstructionModal(false)} + > + 知道了 + + + + ); } @@ -366,7 +435,7 @@ const styles = StyleSheet.create({ }, controlsContainer: { flexDirection: 'row', - justifyContent: 'center', + justifyContent: 'space-between', alignItems: 'center', paddingVertical: 20, paddingHorizontal: 40, @@ -423,4 +492,111 @@ const styles = StyleSheet.create({ fontSize: 14, fontWeight: 'bold', }, + galleryButton: { + width: 50, + height: 50, + borderRadius: 25, + backgroundColor: 'rgba(255, 255, 255, 0.2)', + justifyContent: 'center', + alignItems: 'center', + borderWidth: 2, + borderColor: '#FFF', + }, + helpButton: { + width: 50, + height: 50, + borderRadius: 25, + backgroundColor: 'rgba(255, 255, 255, 0.2)', + justifyContent: 'center', + alignItems: 'center', + borderWidth: 2, + borderColor: '#FFF', + }, + modalOverlay: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.7)', + justifyContent: 'flex-end', + }, + instructionModal: { + backgroundColor: '#FFF', + borderTopLeftRadius: 20, + borderTopRightRadius: 20, + paddingHorizontal: 24, + paddingVertical: 32, + minHeight: 400, + }, + instructionTitle: { + fontSize: 24, + fontWeight: 'bold', + textAlign: 'center', + marginBottom: 32, + color: '#333', + }, + exampleContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + marginBottom: 24, + paddingHorizontal: 16, + }, + exampleItem: { + flex: 1, + marginHorizontal: 8, + }, + exampleImagePlaceholder: { + width: '100%', + aspectRatio: 3 / 4, + backgroundColor: '#F0F0F0', + borderRadius: 16, + position: 'relative', + justifyContent: 'center', + alignItems: 'center', + }, + checkmarkContainer: { + position: 'absolute', + top: 12, + right: 12, + width: 32, + height: 32, + borderRadius: 16, + backgroundColor: '#4CAF50', + justifyContent: 'center', + alignItems: 'center', + zIndex: 10, + }, + crossContainer: { + position: 'absolute', + top: 12, + right: 12, + width: 32, + height: 32, + borderRadius: 16, + backgroundColor: '#F44336', + justifyContent: 'center', + alignItems: 'center', + zIndex: 10, + }, + exampleImage: { + width: '100%', + height: '100%', + }, + instructionDescription: { + fontSize: 16, + textAlign: 'center', + color: '#666', + marginBottom: 32, + lineHeight: 24, + paddingHorizontal: 16, + }, + knowButton: { + backgroundColor: '#000', + borderRadius: 25, + paddingVertical: 16, + marginHorizontal: 16, + }, + knowButtonText: { + color: '#FFF', + fontSize: 16, + fontWeight: '600', + textAlign: 'center', + }, }); \ No newline at end of file diff --git a/app/food/food-recognition.tsx b/app/food/food-recognition.tsx index d4c27c9..7046ed6 100644 --- a/app/food/food-recognition.tsx +++ b/app/food/food-recognition.tsx @@ -29,8 +29,7 @@ export default function FoodRecognitionScreen() { }>(); const { imageUri, mealType } = params; - const { upload, uploading } = useCosUpload(); - const [isUploading, setIsUploading] = useState(false); + const { upload } = useCosUpload(); const [showRecognitionProcess, setShowRecognitionProcess] = useState(false); const [recognitionLogs, setRecognitionLogs] = useState([]); const [currentStep, setCurrentStep] = useState<'idle' | 'uploading' | 'recognizing' | 'completed' | 'failed'>('idle'); @@ -128,7 +127,6 @@ export default function FoodRecognitionScreen() { setShowRecognitionProcess(true); setRecognitionLogs([]); setCurrentStep('uploading'); - setIsUploading(true); dispatch(setLoading(true)); addLog('📤 正在上传图片到云端...'); @@ -167,7 +165,7 @@ export default function FoodRecognitionScreen() { const recognitionId = `recognition_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; // 保存识别结果到 Redux - await dispatch(saveRecognitionResult({ + dispatch(saveRecognitionResult({ id: recognitionId, result: recognitionResult })); @@ -184,7 +182,6 @@ export default function FoodRecognitionScreen() { setCurrentStep('failed'); dispatch(setError('食物识别失败,请重试')); } finally { - setIsUploading(false); dispatch(setLoading(false)); } };