- 新增删除营养分析记录功能,支持本地状态更新和API调用 - 添加图片全屏预览功能,支持缩放和手势操作 - 实现Liquid Glass风格的删除按钮,包含兼容性处理 - 优化历史记录页面布局和交互体验 - 更新Memory Bank文档,添加Liquid Glass按钮实现指南
774 lines
21 KiB
TypeScript
774 lines
21 KiB
TypeScript
import { HeaderBar } from '@/components/ui/HeaderBar';
|
||
import { Colors } from '@/constants/Colors';
|
||
import { useCosUpload } from '@/hooks/useCosUpload';
|
||
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
|
||
import {
|
||
analyzeNutritionImage,
|
||
type NutritionAnalysisResponse
|
||
} from '@/services/nutritionLabelAnalysis';
|
||
import { triggerLightHaptic } from '@/utils/haptics';
|
||
import { Ionicons } from '@expo/vector-icons';
|
||
import dayjs from 'dayjs';
|
||
import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect';
|
||
import { Image } from 'expo-image';
|
||
import * as ImagePicker from 'expo-image-picker';
|
||
import { LinearGradient } from 'expo-linear-gradient';
|
||
import { useRouter } from 'expo-router';
|
||
import React, { useCallback, useRef, useState } from 'react';
|
||
import {
|
||
ActivityIndicator,
|
||
Alert,
|
||
BackHandler,
|
||
ScrollView,
|
||
StyleSheet,
|
||
Text,
|
||
TouchableOpacity,
|
||
View
|
||
} from 'react-native';
|
||
import ImageViewing from 'react-native-image-viewing';
|
||
|
||
export default function NutritionLabelAnalysisScreen() {
|
||
const safeAreaTop = useSafeAreaTop();
|
||
const router = useRouter();
|
||
const { upload, uploading: uploadingToCos, progress: uploadProgress } = useCosUpload({
|
||
prefix: 'nutrition-labels'
|
||
});
|
||
|
||
const [imageUri, setImageUri] = useState<string | null>(null);
|
||
const [isAnalyzing, setIsAnalyzing] = useState(false);
|
||
const [showImagePreview, setShowImagePreview] = useState(false);
|
||
const [newAnalysisResult, setNewAnalysisResult] = useState<NutritionAnalysisResponse | null>(null);
|
||
const [isUploading, setIsUploading] = useState(false);
|
||
|
||
// 流式请求相关引用
|
||
const streamAbortRef = useRef<{ abort: () => void } | null>(null);
|
||
const scrollViewRef = useRef<ScrollView>(null);
|
||
|
||
// 处理Android返回键关闭图片预览
|
||
React.useEffect(() => {
|
||
const backHandler = BackHandler.addEventListener('hardwareBackPress', () => {
|
||
if (showImagePreview) {
|
||
setShowImagePreview(false);
|
||
return true; // 阻止默认返回行为
|
||
}
|
||
return false;
|
||
});
|
||
|
||
return () => backHandler.remove();
|
||
}, [showImagePreview]);
|
||
|
||
// 组件卸载时清理流式请求
|
||
React.useEffect(() => {
|
||
return () => {
|
||
try {
|
||
if (streamAbortRef.current) {
|
||
streamAbortRef.current.abort();
|
||
streamAbortRef.current = null;
|
||
}
|
||
} catch (error) {
|
||
console.warn('[NUTRITION_ANALYSIS] Error aborting stream on unmount:', error);
|
||
}
|
||
};
|
||
}, []);
|
||
|
||
// 请求相机权限
|
||
const requestCameraPermission = async () => {
|
||
const { status } = await ImagePicker.requestCameraPermissionsAsync();
|
||
if (status !== 'granted') {
|
||
Alert.alert('权限不足', '需要相机权限才能拍摄成分表');
|
||
return false;
|
||
}
|
||
return true;
|
||
};
|
||
|
||
// 拍照
|
||
const takePhoto = async () => {
|
||
const hasPermission = await requestCameraPermission();
|
||
if (!hasPermission) return;
|
||
|
||
triggerLightHaptic();
|
||
|
||
const result = await ImagePicker.launchCameraAsync({
|
||
mediaTypes: ['images'],
|
||
allowsEditing: true,
|
||
aspect: [4, 3],
|
||
quality: 0.8,
|
||
});
|
||
|
||
if (!result.canceled && result.assets[0]) {
|
||
setImageUri(result.assets[0].uri);
|
||
setNewAnalysisResult(null); // 清除之前的分析结果
|
||
}
|
||
};
|
||
|
||
// 从相册选择
|
||
const pickImage = async () => {
|
||
triggerLightHaptic();
|
||
|
||
const result = await ImagePicker.launchImageLibraryAsync({
|
||
mediaTypes: ['images'],
|
||
allowsEditing: true,
|
||
aspect: [4, 3],
|
||
quality: 0.8,
|
||
});
|
||
|
||
if (!result.canceled && result.assets[0]) {
|
||
setImageUri(result.assets[0].uri);
|
||
setNewAnalysisResult(null); // 清除之前的分析结果
|
||
}
|
||
};
|
||
|
||
// 新的分析函数:先上传图片到COS,然后调用新API
|
||
const startNewAnalysis = useCallback(async (uri: string) => {
|
||
if (isAnalyzing || isUploading) return;
|
||
|
||
setIsUploading(true);
|
||
setNewAnalysisResult(null);
|
||
|
||
// 延迟滚动到分析结果区域,给UI一些时间更新
|
||
setTimeout(() => {
|
||
scrollViewRef.current?.scrollTo({ y: 350, animated: true });
|
||
}, 300);
|
||
|
||
try {
|
||
// 第一步:上传图片到COS
|
||
console.log('[NUTRITION_ANALYSIS] 开始上传图片到COS...');
|
||
const uploadResult = await upload(uri);
|
||
console.log('[NUTRITION_ANALYSIS] 图片上传成功:', uploadResult.url);
|
||
|
||
setIsUploading(false);
|
||
setIsAnalyzing(true);
|
||
|
||
// 第二步:调用新的营养成分分析API
|
||
console.log('[NUTRITION_ANALYSIS] 开始调用营养成分分析API...');
|
||
const analysisResponse = await analyzeNutritionImage({
|
||
imageUrl: uploadResult.url
|
||
});
|
||
|
||
console.log('[NUTRITION_ANALYSIS] API响应:', analysisResponse);
|
||
|
||
if (analysisResponse.success && analysisResponse.data) {
|
||
// 直接使用服务端返回的数据,不做任何转换
|
||
setNewAnalysisResult(analysisResponse);
|
||
} else {
|
||
throw new Error(analysisResponse.message || '分析失败');
|
||
}
|
||
} catch (error: any) {
|
||
console.error('[NUTRITION_ANALYSIS] 新API分析失败:', error);
|
||
setIsUploading(false);
|
||
setIsAnalyzing(false);
|
||
|
||
// 显示错误提示
|
||
Alert.alert(
|
||
'分析失败',
|
||
error.message || '无法识别成分表,请尝试拍摄更清晰的照片'
|
||
);
|
||
} finally {
|
||
setIsUploading(false);
|
||
setIsAnalyzing(false);
|
||
}
|
||
}, [isAnalyzing, isUploading, upload]);
|
||
|
||
return (
|
||
<View style={styles.container}>
|
||
{/* 背景渐变 */}
|
||
<LinearGradient
|
||
colors={['#f5e5fbff', '#e5fcfeff', '#eefdffff', '#ffffffff']}
|
||
style={styles.gradientBackground}
|
||
start={{ x: 0, y: 0 }}
|
||
end={{ x: 0, y: 1 }}
|
||
/>
|
||
|
||
<HeaderBar
|
||
title="成分表分析"
|
||
onBack={() => router.back()}
|
||
transparent={true}
|
||
right={
|
||
isLiquidGlassAvailable() ? (
|
||
<TouchableOpacity
|
||
onPress={() => router.push('/food/nutrition-analysis-history')}
|
||
activeOpacity={0.7}
|
||
>
|
||
<GlassView
|
||
style={styles.historyButton}
|
||
glassEffectStyle="clear"
|
||
tintColor="rgba(255, 255, 255, 0.2)"
|
||
isInteractive={true}
|
||
>
|
||
<Ionicons name="time-outline" size={24} color="#333" />
|
||
</GlassView>
|
||
</TouchableOpacity>
|
||
) : (
|
||
<TouchableOpacity
|
||
onPress={() => router.push('/food/nutrition-analysis-history')}
|
||
style={[styles.historyButton, styles.fallbackBackground]}
|
||
activeOpacity={0.7}
|
||
>
|
||
<Ionicons name="time-outline" size={24} color="#333" />
|
||
</TouchableOpacity>
|
||
)
|
||
}
|
||
/>
|
||
|
||
<ScrollView
|
||
ref={scrollViewRef}
|
||
style={styles.scrollContainer}
|
||
contentContainerStyle={{
|
||
paddingTop: safeAreaTop
|
||
}}
|
||
showsVerticalScrollIndicator={false}
|
||
>
|
||
{/* 图片区域 */}
|
||
<View style={styles.imageContainer}>
|
||
{imageUri ? (
|
||
<View>
|
||
<TouchableOpacity
|
||
onPress={() => setShowImagePreview(true)}
|
||
activeOpacity={0.9}
|
||
>
|
||
<Image
|
||
source={{ uri: imageUri }}
|
||
style={styles.foodImage}
|
||
cachePolicy={'memory-disk'}
|
||
/>
|
||
{/* 预览提示图标 */}
|
||
<View style={styles.previewHint}>
|
||
<Ionicons name="expand-outline" size={20} color="#FFF" />
|
||
</View>
|
||
</TouchableOpacity>
|
||
|
||
{/* 开始分析按钮 */}
|
||
{!isAnalyzing && !isUploading && !newAnalysisResult && (
|
||
<TouchableOpacity
|
||
style={styles.analyzeButton}
|
||
onPress={() => startNewAnalysis(imageUri)}
|
||
activeOpacity={0.8}
|
||
>
|
||
<Ionicons name="search-outline" size={20} color="#FFF" />
|
||
<Text style={styles.analyzeButtonText}>开始分析</Text>
|
||
</TouchableOpacity>
|
||
)}
|
||
|
||
{/* 删除图片按钮 */}
|
||
<TouchableOpacity
|
||
style={styles.deleteImageButton}
|
||
onPress={() => {
|
||
setImageUri(null);
|
||
setNewAnalysisResult(null);
|
||
triggerLightHaptic();
|
||
}}
|
||
activeOpacity={0.8}
|
||
>
|
||
<Ionicons name="trash-outline" size={16} color="#FFF" />
|
||
</TouchableOpacity>
|
||
</View>
|
||
) : (
|
||
<View style={styles.placeholderContainer}>
|
||
<View style={styles.placeholderContent}>
|
||
<Ionicons name="document-text-outline" size={48} color="#666" />
|
||
<Text style={styles.placeholderText}>拍摄或选择成分表照片</Text>
|
||
</View>
|
||
{/* 操作按钮区域 */}
|
||
<View style={styles.imageActionButtonsContainer}>
|
||
<TouchableOpacity
|
||
style={styles.imageActionButton}
|
||
onPress={takePhoto}
|
||
activeOpacity={0.8}
|
||
>
|
||
<Ionicons name="camera-outline" size={20} color={Colors.light.onPrimary} />
|
||
<Text style={styles.imageActionButtonText}>拍摄</Text>
|
||
</TouchableOpacity>
|
||
<TouchableOpacity
|
||
style={[styles.imageActionButton, styles.imageActionButtonSecondary]}
|
||
onPress={pickImage}
|
||
activeOpacity={0.8}
|
||
>
|
||
<Ionicons name="image-outline" size={20} color={Colors.light.primary} />
|
||
<Text style={[styles.imageActionButtonText, { color: Colors.light.primary }]}>相册</Text>
|
||
</TouchableOpacity>
|
||
</View>
|
||
</View>
|
||
)}
|
||
</View>
|
||
|
||
|
||
{/* 新API营养成分详细分析结果 */}
|
||
{newAnalysisResult && newAnalysisResult.success && newAnalysisResult.data && (
|
||
<View style={styles.analysisSection}>
|
||
<View style={styles.analysisSectionHeader}>
|
||
<View style={styles.analysisSectionHeaderIcon}>
|
||
<Ionicons name="document-text-outline" size={18} color="#6B6ED6" />
|
||
</View>
|
||
<Text style={styles.analysisSectionTitle}>营养成分详细分析</Text>
|
||
</View>
|
||
<View style={styles.analysisCardsWrapper}>
|
||
{newAnalysisResult.data.map((item, index) => (
|
||
<View
|
||
key={item.key || index}
|
||
style={[
|
||
styles.analysisCardItem,
|
||
index === newAnalysisResult.data.length - 1 && styles.analysisCardItemLast
|
||
]}
|
||
>
|
||
<LinearGradient
|
||
colors={['#7F77FF', '#9B7CFF']}
|
||
start={{ x: 0, y: 0 }}
|
||
end={{ x: 1, y: 1 }}
|
||
style={styles.analysisItemIconGradient}
|
||
>
|
||
<Ionicons name="nutrition-outline" size={24} color="#FFFFFF" />
|
||
</LinearGradient>
|
||
<View style={styles.analysisItemContent}>
|
||
<View style={styles.analysisItemHeader}>
|
||
<Text style={styles.analysisItemName}>{item.name}</Text>
|
||
<Text style={styles.analysisItemValue}>{item.value}</Text>
|
||
</View>
|
||
<View style={styles.analysisItemDescriptionRow}>
|
||
<Ionicons
|
||
name="information-circle-outline"
|
||
size={16}
|
||
color="rgba(107, 110, 214, 0.8)"
|
||
style={styles.analysisItemDescriptionIcon}
|
||
/>
|
||
<Text style={styles.analysisItemDescription}>{item.analysis}</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
))}
|
||
</View>
|
||
</View>
|
||
)}
|
||
|
||
{/* 上传状态 */}
|
||
{isUploading && (
|
||
<View style={styles.loadingContainer}>
|
||
<ActivityIndicator size="large" color={Colors.light.primary} />
|
||
<Text style={styles.loadingText}>
|
||
正在上传图片... {uploadProgress > 0 ? `${Math.round(uploadProgress)}%` : ''}
|
||
</Text>
|
||
</View>
|
||
)}
|
||
|
||
{/* 加载状态 */}
|
||
{isAnalyzing && !newAnalysisResult && !isUploading && (
|
||
<View style={styles.loadingContainer}>
|
||
<ActivityIndicator size="large" color={Colors.light.primary} />
|
||
<Text style={styles.loadingText}>正在分析成分表...</Text>
|
||
</View>
|
||
)}
|
||
</ScrollView>
|
||
|
||
{/* 图片预览 */}
|
||
<ImageViewing
|
||
images={imageUri ? [{ uri: imageUri }] : []}
|
||
imageIndex={0}
|
||
visible={showImagePreview}
|
||
onRequestClose={() => setShowImagePreview(false)}
|
||
swipeToCloseEnabled={true}
|
||
doubleTapToZoomEnabled={true}
|
||
HeaderComponent={() => (
|
||
<View style={styles.imageViewerHeader}>
|
||
<Text style={styles.imageViewerHeaderText}>
|
||
{dayjs().format('YYYY年M月D日 HH:mm')}
|
||
</Text>
|
||
</View>
|
||
)}
|
||
FooterComponent={() => (
|
||
<View style={styles.imageViewerFooter}>
|
||
<TouchableOpacity
|
||
style={styles.imageViewerFooterButton}
|
||
onPress={() => setShowImagePreview(false)}
|
||
>
|
||
<Text style={styles.imageViewerFooterButtonText}>关闭</Text>
|
||
</TouchableOpacity>
|
||
</View>
|
||
)}
|
||
/>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
const styles = StyleSheet.create({
|
||
container: {
|
||
flex: 1,
|
||
backgroundColor: '#f5e5fbff',
|
||
},
|
||
gradientBackground: {
|
||
position: 'absolute',
|
||
left: 0,
|
||
right: 0,
|
||
top: 0,
|
||
bottom: 0,
|
||
},
|
||
scrollContainer: {
|
||
flex: 1,
|
||
},
|
||
imageContainer: {
|
||
position: 'relative',
|
||
height: 300,
|
||
marginHorizontal: 16,
|
||
marginTop: 16,
|
||
borderRadius: 20,
|
||
overflow: 'hidden',
|
||
shadowColor: '#000',
|
||
shadowOffset: {
|
||
width: 0,
|
||
height: 2,
|
||
},
|
||
shadowOpacity: 0.1,
|
||
shadowRadius: 4,
|
||
elevation: 3,
|
||
},
|
||
foodImage: {
|
||
width: '100%',
|
||
height: '100%',
|
||
borderRadius: 20,
|
||
},
|
||
previewHint: {
|
||
position: 'absolute',
|
||
top: 16,
|
||
right: 16,
|
||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||
borderRadius: 20,
|
||
padding: 8,
|
||
},
|
||
deleteImageButton: {
|
||
position: 'absolute',
|
||
top: 16,
|
||
left: 16,
|
||
backgroundColor: 'rgba(255, 59, 48, 0.9)',
|
||
borderRadius: 20,
|
||
padding: 8,
|
||
shadowColor: '#000',
|
||
shadowOffset: {
|
||
width: 0,
|
||
height: 2,
|
||
},
|
||
shadowOpacity: 0.2,
|
||
shadowRadius: 4,
|
||
elevation: 3,
|
||
},
|
||
placeholderContainer: {
|
||
width: '100%',
|
||
height: '100%',
|
||
backgroundColor: '#FFFFFF',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
borderRadius: 20,
|
||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||
shadowOffset: {
|
||
width: 0,
|
||
height: 4,
|
||
},
|
||
shadowOpacity: 0.15,
|
||
shadowRadius: 8,
|
||
elevation: 5,
|
||
},
|
||
placeholderContent: {
|
||
alignItems: 'center',
|
||
marginBottom: 40,
|
||
},
|
||
placeholderText: {
|
||
fontSize: 16,
|
||
color: '#666',
|
||
fontWeight: '500',
|
||
marginTop: 8,
|
||
},
|
||
imageActionButtonsContainer: {
|
||
flexDirection: 'row',
|
||
gap: 12,
|
||
paddingHorizontal: 20,
|
||
},
|
||
imageActionButton: {
|
||
flex: 1,
|
||
backgroundColor: Colors.light.primary,
|
||
paddingVertical: 12,
|
||
paddingHorizontal: 16,
|
||
borderRadius: 16,
|
||
alignItems: 'center',
|
||
flexDirection: 'row',
|
||
justifyContent: 'center',
|
||
shadowColor: Colors.light.primary,
|
||
shadowOffset: {
|
||
width: 0,
|
||
height: 2,
|
||
},
|
||
shadowOpacity: 0.2,
|
||
shadowRadius: 4,
|
||
elevation: 3,
|
||
},
|
||
imageActionButtonSecondary: {
|
||
backgroundColor: 'transparent',
|
||
borderWidth: 1.5,
|
||
borderColor: Colors.light.primary,
|
||
shadowOpacity: 0,
|
||
elevation: 0,
|
||
},
|
||
imageActionButtonText: {
|
||
color: Colors.light.onPrimary,
|
||
fontSize: 14,
|
||
fontWeight: '600',
|
||
marginLeft: 6,
|
||
},
|
||
resultCard: {
|
||
backgroundColor: Colors.light.background,
|
||
margin: 16,
|
||
borderRadius: 16,
|
||
padding: 20,
|
||
shadowColor: '#000',
|
||
shadowOffset: {
|
||
width: 0,
|
||
height: 2,
|
||
},
|
||
shadowOpacity: 0.1,
|
||
shadowRadius: 4,
|
||
elevation: 3,
|
||
},
|
||
resultHeader: {
|
||
flexDirection: 'row',
|
||
justifyContent: 'space-between',
|
||
alignItems: 'center',
|
||
marginBottom: 16,
|
||
},
|
||
resultTitle: {
|
||
fontSize: 18,
|
||
fontWeight: '600',
|
||
color: Colors.light.text,
|
||
},
|
||
confidenceContainer: {
|
||
backgroundColor: '#E8F5E8',
|
||
paddingHorizontal: 8,
|
||
paddingVertical: 4,
|
||
borderRadius: 8,
|
||
},
|
||
confidenceText: {
|
||
fontSize: 12,
|
||
color: '#4CAF50',
|
||
fontWeight: '500',
|
||
},
|
||
foodInfoContainer: {
|
||
marginBottom: 12,
|
||
},
|
||
foodNameLabel: {
|
||
fontSize: 14,
|
||
color: Colors.light.textSecondary,
|
||
marginBottom: 4,
|
||
},
|
||
foodNameValue: {
|
||
fontSize: 16,
|
||
color: Colors.light.text,
|
||
fontWeight: '500',
|
||
},
|
||
nutritionGrid: {
|
||
flexDirection: 'row',
|
||
flexWrap: 'wrap',
|
||
marginTop: 8,
|
||
},
|
||
nutritionItem: {
|
||
width: '50%',
|
||
marginBottom: 12,
|
||
paddingRight: 8,
|
||
},
|
||
nutritionLabel: {
|
||
fontSize: 12,
|
||
color: Colors.light.textSecondary,
|
||
marginBottom: 2,
|
||
},
|
||
nutritionValue: {
|
||
fontSize: 16,
|
||
color: Colors.light.text,
|
||
fontWeight: '600',
|
||
},
|
||
loadingContainer: {
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
padding: 40,
|
||
},
|
||
loadingText: {
|
||
fontSize: 16,
|
||
color: Colors.light.textSecondary,
|
||
marginTop: 12,
|
||
},
|
||
// ImageViewing 组件样式
|
||
imageViewerHeader: {
|
||
position: 'absolute',
|
||
top: 60,
|
||
left: 20,
|
||
right: 20,
|
||
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
||
borderRadius: 12,
|
||
paddingHorizontal: 16,
|
||
paddingVertical: 12,
|
||
zIndex: 1,
|
||
},
|
||
imageViewerHeaderText: {
|
||
color: '#FFF',
|
||
fontSize: 14,
|
||
fontWeight: '500',
|
||
textAlign: 'center',
|
||
},
|
||
imageViewerFooter: {
|
||
position: 'absolute',
|
||
bottom: 60,
|
||
left: 20,
|
||
right: 20,
|
||
alignItems: 'center',
|
||
zIndex: 1,
|
||
},
|
||
imageViewerFooterButton: {
|
||
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
||
paddingHorizontal: 24,
|
||
paddingVertical: 12,
|
||
borderRadius: 20,
|
||
},
|
||
imageViewerFooterButtonText: {
|
||
color: '#FFF',
|
||
fontSize: 16,
|
||
fontWeight: '500',
|
||
},
|
||
// 开始分析按钮样式
|
||
analyzeButton: {
|
||
position: 'absolute',
|
||
bottom: 16,
|
||
right: 16,
|
||
backgroundColor: Colors.light.primary,
|
||
paddingHorizontal: 16,
|
||
paddingVertical: 12,
|
||
borderRadius: 20,
|
||
alignItems: 'center',
|
||
flexDirection: 'row',
|
||
shadowColor: '#000',
|
||
shadowOffset: {
|
||
width: 0,
|
||
height: 2,
|
||
},
|
||
shadowOpacity: 0.2,
|
||
shadowRadius: 4,
|
||
elevation: 3,
|
||
},
|
||
analyzeButtonText: {
|
||
color: Colors.light.onPrimary,
|
||
fontSize: 14,
|
||
fontWeight: '600',
|
||
marginLeft: 6,
|
||
},
|
||
// 营养成分详细分析卡片样式
|
||
analysisSection: {
|
||
marginHorizontal: 16,
|
||
marginTop: 28,
|
||
marginBottom: 20,
|
||
},
|
||
analysisSectionHeader: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
marginBottom: 14,
|
||
},
|
||
analysisSectionHeaderIcon: {
|
||
width: 32,
|
||
height: 32,
|
||
borderRadius: 12,
|
||
backgroundColor: 'rgba(107, 110, 214, 0.12)',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
},
|
||
analysisSectionTitle: {
|
||
marginLeft: 10,
|
||
fontSize: 18,
|
||
fontWeight: '600',
|
||
color: Colors.light.text,
|
||
},
|
||
analysisCardsWrapper: {
|
||
backgroundColor: '#FFFFFF',
|
||
borderRadius: 28,
|
||
padding: 18,
|
||
shadowColor: 'rgba(107, 110, 214, 0.25)',
|
||
shadowOffset: {
|
||
width: 0,
|
||
height: 10,
|
||
},
|
||
shadowOpacity: 0.2,
|
||
shadowRadius: 20,
|
||
elevation: 6,
|
||
},
|
||
analysisCardItem: {
|
||
flexDirection: 'row',
|
||
alignItems: 'flex-start',
|
||
padding: 16,
|
||
backgroundColor: 'rgba(127, 119, 255, 0.1)',
|
||
borderRadius: 22,
|
||
shadowColor: 'rgba(127, 119, 255, 0.28)',
|
||
shadowOffset: {
|
||
width: 0,
|
||
height: 6,
|
||
},
|
||
shadowOpacity: 0.16,
|
||
shadowRadius: 12,
|
||
elevation: 4,
|
||
marginBottom: 12,
|
||
},
|
||
analysisCardItemLast: {
|
||
marginBottom: 0,
|
||
},
|
||
analysisItemIconGradient: {
|
||
width: 52,
|
||
height: 52,
|
||
borderRadius: 18,
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
marginRight: 14,
|
||
},
|
||
analysisItemContent: {
|
||
flex: 1,
|
||
},
|
||
analysisItemHeader: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
marginBottom: 8,
|
||
},
|
||
analysisItemName: {
|
||
flex: 1,
|
||
fontSize: 16,
|
||
fontWeight: '600',
|
||
color: '#272753',
|
||
},
|
||
analysisItemValue: {
|
||
fontSize: 16,
|
||
fontWeight: '700',
|
||
color: Colors.light.primary,
|
||
},
|
||
analysisItemDescriptionRow: {
|
||
flexDirection: 'row',
|
||
alignItems: 'flex-start',
|
||
},
|
||
analysisItemDescriptionIcon: {
|
||
marginRight: 6,
|
||
marginTop: 2,
|
||
},
|
||
analysisItemDescription: {
|
||
flex: 1,
|
||
fontSize: 13,
|
||
lineHeight: 18,
|
||
color: 'rgba(39, 39, 83, 0.72)',
|
||
},
|
||
// 历史记录按钮样式
|
||
historyButton: {
|
||
width: 38,
|
||
height: 38,
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
borderRadius: 19,
|
||
overflow: 'hidden',
|
||
},
|
||
fallbackBackground: {
|
||
backgroundColor: 'rgba(255, 255, 255, 0.7)',
|
||
shadowColor: '#000',
|
||
shadowOffset: {
|
||
width: 0,
|
||
height: 1,
|
||
},
|
||
shadowOpacity: 0.1,
|
||
shadowRadius: 2,
|
||
elevation: 2,
|
||
},
|
||
});
|