React Native for OpenHarmony 实战:Steam 资讯 App 个人中心页面

案例开源地址:https://atomgit.com/nutpi/rn_openharmony_steam

个人中心是 App 的"门面"之一,用户在这里可以看到自己的基本信息、快速访问常用功能。这篇文章来实现一个简洁实用的个人中心页面。个人中心的设计看似简单,但实际上涉及到很多细节,比如数据展示、导航逻辑、样式协调等。

页面结构分析

打开任何一个 App 的个人中心,基本都遵循这个套路:

  • 顶部 - 用户信息卡片(头像、昵称、状态)
  • 中间 - 统计数据展示(收藏数、浏览数等)
  • 下面 - 功能菜单列表

我们的 Steam 资讯 App 也按这个思路来。不过因为这是一个资讯类 App,不涉及真正的用户登录系统,所以用户信息部分就用一个默认的 Steam 头像和昵称来展示。

设计思路 - 即使没有真实用户数据,也要把页面框架搭好。这样后续接入真实登录系统时,只需要替换数据源就行,不用改页面结构。

从全局状态获取数据

个人中心需要展示用户的收藏数量和浏览历史数量,这些数据存在全局状态里。先从全局状态中获取必要的数据:

tsx 复制代码
const {navigate, favorites, history} = useApp();

这里获取了三个东西:

  • navigate - 导航函数,用于跳转到其他页面
  • favorites - 收藏列表数组,直接用 .length 就能拿到收藏数量
  • history - 浏览历史数组,同样用 .length 拿到浏览数量

为什么用 Hook?useApp 这个自定义 Hook 来获取全局状态,内部用的是 React Context。好处是任何组件都能方便地访问和修改全局状态,不用一层层传 props。这样代码更简洁,也更容易维护。

用户信息卡片

先来实现顶部的用户信息区域。这是个人中心最重要的视觉元素,需要展示用户的头像、昵称和在线状态。

先看卡片的结构:

tsx 复制代码
<View style={styles.userCard}>
  <Image 
    source={{uri: 'https://avatars.steamstatic.com/fef49e7fa7e1997310d705b2a6158ff8dc1cdfeb_full.jpg'}} 
    style={styles.avatar} 
  />
  <View style={styles.userInfo}>
    <Text style={styles.userName}>Steam 用户</Text>
    <Text style={styles.userStatus}>🟢 在线</Text>
  </View>
</View>

这里用了一个 Steam 的默认头像 URL。实际项目中,如果接入了 Steam 登录,这里应该显示用户真实的头像和昵称。

头像处理 - 用 Image 组件加载网络图片。如果用户没有网络连接,可以加一个本地的默认头像作为备选。

然后看样式设计:

tsx 复制代码
userCard: {
  flexDirection: 'row', 
  alignItems: 'center', 
  padding: 20, 
  backgroundColor: '#1b2838'
},
avatar: {width: 64, height: 64, borderRadius: 32},
userInfo: {marginLeft: 16},
userName: {fontSize: 18, fontWeight: 'bold', color: '#fff'},
userStatus: {fontSize: 14, color: '#8f98a0', marginTop: 4},

样式设计的关键点:

  • 头像 - 用 64x64 的圆形,borderRadius: 32 让它变成圆的
  • 用户名 - 18 号字体加粗,白色,视觉上最突出
  • 在线状态 - 用绿色圆点 emoji + 灰色文字,表示用户在线
  • 布局 - flexDirection: 'row' 让头像和文字信息水平排列,alignItems: 'center' 让它们垂直居中对齐

Flexbox 布局 - 这是 React Native 里最常用的布局方式。flexDirection: 'row' 表示水平排列,alignItems: 'center' 表示垂直居中。这两个属性组合起来就能实现很多常见的布局。

统计数据展示

用户信息下面是三个统计数字:收藏数、浏览数、游戏数。这些数字能让用户一眼看到自己在 App 中的活跃度。

先看统计区域的结构:

tsx 复制代码
<View style={styles.stats}>
  <View style={styles.statItem}>
    <Text style={styles.statNumber}>{favorites.length}</Text>
    <Text style={styles.statLabel}>收藏</Text>
  </View>
  <View style={styles.statItem}>
    <Text style={styles.statNumber}>{history.length}</Text>
    <Text style={styles.statLabel}>浏览</Text>
  </View>
  <View style={styles.statItem}>
    <Text style={styles.statNumber}>0</Text>
    <Text style={styles.statLabel}>游戏</Text>
  </View>
</View>

三个 statItem 平分整行宽度,每个里面是数字在上、标签在下的布局。

为什么"游戏"数量写死是 0? 因为这个 App 没有接入 Steam 账号系统,拿不到用户真正拥有的游戏数量。如果后续要做登录功能,这里可以改成动态数据。

然后看样式部分:

tsx 复制代码
stats: {
  flexDirection: 'row', 
  backgroundColor: '#1b2838', 
  borderTopWidth: 1, 
  borderTopColor: '#2a475e'
},
statItem: {flex: 1, alignItems: 'center', paddingVertical: 16},
statNumber: {fontSize: 24, fontWeight: 'bold', color: '#66c0f4'},
statLabel: {fontSize: 12, color: '#8f98a0', marginTop: 4},

样式设计的考虑:

  • 平分宽度 - flex: 1 让三个 statItem 平分父容器宽度
  • 数字颜色 - 用 Steam 的蓝色 #66c0f4,比较醒目,能吸引用户注意
  • 标签颜色 - 用灰色 #8f98a0,不抢数字的风头
  • 分割线 - 顶部加了一条分割线 borderTopWidth: 1,把统计区域和用户信息卡片在视觉上分开

视觉层级 - 通过颜色和大小的对比,让数字成为视觉焦点,标签作为辅助说明。这样用户一眼就能看到关键信息。

功能菜单列表

菜单列表是个人中心的核心部分,用户通过这里进入各个功能页面。先看菜单的结构:

tsx 复制代码
<View style={styles.menu}>
  <ListItem icon="❤️" title="我的收藏" rightText={`${favorites.length}`} onPress={() => navigate('favorites')} />
  <ListItem icon="📜" title="浏览历史" rightText={`${history.length}`} onPress={() => navigate('history')} />
  <ListItem icon="🔔" title="消息通知" onPress={() => navigate('notifications')} />
  <ListItem icon="⚙️" title="设置" onPress={() => navigate('settings')} />
  <ListItem icon="❓" title="帮助中心" onPress={() => navigate('help')} />
  <ListItem icon="ℹ️" title="关于我们" onPress={() => navigate('about')} />
</View>

这里用了一个通用的 ListItem 组件,每个菜单项都是一个 ListItem。这样做的好处是:

  • 代码复用 - 不用每个菜单项都写一遍样式
  • 统一风格 - 所有菜单项的样式保持一致
  • 易于维护 - 想改样式只需要改 ListItem 组件

组件设计 - 通过提取通用组件,可以大幅减少重复代码。如果后续要添加新的菜单项,只需要再加一行 ListItem 就行。

ListItem 组件实现

既然用到了 ListItem,来看看它是怎么实现的。这是一个通用的列表项组件,可以在很多地方复用。

首先定义 Props 接口:

tsx 复制代码
interface ListItemProps {
  title: string;
  subtitle?: string;
  icon?: string;
  rightText?: string;
  onPress?: () => void;
  showArrow?: boolean;
}

Props 定义用 TypeScript 接口,这样在使用组件时 IDE 会有智能提示,也能在编译时发现类型错误。

  • title - 必传,菜单项的标题
  • subtitle - 可选,副标题或描述文字
  • icon - 可选,左侧图标
  • rightText - 可选,右侧显示的文字(比如数量)
  • onPress - 可选,点击回调函数
  • showArrow - 可选,是否显示右侧箭头,默认为 true

然后看组件的实现:

tsx 复制代码
export const ListItem = ({title, subtitle, icon, rightText, onPress, showArrow = true}: ListItemProps) => (
  <TouchableOpacity style={styles.item} onPress={onPress} disabled={!onPress}>
    {icon && <Text style={styles.icon}>{icon}</Text>}
    <View style={styles.content}>
      <Text style={styles.title}>{title}</Text>
      {subtitle && <Text style={styles.subtitle}>{subtitle}</Text>}
    </View>
    {rightText && <Text style={styles.rightText}>{rightText}</Text>}
    {showArrow && onPress && <Text style={styles.arrow}>›</Text>}
  </TouchableOpacity>
);

组件实现的关键点:

  • 条件渲染 - {icon && <Text>...} 这种写法,只有当 icon 有值时才渲染图标。subtitlerightText 同理
  • 禁用状态 - disabled={!onPress} 表示如果没传 onPress,这个按钮就不可点击。这样可以用 ListItem 来展示纯信息,不一定非要能点
  • 箭头显示逻辑 - {showArrow && onPress && ...} 只有当 showArrow 为 true 且有 onPress 时才显示箭头。纯展示的项不需要箭头

灵活设计 - 通过这些可选属性和条件渲染,ListItem 组件可以适应多种场景。既能用作可点击的菜单项,也能用作纯展示的信息项。

然后看样式部分:

tsx 复制代码
item: {
  flexDirection: 'row',
  alignItems: 'center',
  padding: 16,
  backgroundColor: '#1b2838',
  borderBottomWidth: 1,
  borderBottomColor: '#2a475e',
},
icon: {fontSize: 20, marginRight: 12},
content: {flex: 1},
title: {fontSize: 16, color: '#fff'},
subtitle: {fontSize: 13, color: '#8f98a0', marginTop: 2},
rightText: {fontSize: 14, color: '#8f98a0', marginRight: 8},
arrow: {fontSize: 20, color: '#8f98a0'},

样式设计的要点:

  • 布局 - flexDirection: 'row' 让各个元素水平排列,alignItems: 'center' 让它们垂直居中
  • 内容占位 - content 设置 flex: 1 让它占据剩余空间,这样 rightText 和箭头会被挤到右边
  • 分割线 - 每个菜单项底部加一条分割线,让列表看起来更清晰
  • 颜色 - 标题用白色,副标题和右侧文字用灰色,形成视觉层级

页面的整体结构

个人中心页面的整体布局分为三层:

tsx 复制代码
return (
  <View style={styles.container}>
    <Header title="我的" />
    <ScrollView style={styles.content}>
      {/* 用户卡片 */}
      {/* 统计数据 */}
      {/* 菜单列表 */}
    </ScrollView>
    <TabBar />
  </View>
);

页面结构很清晰:

  • Header - 顶部导航栏,显示"我的"标题
  • ScrollView - 内容区域,包含用户卡片、统计数据和菜单列表
  • TabBar - 底部导航栏,方便用户切换到其他页面

为什么用 ScrollView?ScrollView 包裹内容区域是个好习惯,即使现在内容不多,以后加功能时也不用担心超出屏幕。而且如果内容确实超出屏幕,用户可以滚动查看。

配色方案

个人中心页面的样式采用 Steam 的深色主题。先看容器和基本样式:

tsx 复制代码
const styles = StyleSheet.create({
  container: {flex: 1, backgroundColor: '#171a21'},
  content: {flex: 1},
  userCard: {flexDirection: 'row', alignItems: 'center', padding: 20, backgroundColor: '#1b2838'},
  avatar: {width: 64, height: 64, borderRadius: 32},
  userInfo: {marginLeft: 16},
  userName: {fontSize: 18, fontWeight: 'bold', color: '#fff'},
  userStatus: {fontSize: 14, color: '#8f98a0', marginTop: 4},

然后是统计区域的样式:

tsx 复制代码
  stats: {flexDirection: 'row', backgroundColor: '#1b2838', borderTopWidth: 1, borderTopColor: '#2a475e'},
  statItem: {flex: 1, alignItems: 'center', paddingVertical: 16},
  statNumber: {fontSize: 24, fontWeight: 'bold', color: '#66c0f4'},
  statLabel: {fontSize: 12, color: '#8f98a0', marginTop: 4},
  menu: {marginTop: 16},
});

配色说明:

  • #171a21 - 最深的背景色,用于页面底色
  • #1b2838 - 卡片背景色,比底色稍浅
  • #2a475e - 分割线颜色,用于区分不同区域
  • #66c0f4 - Steam 标志蓝,用于强调数字
  • #8f98a0 - 灰色,用于次要文字
  • #fff - 白色,用于主要文字

配色统一 - 保持配色统一,整个 App 看起来才协调。这些颜色都是 Steam 官方的配色,用户会感到熟悉。

导航逻辑

点击菜单项时调用 navigate 函数跳转到对应页面。先看菜单项的点击处理:

tsx 复制代码
<ListItem icon="❤️" title="我的收藏" rightText={`${favorites.length}`} onPress={() => navigate('favorites')} />
<ListItem icon="📜" title="浏览历史" rightText={`${history.length}`} onPress={() => navigate('history')} />
<ListItem icon="⚙️" title="设置" onPress={() => navigate('settings')} />

每个菜单项都有一个 onPress 回调,点击时调用 navigate() 函数。

导航方式 - navigate 函数在 AppContext 里定义,它会更新 currentScreen 状态,然后 App 根组件根据这个状态渲染对应的页面。这种方式比 React Navigation 简单,适合页面不多的小型 App。如果页面多了、导航逻辑复杂了,还是建议用 React Navigation。

数据绑定

个人中心的关键是要实时显示用户的数据。比如收藏数量和浏览历史数量需要从全局状态中获取:

tsx 复制代码
<ListItem icon="❤️" title="我的收藏" rightText={`${favorites.length}`} onPress={() => navigate('favorites')} />
<ListItem icon="📜" title="浏览历史" rightText={`${history.length}`} onPress={() => navigate('history')} />

以及统计区域的数字:

tsx 复制代码
<Text style={styles.statNumber}>{favorites.length}</Text>
<Text style={styles.statNumber}>{history.length}</Text>

实时更新 - 因为 favoriteshistory 都来自全局状态,当用户在其他页面添加收藏或浏览游戏时,这些数字会自动更新。用户返回个人中心时,就能看到最新的数据。

小结

个人中心页面展示了如何设计一个简洁实用的用户中心:

  • 信息展示 - 用户卡片展示基本信息,统计区域展示关键数据
  • 功能导航 - 菜单列表提供快速访问各个功能的入口
  • 组件复用 - 通过 ListItem 组件实现代码复用和风格统一
  • 数据绑定 - 从全局状态获取数据,实现实时更新
  • 配色协调 - 使用 Steam 的配色方案,保持视觉统一

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
Younglina2 小时前
想提升专注力?我做了一个web端的训练工具
前端·vue.js·游戏
avi91112 小时前
UnityProfiler游戏优化-举一个简单的Editor调试
游戏·unity·游戏引擎·aigc·vibe coding·editor扩展
半世轮回半世寻2 小时前
前端开发里最常用的5种本地存储
前端·javascript
爱上妖精的尾巴2 小时前
7-9 WPS JS宏 对象使用实例6:按条件读取多表再拆成多表
前端·javascript·wps·jsa
有意义2 小时前
现代 React 路由实践指南
前端·vue.js·react.js
-凌凌漆-2 小时前
【JS】JavaScript Promise
开发语言·javascript·ecmascript
二DUAN帝2 小时前
像素流与UE通信
前端·javascript·css·ue5·html·ue4·html5
水手冰激淋2 小时前
rn_for_openharmony狗狗之家app实战-领养详情实现
harmonyos
lili-felicity3 小时前
React Native for Harmony 企业级折叠面板 (Accordion) 组件
react native·react.js·ui