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


在移动应用开发领域,金融理财类应用因其数据密集型特性和对安全性的高要求,成为技术选型的重要考量对象。本文将深入解读一个基于 React Native 开发的财富管理应用代码片段,剖析其架构设计、状态管理策略以及在鸿蒙系统上的跨端实现考量。

数据模型

代码首先通过 TypeScript 定义了四个核心数据模型:InvestmentProductProfitRecordAssetAllocationUser。这种强类型设计不仅提升了代码的可读性和可维护性,更为跨端开发奠定了坚实基础。在鸿蒙系统的 ArkTS 环境中,类型系统的一致性尤为重要,它确保了数据在不同平台间传递时的准确性,减少了运行时错误的可能性。

// 投资产品类型
type InvestmentProduct = {
  id: string;
  name: string;
  type: string;
  currentValue: number;
  investedAmount: number;
  profit: number;
  profitRate: number;
  riskLevel: '低' | '中' | '高';
  daysHeld: number;
};

特别值得注意的是 riskLevel 属性的联合类型定义,它限制了风险等级只能是预设的三种,这种精确的类型约束在金融应用中尤为重要,能有效防止因数据错误导致的业务逻辑问题。

状态管理

应用采用 React Hooks 中的 useState 进行状态管理,这是 React Native 开发中的标准实践。对于投资产品、收益记录、资产配置和用户信息等静态数据,使用 useState 进行初始化后不再修改,这种模式非常适合那些初始化后相对稳定的数据。

const [investments] = useState<InvestmentProduct[]>([
  {
    id: '1',
    name: '稳健理财计划A',
    type: '理财产品',
    // ... 其他属性
  },
  // ... 更多投资产品数据
]);

而对于 activeTab 这样的动态状态,则同时保存了状态值和更新函数。这种状态管理策略在跨端场景下表现出色,因为它不依赖于特定平台的状态管理方案,而是使用了 React 生态的标准 API,确保了在 React Native 和鸿蒙系统上的一致性表现。


代码通过 Dimensions.get('window') 获取屏幕宽度,为后续的响应式布局提供了基础:

const { width } = Dimensions.get('window');

这种方式在 React Native 中非常常见,但在鸿蒙系统的跨端开发中,需要特别注意不同设备尺寸的适配。鸿蒙系统的自适应布局能力虽然强大,但与 React Native 的布局模型存在差异,因此在实际开发中需要进行针对性调整。

应用的布局结构清晰,主要包含投资产品卡片、收益记录和资产配置三个核心部分。投资产品卡片通过 renderInvestmentItem 函数实现,收益记录则通过 renderProfitRecord 函数实现。

const renderInvestmentItem = ({ item }: { item: InvestmentProduct }) => (
  <View style={styles.investmentCard}>
    {/* 投资产品头部 */}
    <View style={styles.investmentHeader}>
      <Text style={styles.investmentName}>{item.name}</Text>
      <Text style={styles.investmentType}>{item.type}</Text>
    </View>
    {/* 投资产品价值 */}
    <View style={styles.investmentValues}>
      {/* ... 投资产品价值内容 */}
    </View>
    {/* 投资产品底部 */}
    <View style={styles.investmentFooter}>
      {/* ... 投资产品底部信息 */}
    </View>
  </View>
);

这种组件化的布局结构在鸿蒙系统上可以很好地映射到 ArkTS 的组件树中,为跨端开发提供了便利。

代码实现了两个格式化函数:formatCurrencyformatPercent,用于格式化货币和百分比数据:

const formatCurrency = (value: number): string => {
  return value.toLocaleString('zh-CN', {
    style: 'currency',
    currency: 'CNY',
    minimumFractionDigits: 2
  });
};

const formatPercent = (value: number): string => {
  return `${value >= 0 ? '+' : ''}${value.toFixed(2)}%`;
};

这些格式化函数不仅提高了数据的可读性,也为跨端开发提供了便利。在鸿蒙系统上,这些函数的实现方式可能需要调整,以适应鸿蒙系统的国际化 API,但核心逻辑是一致的。


应用通过条件渲染和样式实现了数据的可视化反馈,例如根据收益的正负显示不同的颜色:

<Text style={[styles.valueAmount, item.profit >= 0 ? styles.positive : styles.negative]}>
  {item.profit >= 0 ? '+' : ''}{formatCurrency(item.profit)}
</Text>

这种方式在 React Native 和鸿蒙系统中都能很好地工作,为用户提供了直观的数据反馈。

代码中使用了 ScrollView 组件来实现内容的滚动,对于投资产品列表和收益记录,可以考虑使用 FlatList 组件来进一步优化性能:

<ScrollView style={styles.content}>
  {/* 内容 */}
</ScrollView>

在鸿蒙系统上,ScrollView 的滚动性能需要特别关注。由于鸿蒙系统对原生组件的渲染速度较快,但对 JavaScript 执行的性能要求较高,因此应尽量减少 ScrollView 中的复杂计算,确保滚动的流畅性。


React Native 的核心组件(如 ViewTextTouchableOpacityScrollView 等)在鸿蒙系统上都有对应的实现,但在某些属性和行为上可能存在差异。例如,ScrollView 的滚动性能、TouchableOpacity 的点击反馈等,都需要在鸿蒙系统上进行测试和优化。

金融应用通常需要展示大量的数据和图表,在鸿蒙系统上,React Native 应用的数据可视化需要考虑以下几点:

  1. 图表库选择:选择支持鸿蒙系统的图表库,如 ECharts 或 Recharts。

  2. 性能优化:对于复杂的图表,应考虑使用原生实现,以提高渲染性能。

  3. 数据处理:金融数据通常较大,应合理使用分页和缓存策略,减少内存消耗。

在鸿蒙系统上,React Native 应用的性能优化需要考虑以下几点:

  1. 渲染性能:鸿蒙系统对原生组件的渲染速度较快,但对 JavaScript 执行的性能要求较高。因此,应尽量减少 JavaScript 线程的计算负担,将复杂的计算逻辑移至原生层。

  2. 内存管理:金融应用通常会加载大量数据,内存消耗较大。在鸿蒙系统上,需要特别注意内存的分配和释放,避免内存泄漏。

  3. 安全性:金融应用对安全性要求较高,应确保数据传输和存储的安全性,避免敏感信息泄露。


代码使用了 TypeScript 进行类型定义,这不仅提高了代码的可读性,也为跨端开发提供了类型安全保障。在鸿蒙系统的 ArkTS 环境中,类型系统的一致性尤为重要,它确保了数据在不同平台间传递时的准确性。

模块化

应用的代码结构清晰,将数据模型、组件逻辑、样式等分离,便于维护和扩展。这种模块化设计在跨端开发中尤为重要,因为它使得平台特定的代码修改可以被隔离在最小范围内。

金融应用对错误处理要求较高,应确保所有可能的错误都能被正确捕获和处理。在实际开发中,还应考虑添加更全面的错误处理机制,如网络请求错误、数据解析错误等,以提高应用的稳定性。


应用使用了 React Hooks 中的 useState 进行状态管理,这是 React Native 开发中的标准实践。对于静态数据,使用 useState 进行初始化后不再修改;对于动态状态,则同时保存状态值和更新函数。这种状态管理策略简洁明了,易于理解和维护。

应用实现了 formatCurrencyformatPercent 函数,用于格式化货币和百分比数据,提高了数据的可读性。在实际开发中,还可以考虑使用国际化库(如 i18next)来进一步优化国际化支持。

  1. 类型系统:使用 TypeScript 进行类型定义,确保数据在不同平台间传递时的准确性。

  2. 模块化设计:将平台特定的代码隔离,便于维护和扩展。

  3. 性能优化:针对不同平台的性能特点,进行有针对性的优化。

  4. 用户体验:确保在不同平台上的用户体验一致性,包括交互方式、视觉效果等。

  5. 安全性:金融应用对安全性要求较高,应确保数据传输和存储的安全性,避免敏感信息泄露。

通过对这个 React Native 财富管理应用代码片段的深入解读,我们可以看到,一个优秀的跨端应用需要在架构设计、状态管理、性能优化等多个方面进行精心考量。特别是在 React Native 与鸿蒙系统的跨端开发中,需要充分了解两个平台的特性,才能开发出性能优异、用户体验一致的应用。

随着移动应用开发技术的不断演进,跨端开发已成为一种趋势。React Native 作为一种成熟的跨端开发框架,在鸿蒙系统上的应用前景广阔。通过不断优化代码结构、提高性能表现、确保用户体验一致性,我们可以开发出更加优秀的跨端应用,为用户带来更好的使用体验。
也需要密切关注鸿蒙系统的发展动态,及时调整开发策略,以适应新的技术要求。


财富管理类应用是金融场景的典型代表,其核心诉求是数据可视化展示、精准的数值格式化、安全的状态管理,同时对UI的专业性和交互的稳定性有极高要求。本文以一个完整的 React Native 财富管理页面为例,拆解其工程化实现逻辑,并深入探讨该页面向鸿蒙(HarmonyOS)生态迁移的技术要点,为跨端金融类应用开发提供可落地的实践参考。

该财富管理页面采用 React Native 函数式组件+TypeScript 开发范式,融合了金融场景特有的强类型数据定义、数值格式化工具函数、可视化资产配置展示、嵌套 FlatList 渲染等核心技术点,完全贴合金融类应用的开发特性。

页面通过 TypeScript 定义了 InvestmentProductProfitRecordAssetAllocationUser 四类核心金融业务类型,这种强类型约束是金融类应用跨端复用的基石:

  • 金融数据类型精准化InvestmentProduct 中通过 '低' | '中' | '高' 字面量类型约束风险等级,ProfitRecord 中通过 '收益' | '投资' | '赎回' 约束交易类型,避免运行时数据错误。这类字面量类型在鸿蒙 ArkTS 中可通过 enum 枚举或字面量类型无缝适配,核心数据校验逻辑完全一致。
  • 嵌套类型关联设计:所有类型均包含 id 字段作为唯一标识,保证列表渲染的 key 唯一性;AssetAllocation 中关联 percentage(占比)、value(金额)、color(可视化颜色),完整描述资产配置的多维属性。这种关联型数据结构在鸿蒙端可直接复用,仅需将 TypeScript 的 type 改为 ArkTS 的 interface,核心字段定义无需修改。
  • 类型安全的渲染逻辑renderInvestmentItemrenderProfitRecord 等渲染函数通过泛型约束入参类型(如 { item }: { item: ProfitRecord }),确保渲染逻辑仅处理合规的金融数据,这种类型约束在鸿蒙端可直接映射为 ForEach 渲染的数据源类型校验,大幅降低跨端适配的调试成本。

2. 工具函数:

页面封装了 formatCurrency(货币格式化)、formatPercent(百分比格式化)两个核心工具函数,这类纯业务工具函数是金融类应用跨端复用的核心资产:

  • 本地化货币格式化formatCurrency 利用 toLocaleString 实现人民币的本地化展示(如 ¥45,000.00),支持两位小数的精准展示,符合金融场景的数值展示规范。这类纯函数可 100% 跨端复用,鸿蒙端仅需确认 ArkTS 支持 Number.toLocaleString API(实际完全兼容),核心逻辑无需修改。
  • 百分比符号化处理formatPercent 自动为正收益添加 + 号,保证收益/亏损率的直观展示,这种金融场景特有的格式化逻辑与UI框架解耦,是跨端复用的最佳实践。

3. 布局

页面布局完全基于 React Native 的 Flex 布局体系实现,结合金融场景的视觉层级要求,实现专业化的UI展示,而 Flex 是 React Native 与鸿蒙 ArkUI 共有的核心布局模型,为跨端迁移提供了天然优势:

  • 金融卡片视觉层级:通过 borderRadius: 16(总资产卡片)、borderRadius: 12(投资产品卡片)区分核心数据与次要数据的视觉权重;利用 elevation(阴影)、backgroundColor 对比实现卡片的层级感。这种视觉层级设计在鸿蒙端可通过 shadow 属性和 borderRadius 无缝复刻,仅需将 elevation 替换为鸿蒙的 shadow 相关属性(shadowColor/shadowRadius/shadowOffset)。
  • 响应式资产配置展示:资产配置图表通过 flexDirection: 'row' + 百分比宽度(${asset.percentage}%)实现横向占比条,结合 marginLeft: index === 0 ? 0 : 2 处理分段间距,保证资产配置可视化的精准性。这种百分比布局思路在鸿蒙端可直接复用,仅需将 width: ${asset.percentage}%`` 改为 ArkTS 的 width: asset.percentage + '%',核心布局逻辑完全一致。
  • 金融数据排版规范:通过 flexDirection: 'row' + justifyContent: 'space-between' 实现“标签-数值”的左右对齐排版(如总资产、月收益、交易记录),符合金融应用的专业排版规范。这种排版逻辑在鸿蒙端可通过 FlexDirection.Row + JustifyContent.SpaceBetween 等价实现,无需调整核心布局思路。

4. 列表与滚动:

页面采用 FlatList 渲染投资产品、收益记录等核心金融数据列表,结合 slice(0, 2) 实现数据截断展示,体现了金融类应用的列表渲染策略:

  • 数据截断优化体验:投资产品列表通过 investments.slice(0, 2) 仅展示前两项,配合“查看全部”按钮引导用户查看完整数据,这种“摘要+完整”的展示策略在鸿蒙端可直接复用,仅需将数组截断逻辑(slice)迁移至 ArkTS 即可。
  • 虚拟化列表保证性能:采用 FlatList 而非基础 ScrollView 渲染金融数据列表,即使后续数据量增大(如数百条交易记录),也能保证滚动流畅性。这种虚拟化渲染策略在鸿蒙端可通过 List 组件实现,FlatListkeyExtractor 对应鸿蒙 ForEach 的第三个参数(唯一标识),核心渲染逻辑完全对齐。
  • 滚动体验优化:配置 showsVerticalScrollIndicator={false} 隐藏滚动指示器,提升金融应用的专业视觉体验,这种优化策略在鸿蒙端可通过 List/Scroll 组件的 scrollBar: ScrollBar.None 实现等价效果。

将该财富管理页面迁移至鸿蒙端,核心是“金融逻辑复用、布局等价复刻、数值展示一致”,以下从技术维度拆解关键适配点:

鸿蒙端基于 ArkTS(TypeScript 超集)开发,与 React Native 的 TypeScript 语法高度兼容,核心差异集中在组件定义与状态管理:

  • 类型定义迁移:React Native 的类型定义可直接转换为 ArkTS 接口,示例如下:
    // React Native TypeScript 类型
    type ProfitRecord = {
      id: string;
      date: string;
      amount: number;
      type: '收益' | '投资' | '赎回';
    };
    
    // 鸿蒙 ArkTS 等价接口
    interface ProfitRecord {
      id: string;
      date: string;
      amount: number;
      type: '收益' | '投资' | '赎回';
    }
    
  • 组件结构转换:React Native 的函数式组件转换为鸿蒙的 @Entry @Component 装饰的 struct,状态管理从 useState 改为 @State 装饰器:
    // React Native 组件结构
    const FinanceApp: React.FC = () => {
      const [activeTab, setActiveTab] = useState<'home' | 'portfolio' | 'invest' | 'profile'>('home');
      // 数据定义、工具函数...
      return (/* JSX 渲染 */);
    };
    
    // 鸿蒙 ArkTS 等价结构
    @Entry
    @Component
    struct FinanceApp {
      @State activeTab: 'home' | 'portfolio' | 'invest' | 'profile' = 'home';
      // 数据定义、工具函数(完全复用)...
      
      build() {
        // ArkUI 渲染(复用 Flex 布局)
      }
    }
    
  • 工具函数复用formatCurrencyformatPercent 等纯工具函数可直接复制到鸿蒙端使用,ArkTS 完全兼容 TypeScript 的数值处理 API,无需修改核心逻辑。

React Native 原生组件与鸿蒙 ArkUI 组件存在清晰的映射关系,是金融类应用跨端迁移的核心落地环节:

React Native 组件 鸿蒙 ArkUI 组件 适配核心说明
SafeAreaView SafeArea 均用于适配刘海屏/底部安全区,属性一致
View Column/Row/Stack 鸿蒙通过布局组件替代通用容器,Flex 布局逻辑复用
Text Text 样式属性(fontSize/color等)仅命名规范差异,金融数值展示完全一致
TouchableOpacity Button/Text(带点击态) 鸿蒙无直接等价组件,可通过 Button 去除默认样式实现,保证“查看全部”“快速操作”等按钮的交互一致性
FlatList List 鸿蒙 List 组件支持虚拟化渲染,keyExtractor 对应 ForEach 的第三个参数
ScrollView Scroll 鸿蒙 Scroll 组件实现纵向滚动,属性配置逻辑一致

以核心的收益记录列表渲染为例,React Native 实现与鸿蒙 ArkTS 实现的核心映射:

// React Native 收益记录渲染
<FlatList
  data={profitRecords}
  renderItem={renderProfitRecord}
  keyExtractor={item => item.id}
  showsVerticalScrollIndicator={false}
/>

// 鸿蒙 ArkTS 等价实现
<List
  data={this.profitRecords}
  scrollBar={ScrollBar.None}
  cachedCount={2} // 金融数据预加载,保证滚动流畅
>
  {ForEach(this.profitRecords, (record) => {
    return this.renderProfitRecord(record);
  }, record => record.id)}
</List>

3. 样式体系

React Native 的 StyleSheet.create 封装样式的方式,在鸿蒙端可通过 @Styles/@Extend 装饰器实现等价封装,核心样式属性的适配规则如下:

  • 布局属性flex/flexDirection/justifyContent/alignItems 等 Flex 核心属性完全复用,仅鸿蒙需将字符串值改为枚举值(如 justifyContent: 'space-between'justifyContent: FlexAlign.SpaceBetween)。
  • 金融卡片样式borderRadius 完全复用,保证总资产卡片(16px)、投资产品卡片(12px)的圆角一致性;backgroundColor 直接映射,金融场景特有的蓝色系(#3b82f6)、绿色系(#10b981)、红色系(#ef4444)可 100% 复用。
  • 文本样式适配fontWeight: 'bold' 对应鸿蒙的 fontWeight: FontWeight.BoldtextDecorationLine: 'line-through'(如需展示亏损划线)对应鸿蒙的 decoration: TextDecorationType.LineThrough;金融数值的颜色区分(positive/negative)可通过 @Styles 封装复用。
  • 资产配置图表适配:React Native 的 width: ${asset.percentage}%`` 对应鸿蒙的 width: asset.percentage + '%'marginLeft 直接映射,保证资产占比条的可视化精准性。

金融类应用对稳定性和流畅性要求极高,跨端迁移需重点保证以下体验一致性:

  • 滚动性能优化:React Native 的 FlatList 和鸿蒙的 List 均为虚拟化列表,鸿蒙端可通过 cachedCount: 2 配置预加载数量,保证数百条交易记录的滚动流畅;关闭滚动指示器(scrollBar: ScrollBar.None),保持金融应用的简洁专业视觉。
  • 交互响应优化:React Native 的 TouchableOpacity 点击反馈在鸿蒙端可通过 Button 组件的 stateEffect: true 实现,保证“投资”“转入”等核心操作按钮的点击反馈一致性;底部导航的选中态(activeNavIcon/activeNavText)可通过 @State 驱动样式变化,逻辑完全复用。
  • 金融数据展示优化:鸿蒙端可利用 @ohos.data.preferences 实现金融数据的本地缓存,补充 React Native 版本的本地存储能力,提升金融应用的离线使用体验;利用鸿蒙的 Canvas 组件增强资产配置的可视化效果(如饼图),提升鸿蒙端的原生体验。

从该 React Native 财富管理页面的鸿蒙适配过程中,可提炼出金融类应用跨端开发的通用方法论:

页面的数值格式化(formatCurrency/formatPercent)、数据类型定义等纯业务逻辑与 React Native UI 框架解耦,可 100% 跨端复用,这是金融类应用跨端开发效率最大化的关键。

金融数据的精准性要求极高,TypeScript/ArkTS 的强类型约束可提前发现数据类型错误,避免跨端适配过程中因数据格式问题导致的金融数值展示错误。

金融应用的UI排版强调“标签-数值”的对齐、数据卡片的层级感,Flex 布局可 100% 复用于 React Native 和鸿蒙,保证跨端UI的专业性和一致性。


若将该财富管理页面落地到鸿蒙端,建议分阶段实施:

  1. 工具函数与类型迁移:首先迁移 formatCurrencyformatPercent 工具函数和所有类型定义,验证金融数据处理的一致性,这一阶段几乎无适配成本。
  2. 基础组件迁移:迁移头部、总资产卡片、底部导航等基础组件,验证布局、样式的适配可行性。
  3. 核心列表迁移:迁移投资产品、收益记录等核心列表组件,重点验证虚拟化列表的滚动性能和数据展示一致性。
  4. 金融特性增强:针对鸿蒙端特性做增强,例如利用鸿蒙的 Chart 组件实现资产配置的饼图展示,利用 @ohos.security 模块增强金融数据的安全性。

从性能优化角度,React Native 端可通过 FlatListgetItemLayout 预计算金融数据列表项高度,鸿蒙端可通过 ListestimateSize 预估列表项尺寸,进一步提升大量交易记录的滚动性能。

该 React Native 财富管理页面的实现,充分体现了金融类应用跨端开发的工程化思想:强类型约束保证数据精准性,纯工具函数实现业务逻辑复用,Flex 布局保证UI一致性,虚拟化列表保证性能稳定性。向鸿蒙端迁移时,核心业务逻辑、数据类型、布局逻辑可完全复用,仅需适配组件语法、样式属性和原生 API 调用,适配成本可控制在 20%-30% 以内。

对于金融类跨端应用开发,无需追求“一行代码多端运行”,而是通过“逻辑解耦 + 类型复用 + 布局等价”的方式,平衡开发效率与金融应用的专业性、稳定性。React Native 与鸿蒙 ArkTS 均基于 TypeScript 生态,Flex 布局体系一致,这为金融类应用的跨端开发提供了天然的技术基础,也是未来金融科技跨端开发的主流方向。


真实演示案例代码:



// app.tsx
import React, { useState } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Dimensions, Alert, FlatList } from 'react-native';

// 图标库
const ICONS = {
  home: '🏠',
  portfolio: '💼',
  invest: '📈',
  bank: '🏦',
  chart: '📊',
  calculator: '🔢',
  settings: '⚙️',
  profile: '👤',
};

const { width } = Dimensions.get('window');

// 投资产品类型
type InvestmentProduct = {
  id: string;
  name: string;
  type: string;
  currentValue: number;
  investedAmount: number;
  profit: number;
  profitRate: number;
  riskLevel: '低' | '中' | '高';
  daysHeld: number;
};

// 收益记录类型
type ProfitRecord = {
  id: string;
  date: string;
  amount: number;
  type: '收益' | '投资' | '赎回';
};

// 资产配置类型
type AssetAllocation = {
  id: string;
  name: string;
  percentage: number;
  value: number;
  color: string;
};

// 用户类型
type User = {
  id: string;
  name: string;
  avatar: string;
  totalAssets: number;
  monthlyProfit: number;
};

// 主页面组件
const FinanceApp: React.FC = () => {
  const [investments] = useState<InvestmentProduct[]>([
    {
      id: '1',
      name: '稳健理财计划A',
      type: '理财产品',
      currentValue: 12500,
      investedAmount: 10000,
      profit: 2500,
      profitRate: 25.0,
      riskLevel: '低',
      daysHeld: 90
    },
    {
      id: '2',
      name: '股票基金组合',
      type: '基金',
      currentValue: 8500,
      investedAmount: 8000,
      profit: 500,
      profitRate: 6.25,
      riskLevel: '中',
      daysHeld: 45
    },
    {
      id: '3',
      name: '债券投资',
      type: '债券',
      currentValue: 5000,
      investedAmount: 5000,
      profit: 150,
      profitRate: 3.0,
      riskLevel: '低',
      daysHeld: 180
    },
    {
      id: '4',
      name: '科技股投资',
      type: '股票',
      currentValue: 15000,
      investedAmount: 12000,
      profit: 3000,
      profitRate: 25.0,
      riskLevel: '高',
      daysHeld: 30
    }
  ]);

  const [profitRecords] = useState<ProfitRecord[]>([
    { id: 'p1', date: '2023-05-15', amount: 250.50, type: '收益' },
    { id: 'p2', date: '2023-05-14', amount: 120.30, type: '收益' },
    { id: 'p3', date: '2023-05-10', amount: 500.00, type: '投资' },
    { id: 'p4', date: '2023-05-08', amount: 89.20, type: '收益' },
    { id: 'p5', date: '2023-05-05', amount: 150.75, type: '收益' },
  ]);

  const [assetAllocations] = useState<AssetAllocation[]>([
    { id: 'a1', name: '股票', percentage: 45, value: 20000, color: '#3b82f6' },
    { id: 'a2', name: '基金', percentage: 30, value: 13000, color: '#10b981' },
    { id: 'a3', name: '债券', percentage: 15, value: 6500, color: '#f59e0b' },
    { id: 'a4', name: '现金', percentage: 10, value: 4500, color: '#8b5cf6' },
  ]);

  const [user] = useState<User>({
    id: 'u1',
    name: '张三',
    avatar: '',
    totalAssets: 45000,
    monthlyProfit: 1250.75
  });

  const [activeTab, setActiveTab] = useState<'home' | 'portfolio' | 'invest' | 'profile'>('home');

  const formatCurrency = (value: number): string => {
    return value.toLocaleString('zh-CN', {
      style: 'currency',
      currency: 'CNY',
      minimumFractionDigits: 2
    });
  };

  const formatPercent = (value: number): string => {
    return `${value >= 0 ? '+' : ''}${value.toFixed(2)}%`;
  };

  const renderInvestmentItem = ({ item }: { item: InvestmentProduct }) => (
    <View style={styles.investmentCard}>
      <View style={styles.investmentHeader}>
        <Text style={styles.investmentName}>{item.name}</Text>
        <Text style={styles.investmentType}>{item.type}</Text>
      </div>
      
      <View style={styles.investmentValues}>
        <View style={styles.valueItem}>
          <Text style={styles.valueLabel}>当前价值</Text>
          <Text style={styles.valueAmount}>{formatCurrency(item.currentValue)}</Text>
        </div>
        <View style={styles.valueItem}>
          <Text style={styles.valueLabel}>累计收益</Text>
          <Text style={[styles.valueAmount, item.profit >= 0 ? styles.positive : styles.negative]}>
            {item.profit >= 0 ? '+' : ''}{formatCurrency(item.profit)}
          </Text>
        </div>
      </div>
      
      <View style={styles.investmentFooter}>
        <Text style={styles.riskLevel}>风险等级: {item.riskLevel}</Text>
        <Text style={styles.daysHeld}>持有天数: {item.daysHeld}</Text>
        <Text style={[styles.profitRate, item.profitRate >= 0 ? styles.positive : styles.negative]}>
          {formatPercent(item.profitRate)}
        </Text>
      </div>
    </View>
  );

  const renderProfitRecord = ({ item }: { item: ProfitRecord }) => (
    <View style={styles.recordItem}>
      <View style={styles.recordLeft}>
        <Text style={styles.recordDate}>{item.date}</Text>
        <Text style={styles.recordType}>{item.type}</Text>
      </div>
      <Text style={[styles.recordAmount, item.type === '收益' ? styles.positive : styles.negative]}>
        {item.type === '收益' ? '+' : ''}{formatCurrency(item.amount)}
      </Text>
    </View>
  );

  return (
    <SafeAreaView style={styles.container}>
      {/* 头部 */}
      <View style={styles.header}>
        <Text style={styles.title}>财富管理</Text>
        <View style={styles.headerActions}>
          <TouchableOpacity style={styles.chartButton}>
            <Text style={styles.chartIcon}>{ICONS.chart}</Text>
          </TouchableOpacity>
          <TouchableOpacity style={styles.settingsButton}>
            <Text style={styles.settingsIcon}>{ICONS.settings}</Text>
          </TouchableOpacity>
        </div>
      </View>

      <ScrollView style={styles.content}>
        {/* 总资产卡片 */}
        <View style={styles.assetCard}>
          <Text style={styles.assetLabel}>总资产</Text>
          <Text style={styles.totalAsset}>{formatCurrency(user.totalAssets)}</Text>
          <View style={styles.monthlyProfitContainer}>
            <Text style={styles.monthlyProfitLabel}>月收益</Text>
            <Text style={styles.monthlyProfitAmount}>{formatCurrency(user.monthlyProfit)}</Text>
          </div>
        </View>

        {/* 资产配置图表 */}
        <Text style={styles.sectionTitle}>资产配置</Text>
        <View style={styles.allocationChart}>
          <View style={styles.chartContainer}>
            {assetAllocations.map((asset, index) => (
              <View 
                key={asset.id} 
                style={[styles.chartSegment, { 
                  width: `${asset.percentage}%`, 
                  backgroundColor: asset.color,
                  marginLeft: index === 0 ? 0 : 2
                }]}
              />
            ))}
          </div>
          <View style={styles.legendContainer}>
            {assetAllocations.map(asset => (
              <View key={asset.id} style={styles.legendItem}>
                <View style={[styles.legendColor, { backgroundColor: asset.color }]} />
                <Text style={styles.legendText}>{asset.name} {asset.percentage}%</Text>
              </View>
            ))}
          </div>
        </View>

        {/* 投资产品 */}
        <View style={styles.sectionHeader}>
          <Text style={styles.sectionTitle}>我的投资</Text>
          <TouchableOpacity>
            <Text style={styles.seeAllText}>查看全部</Text>
          </TouchableOpacity>
        </View>

        <FlatList
          data={investments.slice(0, 2)}
          renderItem={renderInvestmentItem}
          keyExtractor={item => item.id}
          showsVerticalScrollIndicator={false}
        />

        {/* 收益记录 */}
        <View style={styles.sectionHeader}>
          <Text style={styles.sectionTitle}>收益记录</Text>
          <TouchableOpacity>
            <Text style={styles.seeAllText}>查看全部</Text>
          </TouchableOpacity>
        </View>

        <FlatList
          data={profitRecords}
          renderItem={renderProfitRecord}
          keyExtractor={item => item.id}
          showsVerticalScrollIndicator={false}
        />

        {/* 理财建议 */}
        <View style={styles.adviceCard}>
          <Text style={styles.adviceTitle}>理财建议</Text>
          <Text style={styles.adviceText}>• 分散投资降低风险,建议配置不同类型的资产</Text>
          <Text style={styles.adviceText}>• 定期评估投资组合,适时调整策略</Text>
          <Text style={styles.adviceText}>• 长期投资通常比短期投机收益更稳定</Text>
          <Text style={styles.adviceText}>• 根据风险承受能力选择合适的投资产品</Text>
        </div>

        {/* 快速操作 */}
        <View style={styles.quickActions}>
          <TouchableOpacity style={styles.quickActionItem}>
            <Text style={styles.quickActionIcon}>{ICONS.invest}</Text>
            <Text style={styles.quickActionText}>投资</Text>
          </TouchableOpacity>
          <TouchableOpacity style={styles.quickActionItem}>
            <Text style={styles.quickActionIcon}>{ICONS.bank}</Text>
            <Text style={styles.quickActionText}>转入</Text>
          </TouchableOpacity>
          <TouchableOpacity style={styles.quickActionItem}>
            <Text style={styles.quickActionIcon}>{ICONS.portfolio}</Text>
            <Text style={styles.quickActionText}>持仓</Text>
          </TouchableOpacity>
          <TouchableOpacity style={styles.quickActionItem}>
            <Text style={styles.quickActionIcon}>{ICONS.calculator}</Text>
            <Text style={styles.quickActionText}>计算器</Text>
          </TouchableOpacity>
        </View>
      </ScrollView>

      {/* 底部导航 */}
      <View style={styles.bottomNav}>
        <TouchableOpacity 
          style={styles.navItem} 
          onPress={() => setActiveTab('home')}
        >
          <Text style={[styles.navIcon, activeTab === 'home' && styles.activeNavIcon]}>{ICONS.home}</Text>
          <Text style={[styles.navText, activeTab === 'home' && styles.activeNavText]}>首页</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.navItem} 
          onPress={() => setActiveTab('portfolio')}
        >
          <Text style={[styles.navIcon, activeTab === 'portfolio' && styles.activeNavIcon]}>{ICONS.portfolio}</Text>
          <Text style={[styles.navText, activeTab === 'portfolio' && styles.activeNavText]}>持仓</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.navItem} 
          onPress={() => setActiveTab('invest')}
        >
          <Text style={[styles.navIcon, activeTab === 'invest' && styles.activeNavIcon]}>{ICONS.invest}</Text>
          <Text style={[styles.navText, activeTab === 'invest' && styles.activeNavText]}>投资</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.navItem} 
          onPress={() => setActiveTab('profile')}
        >
          <Text style={[styles.navIcon, activeTab === 'profile' && styles.activeNavIcon]}>{ICONS.profile}</Text>
          <Text style={[styles.navText, activeTab === 'profile' && styles.activeNavText]}>我的</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',
  },
  headerActions: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  chartButton: {
    width: 36,
    height: 36,
    borderRadius: 18,
    backgroundColor: '#f1f5f9',
    alignItems: 'center',
    justifyContent: 'center',
    marginRight: 12,
  },
  chartIcon: {
    fontSize: 18,
    color: '#64748b',
  },
  settingsButton: {
    width: 36,
    height: 36,
    borderRadius: 18,
    backgroundColor: '#f1f5f9',
    alignItems: 'center',
    justifyContent: 'center',
  },
  settingsIcon: {
    fontSize: 18,
    color: '#64748b',
  },
  content: {
    flex: 1,
    padding: 16,
  },
  assetCard: {
    backgroundColor: '#3b82f6',
    borderRadius: 16,
    padding: 20,
    marginBottom: 16,
  },
  assetLabel: {
    fontSize: 14,
    color: '#e0f2fe',
    marginBottom: 8,
  },
  totalAsset: {
    fontSize: 32,
    fontWeight: 'bold',
    color: '#ffffff',
    marginBottom: 12,
  },
  monthlyProfitContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
  },
  monthlyProfitLabel: {
    fontSize: 14,
    color: '#bae6fd',
  },
  monthlyProfitAmount: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#ffffff',
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#1e293b',
    marginVertical: 12,
  },
  sectionHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
  },
  seeAllText: {
    fontSize: 14,
    color: '#3b82f6',
    fontWeight: '500',
  },
  allocationChart: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 16,
  },
  chartContainer: {
    flexDirection: 'row',
    height: 30,
    borderRadius: 15,
    overflow: 'hidden',
    marginBottom: 12,
  },
  chartSegment: {
    height: '100%',
    borderRadius: 15,
  },
  legendContainer: {
    flexDirection: 'row',
    flexWrap: 'wrap',
  },
  legendItem: {
    flexDirection: 'row',
    alignItems: 'center',
    marginRight: 16,
    marginBottom: 8,
  },
  legendColor: {
    width: 12,
    height: 12,
    borderRadius: 6,
    marginRight: 6,
  },
  legendText: {
    fontSize: 12,
    color: '#64748b',
  },
  investmentCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 12,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  investmentHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 12,
  },
  investmentName: {
    fontSize: 16,
    fontWeight: '600',
    color: '#1e293b',
    flex: 1,
  },
  investmentType: {
    fontSize: 12,
    color: '#64748b',
    backgroundColor: '#f1f5f9',
    paddingHorizontal: 8,
    paddingVertical: 4,
    borderRadius: 12,
  },
  investmentValues: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    marginBottom: 12,
  },
  valueItem: {
    flex: 1,
  },
  valueLabel: {
    fontSize: 12,
    color: '#64748b',
    marginBottom: 4,
  },
  valueAmount: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
  },
  positive: {
    color: '#10b981',
  },
  negative: {
    color: '#ef4444',
  },
  investmentFooter: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
  },
  riskLevel: {
    fontSize: 12,
    color: '#64748b',
  },
  daysHeld: {
    fontSize: 12,
    color: '#64748b',
  },
  profitRate: {
    fontSize: 14,
    fontWeight: 'bold',
  },
  recordItem: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 12,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  recordLeft: {
    flex: 1,
  },
  recordDate: {
    fontSize: 14,
    color: '#1e293b',
    marginBottom: 4,
  },
  recordType: {
    fontSize: 12,
    color: '#64748b',
  },
  recordAmount: {
    fontSize: 16,
    fontWeight: 'bold',
  },
  adviceCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginTop: 16,
    marginBottom: 16,
  },
  adviceTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 12,
  },
  adviceText: {
    fontSize: 12,
    color: '#64748b',
    lineHeight: 18,
    marginBottom: 6,
  },
  quickActions: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    marginBottom: 16,
  },
  quickActionItem: {
    alignItems: 'center',
    flex: 1,
    marginHorizontal: 8,
  },
  quickActionIcon: {
    fontSize: 24,
    marginBottom: 6,
  },
  quickActionText: {
    fontSize: 12,
    color: '#1e293b',
  },
  bottomNav: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    backgroundColor: '#ffffff',
    borderTopWidth: 1,
    borderTopColor: '#e2e8f0',
    paddingVertical: 12,
  },
  navItem: {
    alignItems: 'center',
    flex: 1,
  },
  navIcon: {
    fontSize: 20,
    color: '#94a3b8',
    marginBottom: 4,
  },
  activeNavIcon: {
    color: '#3b82f6',
  },
  navText: {
    fontSize: 12,
    color: '#94a3b8',
  },
  activeNavText: {
    color: '#3b82f6',
    fontWeight: '500',
  },
});

export default FinanceApp;

请添加图片描述


打包

接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

在这里插入图片描述

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

在这里插入图片描述

最后运行效果图如下显示:
请添加图片描述
本文解析了一个基于React Native开发的财富管理应用,重点探讨其架构设计、状态管理和跨平台实现策略。应用采用TypeScript定义核心数据模型,使用React Hooks进行状态管理,并实现了响应式布局和数据格式化功能。文章特别分析了该应用向鸿蒙系统迁移的技术要点,包括组件适配、性能优化和安全性考量,为金融类跨平台应用开发提供了实践参考,展示了React Native在鸿蒙生态中的开发潜力。

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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐