通过 value/max 计算百分比并以宽度百分比填充条形图,在React Native鸿蒙跨平台显示当前值与最大值标签, 避免溢出
本文介绍了一个基于React Native构建的营养分析应用,重点分析了其核心架构设计与跨端适配方案。应用采用多维营养数据结构体系,包含7个核心营养维度,为数据可视化与分析提供基础。自定义图表组件通过CSS实现动态可视化效果,响应式计算引擎采用函数式reduce操作进行营养统计。智能建议系统结合数值分析与定性建议生成个性化内容。在鸿蒙OS适配中,通过组件映射策略、样式系统转换和状态管理迁移实现跨端
概述
本文分析的是一个基于React Native构建的营养分析应用,集成了营养图表可视化、多维数据统计、个性化建议等核心功能。该应用采用了复杂的数据可视化技术、响应式营养计算和智能建议系统,展现了健康数据分析类应用的典型技术架构。在鸿蒙OS的跨端适配场景中,这种涉及复杂数据可视化和智能分析的应用具有重要的技术参考价值。
核心架构设计深度解析
多维营养数据结构体系
应用定义了完整的营养数据结构,涵盖7个核心营养维度:
type FoodItem = {
id: string;
name: string;
calories: number;
protein: number;
carbs: number;
fat: number;
fiber: number;
vitamins: number;
water: number;
timestamp: string;
};
这种数据结构设计体现了营养学的完整体系:基础热量(卡路里)、宏量营养素(蛋白质、碳水化合物、脂肪)、微量营养素(纤维素、维生素)和水分。每个字段都有明确的计量单位和业务含义,为后续的分析计算提供了完整的数据基础。
在鸿蒙ArkUI体系中,接口定义保持了相同的结构:
interface FoodItem {
id: string;
name: string;
calories: number;
protein: number;
carbs: number;
fat: number;
fiber: number;
vitamins: number;
water: number;
timestamp: string;
}
可视化图表组件系统
NutritionChart组件实现了自定义的数据可视化:
const NutritionChart = ({ value, max, label, color }: {
value: number;
max: number;
label: string;
color: string
}) => {
const percentage = Math.min((value / max) * 100, 100);
return (
<View style={styles.chartContainer}>
<View style={styles.chartHeader}>
<Text style={styles.chartLabel}>{label}</Text>
<Text style={styles.chartValue}>{value}</Text>
</View>
<View style={styles.chartBar}>
<View
style={[
styles.chartFill,
{ width: `${percentage}%`, backgroundColor: color }
]}
/>
</View>
<Text style={styles.chartMax}>最大值: {max}</Text>
</View>
);
};
这种图表实现采用了纯CSS方式,通过百分比宽度控制填充进度。Math.min确保了百分比不超过100%,避免了视觉溢出。颜色参数化设计支持动态主题配置。
鸿蒙平台的图表实现需要采用Canvas或自定义绘制:
@Component
struct NutritionChart {
@Prop value: number;
@Prop max: number;
@Prop label: string;
@Prop color: string = '#3b82f6';
build() {
Column() {
// 标题和数值
Row() {
Text(this.label)
Text(this.value.toString())
}
// 进度条
Stack() {
// 背景条
Column()
.width('100%')
.height(8)
.backgroundColor('#e2e8f0')
// 填充条
Column()
.width(`${Math.min((this.value / this.max) * 100, 100)}%`)
.height(8)
.backgroundColor(this.color)
}
Text(`最大值: ${this.max}`)
}
}
}
响应式营养计算引擎
应用实现了多层级的营养计算体系:
// 单项营养计算
const totalCalories = dailyFoods.reduce((sum, food) => sum + food.calories, 0);
const totalProtein = dailyFoods.reduce((sum, food) => sum + food.protein, 0);
// ...更多营养计算
// 推荐摄入量配置
const recommendedCalories = 2000;
const recommendedProtein = 50;
const recommendedCarbs = 250;
const recommendedFat = 70;
这种计算模式采用了函数式reduce操作,确保了计算的纯净性和可预测性。推荐摄入量的配置化设计支持个性化调整,为不同的用户需求提供了灵活性。
鸿蒙的实现采用计算属性:
@State dailyFoods: FoodItem[] = [];
// 总营养计算
get totalCalories(): number {
return this.dailyFoods.reduce((sum, food) => sum + food.calories, 0);
}
get totalProtein(): number {
return this.dailyFoods.reduce((sum, food) => sum + food.protein, 0);
}
// 推荐摄入量
@State recommendedCalories: number = 2000;
@State recommendedProtein: number = 50;
智能建议生成系统
应用基于营养数据生成个性化建议:
<View style={styles.adviceContainer}>
<View style={styles.adviceItem}>
<Text style={styles.adviceIcon}>💡</Text>
<Text style={styles.adviceText}>
您今日摄入蛋白质{totalProtein.toFixed(1)}g,建议继续增加优质蛋白质来源
</Text>
</View>
<View style={styles.adviceItem}>
<Text style={styles.adviceIcon}>💡</Text>
<Text style={styles.adviceText}>
您今日卡路里摄入{totalCalories}卡,符合减脂需求
</Text>
</View>
</View>
这种建议系统结合了具体数据和定性建议,通过模板字符串动态生成内容。toFixed(1)确保了数值显示的精度,提高了专业性。
鸿蒙的建议系统实现:
@Component
struct AdviceItem {
@Prop icon: string;
@Prop text: string;
build() {
Row() {
Text(this.icon)
Text(this.text)
}
}
}
// 使用
AdviceItem({
icon: '💡',
text: `您今日摄入蛋白质${this.totalProtein.toFixed(1)}g,建议继续增加优质蛋白质来源`
})
跨端适配技术方案
组件映射策略
| React Native组件 | 鸿蒙ArkUI组件 | 关键适配点 |
|---|---|---|
| View | Column/Row/Stack | 布局系统转换 |
| Text | Text | 文本属性基本一致 |
| ScrollView | Scroll | 滚动行为一致 |
| FlatList | List | 列表实现差异 |
样式系统转换
// React Native
foodCard: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
elevation: 1,
shadowColor: '#000',
}
// 鸿蒙
Column()
.backgroundColor(Color.White)
.borderRadius(12)
.padding(16)
.shadow({ radius: 2, color: '#000000' })
状态管理迁移
// React Native
const [dailyFoods, setDailyFoods] = useState<FoodItem[]>([]);
const [selectedPeriod, setSelectedPeriod] = useState('今日');
// 鸿蒙
@State dailyFoods: FoodItem[] = [];
@State selectedPeriod: string = '今日';
性能优化与最佳实践
计算性能优化
使用useMemo缓存计算结果:
const totalCalories = useMemo(() => {
return dailyFoods.reduce((sum, food) => sum + food.calories, 0);
}, [dailyFoods]);
列表渲染优化
配置keyExtractor提升列表性能:
<FlatList
data={dailyFoods}
keyExtractor={item => item.id}
renderItem={({ item }) => <FoodItemCard food={item} />}
/>
这个任务是代码讲解,不需要额外的专用工具或技能支持;下面直接开始技术解读。
React Native × 鸿蒙跨端技术解读:营养分析与图表展示
[app.tsx](file:///Users/david/workspace/demo/book/app.tsx)
概览
- 页面聚焦“每日营养分析”:顶部摘要卡片、占比图表、饮食记录列表、建议与目标。数据通过函数式组件与本地状态派生,UI由卡片与条形图组合。
- iOS/Android 走 RN 标准组件栈;在鸿蒙(OpenHarmony)落地,关键在滚动虚拟化、窗口事件、字体图标、Alert 与数据源切换的适配。
核心组件
- NutritionChart
- 通过 value/max 计算百分比并以宽度百分比填充条形图,显示当前值与最大值标签。
- 技术要点:百分比用 Math.min 截断到 100% 避免溢出;条形图采用父容器 overflow: hidden 保证裁切。
- 跨端注意:条形图动画若加入(如进度过渡),需使用原生驱动(useNativeDriver 对 transform/opacity),鸿蒙端映射到 ArkUI 动画能力,避免布局/颜色动画造成性能波动。
- FoodItemCard
- 展示食物的时间戳与四项主要营养(卡路里/蛋白质/碳水/脂肪),采用“图标 + 数值 + 标签”的三元结构。
- 技术要点:数值单位一致性(例如 “g” 与 “卡”),建议统一格式器封装,避免不同 locale 下的分隔符与精度差异。
- 跨端注意:图标使用 emoji 简便但在不同系统字体下存在对齐/渲染差异;生产建议迁移为统一矢量/字体图标,并在鸿蒙端通过 ArkUI 渲染能力桥接。
- NutritionSummaryCard
- 顶部摘要卡片,提供总卡路里、蛋白质、碳水的速览,颜色用于语义分组。
- 技术要点:卡片尺寸以屏宽计算,需适配横竖屏与分屏;用 useWindowDimensions 替代静态 Dimensions 并监听窗口变化,鸿蒙端确保窗口事件正确传递到 RN。
数据与派生
- 合计计算
- totalCalories/totalProtein/totalCarbs/totalFat/fiber/vitamins/water 使用多次 reduce 进行整合;可用单次 reduce 聚合提升性能,并用 useMemo([dailyFoods]) 缓存派生结果,避免重复计算。
- 推荐摄入量
- recommendedCalories/Protein/Carbs/Fat 为静态建议值;后续可基于用户画像(体重、身高、活动水平)动态计算,并抽象为策略模块(服务层)。
周期切换与数据源
- handlePeriodChange
- 切换“今日/本周/本月”并用 Alert 占位提示;生产应在切换时请求对应周期数据(缓存+loading态),统一错误处理与降级策略。
- 跨端注意:Alert 外观与行为由平台控制;鸿蒙端建议用自定义 Modal/Portal 实现统一视觉与返回手势,适配 ArkUI 弹窗能力。
列表渲染与虚拟化
- 饮食记录列表
- 当前使用 ScrollView 包裹 FlatList,这是一个常见反模式,会破坏虚拟化优势与滚动事件管理;应仅使用 FlatList,并用 ListHeaderComponent / ListFooterComponent 承载“今日饮食记录标题、建议、目标”等静态区块。
- 关键优化:renderItem 用 useCallback 缓存,FoodItemCard 使用 React.memo,keyExtractor 使用稳定 id,避免不必要重渲染。
- 跨端注意:滚动物理(回弹、阻尼、惯性)与事件触发窗口在不同平台略有差异;鸿蒙端需通过适配层映射到 ArkUI 滚动容器,保证手感一致。
图标与本地化
- emoji 图标
- 适合原型,但在不同系统字体下存在大小、基线与色彩差异;建议迁移到统一图标栈(SVG/字体),在鸿蒙端用 ArkUI 图形能力渲染,确保像素与对齐一致。
- 数值与单位
- 建议统一格式器(如 Intl.NumberFormat 或轻量封装)管理精度、千分位与单位缩写;文本资源抽取为字典并接入 i18n,保证跨语言一致性。
尺寸与旋转
- 使用 Dimensions.get(‘window’) 的初始值计算卡片宽度;在横竖屏与分屏场景下会失效。
- 建议:用 useWindowDimensions 动态获取宽度,并在鸿蒙端确保窗口事件能正确传递到 RN 层以触发重渲染。
性能与工程化
- 记忆化与派生层
- 合计与摘要使用 useMemo;renderItem/useCallback 与组件 memo 化减少重渲染。
- 统一数据层
- 抽象合计与建议逻辑到服务层或 selectors,保证 UI 只消费派生结果,降低耦合。
- 可访问性
- 为按钮与卡片添加 accessibilityLabel,提升读屏与键盘导航体验;在弹窗中管理焦点,跨端保持一致。
鸿蒙适配要点
- 滚动与虚拟化:FlatList/SectionList 的滚动需映射到 ArkUI 的滚动控件,确保性能与事件一致。
- 弹窗与返回手势:使用 RN Modal/Portal 并在适配层下桥接 ArkUI 弹窗能力,保证统一关闭手势与焦点管理。
- 字体与图标:避免 emoji 差异,改用统一图标栈;颜色与字号在不同端做最小差异化处理。
- 语言与时区:时间戳(如 08:00)若来自后端,应统一时区处理与本地化格式,确保三端显示一致。
完整代码演示:
// app.tsx
import React, { useState } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Dimensions, Alert, FlatList } from 'react-native';
// 图标库
const ICONS = {
food: '🍎',
protein: '🥩',
carb: '🍞',
fat: '🥑',
calorie: '🔥',
water: '💧',
fiber: '🥬',
vitamin: '🍊',
};
const { width } = Dimensions.get('window');
// 食物类型
type FoodItem = {
id: string;
name: string;
calories: number;
protein: number;
carbs: number;
fat: number;
fiber: number;
vitamins: number;
water: number;
timestamp: string;
};
// 营养图表组件
const NutritionChart = ({ value, max, label, color }: {
value: number;
max: number;
label: string;
color: string
}) => {
const percentage = Math.min((value / max) * 100, 100);
return (
<View style={styles.chartContainer}>
<View style={styles.chartHeader}>
<Text style={styles.chartLabel}>{label}</Text>
<Text style={styles.chartValue}>{value}</Text>
</View>
<View style={styles.chartBar}>
<View
style={[
styles.chartFill,
{ width: `${percentage}%`, backgroundColor: color }
]}
/>
</View>
<Text style={styles.chartMax}>最大值: {max}</Text>
</View>
);
};
// 食物项组件
const FoodItemCard = ({ food }: { food: FoodItem }) => {
return (
<View style={styles.foodCard}>
<View style={styles.foodHeader}>
<Text style={styles.foodName}>{food.name}</Text>
<Text style={styles.foodTime}>{food.timestamp}</Text>
</View>
<View style={styles.nutritionRow}>
<View style={styles.nutritionItem}>
<Text style={styles.nutritionIcon}>{ICONS.calorie}</Text>
<Text style={styles.nutritionValue}>{food.calories}</Text>
<Text style={styles.nutritionLabel}>卡路里</Text>
</View>
<View style={styles.nutritionItem}>
<Text style={styles.nutritionIcon}>{ICONS.protein}</Text>
<Text style={styles.nutritionValue}>{food.protein}g</Text>
<Text style={styles.nutritionLabel}>蛋白质</Text>
</View>
<View style={styles.nutritionItem}>
<Text style={styles.nutritionIcon}>{ICONS.carb}</Text>
<Text style={styles.nutritionValue}>{food.carbs}g</Text>
<Text style={styles.nutritionLabel}>碳水</Text>
</View>
<View style={styles.nutritionItem}>
<Text style={styles.nutritionIcon}>{ICONS.fat}</Text>
<Text style={styles.nutritionValue}>{food.fat}g</Text>
<Text style={styles.nutritionLabel}>脂肪</Text>
</View>
</View>
</View>
);
};
// 营养摘要卡片组件
const NutritionSummaryCard = ({ icon, title, value, unit, color }: {
icon: string;
title: string;
value: number;
unit: string;
color: string
}) => {
return (
<View style={styles.summaryCard}>
<Text style={[styles.summaryIcon, { color }]}> {icon}</Text>
<Text style={styles.summaryValue}>{value}</Text>
<Text style={styles.summaryUnit}>{unit}</Text>
<Text style={styles.summaryTitle}>{title}</Text>
</View>
);
};
// 主页面组件
const DietNutritionAnalysisApp: React.FC = () => {
const [dailyFoods] = useState<FoodItem[]>([
{
id: '1',
name: '燕麦片',
calories: 389,
protein: 16.9,
carbs: 66.3,
fat: 6.9,
fiber: 10.6,
vitamins: 2.4,
water: 8.0,
timestamp: '08:00'
},
{
id: '2',
name: '鸡胸肉',
calories: 165,
protein: 31.0,
carbs: 0,
fat: 3.6,
fiber: 0,
vitamins: 1.2,
water: 70.0,
timestamp: '12:30'
},
{
id: '3',
name: '牛油果沙拉',
calories: 240,
protein: 3.0,
carbs: 12.0,
fat: 22.0,
fiber: 10.0,
vitamins: 4.8,
water: 60.0,
timestamp: '18:00'
},
{
id: '4',
name: '希腊酸奶',
calories: 59,
protein: 10.0,
carbs: 3.6,
fat: 0.4,
fiber: 0,
vitamins: 0.8,
water: 75.0,
timestamp: '10:15'
},
]);
const [selectedPeriod, setSelectedPeriod] = useState('今日');
// 计算总营养
const totalCalories = dailyFoods.reduce((sum, food) => sum + food.calories, 0);
const totalProtein = dailyFoods.reduce((sum, food) => sum + food.protein, 0);
const totalCarbs = dailyFoods.reduce((sum, food) => sum + food.carbs, 0);
const totalFat = dailyFoods.reduce((sum, food) => sum + food.fat, 0);
const totalFiber = dailyFoods.reduce((sum, food) => sum + food.fiber, 0);
const totalVitamins = dailyFoods.reduce((sum, food) => sum + food.vitamins, 0);
const totalWater = dailyFoods.reduce((sum, food) => sum + food.water, 0);
// 推荐摄入量
const recommendedCalories = 2000;
const recommendedProtein = 50;
const recommendedCarbs = 250;
const recommendedFat = 70;
const handlePeriodChange = (period: string) => {
setSelectedPeriod(period);
// 这里可以添加数据获取逻辑
Alert.alert('切换周期', `显示${period}的营养分析`);
};
return (
<SafeAreaView style={styles.container}>
{/* 头部 */}
<View style={styles.header}>
<Text style={styles.title}>营养分析</Text>
<TouchableOpacity style={styles.infoButton} onPress={() => Alert.alert('帮助', '查看营养分析说明')}>
<Text style={styles.infoText}>?</Text>
</TouchableOpacity>
</View>
{/* 周期选择 */}
<View style={styles.periodContainer}>
{['今日', '本周', '本月'].map(period => (
<TouchableOpacity
key={period}
style={[
styles.periodButton,
selectedPeriod === period && styles.periodButtonActive
]}
onPress={() => handlePeriodChange(period)}
>
<Text style={[
styles.periodText,
selectedPeriod === period && styles.periodTextActive
]}>
{period}
</Text>
</TouchableOpacity>
))}
</View>
{/* 营养摘要 */}
<View style={styles.summaryContainer}>
<NutritionSummaryCard
icon={ICONS.calorie}
title="总卡路里"
value={totalCalories}
unit="卡"
color="#3b82f6"
/>
<NutritionSummaryCard
icon={ICONS.protein}
title="蛋白质"
value={totalProtein}
unit="克"
color="#10b981"
/>
<NutritionSummaryCard
icon={ICONS.carb}
title="碳水"
value={totalCarbs}
unit="克"
color="#f59e0b"
/>
</View>
{/* 营养图表 */}
<View style={styles.chartsContainer}>
<Text style={styles.sectionTitle}>营养占比</Text>
<NutritionChart
value={totalCalories}
max={recommendedCalories}
label="卡路里"
color="#3b82f6"
/>
<NutritionChart
value={totalProtein}
max={recommendedProtein}
label="蛋白质(g)"
color="#10b981"
/>
<NutritionChart
value={totalCarbs}
max={recommendedCarbs}
label="碳水(g)"
color="#f59e0b"
/>
<NutritionChart
value={totalFat}
max={recommendedFat}
label="脂肪(g)"
color="#ef4444"
/>
</View>
{/* 食物列表 */}
<ScrollView style={styles.content}>
<Text style={styles.sectionTitle}>今日饮食记录</Text>
<FlatList
data={dailyFoods}
keyExtractor={item => item.id}
renderItem={({ item }) => <FoodItemCard food={item} />}
showsVerticalScrollIndicator={false}
/>
{/* 营养建议 */}
<Text style={styles.sectionTitle}>营养建议</Text>
<View style={styles.adviceContainer}>
<View style={styles.adviceItem}>
<Text style={styles.adviceIcon}>💡</Text>
<Text style={styles.adviceText}>
您今日摄入蛋白质{totalProtein.toFixed(1)}g,建议继续增加优质蛋白质来源
</Text>
</View>
<View style={styles.adviceItem}>
<Text style={styles.adviceIcon}>💡</Text>
<Text style={styles.adviceText}>
您今日卡路里摄入{totalCalories}卡,符合减脂需求
</Text>
</View>
<View style={styles.adviceItem}>
<Text style={styles.adviceIcon}>💡</Text>
<Text style={styles.adviceText}>
建议增加蔬菜摄入,提高纤维素和维生素含量
</Text>
</View>
</View>
{/* 营养目标 */}
<Text style={styles.sectionTitle}>营养目标</Text>
<View style={styles.goalContainer}>
<View style={styles.goalItem}>
<Text style={styles.goalLabel}>卡路里目标</Text>
<Text style={styles.goalValue}>{recommendedCalories} 卡</Text>
</View>
<View style={styles.goalItem}>
<Text style={styles.goalLabel}>蛋白质目标</Text>
<Text style={styles.goalValue}>{recommendedProtein}g</Text>
</View>
<View style={styles.goalItem}>
<Text style={styles.goalLabel}>碳水目标</Text>
<Text style={styles.goalValue}>{recommendedCarbs}g</Text>
</View>
<View style={styles.goalItem}>
<Text style={styles.goalLabel}>脂肪目标</Text>
<Text style={styles.goalValue}>{recommendedFat}g</Text>
</View>
</View>
</ScrollView>
{/* 底部导航 */}
<View style={styles.bottomNav}>
<TouchableOpacity style={styles.navItem}>
<Text style={styles.navIcon}>{ICONS.food}</Text>
<Text style={styles.navText}>食物</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navItem}>
<Text style={styles.navIcon}>{ICONS.water}</Text>
<Text style={styles.navText}>饮水</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.navItem, styles.activeNavItem]}>
<Text style={styles.navIcon}>{ICONS.calorie}</Text>
<Text style={styles.navText}>分析</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navItem}>
<Text style={styles.navIcon}>{ICONS.vitamin}</Text>
<Text style={styles.navText}>目标</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f8fafc',
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: 20,
backgroundColor: '#ffffff',
borderBottomWidth: 1,
borderBottomColor: '#e2e8f0',
},
title: {
fontSize: 20,
fontWeight: 'bold',
color: '#1e293b',
},
infoButton: {
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: '#f1f5f9',
alignItems: 'center',
justifyContent: 'center',
},
infoText: {
fontSize: 16,
color: '#64748b',
},
periodContainer: {
flexDirection: 'row',
justifyContent: 'space-around',
padding: 16,
backgroundColor: '#ffffff',
borderBottomWidth: 1,
borderBottomColor: '#e2e8f0',
},
periodButton: {
paddingHorizontal: 16,
paddingVertical: 8,
borderRadius: 20,
backgroundColor: '#f1f5f9',
},
periodButtonActive: {
backgroundColor: '#3b82f6',
},
periodText: {
fontSize: 14,
color: '#64748b',
},
periodTextActive: {
color: '#ffffff',
},
summaryContainer: {
flexDirection: 'row',
justifyContent: 'space-around',
padding: 16,
backgroundColor: '#ffffff',
marginBottom: 16,
},
summaryCard: {
alignItems: 'center',
width: (width - 48) / 3,
},
summaryIcon: {
fontSize: 24,
marginBottom: 4,
},
summaryValue: {
fontSize: 18,
fontWeight: 'bold',
color: '#1e293b',
},
summaryUnit: {
fontSize: 12,
color: '#64748b',
},
summaryTitle: {
fontSize: 12,
color: '#475569',
marginTop: 4,
},
chartsContainer: {
backgroundColor: '#ffffff',
padding: 16,
marginHorizontal: 16,
borderRadius: 12,
marginBottom: 16,
},
sectionTitle: {
fontSize: 18,
fontWeight: 'bold',
color: '#1e293b',
marginVertical: 12,
paddingHorizontal: 16,
},
chartContainer: {
marginBottom: 16,
},
chartHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 8,
},
chartLabel: {
fontSize: 14,
color: '#64748b',
},
chartValue: {
fontSize: 16,
fontWeight: 'bold',
color: '#1e293b',
},
chartBar: {
height: 8,
backgroundColor: '#e2e8f0',
borderRadius: 4,
overflow: 'hidden',
},
chartFill: {
height: '100%',
borderRadius: 4,
},
chartMax: {
fontSize: 12,
color: '#94a3b8',
marginTop: 4,
alignSelf: 'flex-end',
},
content: {
flex: 1,
padding: 16,
},
foodCard: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
marginBottom: 12,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
foodHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
},
foodName: {
fontSize: 16,
fontWeight: 'bold',
color: '#1e293b',
},
foodTime: {
fontSize: 12,
color: '#94a3b8',
},
nutritionRow: {
flexDirection: 'row',
justifyContent: 'space-between',
},
nutritionItem: {
alignItems: 'center',
width: '22%',
},
nutritionIcon: {
fontSize: 18,
marginBottom: 4,
},
nutritionValue: {
fontSize: 14,
fontWeight: 'bold',
color: '#1e293b',
},
nutritionLabel: {
fontSize: 12,
color: '#64748b',
marginTop: 2,
},
adviceContainer: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
marginBottom: 16,
},
adviceItem: {
flexDirection: 'row',
alignItems: 'flex-start',
marginBottom: 12,
},
adviceItemLast: {
marginBottom: 0,
},
adviceIcon: {
fontSize: 16,
marginRight: 8,
marginTop: 2,
},
adviceText: {
fontSize: 14,
color: '#334155',
flex: 1,
lineHeight: 20,
},
goalContainer: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
},
goalItem: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: 8,
borderBottomWidth: 1,
borderBottomColor: '#e2e8f0',
},
goalItemLast: {
borderBottomWidth: 0,
},
goalLabel: {
fontSize: 14,
color: '#64748b',
},
goalValue: {
fontSize: 14,
fontWeight: 'bold',
color: '#1e293b',
},
bottomNav: {
flexDirection: 'row',
justifyContent: 'space-around',
backgroundColor: '#ffffff',
borderTopWidth: 1,
borderTopColor: '#e2e8f0',
paddingVertical: 12,
},
navItem: {
alignItems: 'center',
flex: 1,
},
activeNavItem: {
paddingBottom: 2,
borderBottomWidth: 2,
borderBottomColor: '#3b82f6',
},
navIcon: {
fontSize: 20,
color: '#94a3b8',
marginBottom: 4,
},
activeNavIcon: {
color: '#3b82f6',
},
navText: {
fontSize: 12,
color: '#94a3b8',
},
activeNavText: {
color: '#3b82f6',
fontWeight: '500',
},
});
export default DietNutritionAnalysisApp;
打包
接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

打包之后再将打包后的鸿蒙OpenHarmony文件拷贝到鸿蒙的DevEco-Studio工程目录去:

最后运行效果图如下显示:

欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
更多推荐



所有评论(0)