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

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

状态管理的设计考虑了应用的复杂性,通过集中管理状态变量,使得组件逻辑清晰,易于维护和扩展。特别是书籍数据的管理,通过 setBooks 函数实现了数据的增删改查,确保了数据的一致性。

图标系统

应用使用了 Base64 编码的图标资源,通过 ICONS_BASE64 对象集中管理。这种资源管理方式在跨端开发中具有明显优势:

  1. 减少网络请求:Base64 编码的图标直接嵌入代码中,无需额外的网络请求,提高了应用加载速度。
  2. 跨平台兼容:避免了不同平台对资源文件格式和路径的差异,确保了图标在所有平台上的一致显示。
  3. 代码简洁:通过对象集中管理图标,使得代码更加模块化,易于维护。
  4. 性能优化:减少了应用的打包体积,特别是在图标数量较多的情况下。

布局

应用采用了标签页导航结构,通过 activeTab 状态控制不同内容的显示。主要包含以下几个部分:

  1. 标签页导航:底部或顶部的标签页按钮,用于切换不同的功能模块。
  2. 主页:显示书籍列表,包括正在阅读和已完成的书籍。
  3. 添加书籍:表单页面,用于添加新的书籍。
  4. 统计页面:显示阅读统计数据,如总书籍数、已完成书籍数等。
  5. 个人页面:用户个人信息和设置。
  6. 书籍详情:显示选中书籍的详细信息和阅读进度。

布局设计上,应用使用了 SafeAreaView 确保在不同设备上的显示兼容性,使用 ScrollViewFlatList 确保在内容较长时可以滚动查看。视觉设计简洁明了,通过不同的样式区分不同的功能区域和状态。

用户体验

应用实现了丰富的交互功能,包括:

  1. 标签页切换:点击标签页按钮切换 activeTab 状态,实现功能模块的切换。
  2. 书籍详情查看:点击书籍项打开书籍详情页,显示选中书籍的详细信息。
  3. 添加新书:填写表单并提交,添加新的书籍到阅读列表。
  4. 更新阅读进度:通过 updateProgress 函数更新书籍的阅读进度。
  5. 统计数据计算:实时计算总书籍数、已完成书籍数、总页数等统计数据。

这些交互功能的实现遵循了 React 的最佳实践,通过状态更新驱动 UI 变化,确保了交互的一致性和可靠性。特别是书籍进度的更新,通过 Math.min(100, Math.round((newPage / book.pages) * 100)) 确保了进度值不会超过 100%,并自动将进度达到 100% 的书籍标记为已完成。

数据处理

应用实现了多个数据处理和计算函数,包括:

  1. 统计数据计算:计算总书籍数、已完成书籍数、进行中书籍数、总页数和已读页数。
  2. 添加新书:验证表单数据,创建新的书籍对象,并添加到书籍列表。
  3. 更新阅读进度:根据新的页码更新书籍的进度和状态。

这些数据处理函数确保了应用的数据一致性和准确性,特别是在添加新书时的表单验证,通过 if (!newBook.title || !newBook.author || !newBook.pages) 确保了必填字段的完整性。


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

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

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

  3. 资源管理:使用 Base64 编码的图标资源,避免了不同平台对资源文件格式和路径的差异。

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

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

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

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

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


阅读追踪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,
      description: '一部讲述人生苦难与坚韧的小说...',
      rating: 4.8
    },
    // 其余书籍数据省略...
  ]);
  
  // 选中书籍状态(用于详情页展示)
  const [selectedBook, setSelectedBook] = useState<Book | null>(null);
  // 详情页显示/隐藏状态
  const [showBookDetail, setShowBookDetail] = useState(false);
  
  // 新增书籍表单输入状态
  const [newBook, setNewBook] = useState({
    title: '',
    author: '',
    pages: '',
    category: '小说',
    description: ''
  });

  // 计算总统计数据(依赖books状态,自动联动更新)
  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);

  // 其余方法省略...
};

这段代码是APP的核心状态中枢,涵盖5个关键状态,每个状态的设计都兼顾了跨端兼容性与业务需求,我们逐一对接代码片段,拆解其实现逻辑与鸿蒙适配细节:

  1. 页面切换状态(activeTab):用于管理底部导航对应的四大核心页面,其类型定义采用TypeScript联合类型(‘home’ | ‘add’ | ‘stats’ | ‘profile’),确保状态值的类型安全,避免因类型错误导致的鸿蒙设备上页面切换异常。在鸿蒙系统中,setActiveTab(状态更新函数)通过适配层映射为鸿蒙原生的状态更新机制,页面切换时的渲染性能与其他平台保持一致——比如点击底部“添加”导航项,setActiveTab(‘add’)触发状态更新,APP会自动渲染添加书籍页面,无卡顿、无延迟,同时导航项的高亮反馈(通过activeTab === 'add’判断样式),鸿蒙系统可正常解析,确保交互一致性。

  2. 书籍列表状态(books):这是APP的核心数据载体,初始值为4本示例书籍,类型为Book[](严格遵循前文定义的Book接口)。该状态的核心价值在于“跨端数据联动”,后续的添加新书、更新阅读进度、标记书籍完成等操作,本质都是通过setBooks更新该状态,进而驱动UI渲染。这里重点解读其鸿蒙适配细节:setBooks的更新逻辑采用不可变数据更新方式(通过展开运算符…实现,如后续addNewBook方法中的setBooks([book, …books])),这种方式符合React最佳实践,也完全兼容鸿蒙系统的状态更新机制——鸿蒙适配层对RN的不可变数据更新有良好的支持,能够准确捕捉状态变化,驱动UI同步更新,避免出现“数据更新但UI未变化”的鸿蒙适配bug。同时,books状态中的progress(阅读进度)、isCompleted(是否完成)等属性,会直接影响统计数据(totalBooks、completedBooks等),这种依赖状态的计算逻辑,在鸿蒙系统中会自动联动更新,无需额外编写适配代码。

  3. 详情页相关状态(selectedBook、showBookDetail):这两个状态联动实现书籍详情页的展示与隐藏。selectedBook用于存储当前点击的书籍数据(类型为Book | null),showBookDetail用于控制详情页的显示/隐藏(boolean类型)。在鸿蒙系统中,这两个状态的更新逻辑完全兼容——比如点击某本进行中的书籍,setSelectedBook(item)存储当前书籍数据,setShowBookDetail(true)显示详情页;点击详情页“返回”按钮,setShowBookDetail(false)、setSelectedBook(null)重置状态,关闭详情页,整个交互流程与iOS、Android端完全一致,无任何适配差异。

  4. 表单输入状态(newBook):用于管理添加书籍页面的表单输入数据,状态结构与表单字段一一对应,其中title、author、pages、description为字符串类型,category为默认值为“小说”的字符串类型。该状态的核心适配点在于“表单输入与状态同步”——后续TextInput组件的onChangeText事件,会实时更新该状态(如title输入框的onChangeText={(text) => setNewBook({…newBook, title: text})}),在鸿蒙系统中,TextInput的输入事件与状态更新逻辑完全兼容,无论是普通文本输入(书名、作者)、数字输入(页数),还是多行输入(简介),都能实现“输入即更新”,响应流畅,同时表单分类选择的状态更新(setNewBook({…newBook, category})),也能正常驱动UI反馈(选中分类高亮)。

最后补充:代码中通过books状态计算的统计数据(totalBooks、completedBooks等),属于“派生状态”,这种写法无需额外引入useMemo钩子(中小型应用可简化),在鸿蒙设备上不会出现性能问题,因为这些计算逻辑简单,适配层能够高效处理,同时也减少了代码复杂度,符合“轻量跨端”的开发思路。


状态管理是基础,核心功能的方法封装则是APP落地的关键。代码片段中封装了addNewBook(添加新书)、updateProgress(更新阅读进度)、showBookDetails(显示详情页)、closeBookDetail(关闭详情页)4个核心方法,所有方法的封装均遵循“通用化、无平台依赖”原则,确保在鸿蒙系统中可直接复用。我们逐一看代码片段与技术解读:

4.1 添加新书:


// 添加新书
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,
    description: newBook.description,
    rating: 0
  };

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

这段代码实现了“添加新书”的全流程逻辑,从表单校验、书籍对象创建,到状态更新、弹窗提示,每一步都兼顾了业务逻辑与鸿蒙跨端适配,核心解读如下:

  1. 表单校验与Alert组件适配:方法开篇通过简单的非空校验,判断书名、作者、页数是否填写完整,若未填写,通过Alert.alert弹出错误提示。在鸿蒙系统中,Alert组件会被适配层渲染为鸿蒙原生弹窗样式(而非RN模拟弹窗),既贴合鸿蒙系统的原生视觉风格,又确保弹窗的交互流畅性(如点击确认按钮关闭弹窗),其API用法与RN原生完全一致,无需额外适配。

  2. 书籍对象创建的类型规范:创建新书籍对象时,严格遵循Book接口类型,其中pages属性通过parseInt(newBook.pages)转为number类型(因为newBook.pages是表单输入的字符串类型),避免类型错误导致的鸿蒙适配bug。同时,startDate(开始日期)采用new Date().toISOString().split(‘T’)[0]获取当前日期(格式为YYYY-MM-DD),targetDate(目标日期)默认设置为30天后,这种日期处理方式是跨端通用的,鸿蒙系统对RN的Date API完全兼容,无需担心日期格式异常。

  3. 状态更新与表单重置:通过setBooks([book, …books])将新书添加到书籍列表头部(展开运算符…确保不可变数据更新),该逻辑在鸿蒙系统中可正常运行,书籍列表会实时同步更新,显示新增的书籍;setNewBook重置表单输入状态,确保添加完成后表单清空,为下一次添加做准备,其状态更新逻辑完全兼容鸿蒙系统;最后通过Alert.alert弹出添加成功提示,与错误提示一致,适配鸿蒙原生弹窗。

关键注意点:newBook.pages是表单输入的字符串类型,必须转为number类型才能符合Book接口的pages属性要求,否则TypeScript会报错,同时在鸿蒙设备上,可能会导致后续阅读进度计算(progress = (currentPage / pages) * 100)出现NaN异常,这是跨端开发中容易忽略的细节。

4.2 更新阅读进度:


// 更新阅读进度
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;
  }));
};

这是APP的核心交互方法之一,用于更新指定书籍的阅读进度,同时自动标记书籍是否完成,其逻辑设计充分考虑了跨端兼容性,核心解读如下:

  1. 进度计算的跨端通用性:通过(newPage / book.pages) * 100计算阅读进度,Math.round用于取整,Math.min(100, …)确保进度不会超过100%(避免newPage超过书籍总页数时,进度显示异常)。这种计算逻辑是纯JavaScript逻辑,无任何平台依赖,在鸿蒙系统中可正常运行,无论是整数页数还是小数页数(实际开发中页数均为整数),都能准确计算进度。

  2. 不可变数据更新的适配:通过books.map遍历书籍列表,找到对应id的书籍,通过展开运算符…复制原书籍对象,再更新currentPage(当前页数)、progress(阅读进度)、isCompleted(是否完成)属性,最后通过setBooks更新状态。这种不可变数据更新方式,符合React最佳实践,也完全兼容鸿蒙系统的状态更新机制——鸿蒙适配层能够准确捕捉到数组中单个书籍对象的变化,仅重新渲染该书籍对应的UI(如书籍卡片的进度条),而非重新渲染整个列表,确保鸿蒙设备上的渲染性能。

  3. 状态联动的自动适配:isCompleted属性由progress === 100判断,当进度达到100%时,自动标记书籍为已完成,该状态变化会自动联动更新completedBooks、inProgressBooks等统计数据,同时联动更新书籍列表的UI(已完成书籍卡片样式变化),整个联动过程无需额外编写适配代码,鸿蒙系统会自动完成渲染更新。

4.3 详情页控制:


// 显示书籍详情
const showBookDetails = (book: Book) => {
  setSelectedBook(book);
  setShowBookDetail(true);
};

// 关闭书籍详情
const closeBookDetail = () => {
  setShowBookDetail(false);
  setSelectedBook(null);
};

这两个方法实现了详情页的显示与关闭,逻辑简洁,且完全兼容鸿蒙系统,核心解读如下:

  1. 数据传递的跨端通用性:showBookDetails方法接收Book类型的参数(当前点击的书籍对象),通过setSelectedBook(book)存储该书籍数据,为详情页渲染提供数据源。这种数据传递方式是RN组件间通信的常用方式,无任何平台依赖,鸿蒙系统中可正常传递复杂对象数据,无需担心数据丢失或类型转换异常。

  2. 状态联动的适配:showBookDetails方法同时更新两个状态(selectedBook、showBookDetail),鸿蒙系统支持RN的批量状态更新机制,会一次性完成两个状态的更新,避免出现“详情页显示但无数据”或“有数据但详情页未显示”的异常;closeBookDetail方法重置两个状态,确保详情页关闭后,数据与UI完全重置,为下一次点击详情页做准备,其状态更新逻辑在鸿蒙设备上无任何延迟。

五、组件渲染:

组件化是RN跨端开发的核心优势,代码片段中采用“页面级组件拆分+业务组件复用”的方式,将四大核心页面与详情页分别封装为独立的渲染函数,所有组件的渲染逻辑均遵循RN通用规范,同时结合鸿蒙系统的渲染特性,进行了细微优化,确保在鸿蒙设备上的适配性与流畅性。我们以核心渲染函数为例,结合代码片段解读:

5.1 主渲染逻辑:


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>

    {/* 内容区域:根据showBookDetail和activeTab切换渲染内容 */}
    {!showBookDetail ? (
      <>
        {activeTab === 'home' && renderHomeContent()}
        {activeTab === 'add' && renderAddBookContent()}
        {activeTab === 'stats' && renderStatsContent()}
        {activeTab === 'profile' && renderProfileContent()}
      </>
    ) : (
      renderBookDetail()
    )}

    {/* 底部导航:详情页隐藏 */}
    {!showBookDetail && (
      <View style={styles.bottomNav}>
        {/* 导航项省略... */}
      </View>
    )}
  </SafeAreaView>
);

这段代码是APP的主渲染入口,核心逻辑是“根据状态切换渲染内容”,其鸿蒙适配细节主要体现在3个方面:

  1. 根组件的适配:根组件采用SafeAreaView,如前文所述,鸿蒙适配层会自动适配设备安全区域,确保头部标题不会被刘海屏遮挡;外层容器style={styles.container}设置flex: 1,占据整个屏幕空间,鸿蒙系统完全支持RN的flex布局,能够正常解析容器的布局逻辑,确保页面充满屏幕。

  2. 条件渲染的适配:通过{!showBookDetail ? (…) : (…)}判断渲染首页/添加页面/统计页面/个人中心,还是渲染详情页;通过{activeTab === ‘home’ && renderHomeContent()}根据activeTab状态切换四大核心页面。这种条件渲染逻辑是RN通用写法,鸿蒙系统可正常解析,页面切换时的渲染流畅,无卡顿、无闪烁,同时详情页显示时隐藏底部导航({!showBookDetail && (…)}),这种UI联动逻辑也完全兼容鸿蒙系统。

  3. 底部导航的适配:底部导航容器style={styles.bottomNav}采用flex横向布局,导航项均匀分布,鸿蒙系统可正常解析flex布局,确保导航项在不同尺寸的鸿蒙设备上均匀排列;导航项采用TouchableOpacity组件,点击事件(setActiveTab)在鸿蒙系统中可正常响应,同时导航项的高亮样式(根据activeTab判断),也能正常渲染,贴合鸿蒙系统的原生交互风格。

5.2 首页渲染:

首页是APP的核心展示页面,包含阅读概览卡片、快速操作按钮、进行中书籍列表、已完成书籍列表四大模块,代码片段中的renderHomeContent方法封装了其完整渲染逻辑,我们重点解读列表渲染的鸿蒙适配细节:


// 渲染主页内容
const renderHomeContent = () => (
  <ScrollView style={styles.content}>
    {/* 今日概览卡片、快速操作按钮省略... */}

    {/* 进行中的书籍 */}
    <Text style={styles.sectionTitle}>进行中的书籍</Text>
    <FlatList
      data={books.filter(book => !book.isCompleted)}
      renderItem={({ item }) => (
        <TouchableOpacity 
          style={styles.bookItem}
          onPress={() => showBookDetails(item)}
        >
          <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>
            {/* 其余信息省略... */}
          </View>
          <TouchableOpacity 
            style={styles.updateButton}
            onPress={(e) => {
              e.stopPropagation(); // 防止触发外层点击事件
              const newPage = item.currentPage + 10;
              updateProgress(item.id, newPage > item.pages ? item.pages : newPage);
            }}
          >
            <Text style={styles.updateButtonText}>+10</Text>
          </TouchableOpacity>
        </TouchableOpacity>
      )}
      keyExtractor={item => item.id}
      showsVerticalScrollIndicator={false}
    />

    {/* 已完成的书籍省略... */}
  </ScrollView>
);

这段代码的核心是FlatList组件的使用,以及书籍卡片的渲染,其鸿蒙适配细节如下:

  1. FlatList的鸿蒙适配:FlatList的data属性为过滤后的进行中书籍列表(books.filter(book => !book.isCompleted)),renderItem属性渲染单个书籍卡片,keyExtractor属性指定item.id为唯一标识(避免列表渲染异常),showsVerticalScrollIndicator={false}隐藏滚动条。这些属性在鸿蒙系统中均完全生效,适配层会将FlatList映射为鸿蒙原生的ListContainer组件,实现高效列表渲染,同时支持下拉刷新、上拉加载(本次代码未实现,可直接扩展),无需额外适配。

  2. 书籍卡片的交互适配:书籍卡片采用TouchableOpacity组件,点击卡片触发showBookDetails(item)方法,跳转详情页;卡片内部的“+10页”按钮,也采用TouchableOpacity组件,点击触发进度更新,同时通过e.stopPropagation()阻止事件冒泡(避免点击按钮时同时触发卡片点击事件)。在鸿蒙系统中,TouchableOpacity的点击反馈(透明度变化)与其他平台保持一致,事件冒泡机制也完全兼容,无需额外处理。

  3. 进度条的动态适配:进度条由两个View组件实现(外层progressBar为进度条背景,内层progressFill为进度填充),其中progressFill的宽度通过{ width: ${item.progress}% }动态设置,与阅读进度联动。这种动态样式绑定逻辑,鸿蒙系统可正常解析,进度更新时,进度条会平滑变化,无卡顿,同时进度条的圆角、背景色等样式(通过StyleSheet定义),也能正常渲染,与其他平台保持视觉一致。

六、样式设计:

样式设计是RN鸿蒙跨端开发中最容易出现平台差异的环节,代码片段中通过StyleSheet.create定义所有样式,遵循“动态尺寸+全局规范+鸿蒙特性适配”的原则,确保样式在鸿蒙设备上的正常渲染与视觉一致性。我们结合代码片段中的样式定义,解读其跨端适配技巧:


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',
  },
  // 其余样式省略...
  bookItem: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 12,
    flexDirection: 'row',
    alignItems: 'center',
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  progressBar: {
    flex: 1,
    height: 8,
    backgroundColor: '#e2e8f0',
    borderRadius: 4,
    marginRight: 8,
  },
  progressFill: {
    height: '100%',
    backgroundColor: '#3b82f6',
    borderRadius: 4,
  },
});

核心适配解读如下:

  1. 全局样式规范:代码中定义了统一的颜色规范(主色调#3b82f6、背景色#f8fafc、#ffffff等)、圆角规范(卡片borderRadius: 12、按钮borderRadius: 8、进度条borderRadius: 4),所有组件的样式均遵循该规范,鸿蒙系统与其他平台一样,支持RGB颜色值与圆角样式,确保跨端视觉风格一致。

  2. flex布局的全量使用:所有组件的布局均采用flex布局(flexDirection、justifyContent、alignItems等属性),鸿蒙系统完全支持RN的flex布局语法,能够正常解析组件的排列逻辑——比如header组件的flexDirection: ‘row’(横向排列)、justifyContent: ‘space-between’(两端对齐),bookItem组件的flexDirection: ‘row’,均能在鸿蒙设备上正常渲染,确保组件排列合理。

  3. 阴影效果的跨端适配:这是鸿蒙样式适配的重点,RN在iOS端通过shadowColor、shadowOffset、shadowOpacity、shadowRadius四个属性实现阴影,而鸿蒙系统与Android系统一样,通过elevation属性实现阴影。代码中所有需要添加阴影的组件(如bookItem、overviewCard),均同时定义了iOS端阴影属性和elevation属性,确保阴影效果在鸿蒙设备上正常显示,且与iOS端保持一致——比如bookItem组件的elevation: 2,对应iOS端的阴影效果,适配层会自动识别平台,渲染对应的阴影样式,避免出现“鸿蒙设备无阴影”或“阴影样式异常”的问题。

  4. 动态样式的适配:代码中通过动态绑定样式(如progressFill的width: ${item.progress}%),实现进度条的动态变化,这种动态样式绑定逻辑,鸿蒙系统可正常解析,样式会随着状态更新实时变化;同时,结合前文提到的Dimensions API获取屏幕宽度,实现动态尺寸适配(如快速操作按钮的width: width / 4.5),确保样式在不同尺寸的鸿蒙设备上均能正常显示。


通过对阅读追踪APP代码片段的逐行深度解读,我们可以总结出RN鸿蒙跨端开发的核心逻辑:以RN官方通用组件与API为基础,以TypeScript规范数据类型,以useState钩子实现轻量状态管理,以组件化封装提升复用性,以StyleSheet的平台适配优化确保视觉与交互一致性,借助鸿蒙系统的RN适配层,实现“一套代码,多端流畅运行”。


真实演示案例代码:





// 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;
  description: string;
  rating: number;
};

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,
      description: '一部讲述人生苦难与坚韧的小说,通过福贵的人生经历展现了生命的顽强与希望。',
      rating: 4.8
    },
    {
      id: '2',
      title: '百年孤独',
      author: '加西亚·马尔克斯',
      pages: 368,
      currentPage: 200,
      progress: 54,
      startDate: '2023-05-10',
      targetDate: '2023-07-10',
      category: '文学',
      isCompleted: false,
      description: '魔幻现实主义的经典之作,讲述了布恩迪亚家族七代人的传奇故事。',
      rating: 4.7
    },
    {
      id: '3',
      title: '人类简史',
      author: '尤瓦尔·赫拉利',
      pages: 443,
      currentPage: 300,
      progress: 68,
      startDate: '2023-05-01',
      targetDate: '2023-08-01',
      category: '历史',
      isCompleted: false,
      description: '从认知革命到今天,人类如何成为地球的主宰,又将走向何方?',
      rating: 4.6
    },
    {
      id: '4',
      title: '高效能人士的七个习惯',
      author: '史蒂芬·柯维',
      pages: 381,
      currentPage: 381,
      progress: 100,
      startDate: '2023-04-01',
      targetDate: '2023-04-30',
      category: '自我提升',
      isCompleted: true,
      description: '全球畅销30年的经典著作,教你如何成为高效能人士,实现个人和事业的成功。',
      rating: 4.9
    },
  ]);
  
  const [selectedBook, setSelectedBook] = useState<Book | null>(null);
  const [showBookDetail, setShowBookDetail] = useState(false);
  
  const [newBook, setNewBook] = useState({
    title: '',
    author: '',
    pages: '',
    category: '小说',
    description: ''
  });

  // 计算总统计数据
  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,
      description: newBook.description,
      rating: 0
    };

    setBooks([book, ...books]);
    setNewBook({ title: '', author: '', pages: '', category: '小说', description: '' });
    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 showBookDetails = (book: Book) => {
    setSelectedBook(book);
    setShowBookDetail(true);
  };

  // 关闭书籍详情
  const closeBookDetail = () => {
    setShowBookDetail(false);
    setSelectedBook(null);
  };

  // 渲染书籍详情页面
  const renderBookDetail = () => (
    <View style={styles.detailContainer}>
      <View style={styles.detailHeader}>
        <TouchableOpacity onPress={closeBookDetail}>
          <Text style={styles.backButton}>← 返回</Text>
        </TouchableOpacity>
        <Text style={styles.detailTitle}>{selectedBook?.title}</Text>
      </View>
      
      <ScrollView style={styles.detailContent}>
        <View style={styles.detailCard}>
          <Text style={styles.detailBookTitle}>{selectedBook?.title}</Text>
          <Text style={styles.detailAuthor}>作者: {selectedBook?.author}</Text>
          <Text style={styles.detailCategory}>分类: {selectedBook?.category}</Text>
          <Text style={styles.detailRating}>评分:{selectedBook?.rating}</Text>
          
          <View style={styles.progressContainer}>
            <Text style={styles.progressLabel}>阅读进度</Text>
            <View style={styles.progressBar}>
              <View style={[styles.progressFill, { width: `${selectedBook?.progress}%` }]} />
            </View>
            <Text style={styles.progressText}>{selectedBook?.progress}% ({selectedBook?.currentPage}/{selectedBook?.pages})</Text>
          </View>
          
          <Text style={styles.detailSectionTitle}>简介</Text>
          <Text style={styles.detailDescription}>{selectedBook?.description}</Text>
          
          <Text style={styles.detailSectionTitle}>阅读计划</Text>
          <Text style={styles.detailPlan}>
            开始日期: {selectedBook?.startDate}
            {'\n'}目标完成: {selectedBook?.targetDate}
          </Text>
          
          <View style={styles.detailActions}>
            <TouchableOpacity 
              style={styles.detailButton}
              onPress={() => {
                if (selectedBook) {
                  const newPage = selectedBook.currentPage + 10;
                  updateProgress(selectedBook.id, newPage > selectedBook.pages ? selectedBook.pages : newPage);
                  Alert.alert('进度更新', `已更新到第 ${Math.min(newPage, selectedBook.pages)}`);
                }
              }}
            >
              <Text style={styles.detailButtonText}>+10</Text>
            </TouchableOpacity>
            
            <TouchableOpacity 
              style={styles.detailButton}
              onPress={() => {
                if (selectedBook) {
                  const newPage = selectedBook.currentPage + 20;
                  updateProgress(selectedBook.id, newPage > selectedBook.pages ? selectedBook.pages : newPage);
                  Alert.alert('进度更新', `已更新到第 ${Math.min(newPage, selectedBook.pages)}`);
                }
              }}
            >
              <Text style={styles.detailButtonText}>+20</Text>
            </TouchableOpacity>
            
            <TouchableOpacity 
              style={styles.detailButton}
              onPress={() => {
                if (selectedBook) {
                  updateProgress(selectedBook.id, selectedBook.pages);
                  Alert.alert('恭喜', '已完成此书!');
                }
              }}
            >
              <Text style={styles.detailButtonText}>完成</Text>
            </TouchableOpacity>
          </View>
        </View>
      </ScrollView>
    </View>
  );

  // 渲染主页内容
  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 }) => (
          <TouchableOpacity 
            style={styles.bookItem}
            onPress={() => showBookDetails(item)}
          >
            <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={(e) => {
                e.stopPropagation(); // 防止触发外层点击事件
                const newPage = item.currentPage + 10;
                updateProgress(item.id, newPage > item.pages ? item.pages : newPage);
              }}
            >
              <Text style={styles.updateButtonText}>+10</Text>
            </TouchableOpacity>
          </TouchableOpacity>
        )}
        keyExtractor={item => item.id}
        showsVerticalScrollIndicator={false}
      />

      {/* 已完成的书籍 */}
      <Text style={styles.sectionTitle}>已完成的书籍</Text>
      <FlatList
        data={books.filter(book => book.isCompleted)}
        renderItem={({ item }) => (
          <TouchableOpacity 
            style={styles.completedBookItem}
            onPress={() => showBookDetails(item)}
          >
            <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>
          </TouchableOpacity>
        )}
        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>
        
        {/* 简介 */}
        <View style={styles.inputGroup}>
          <Text style={styles.inputLabel}>简介</Text>
          <TextInput
            style={[styles.inputField, styles.textArea]}
            value={newBook.description}
            onChangeText={(text) => setNewBook({...newBook, description: text})}
            placeholder="简要描述书籍内容"
            multiline
          />
        </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>

      {/* 内容区域 */}
      {!showBookDetail ? (
        <>
          {activeTab === 'home' && renderHomeContent()}
          {activeTab === 'add' && renderAddBookContent()}
          {activeTab === 'stats' && renderStatsContent()}
          {activeTab === 'profile' && renderProfileContent()}
        </>
      ) : (
        renderBookDetail()
      )}

      {/* 底部导航 */}
      {!showBookDetail && (
        <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',
  },
  textArea: {
    height: 80,
    textAlignVertical: 'top',
  },
  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',
  },
  detailContainer: {
    flex: 1,
    backgroundColor: '#f8fafc',
  },
  detailHeader: {
    padding: 20,
    backgroundColor: '#ffffff',
    borderBottomWidth: 1,
    borderBottomColor: '#e2e8f0',
  },
  backButton: {
    fontSize: 16,
    color: '#3b82f6',
    marginBottom: 8,
  },
  detailTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#1e293b',
  },
  detailContent: {
    flex: 1,
    padding: 16,
  },
  detailCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  detailBookTitle: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 8,
  },
  detailAuthor: {
    fontSize: 16,
    color: '#64748b',
    marginBottom: 4,
  },
  detailCategory: {
    fontSize: 14,
    color: '#3b82f6',
    marginBottom: 4,
  },
  detailRating: {
    fontSize: 14,
    color: '#f59e0b',
    marginBottom: 12,
  },
  progressLabel: {
    fontSize: 14,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 8,
  },
  detailSectionTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
    marginVertical: 12,
  },
  detailDescription: {
    fontSize: 14,
    color: '#64748b',
    lineHeight: 20,
    marginBottom: 12,
  },
  detailPlan: {
    fontSize: 14,
    color: '#64748b',
    marginBottom: 16,
  },
  detailActions: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    marginTop: 16,
  },
  detailButton: {
    backgroundColor: '#3b82f6',
    padding: 12,
    borderRadius: 8,
    flex: 1,
    marginHorizontal: 4,
    alignItems: 'center',
  },
  detailButtonText: {
    color: '#ffffff',
    fontWeight: 'bold',
  },
});

export default ReadingTrackerApp;

请添加图片描述


打包

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

在这里插入图片描述

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

在这里插入图片描述

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

请添加图片描述
本文介绍了一个基于React Native的阅读追踪应用,采用函数组件和useState Hook实现状态管理。应用包含书籍列表、详情页、添加书籍和统计功能,通过TypeScript接口确保数据结构一致性。核心设计包括:

  • 使用useState管理5个关键状态(页面切换、书籍数据、详情页控制等)
  • 采用Base64编码图标实现跨平台资源管理
  • 布局使用标签页导航和Flexbox系统
  • 数据处理包含书籍CRUD和统计计算
  • 特别考虑了React Native与鸿蒙系统的兼容性

应用展示了轻量级状态管理方案在跨端开发中的优势,通过原生Hook实现状态联动,确保在不同平台的功能一致性。

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

Logo

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

更多推荐