在React Native(RN)鸿蒙跨端开发领域,工具类APP的核心诉求是「多端UI一致性、数据联动流畅性、基础组件适配兼容性」,尤其是涉及表单交互、列表渲染、状态联动等高频场景时,鸿蒙系统的适配质量直接决定用户体验。本次解读的代码片段,是一款完整的阅读追踪APP(看书管理记录)的核心实现,涵盖首页概览、书籍添加、统计分析、个人中心四大模块,支持书籍管理、进度更新、数据统计等核心功能,完全遵循RN跨端开发规范,可直接在鸿蒙设备上编译运行,无需额外修改核心逻辑。

本文将以技术博客解读的语气,逐模块拆解这段代码的实现逻辑,重点聚焦RN与鸿蒙系统的跨端适配细节——从依赖引入、类型定义、状态管理,到组件封装、样式适配、交互逻辑,深入剖析每一个技术点背后的跨端设计思路,帮助开发者吃透工具类APP的RN鸿蒙跨端开发技巧,理解「一套代码,多端流畅运行」的核心实现逻辑,同时规避跨端开发中常见的适配坑,为同类工具类应用跨端开发提供可复用的实战参考。

一、依赖引入:

RN鸿蒙跨端开发的核心原则之一是「优先使用RN官方通用组件与API」,避免引入平台专属依赖,借助鸿蒙系统提供的RN适配层(HarmonyOS React Native Adapter),实现API的自动映射,减少手动适配成本。我们先看代码开篇的依赖引入与基础定义部分,这是整个APP跨端运行的核心基石:


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

// Base64 图标库
const ICONS_BASE64 = {
  home: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  // 其余图标省略...
};

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

// 书籍类型
type Book = {
  id: string;
  title: string;
  author: string;
  pages: number;
  currentPage: number;
  progress: number;
  startDate: string;
  targetDate: string;
  category: string;
  isCompleted: boolean;
};

这部分代码看似简单,实则奠定了整个APP的跨端适配基础,没有任何平台专属代码,完全基于RN官方能力构建,我们逐点拆解其核心技术点与鸿蒙适配细节:

1.1 核心依赖:

代码引入的React核心(useState钩子)与RN官方组件,均为鸿蒙适配层完全兼容的通用能力,无需任何手动适配即可在鸿蒙设备上正常运行,具体适配细节如下,也是工具类APP跨端开发的最优实践:

  • SafeAreaView:鸿蒙设备(尤其是刘海屏、曲面屏)的核心适配组件,鸿蒙RN适配层会自动识别当前设备的安全区域参数(状态栏高度、导航栏高度),动态调整组件内边距,确保APP内容不会被状态栏、导航栏遮挡。其适配效果与iOS、Android端完全一致,无需开发者单独判断设备类型(如鸿蒙手机与iOS手机的安全区域计算逻辑,适配层已自动兼容),省去了手动适配安全区域的繁琐代码——这对于工具类APP而言,能极大提升多设备适配效率。

  • 基础布局与交互组件(View、Text、TouchableOpacity):这三个是RN开发中最基础的组件,也是工具类APP的核心组件,鸿蒙适配层已将其完全映射为鸿蒙原生组件——View对应鸿蒙的ComponentContainer,用于构建布局容器;Text对应TextComponent,用于文本渲染;TouchableOpacity对应鸿蒙的ButtonComponent,并保留RN的透明度点击反馈效果(点击时透明度降低,松开后恢复)。

需要重点说明的是,TouchableOpacity的点击事件(onPress)在鸿蒙系统中响应流畅,无延迟,且点击反馈与其他平台保持一致,无需额外处理兼容性问题。对于阅读追踪APP来说,这类基础交互的一致性是提升用户体验的关键,而这段代码完全复用RN原生逻辑,实现了跨端交互统一。

  • 滚动与尺寸组件(ScrollView、FlatList、Dimensions):ScrollView用于实现纵向滚动布局(如各页面的内容滚动),鸿蒙适配层会将其映射为鸿蒙原生的ScrollView组件,支持纵向滚动、隐藏滚动条(showsVerticalScrollIndicator={false}),且滚动流畅不卡顿,无滚动错位、卡顿等适配问题;FlatList用于高效渲染长列表(如进行中书籍、已完成书籍列表),其核心属性(data、renderItem、keyExtractor)在鸿蒙系统中均完全兼容,鸿蒙适配层会将FlatList映射为鸿蒙原生的ListContainer组件,避免传统map遍历渲染长列表导致的性能问题,确保鸿蒙设备上列表滚动流畅。

Dimensions.get(‘window’)用于获取设备屏幕可视宽度(不含状态栏、导航栏),其返回值格式在鸿蒙系统中与iOS、Android端完全一致({ width: number, height: number }),代码中const { width } = Dimensions.get(‘window’)获取屏幕宽度,用于后续动态样式适配(如快速操作按钮宽度、画廊图片宽度),确保在不同尺寸的鸿蒙设备上(小屏手机、大屏平板),UI布局均匀合理,避免出现“小屏溢出、大屏留白”的问题——这是工具类APP多设备适配的核心技巧之一。

  • 表单与弹窗组件(TextInput、Alert):TextInput用于实现表单输入(如添加书籍时的标题、作者、页数输入),鸿蒙适配层完全支持其核心属性(value、onChangeText、placeholder、keyboardType),无论是输入响应、键盘类型切换(数字键盘、默认键盘),还是占位提示显示,均与其他平台保持一致,无需额外适配;Alert用于弹出提示弹窗(如输入校验失败、操作成功提示),鸿蒙适配层会将其渲染为鸿蒙原生弹窗样式(而非RN模拟弹窗),贴合鸿蒙系统的原生视觉风格,提升用户亲切感,且API用法(Alert.alert(标题, 内容))与RN原生完全一致,无需修改任何代码即可正常使用。

1.2 Base64图标:

代码中定义的ICONS_BASE64对象,存储了APP所需的所有图标(首页、书籍、阅读、历史等),均采用Base64格式编码。这种方式在工具类APP跨端开发中优势极为明显,完美解决了传统图片适配的痛点,具体适配优势如下:

  1. 无平台格式差异:鸿蒙、iOS、Android均完美支持Base64图片渲染,无需单独为鸿蒙设备准备专属图标(如鸿蒙的.svg格式、iOS的.png格式),实现“一套图标,多端复用”,极大降低了图标资源的维护成本——对于工具类APP而言,图标数量不多但复用性强,这种方式能显著提升开发效率。

  2. 无需路径适配:避免了RN跨端开发中常见的“图片路径找不到”问题——传统图片适配中,iOS需要将图片放入images.xcassets文件夹,Android放入drawable文件夹,鸿蒙放入media文件夹,路径规则不同,容易出现路径错误导致图片加载失败。而Base64图标可直接通过uri引用(Image source={{ uri: ICONS_BASE64.home }}),简化开发流程,杜绝路径适配bug。

  3. 优化性能,适配工具类场景:工具类APP中的图标均为小型图标(尺寸较小),采用Base64格式,可减少APP的网络请求(无需远程加载图标)和本地资源占用,提升鸿蒙设备上的APP启动速度和渲染流畅度。对于工具类APP来说,启动速度和操作流畅度直接影响用户留存,这种适配方式恰好契合了工具类APP的性能需求。

注意:Base64格式仅适合小型图标(建议尺寸≤100x100px),大型图片(如Banner、封面图)仍建议使用远程图片(http/https链接)或平台通用格式图片(.png),避免Base64格式导致代码冗余、渲染性能下降。本案例中所有图标均为小型图标,采用Base64格式是最优选择。

1.3 TypeScript类型:

代码采用TypeScript编写,通过type定义了Book接口,规范了书籍数据的结构:包含id、title、author等10个属性,明确了每个属性的类型(如pages为数字、title为字符串、isCompleted为布尔值)。这一设计在跨端开发中至关重要,尤其是工具类APP,数据结构的一致性直接决定了列表渲染、表单提交、数据统计等核心功能的稳定性,其核心价值在于“统一跨端数据结构”,避免因平台差异导致的数据类型不兼容问题。

  • 鸿蒙系统中,RN适配层对TypeScript的类型校验有良好的支持,Book接口明确了每一个属性的类型,确保开发者在开发阶段即可规避“数据类型错误”(如将pages赋值为字符串),避免该类错误被带入鸿蒙设备的运行阶段,导致列表渲染异常、表单提交失败等bug,提升开发效率。

  • 后续的书籍数据源(books数组)、新增书籍状态(newBook)、数据更新方法(updateProgress)均严格遵循Book接口规范,确保跨端数据结构一致。无论是鸿蒙、iOS还是Android端,数据的传递与渲染逻辑完全复用,无需额外适配,实现了“一套数据逻辑,多端复用”,降低了跨端维护成本——这对于工具类APP而言,能有效减少多端数据同步的繁琐工作。

二、useState钩子:

阅读追踪APP的核心交互(底部导航切换、书籍添加、进度更新、数据统计),本质都是状态的更新与联动。代码未引入任何第三方状态管理库(如Redux、MobX),而是采用React原生的useState钩子实现全量状态管理,这种轻量型方案既简化了跨端开发的复杂度,又完全兼容鸿蒙系统的状态更新机制,非常适合工具类这类轻量级应用。

我们来看核心状态定义与实现代码,逐一拆解每一个状态的业务意义与鸿蒙适配细节:


const ReadingTrackerApp = () => {
  const [activeTab, setActiveTab] = useState<'home' | 'add' | 'stats' | 'profile'>('home');
  const [books, setBooks] = useState<Book[]>([
    {
      id: '1',
      title: '活着',
      author: '余华',
      pages: 191,
      currentPage: 120,
      progress: 63,
      startDate: '2023-05-15',
      targetDate: '2023-06-15',
      category: '小说',
      isCompleted: false
    },
    // 其余书籍数据省略...
  ]);
  
  const [newBook, setNewBook] = useState({
    title: '',
    author: '',
    pages: '',
    category: '小说'
  });

  // 计算总统计数据
  const totalBooks = books.length;
  const completedBooks = books.filter(book => book.isCompleted).length;
  const inProgressBooks = books.filter(book => !book.isCompleted).length;
  const totalPages = books.reduce((sum, book) => sum + book.pages, 0);
  const totalReadPages = books.reduce((sum, book) => sum + book.currentPage, 0);
  // 其余方法省略...
};

2.1 页面切换状态(activeTab):

activeTab状态用于管理底部导航的页面切换,类型限定为<‘home’ | ‘add’ | ‘stats’ | ‘profile’>,初始值为’home’(默认显示首页),通过setActiveTab方法更新状态,实现首页、添加书籍、统计分析、个人中心四大页面的切换。

适配细节:鸿蒙系统对RN的状态更新与条件渲染完全兼容,页面切换逻辑({activeTab === ‘home’ && renderHomeContent()})能够正常执行,状态更新后,UI会同步渲染对应页面,无卡顿、无延迟、无页面闪烁。同时,底部导航的选中状态(文字颜色、图标颜色切换)会同步更新,用户能够清晰感知当前所在页面——这种状态管理方式完全复用RN原生逻辑,无需任何鸿蒙专属适配,实现了跨端页面切换的一致性。

2.2 核心业务状态(books、newBook):

这两个状态是APP核心业务数据的载体,直接决定了书籍管理、表单提交等核心功能的跨端适配效果,也是工具类APP状态管理的核心:

  • books状态:用于存储所有书籍数据,类型为Book[]数组,初始值为4条模拟书籍数据,涵盖小说、文学、历史、自我提升等类别,包含已完成和进行中两种状态。books状态的更新(添加书籍、更新进度)均采用不可变数据更新方式(展开运算符…),确保鸿蒙系统能够准确捕捉到状态变化,驱动UI实时渲染——例如,添加书籍时setBooks([book, …books]),更新进度时setBooks(books.map(…)),这种方式既符合React的最佳实践,也完全兼容鸿蒙系统的状态更新机制。

适配细节:在鸿蒙系统中,books数组的map、filter、reduce等方法均能正常运行,无语法错误;书籍数据的传递(如列表渲染、详情页展示)不会出现数据丢失、类型转换异常等问题,确保跨端数据联动的一致性——这对于工具类APP而言,数据的准确性是核心,这种状态管理方式能有效规避跨端数据异常。

  • newBook状态:用于管理新增书籍的表单数据,类型为对象,包含title、author、pages、category四个属性,初始值为空(category默认值为’小说’)。newBook状态通过onChangeText方法实时更新表单输入内容,确保表单输入与状态同步,为后续表单提交做准备。

适配细节:newBook状态的更新与表单输入的联动在鸿蒙系统中响应流畅,无延迟;表单输入的校验逻辑(如判断标题、作者、页数是否为空)能够正常执行,弹窗提示(Alert.alert)能够正常显示,确保跨端表单交互的一致性。

2.3 衍生统计状态(totalBooks、completedBooks等):

代码中通过books状态衍生出5个统计状态,用于实现APP的数据统计功能(如首页概览、统计分析页面),这些衍生状态的计算逻辑均为纯JavaScript逻辑,无任何平台依赖,完全支持跨端复用:

  • totalBooks:总书籍数量,通过books.length计算;

  • completedBooks:已完成书籍数量,通过books.filter(book => book.isCompleted).length计算;

  • inProgressBooks:进行中书籍数量,通过books.filter(book => !book.isCompleted).length计算;

  • totalPages:总页数,通过books.reduce((sum, book) => sum + book.pages, 0)计算;

  • totalReadPages:已读页数,通过books.reduce((sum, book) => sum + book.currentPage, 0)计算。

适配细节:这些衍生状态的计算逻辑在鸿蒙系统中能够正常执行,计算结果准确无误;当books状态更新时,衍生状态会自动同步更新,驱动统计页面、首页概览的UI实时渲染,无数据延迟——这对于工具类APP的统计功能而言,数据的实时性至关重要,这种衍生状态的设计方式完美适配跨端需求。


状态管理是基础,核心方法的封装则是APP落地的关键。代码片段中封装了3个核心方法(addNewBook、updateProgress、renderXXXContent系列方法),所有方法的封装均遵循“通用化、无平台依赖”原则,确保在鸿蒙系统中可直接复用,无需任何手动适配。这些方法覆盖了APP的所有核心交互,是跨端代码复用的核心载体,我们逐一看代码片段与技术解读:


// 添加新书
const addNewBook = () => {
  if (!newBook.title || !newBook.author || !newBook.pages) {
    Alert.alert('错误', '请填写完整的书籍信息');
    return;
  }

  const book: Book = {
    id: Date.now().toString(),
    title: newBook.title,
    author: newBook.author,
    pages: parseInt(newBook.pages),
    currentPage: 0,
    progress: 0,
    startDate: new Date().toISOString().split('T')[0],
    targetDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], // 默认30天后
    category: newBook.category,
    isCompleted: false
  };

  setBooks([book, ...books]);
  setNewBook({ title: '', author: '', pages: '', category: '小说' });
  Alert.alert('成功', '书籍已添加到阅读列表');
};

// 更新阅读进度
const updateProgress = (bookId: string, newPage: number) => {
  setBooks(books.map(book => {
    if (book.id === bookId) {
      const progress = Math.min(100, Math.round((newPage / book.pages) * 100));
      const isCompleted = progress === 100;
      return {
        ...book,
        currentPage: newPage,
        progress,
        isCompleted
      };
    }
    return book;
  }));
};

// 渲染主页内容
const renderHomeContent = () => (/* 主页渲染逻辑省略... */);

3.1 表单提交方法(addNewBook):

addNewBook方法用于处理新增书籍的表单提交逻辑,涵盖表单校验、书籍数据构造、状态更新、弹窗提示四个核心步骤,是工具类APP表单交互的核心方法,其鸿蒙适配的核心是“表单校验与数据构造的跨端复用”:

  1. 表单校验逻辑:通过判断newBook.title、newBook.author、newBook.pages是否为空,实现表单必填项校验,若有一项为空,通过Alert.alert弹出错误提示。适配细节:表单校验逻辑为纯JavaScript逻辑,无平台依赖,在鸿蒙系统中能够正常执行,弹窗提示样式与交互均贴合鸿蒙原生风格,无需额外适配。

  2. 书籍数据构造:根据newBook状态的表单数据,构造符合Book接口规范的书籍对象,其中id采用Date.now().toString()生成唯一标识,startDate为当前日期,targetDate为默认30天后的日期,currentPage和progress初始值为0,isCompleted初始值为false。适配细节:日期处理逻辑(new Date().toISOString().split(‘T’)[0])在鸿蒙系统中能够正常运行,生成的日期格式与其他平台一致;数据类型转换(如pages从字符串转为数字parseInt(newBook.pages))无异常,确保构造的书籍数据符合Book接口规范,避免数据类型错误。

  3. 状态更新逻辑:通过setBooks([book, …books])将新书籍添加到books数组头部,同时通过setNewBook重置表单状态,实现表单提交后清空输入框。适配细节:状态更新逻辑完全复用RN原生语法,鸿蒙系统能够准确捕捉到状态变化,驱动列表UI实时渲染(新增书籍会立即显示在列表顶部),无延迟、无数据异常;表单重置逻辑在鸿蒙系统中响应流畅,输入框会同步清空,提升用户体验。

  4. 成功提示逻辑:表单提交成功后,通过Alert.alert弹出成功提示,告知用户书籍已添加。适配细节:弹窗提示在鸿蒙系统中正常显示,点击确认按钮后弹窗关闭,交互逻辑与其他平台保持一致,无需额外适配。

3.2 数据更新方法(updateProgress)

updateProgress方法用于更新书籍的阅读进度,接收bookId(书籍唯一标识)和newPage(新页码)两个参数,涵盖进度计算、状态更新两个核心步骤,是阅读追踪APP的核心业务方法,其鸿蒙适配的核心是“不可变数据更新与状态联动的跨端复用”:

  1. 进度计算逻辑:通过Math.round((newPage / book.pages) * 100)计算阅读进度,同时通过Math.min(100, …)确保进度不超过100%;若进度为100%,则将isCompleted状态设为true(标记书籍为已完成)。适配细节:进度计算逻辑为纯JavaScript逻辑,无平台依赖,在鸿蒙系统中计算准确无误;数据类型判断(如newPage与book.pages的除法运算)无异常,避免出现NaN等错误。

  2. 状态更新逻辑:通过books.map遍历书籍数组,找到对应bookId的书籍,采用不可变数据更新方式(展开运算符…)更新currentPage、progress、isCompleted三个属性,其余书籍保持不变。适配细节:map方法在鸿蒙系统中能够正常运行,状态更新后,列表UI会同步渲染更新后的进度和书籍状态(如进度条宽度变化、已完成书籍样式切换),无延迟、无渲染异常;这种不可变数据更新方式,确保鸿蒙系统能够准确捕捉到状态变化,避免出现UI与状态不一致的问题——这是跨端数据联动适配的关键技巧。

3.3 页面渲染方法:

代码中封装了4个页面渲染方法(renderHomeContent、renderAddBookContent、renderStatsContent、renderProfileContent),分别对应首页、添加书籍、统计分析、个人中心四大页面的渲染逻辑,采用“拆分渲染”的设计思路,将复杂的页面渲染逻辑拆分为独立方法,提升代码可读性和复用性,同时确保跨端渲染的一致性——这是工具类APP跨端开发的常用设计模式。

适配细节:这4个渲染方法均返回RN组件,无任何平台专属代码,完全基于RN官方组件构建,鸿蒙适配层能够正常解析所有组件和样式,实现跨端页面渲染的一致性。每个渲染方法内部的组件布局、样式、交互逻辑,均遵循RN通用规范,无需额外修改即可在鸿蒙设备上正常渲染——例如,首页的概览卡片、快速操作按钮、书籍列表,添加书籍页面的表单组件,统计页面的统计卡片,均能在鸿蒙设备上完美适配,与其他平台保持一致的视觉效果和交互体验。

3.4 方法封装的跨端优势(工具类APP适配核心参考)

代码中所有方法的封装都遵循“通用化、无平台依赖”原则,没有引入任何iOS/Android平台专属API,也没有编写任何鸿蒙专属适配代码,其跨端优势极为明显,非常适合工具类这类轻量级应用的跨端开发:

  1. 代码复用率100%:所有方法可直接在鸿蒙、iOS、Android端复用,无需修改任何逻辑,极大降低了跨端开发成本,缩短开发周期——对于工具类APP来说,快速迭代、多端覆盖是核心需求,这种封装方式恰好契合这一需求。

  2. 适配成本低:借助鸿蒙RN适配层的自动映射能力,方法中的RN API(setState、Alert、数组操作等)均能正常运行,无需手动编写鸿蒙原生方法,开发者无需掌握鸿蒙原生开发技术,仅需熟悉RN开发,即可完成鸿蒙跨端适配。

  3. 维护成本低:方法逻辑与平台解耦,后续修改业务逻辑(如添加书籍的校验规则、进度更新的计算方式),只需修改一处代码,即可同步应用到所有平台,无需单独维护各平台的方法逻辑,降低了后续维护成本。


ReadingTrackerApp 组件采用了现代 React 函数组件架构,结合 useState Hook 实现了复杂的状态管理。应用通过多个状态变量控制不同的 UI 状态:activeTab 管理当前激活的标签页,books 存储书籍列表,newBook 管理添加新书的表单数据。

在类型定义上,使用了 TypeScript 的 Book 接口明确数据结构,包含书籍的各项属性如标题、作者、页数、当前页数、进度等。这种类型定义在跨端开发中尤为重要,确保了在不同平台上的数据结构一致性,减少了类型错误的可能性。

数据处理

应用实现了多个数据处理和计算函数,包括统计数据计算、添加新书和更新阅读进度等。统计数据计算通过数组方法如 filterreduce 实现,计算总书籍数、已完成书籍数、进行中书籍数、总页数和已读页数。

添加新书功能通过表单验证确保必填字段的完整性,然后创建新的书籍对象并添加到书籍列表。更新阅读进度功能通过 map 方法更新指定书籍的进度,并自动将进度达到 100% 的书籍标记为已完成。

视觉设计

应用采用了标签页导航结构,通过 activeTab 状态控制不同内容的显示。主页内容包括阅读概览卡片、快速操作按钮和进行中的书籍列表。布局设计上,使用了 ScrollViewFlatList 确保在内容较长时可以滚动查看。

视觉设计简洁明了,通过不同的样式区分不同的功能区域和状态。快速操作按钮使用了 emoji 图标,增强了视觉效果和用户体验。进度条的设计直观显示了书籍的阅读进度,通过动态宽度实现了进度的可视化。

交互

应用实现了丰富的交互功能,包括标签页切换、添加新书、更新阅读进度和快速操作等。这些交互功能的实现遵循了 React 的最佳实践,通过状态更新驱动 UI 变化,确保了交互的一致性和可靠性。

特别是书籍进度的更新,自动将进度达到 100% 的书籍标记为已完成,提升了用户体验。表单验证和操作反馈通过 Alert.alert 提供明确的操作指导,增强了用户的操作信心。

跨端

在 React Native 与鸿蒙系统跨端开发中,该应用展现了多项兼容性设计:

  1. 基础组件选择:使用了 ScrollViewTouchableOpacityFlatList 等基础组件,这些组件在 React Native 和鸿蒙系统中都有对应的实现。

  2. 样式管理:通过 StyleSheet.create 管理样式,确保了在不同平台上的一致表现。

  3. 状态管理:使用 useState Hook 进行状态管理,在鸿蒙系统中可以通过相应的状态管理机制(如 @State 装饰器)实现类似功能。

  4. 类型定义:使用 TypeScript 类型定义,确保了在不同平台上的数据结构一致性。

  5. 布局系统:使用了 Flexbox 布局系统,这是 React Native 和鸿蒙系统都支持的布局方式,确保了在不同平台上的一致布局效果。

  6. API 兼容性:使用了 Alert.alert 等跨平台 API,确保了在不同平台上的一致表现。

该阅读追踪应用展示了一个功能完整、设计优雅的 React Native 应用实现,涵盖了状态管理、数据处理、布局设计、交互处理等多个方面的技术点。通过合理的组件架构和状态管理,以及对跨端兼容性的考虑,该应用不仅在 React Native 环境下运行良好,也为后续的鸿蒙系统适配奠定了基础。



真实演示案例代码:





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

// Base64 图标库
const ICONS_BASE64 = {
  home: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  book: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  reading: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  history: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  stats: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  profile: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  plus: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  check: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};

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

// 书籍类型
type Book = {
  id: string;
  title: string;
  author: string;
  pages: number;
  currentPage: number;
  progress: number;
  startDate: string;
  targetDate: string;
  category: string;
  isCompleted: boolean;
};

const ReadingTrackerApp = () => {
  const [activeTab, setActiveTab] = useState<'home' | 'add' | 'stats' | 'profile'>('home');
  const [books, setBooks] = useState<Book[]>([
    {
      id: '1',
      title: '活着',
      author: '余华',
      pages: 191,
      currentPage: 120,
      progress: 63,
      startDate: '2023-05-15',
      targetDate: '2023-06-15',
      category: '小说',
      isCompleted: false
    },
    {
      id: '2',
      title: '百年孤独',
      author: '加西亚·马尔克斯',
      pages: 368,
      currentPage: 200,
      progress: 54,
      startDate: '2023-05-10',
      targetDate: '2023-07-10',
      category: '文学',
      isCompleted: false
    },
    {
      id: '3',
      title: '人类简史',
      author: '尤瓦尔·赫拉利',
      pages: 443,
      currentPage: 300,
      progress: 68,
      startDate: '2023-05-01',
      targetDate: '2023-08-01',
      category: '历史',
      isCompleted: false
    },
    {
      id: '4',
      title: '高效能人士的七个习惯',
      author: '史蒂芬·柯维',
      pages: 381,
      currentPage: 381,
      progress: 100,
      startDate: '2023-04-01',
      targetDate: '2023-04-30',
      category: '自我提升',
      isCompleted: true
    },
  ]);
  
  const [newBook, setNewBook] = useState({
    title: '',
    author: '',
    pages: '',
    category: '小说'
  });

  // 计算总统计数据
  const totalBooks = books.length;
  const completedBooks = books.filter(book => book.isCompleted).length;
  const inProgressBooks = books.filter(book => !book.isCompleted).length;
  const totalPages = books.reduce((sum, book) => sum + book.pages, 0);
  const totalReadPages = books.reduce((sum, book) => sum + book.currentPage, 0);

  // 添加新书
  const addNewBook = () => {
    if (!newBook.title || !newBook.author || !newBook.pages) {
      Alert.alert('错误', '请填写完整的书籍信息');
      return;
    }

    const book: Book = {
      id: Date.now().toString(),
      title: newBook.title,
      author: newBook.author,
      pages: parseInt(newBook.pages),
      currentPage: 0,
      progress: 0,
      startDate: new Date().toISOString().split('T')[0],
      targetDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], // 默认30天后
      category: newBook.category,
      isCompleted: false
    };

    setBooks([book, ...books]);
    setNewBook({ title: '', author: '', pages: '', category: '小说' });
    Alert.alert('成功', '书籍已添加到阅读列表');
  };

  // 更新阅读进度
  const updateProgress = (bookId: string, newPage: number) => {
    setBooks(books.map(book => {
      if (book.id === bookId) {
        const progress = Math.min(100, Math.round((newPage / book.pages) * 100));
        const isCompleted = progress === 100;
        return {
          ...book,
          currentPage: newPage,
          progress,
          isCompleted
        };
      }
      return book;
    }));
  };

  // 渲染主页内容
  const renderHomeContent = () => (
    <ScrollView style={styles.content}>
      {/* 今日概览卡片 */}
      <View style={styles.overviewCard}>
        <Text style={styles.overviewTitle}>阅读概览</Text>
        <View style={styles.statsRow}>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>{totalBooks}</Text>
            <Text style={styles.statLabel}>本书籍</Text>
          </View>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>{completedBooks}</Text>
            <Text style={styles.statLabel}>本完成</Text>
          </View>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>{inProgressBooks}</Text>
            <Text style={styles.statLabel}>进行中</Text>
          </View>
        </View>
      </View>

      {/* 快速操作按钮 */}
      <View style={styles.quickActions}>
        <TouchableOpacity style={styles.quickAction} onPress={() => setActiveTab('add')}>
          <Text style={styles.quickActionIcon}>📚</Text>
          <Text style={styles.quickActionText}>添加书籍</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.quickAction} onPress={() => Alert.alert('阅读目标', '设置每日阅读目标')}>
          <Text style={styles.quickActionIcon}>🎯</Text>
          <Text style={styles.quickActionText}>阅读目标</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.quickAction} onPress={() => Alert.alert('读书笔记', '查看我的读书笔记')}>
          <Text style={styles.quickActionIcon}>📝</Text>
          <Text style={styles.quickActionText}>读书笔记</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.quickAction} onPress={() => Alert.alert('推荐书籍', '查看推荐书籍')}>
          <Text style={styles.quickActionIcon}>💡</Text>
          <Text style={styles.quickActionText}>推荐书籍</Text>
        </TouchableOpacity>
      </View>

      {/* 进行中的书籍 */}
      <Text style={styles.sectionTitle}>进行中的书籍</Text>
      <FlatList
        data={books.filter(book => !book.isCompleted)}
        renderItem={({ item }) => (
          <View style={styles.bookItem}>
            <View style={styles.bookIcon}>
              <Text style={styles.bookIconText}>📖</Text>
            </View>
            <View style={styles.bookInfo}>
              <Text style={styles.bookTitle}>{item.title}</Text>
              <Text style={styles.bookAuthor}>作者: {item.author}</Text>
              <View style={styles.progressContainer}>
                <View style={styles.progressBar}>
                  <View style={[styles.progressFill, { width: `${item.progress}%` }]} />
                </View>
                <Text style={styles.progressText}>{item.progress}%</Text>
              </View>
              <Text style={styles.bookDetails}>
                {item.currentPage}/{item.pages}页 • {item.category}
              </Text>
            </View>
            <TouchableOpacity 
              style={styles.updateButton}
              onPress={() => {
                const newPage = item.currentPage + 10;
                updateProgress(item.id, newPage > item.pages ? item.pages : newPage);
              }}
            >
              <Text style={styles.updateButtonText}>+10</Text>
            </TouchableOpacity>
          </View>
        )}
        keyExtractor={item => item.id}
        showsVerticalScrollIndicator={false}
      />

      {/* 已完成的书籍 */}
      <Text style={styles.sectionTitle}>已完成的书籍</Text>
      <FlatList
        data={books.filter(book => book.isCompleted)}
        renderItem={({ item }) => (
          <View style={styles.completedBookItem}>
            <View style={styles.bookIcon}>
              <Text style={styles.bookIconText}></Text>
            </View>
            <View style={styles.bookInfo}>
              <Text style={styles.bookTitle}>{item.title}</Text>
              <Text style={styles.bookAuthor}>作者: {item.author}</Text>
              <Text style={styles.completedText}>已完成 • {item.startDate} 开始 • {item.targetDate} 完成</Text>
            </View>
          </View>
        )}
        keyExtractor={item => item.id}
        showsVerticalScrollIndicator={false}
      />
    </ScrollView>
  );

  // 渲染添加书籍页面
  const renderAddBookContent = () => (
    <ScrollView style={styles.content}>
      <Text style={styles.sectionTitle}>添加新书籍</Text>
      
      <View style={styles.formCard}>
        {/* 书籍标题 */}
        <View style={styles.inputGroup}>
          <Text style={styles.inputLabel}>书籍标题</Text>
          <TextInput
            style={styles.inputField}
            value={newBook.title}
            onChangeText={(text) => setNewBook({...newBook, title: text})}
            placeholder="请输入书籍标题"
            keyboardType="default"
          />
        </View>
        
        {/* 作者 */}
        <View style={styles.inputGroup}>
          <Text style={styles.inputLabel}>作者</Text>
          <TextInput
            style={styles.inputField}
            value={newBook.author}
            onChangeText={(text) => setNewBook({...newBook, author: text})}
            placeholder="请输入作者姓名"
            keyboardType="default"
          />
        </View>
        
        {/* 页数 */}
        <View style={styles.inputGroup}>
          <Text style={styles.inputLabel}>总页数</Text>
          <TextInput
            style={styles.inputField}
            value={newBook.pages}
            onChangeText={(text) => setNewBook({...newBook, pages: text})}
            placeholder="请输入总页数"
            keyboardType="numeric"
          />
        </View>
        
        {/* 分类 */}
        <View style={styles.inputGroup}>
          <Text style={styles.inputLabel}>分类</Text>
          <View style={styles.pickerContainer}>
            {['小说', '文学', '历史', '自我提升', '科幻', '其他'].map(category => (
              <TouchableOpacity
                key={category}
                style={[
                  styles.pickerOption,
                  newBook.category === category && styles.pickerOptionSelected
                ]}
                onPress={() => setNewBook({...newBook, category})}
              >
                <Text style={styles.pickerOptionText}>{category}</Text>
              </TouchableOpacity>
            ))}
          </View>
        </View>
        
        {/* 提交按钮 */}
        <TouchableOpacity style={styles.submitButton} onPress={addNewBook}>
          <Text style={styles.submitButtonText}>添加书籍</Text>
        </TouchableOpacity>
      </View>
      
      {/* 阅读建议卡片 */}
      <View style={styles.suggestionCard}>
        <Text style={styles.suggestionTitle}>阅读建议</Text>
        <Text style={styles.suggestionText}>• 每天坚持阅读30分钟</Text>
        <Text style={styles.suggestionText}>• 做好读书笔记加深理解</Text>
        <Text style={styles.suggestionText}>• 设定合理的阅读目标</Text>
        <Text style={styles.suggestionText}>• 选择适合自己的阅读环境</Text>
        <Text style={styles.suggestionText}>• 定期回顾已完成的书籍</Text>
      </View>
    </ScrollView>
  );

  // 渲染统计分析页面
  const renderStatsContent = () => (
    <ScrollView style={styles.content}>
      {/* 统计概览卡片 */}
      <View style={styles.statsOverviewCard}>
        <Text style={styles.overviewTitle}>阅读统计</Text>
        <View style={styles.statsRow}>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>{totalBooks}</Text>
            <Text style={styles.statLabel}>总书籍</Text>
          </View>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>{totalPages}</Text>
            <Text style={styles.statLabel}>总页数</Text>
          </View>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>{totalReadPages}</Text>
            <Text style={styles.statLabel}>已读页数</Text>
          </View>
        </View>
      </View>

      {/* 完成率卡片 */}
      <View style={styles.rateCard}>
        <Text style={styles.rateTitle}>完成率</Text>
        <View style={styles.rateRow}>
          <Text style={styles.rateLabel}>已完成</Text>
          <Text style={styles.rateValue}>{completedBooks}</Text>
        </View>
        <View style={styles.rateRow}>
          <Text style={styles.rateLabel}>进行中</Text>
          <Text style={styles.rateValue}>{inProgressBooks}</Text>
        </View>
        <View style={styles.rateRow}>
          <Text style={styles.rateLabel}>完成比例</Text>
          <Text style={styles.rateValue}>{totalBooks > 0 ? Math.round((completedBooks / totalBooks) * 100) : 0}%</Text>
        </View>
      </View>

      {/* 类别分布 */}
      <Text style={styles.sectionTitle}>类别分布</Text>
      <View style={styles.categoryStats}>
        {['小说', '文学', '历史', '自我提升', '科幻', '其他'].map(cat => {
          const count = books.filter(b => b.category === cat).length;
          if (count === 0) return null;
          
          return (
            <View key={cat} style={styles.categoryItem}>
              <Text style={styles.categoryName}>{cat}</Text>
              <Text style={styles.categoryCount}>{count}</Text>
            </View>
          );
        })}
      </View>

      {/* 阅读进度卡片 */}
      <Text style={styles.sectionTitle}>阅读进度</Text>
      <View style={styles.progressCard}>
        <Text style={styles.progressTitle}>总进度</Text>
        <View style={styles.progressBarLarge}>
          <View 
            style={[styles.progressFillLarge, { 
              width: `${totalPages > 0 ? (totalReadPages / totalPages) * 100 : 0}%` 
            }]} 
          />
        </View>
        <Text style={styles.progressTextLarge}>
          {totalPages > 0 ? Math.round((totalReadPages / totalPages) * 100) : 0}% ({totalReadPages}/{totalPages})
        </Text>
      </View>

      {/* 阅读时间统计 */}
      <Text style={styles.sectionTitle}>阅读时间统计</Text>
      <View style={styles.timeStatsCard}>
        <View style={styles.timeStatItem}>
          <Text style={styles.timeStatLabel}>平均每日阅读页数</Text>
          <Text style={styles.timeStatValue}>15</Text>
        </View>
        <View style={styles.timeStatItem}>
          <Text style={styles.timeStatLabel}>平均完成一本书所需时间</Text>
          <Text style={styles.timeStatValue}>12</Text>
        </View>
        <View style={styles.timeStatItem}>
          <Text style={styles.timeStatLabel}>最常阅读时间段</Text>
          <Text style={styles.timeStatValue}>晚上8-10</Text>
        </View>
      </View>
    </ScrollView>
  );

  // 渲染个人资料页面
  const renderProfileContent = () => (
    <ScrollView style={styles.content}>
      <Text style={styles.sectionTitle}>个人阅读档案</Text>
      
      <View style={styles.profileCard}>
        <View style={styles.profileHeader}>
          <Text style={styles.profileName}>张三</Text>
          <Text style={styles.profileLevel}>阅读达人</Text>
        </View>
        
        <View style={styles.profileStats}>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>{totalBooks}</Text>
            <Text style={styles.statLabel}>总书籍</Text>
          </View>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>{completedBooks}</Text>
            <Text style={styles.statLabel}>已完成</Text>
          </View>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>3</Text>
            <Text style={styles.statLabel}>年经验</Text>
          </View>
        </View>
      </View>

      {/* 最喜欢的类别 */}
      <Text style={styles.sectionTitle}>最喜欢阅读的类别</Text>
      <View style={styles.favoriteCategoryCard}>
        <Text style={styles.favoriteCategoryText}>小说</Text>
      </View>

      {/* 设置选项 */}
      <Text style={styles.sectionTitle}>设置选项</Text>
      <View style={styles.settingCard}>
        <TouchableOpacity style={styles.listItem} onPress={() => Alert.alert('提醒设置', '设置阅读提醒时间')}>
          <Text style={styles.listItemText}>阅读提醒设置</Text>
          <Text style={styles.listItemIcon}></Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.listItem} onPress={() => Alert.alert('目标管理', '设置年度阅读目标')}>
          <Text style={styles.listItemText}>年度目标管理</Text>
          <Text style={styles.listItemIcon}></Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.listItem} onPress={() => Alert.alert('数据备份', '备份阅读记录')}>
          <Text style={styles.listItemText}>数据备份</Text>
          <Text style={styles.listItemIcon}></Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.listItem} onPress={() => Alert.alert('隐私设置', '管理数据权限')}>
          <Text style={styles.listItemText}>隐私设置</Text>
          <Text style={styles.listItemIcon}></Text>
        </TouchableOpacity>
      </View>
    </ScrollView>
  );

  return (
    <SafeAreaView style={styles.container}>
      {/* 头部 */}
      <View style={styles.header}>
        <Text style={styles.title}>看书管理记录</Text>
        <TouchableOpacity onPress={() => Alert.alert('搜索', '搜索功能')}>
          <Text style={styles.searchIcon}>🔍</Text>
        </TouchableOpacity>
      </View>

      {/* 内容区域 */}
      {activeTab === 'home' && renderHomeContent()}
      {activeTab === 'add' && renderAddBookContent()}
      {activeTab === 'stats' && renderStatsContent()}
      {activeTab === 'profile' && renderProfileContent()}

      {/* 底部导航 */}
      <View style={styles.bottomNav}>
        <TouchableOpacity 
          style={styles.navItem} 
          onPress={() => setActiveTab('home')}
        >
          <Text style={activeTab === 'home' ? styles.navIconActive : styles.navIcon}>🏠</Text>
          <Text style={activeTab === 'home' ? styles.navTextActive : styles.navText}>首页</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.navItem} 
          onPress={() => setActiveTab('add')}
        >
          <Text style={activeTab === 'add' ? styles.navIconActive : styles.navIcon}>📚</Text>
          <Text style={activeTab === 'add' ? styles.navTextActive : styles.navText}>添加</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.navItem} 
          onPress={() => setActiveTab('stats')}
        >
          <Text style={activeTab === 'stats' ? styles.navIconActive : styles.navIcon}>📊</Text>
          <Text style={activeTab === 'stats' ? styles.navTextActive : styles.navText}>统计</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.navItem} 
          onPress={() => setActiveTab('profile')}
        >
          <Text style={activeTab === 'profile' ? styles.navIconActive : styles.navIcon}>👤</Text>
          <Text style={activeTab === 'profile' ? styles.navTextActive : 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: 18,
    fontWeight: 'bold',
    color: '#1e293b',
  },
  searchIcon: {
    fontSize: 20,
    color: '#64748b',
  },
  content: {
    flex: 1,
    padding: 16,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#1e293b',
    marginVertical: 12,
  },
  overviewCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 20,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  overviewTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 12,
  },
  statsRow: {
    flexDirection: 'row',
    justifyContent: 'space-around',
  },
  statItem: {
    alignItems: 'center',
  },
  statNumber: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#3b82f6',
    marginBottom: 4,
  },
  statLabel: {
    fontSize: 12,
    color: '#64748b',
  },
  quickActions: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    marginBottom: 20,
  },
  quickAction: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    alignItems: 'center',
    width: width / 4.5,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  quickActionIcon: {
    fontSize: 24,
    marginBottom: 8,
  },
  quickActionText: {
    fontSize: 12,
    color: '#1e293b',
    textAlign: 'center',
  },
  bookItem: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 12,
    flexDirection: 'row',
    alignItems: 'center',
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  completedBookItem: {
    backgroundColor: '#f0fdf4',
    borderRadius: 12,
    padding: 16,
    marginBottom: 12,
    flexDirection: 'row',
    alignItems: 'center',
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  bookIcon: {
    width: 40,
    height: 40,
    borderRadius: 20,
    backgroundColor: '#dbeafe',
    alignItems: 'center',
    justifyContent: 'center',
    marginRight: 12,
  },
  bookIconText: {
    fontSize: 20,
  },
  bookInfo: {
    flex: 1,
  },
  bookTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
  },
  bookAuthor: {
    fontSize: 14,
    color: '#64748b',
    marginBottom: 4,
  },
  progressContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 4,
  },
  progressBar: {
    flex: 1,
    height: 8,
    backgroundColor: '#e2e8f0',
    borderRadius: 4,
    marginRight: 8,
  },
  progressFill: {
    height: '100%',
    backgroundColor: '#3b82f6',
    borderRadius: 4,
  },
  progressText: {
    fontSize: 12,
    color: '#64748b',
    minWidth: 30,
  },
  bookDetails: {
    fontSize: 12,
    color: '#94a3b8',
  },
  completedText: {
    fontSize: 12,
    color: '#10b981',
    fontWeight: 'bold',
  },
  updateButton: {
    backgroundColor: '#3b82f6',
    padding: 8,
    borderRadius: 6,
  },
  updateButtonText: {
    color: '#ffffff',
    fontSize: 12,
    fontWeight: 'bold',
  },
  formCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 20,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  inputGroup: {
    marginBottom: 16,
  },
  inputLabel: {
    fontSize: 14,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 8,
  },
  pickerContainer: {
    flexDirection: 'row',
    flexWrap: 'wrap',
  },
  pickerOption: {
    backgroundColor: '#f1f5f9',
    padding: 8,
    borderRadius: 8,
    marginRight: 8,
    marginBottom: 8,
  },
  pickerOptionSelected: {
    backgroundColor: '#3b82f6',
  },
  pickerOptionText: {
    fontSize: 14,
    color: '#1e293b',
  },
  inputField: {
    backgroundColor: '#f8fafc',
    borderWidth: 1,
    borderColor: '#cbd5e1',
    borderRadius: 8,
    padding: 12,
    fontSize: 16,
    color: '#1e293b',
  },
  submitButton: {
    backgroundColor: '#3b82f6',
    padding: 16,
    borderRadius: 8,
    alignItems: 'center',
    marginTop: 8,
  },
  submitButtonText: {
    color: '#ffffff',
    fontWeight: 'bold',
    fontSize: 16,
  },
  suggestionCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 20,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  suggestionTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 8,
  },
  suggestionText: {
    fontSize: 14,
    color: '#64748b',
    marginBottom: 4,
  },
  statsOverviewCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 20,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  rateCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 20,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  rateTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 12,
  },
  rateRow: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    marginBottom: 8,
  },
  rateLabel: {
    fontSize: 14,
    color: '#64748b',
  },
  rateValue: {
    fontSize: 14,
    fontWeight: 'bold',
    color: '#1e293b',
  },
  categoryStats: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 20,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  categoryItem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    paddingVertical: 8,
    borderBottomWidth: 1,
    borderBottomColor: '#e2e8f0',
  },
  categoryName: {
    fontSize: 14,
    color: '#1e293b',
  },
  categoryCount: {
    fontSize: 14,
    color: '#3b82f6',
    fontWeight: 'bold',
  },
  progressCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 20,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  progressTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 12,
  },
  progressBarLarge: {
    height: 12,
    backgroundColor: '#e2e8f0',
    borderRadius: 6,
    marginBottom: 8,
  },
  progressFillLarge: {
    height: '100%',
    backgroundColor: '#10b981',
    borderRadius: 6,
  },
  progressTextLarge: {
    fontSize: 14,
    color: '#64748b',
    textAlign: 'center',
  },
  timeStatsCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 20,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  timeStatItem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    paddingVertical: 8,
    borderBottomWidth: 1,
    borderBottomColor: '#e2e8f0',
  },
  timeStatLabel: {
    fontSize: 14,
    color: '#1e293b',
  },
  timeStatValue: {
    fontSize: 14,
    color: '#3b82f6',
    fontWeight: 'bold',
  },
  profileCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 20,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  profileHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 16,
  },
  profileName: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#1e293b',
  },
  profileLevel: {
    fontSize: 14,
    color: '#3b82f6',
    fontWeight: 'bold',
  },
  profileStats: {
    flexDirection: 'row',
    justifyContent: 'space-around',
  },
  favoriteCategoryCard: {
    backgroundColor: '#dbeafe',
    borderRadius: 12,
    padding: 16,
    marginBottom: 20,
    alignItems: 'center',
  },
  favoriteCategoryText: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
  },
  settingCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 0,
    marginBottom: 16,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  listItem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    paddingVertical: 16,
    paddingHorizontal: 16,
    borderBottomWidth: 1,
    borderBottomColor: '#e2e8f0',
  },
  listItemText: {
    fontSize: 16,
    color: '#1e293b',
  },
  listItemIcon: {
    fontSize: 18,
    color: '#94a3b8',
  },
  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,
  },
  navIconActive: {
    fontSize: 20,
    color: '#3b82f6',
    marginBottom: 4,
  },
  navText: {
    fontSize: 12,
    color: '#94a3b8',
  },
  navTextActive: {
    fontSize: 12,
    color: '#3b82f6',
    fontWeight: '500',
  },
});

export default ReadingTrackerApp;

请添加图片描述


打包

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

在这里插入图片描述

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

在这里插入图片描述

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

请添加图片描述
本文深入解析了React Native(RN)鸿蒙跨端开发中工具类APP的实现要点,以一款阅读追踪应用为例,重点剖析了多端适配的核心技术。文章从依赖引入、基础组件适配、Base64图标优化、TypeScript类型规范等方面,详细阐述了如何实现"一套代码多端运行"的目标。通过RN官方通用组件与鸿蒙适配层的无缝对接,确保了UI一致性、交互流畅性和数据联动性。特别强调了轻量级状态管理、安全区域适配、性能优化等关键技巧,为同类工具类应用的跨端开发提供了可复用的实战方案,有效规避了平台差异带来的适配问题。

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

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐